test_plc.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * test_plc.c - local mock S7 server + client round-trip self test.
  3. *
  4. * Verifies byte-exact S7comm frames (COTP CR, setup, read request/response)
  5. * against the python-snap7 reference implementation layout.
  6. */
  7. #include <arpa/inet.h>
  8. #include <errno.h>
  9. #include <pthread.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <sys/socket.h>
  14. #include <unistd.h>
  15. #include "plc.h"
  16. #define TEST_PORT 12022
  17. #define COTP_CR 0xE0
  18. #define COTP_CC 0xD0
  19. #define COTP_DT 0xF0
  20. static int g_server_fail = 0;
  21. static int g_server_done = 0;
  22. static int recv_exact(int fd, void *buf, size_t len) {
  23. size_t got = 0;
  24. while (got < len) {
  25. ssize_t r = recv(fd, (uint8_t *)buf + got, len - got, 0);
  26. if (r <= 0) return -1;
  27. got += (size_t)r;
  28. }
  29. return 0;
  30. }
  31. static int send_all(int fd, const void *buf, size_t len) {
  32. size_t sent = 0;
  33. while (sent < len) {
  34. ssize_t r = send(fd, (const uint8_t *)buf + sent, len - sent, MSG_NOSIGNAL);
  35. if (r <= 0) return -1;
  36. sent += (size_t)r;
  37. }
  38. return 0;
  39. }
  40. static void put_u16(uint8_t *p, uint16_t v) {
  41. p[0] = (uint8_t)(v >> 8);
  42. p[1] = (uint8_t)(v & 0xFF);
  43. }
  44. static uint16_t get_u16(const uint8_t *p) {
  45. return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
  46. }
  47. /* Read one TPKT frame; returns COTP/S7 payload after the 3-byte DT header. */
  48. static int read_frame(int fd, uint8_t *out, int maxout, int *out_len) {
  49. uint8_t hdr[4];
  50. if (recv_exact(fd, hdr, 4) < 0) return -1;
  51. int total = ((int)hdr[2] << 8) | hdr[3];
  52. if (total < 7 || total > 1200) return -1;
  53. uint8_t payload[1200];
  54. if (recv_exact(fd, payload, total - 4) < 0) return -1;
  55. if (payload[0] != 2 || payload[1] != COTP_DT) return -1;
  56. int n = total - 4 - 3;
  57. if (n > maxout) return -1;
  58. memcpy(out, payload + 3, n);
  59. *out_len = n;
  60. return 0;
  61. }
  62. static void send_s7(int fd, const uint8_t *s7, int len) {
  63. uint8_t frame[4 + 3 + 1200];
  64. int total = 4 + 3 + len;
  65. frame[0] = 3; frame[1] = 0;
  66. frame[2] = (uint8_t)(total >> 8);
  67. frame[3] = (uint8_t)(total & 0xFF);
  68. frame[4] = 2; frame[5] = COTP_DT; frame[6] = 0x80;
  69. memcpy(frame + 7, s7, len);
  70. send_all(fd, frame, total);
  71. }
  72. static void fail(const char *msg, const uint8_t *got, int glen,
  73. const uint8_t *exp, int elen) {
  74. g_server_fail = 1;
  75. fprintf(stderr, "MOCK FAIL: %s (got %d bytes, want %d)\n", msg, glen, elen);
  76. fprintf(stderr, " got: ");
  77. for (int i = 0; i < glen; i++) fprintf(stderr, "%02x ", got[i]);
  78. fprintf(stderr, "\n exp: ");
  79. for (int i = 0; i < elen; i++) fprintf(stderr, "%02x ", exp[i]);
  80. fprintf(stderr, "\n");
  81. }
  82. static void serve_connection(int cfd) {
  83. /* --- COTP Connection Request --- */
  84. uint8_t hdr[4], payload[1200];
  85. if (recv_exact(cfd, hdr, 4) < 0) goto close;
  86. int total = ((int)hdr[2] << 8) | hdr[3];
  87. if (recv_exact(cfd, payload, total - 4) < 0) goto close;
  88. {
  89. uint8_t exp[18] = {
  90. 0x11, COTP_CR, 0x00, 0x00, 0x00, 0x01, 0x00,
  91. 0xC1, 0x02, 0x01, 0x00, /* calling TSAP 0x0100 */
  92. 0xC2, 0x02, 0x01, 0x02, /* called TSAP 0x0102 */
  93. 0xC0, 0x01, 0x0A,
  94. };
  95. if (total - 4 != 18 || memcmp(payload, exp, sizeof(exp)) != 0)
  96. fail("COTP CR", payload, total - 4, exp, sizeof(exp));
  97. }
  98. /* --- COTP Connection Confirm --- */
  99. {
  100. uint8_t cc[10] = {0x09, COTP_CC, 0x00, 0x01, 0x00, 0x01, 0x00,
  101. 0xC0, 0x01, 0x0A};
  102. uint8_t frame[4 + 10];
  103. int t = 4 + 10;
  104. frame[0] = 3; frame[1] = 0;
  105. frame[2] = (uint8_t)(t >> 8); frame[3] = (uint8_t)(t & 0xFF);
  106. memcpy(frame + 4, cc, 10);
  107. send_all(cfd, frame, t);
  108. }
  109. /* --- S7 Setup Communication --- */
  110. {
  111. uint8_t data[1200];
  112. int n;
  113. if (read_frame(cfd, data, sizeof(data), &n) < 0) goto close;
  114. uint8_t exp[18] = {
  115. 0x32, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
  116. 0xF0, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0xE0,
  117. };
  118. exp[4] = data[4];
  119. exp[5] = data[5];
  120. if (n != 18 || memcmp(data, exp, sizeof(exp)) != 0)
  121. fail("S7 setup", data, n, exp, sizeof(exp));
  122. uint8_t resp[20] = {
  123. 0x32, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
  124. 0x00, 0x00,
  125. 0xF0, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0xE0,
  126. };
  127. send_s7(cfd, resp, sizeof(resp));
  128. }
  129. /* --- Read requests (loop) --- */
  130. {
  131. const uint8_t *data_pattern = (const uint8_t *)"\x10\x11\x12\x13";
  132. int read_len = 4;
  133. uint8_t data[1200];
  134. int n;
  135. while (recv_exact(cfd, hdr, 4) >= 0) {
  136. total = ((int)hdr[2] << 8) | hdr[3];
  137. if (recv_exact(cfd, data, total - 4) < 0) break;
  138. if (data[0] != 2 || data[1] != COTP_DT) break;
  139. n = total - 4 - 3;
  140. uint8_t *s7 = data + 3;
  141. uint8_t exp[24] = {
  142. 0x32, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00,
  143. 0x04, 0x01, 0x12, 0x0A, 0x10, 0x02, /* READ VAR + spec */
  144. 0x00, 0x04, /* count = 4 bytes */
  145. 0x00, 0x00, /* db = 0 */
  146. 0x83, /* area = M */
  147. 0x00, 0x00, 0x10, /* addr = 2*8 */
  148. };
  149. exp[4] = s7[4];
  150. exp[5] = s7[5];
  151. if (n != 24 || memcmp(s7, exp, sizeof(exp)) != 0)
  152. fail("S7 read request", s7, n, exp, sizeof(exp));
  153. uint16_t seq = get_u16(s7 + 4);
  154. uint8_t resp[12 + 2 + 4 + read_len];
  155. int r = 0;
  156. resp[r++] = 0x32; resp[r++] = 0x03;
  157. resp[r++] = 0x00; resp[r++] = 0x00;
  158. put_u16(resp + r, seq); r += 2;
  159. put_u16(resp + r, 0x0002); r += 2; /* param len */
  160. put_u16(resp + r, 0x0004 + read_len); r += 2; /* data len */
  161. resp[r++] = 0x00; resp[r++] = 0x00; /* error class/code */
  162. resp[r++] = 0x04; resp[r++] = 0x01; /* read response */
  163. resp[r++] = 0xFF; /* return code OK */
  164. resp[r++] = 0x04; /* transport size */
  165. put_u16(resp + r, (uint16_t)(read_len * 8)); r += 2;
  166. memcpy(resp + r, data_pattern, read_len); r += read_len;
  167. send_s7(cfd, resp, r);
  168. }
  169. }
  170. close:
  171. close(cfd);
  172. g_server_done = 1;
  173. }
  174. static void *mock_server(void *arg) {
  175. int persistent = (arg != NULL);
  176. int lfd = socket(AF_INET, SOCK_STREAM, 0);
  177. if (lfd < 0) { g_server_fail = 1; g_server_done = 1; return NULL; }
  178. int one = 1;
  179. setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  180. struct sockaddr_in sa;
  181. memset(&sa, 0, sizeof(sa));
  182. sa.sin_family = AF_INET;
  183. sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  184. sa.sin_port = htons(TEST_PORT);
  185. if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)) < 0 ||
  186. listen(lfd, 4) < 0) {
  187. perror("bind/listen");
  188. g_server_fail = 1;
  189. g_server_done = 1;
  190. return NULL;
  191. }
  192. if (persistent) {
  193. for (;;) {
  194. int cfd = accept(lfd, NULL, NULL);
  195. if (cfd < 0) {
  196. if (errno == EINTR) continue;
  197. break;
  198. }
  199. serve_connection(cfd);
  200. }
  201. } else {
  202. int cfd = accept(lfd, NULL, NULL);
  203. if (cfd >= 0) serve_connection(cfd);
  204. }
  205. close(lfd);
  206. return NULL;
  207. }
  208. int main(int argc, char **argv) {
  209. /* Standalone mock PLC server mode: ./test_plc server
  210. * Handy for on-device testing against the real rtsp_multi_view. */
  211. if (argc > 1 && strcmp(argv[1], "server") == 0) {
  212. mock_server((void *)1);
  213. return 0;
  214. }
  215. pthread_t server;
  216. pthread_create(&server, NULL, mock_server, NULL);
  217. PlcConfig cfg;
  218. memset(&cfg, 0, sizeof(cfg));
  219. strcpy(cfg.ip, "127.0.0.1");
  220. cfg.port = TEST_PORT;
  221. cfg.area = PLC_AREA_M;
  222. cfg.read_start = 2;
  223. cfg.read_len = 4;
  224. cfg.poll_ms = 50;
  225. cfg.reconnect_ms = 200;
  226. cfg.tsap_local = 0x0100;
  227. cfg.tsap_remote = 0x0102;
  228. int failures = 0;
  229. if (plc_init(&cfg) < 0) {
  230. fprintf(stderr, "plc_init failed\n");
  231. return 1;
  232. }
  233. /* Wait for connection */
  234. int ok = 0;
  235. for (int i = 0; i < 100; i++) {
  236. if (plc_is_connected()) { ok = 1; break; }
  237. usleep(30000);
  238. }
  239. if (!ok) {
  240. fprintf(stderr, "FAIL: never connected (%s)\n", plc_last_error());
  241. failures++;
  242. } else {
  243. uint8_t b;
  244. if (plc_get_byte(0, &b) == 0 && b == 0x10) {
  245. fprintf(stderr, "PASS: byte0 = 0x%02x\n", b);
  246. } else {
  247. fprintf(stderr, "FAIL: byte0 = %02x\n", b);
  248. failures++;
  249. }
  250. if (plc_get_byte(3, &b) == 0 && b == 0x13) {
  251. fprintf(stderr, "PASS: byte3 = 0x%02x\n", b);
  252. } else {
  253. fprintf(stderr, "FAIL: byte3 = %02x\n", b);
  254. failures++;
  255. }
  256. /* 0x10 = bit4 set, bit0 clear */
  257. if (plc_get_bit(0, 4) == 1 && plc_get_bit(0, 0) == 0) {
  258. fprintf(stderr, "PASS: bit4=1 bit0=0\n");
  259. } else {
  260. fprintf(stderr, "FAIL: bit checks %d %d\n",
  261. plc_get_bit(0, 4), plc_get_bit(0, 0));
  262. failures++;
  263. }
  264. /* out-of-range access must fail gracefully */
  265. if (plc_get_bit(7, 0) == -1 && plc_get_byte(9, &b) == -1) {
  266. fprintf(stderr, "PASS: out-of-range rejected\n");
  267. } else {
  268. fprintf(stderr, "FAIL: out-of-range not rejected\n");
  269. failures++;
  270. }
  271. }
  272. plc_shutdown();
  273. pthread_join(server, NULL);
  274. if (g_server_fail) {
  275. fprintf(stderr, "FAIL: mock server reported frame mismatches\n");
  276. failures++;
  277. } else {
  278. fprintf(stderr, "PASS: all S7 frames byte-exact\n");
  279. }
  280. if (failures == 0) {
  281. fprintf(stderr, "ALL TESTS PASSED\n");
  282. return 0;
  283. }
  284. fprintf(stderr, "%d FAILURE(S)\n", failures);
  285. return 1;
  286. }