--- /dev/null
+/*
+ File name : mapec_valid.c
+ Projet : MERLIN
+ Date of creation : 2025/05/23
+ Version : 1.0
+ Copyright : Thales SIX
+ Author : Laurent Mazet <laurent.mazet@thalesgroup.com>
+
+ Description : Minimal API for Packet Exchange Commmunication validation
+ program
+
+ History :
+ - initial version
+*/
+
+/* depend: */
+/* cflags: */
+/* linker: mapec.o */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mapec.h"
+#include "verbose.h"
+
+char *progname = NULL;
+
+int stop = 0;
+
+#define BUFMAX 4096
+
+#define MAXPAYLOAD 1500
+
+void sig_handler (int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ //stop = 1;
+ exit (0);
+ break;
+ case SIGTERM:
+ exit (0);
+ break;
+ }
+}
+
+#define TEST_CHARS(str, delim, stop) \
+ while (*str != '\0') { \
+ int i, stat = 0; \
+ for (i = 0; (delim[i] != '\0') && (!stat); i++) { \
+ if (*str == delim[i]) { \
+ stat = 1; \
+ } \
+ } \
+ if (stat == stop) { \
+ break; \
+ } \
+ str++; \
+ }
+
+int parse_array (char *str, uint8_t *buffer, int maxlen)
+{
+ int len = 0;
+ int slen = strlen (str);
+
+ /* string payload: "..." (space must be protected by '\') */
+ if ((*str == '"') && (slen > 1)) {
+ VERBOSE (mapec, TRACE, PRINTF ("string payload: \"...\"\n"));
+ if (maxlen < slen - 2) slen = maxlen + 2;
+ if (str[slen - 1] == '"') {
+ len = slen - 2;
+ if (len > maxlen) {
+ VERBOSE (mapec, WARNING, PRINTF ("string too large (%d > %d) for '%s'\n", len, maxlen, str));
+ len = maxlen;
+ }
+ int j = 0;
+ for (int i = 1; i < len + 1; i++, j++) {
+ if ((str[i] == '\\') && (str[i + 1] == ' ')) {
+ i++;
+ }
+ buffer[j] = str[i];
+ }
+ len = j;
+ VERBOSE (mapec, TRACE, buffer[len] = '\0'; PRINTF ("string[%d]: '%s'\n", len, buffer));
+ } else {
+ VERBOSE (mapec, ERROR, PRINTF ("incomplet string '%s'\n", str));
+ }
+ }
+
+ /* file payload: @filename */
+ else if (*str == '@') {
+ VERBOSE (mapec, TRACE, PRINTF ("file payload: @filename\n"));
+ FILE *fid = fopen (str + 1, "r");
+ if (fid != NULL) {
+ while ((len < maxlen) && (!feof (fid)) && (!ferror (fid))) {
+ len += fread (buffer + len, 1, maxlen - len, fid);
+ }
+ if (ferror (fid)) {
+ VERBOSE (mapec, ERROR, PRINTF ("can't read file '%s'\n", str));
+ } else if (!feof (fid)) {
+ fseek (fid, 0L, SEEK_SET);
+ fseek (fid, 0L, SEEK_END);
+ int flen = ftell (fid);
+ if (flen > maxlen) {
+ VERBOSE (mapec, WARNING, PRINTF ("file too large (%d > %d) for '%s'\n", flen, maxlen, str));
+ }
+ }
+ fclose (fid);
+ } else {
+ VERBOSE (mapec, ERROR, PRINTF ("can't open file '%s'\n", str));
+ }
+ }
+
+ /* hexa payload: xx:xx:xx [0-9a-fA-F] */
+ else {
+ if (maxlen * 3 - 1 < slen) {
+ slen = maxlen * 3 - 1;
+ }
+ if (slen % 3 == 2) {
+ VERBOSE (mapec, TRACE, PRINTF ("hexa payload: xx:xx:xx\n"));
+ len = slen / 3 + 1;
+ if (len > maxlen) {
+ VERBOSE (mapec, WARNING, PRINTF ("string too large (%d > %d) for '%s'\n", len, maxlen, str));
+ len = maxlen;
+ }
+ for (int i = 0; i < len; i++) {
+ char digit[3] = {0};
+ char *ptr = NULL;
+ digit[0] = str[3 * i];
+ digit[1] = str[3 * i + 1];
+ buffer[i] = strtol (digit, &ptr, 16);
+ if ((*ptr != ':') && (*ptr != '\0') && (*ptr != ' ') && (*ptr != '\t')) {
+ VERBOSE (mapec, ERROR, PRINTF ("unrecognize hexa-string (%d) '%s'\n", 3 * i, str));
+ break;
+ }
+ }
+ }
+
+ /* unrecognize format */
+ else {
+ VERBOSE (mapec, WARNING, PRINTF ("can't parse buffer '%s'\n", str));
+ }
+ }
+ return len;
+}
+
+char *read_stream (FILE *sd, int *plen)
+{
+ VERBOSE (mapec, TRACE, PRINTF ("read_stream\n"));
+
+ /* read and store */
+ char *buffer = NULL;
+ size_t size = 0;
+ int blocklen = 0;
+ int length = 0;
+ do {
+ size += BUFMAX + (size == 0);
+ buffer = (char *) realloc (buffer, size);
+ memset (buffer + size - BUFMAX - 1, 0, BUFMAX + 1);
+ blocklen = fread (buffer + size - BUFMAX - 1, 1, BUFMAX, sd);
+ length += blocklen;
+ } while (blocklen > 0);
+
+ /* check size */
+ VERBOSE (mapec, DEBUG, PRINTF ("read length: %d\n", length));
+ if (length == 0) {
+ free (buffer);
+ buffer = NULL;
+ }
+
+ if (plen) {
+ *plen = length;
+ }
+
+ return buffer;
+}
+
+void print_message (FILE *fd, char *serv, int mode, uint8_t *payload, int len)
+{
+ fprintf (fd, "%c%s LEN=%d", mode ? 'T' : 'R', serv, len);
+ if (len > 0) {
+ int i;
+ fprintf (fd, " PAYLOAD=");
+ for (i = 0; i < len; i++) {
+ fprintf (fd, "%02x%c", payload[i], (i == len - 1) ? '\n' : ':');
+ }
+ }
+}
+
+typedef struct {
+ char *serv;
+ char *rxurl;
+ char *txurl;
+ int fid;
+} comm_t;
+
+#define MAXCOMMS 32
+
+int main (int argc, char **argv)
+{
+ char *filename = NULL;
+ char *logname = NULL;
+ char *url = NULL;
+ char *serv = NULL;
+ int mode = -1;
+ int nbcomms = 0;
+ comm_t comm_list[MAXCOMMS] = {0};
+ int reverse = 0;
+
+ /* get basename */
+ char *ptr = progname = argv[0];
+ while (*ptr) {
+ if ((*ptr == '/') || (*ptr == '\\')) {
+ progname = ptr + 1;
+ }
+ ptr++;
+ }
+
+ /* process argument */
+ while (argc-- > 1) {
+ char *arg = *(++argv);
+ if (arg[0] != '-') {
+ filename = arg;
+ continue;
+ }
+ char c = arg[1];
+ switch (c) {
+ case 'l':
+ arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
+ if (arg == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("%s: log file not specified\n", progname));
+ return 1;
+ }
+ logname = arg;
+ break;
+ case 'n':
+ reverse = 1;
+ break;
+ case 'r':
+ arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
+ if (arg == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("%s: receiver url not specified\n", progname));
+ return 1;
+ }
+ url = arg;
+ mode = 0;
+ break;
+ case 's':
+ arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
+ if (arg == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("%s: service name not specified\n", progname));
+ return 1;
+ }
+ serv = arg;
+ break;
+ case 't':
+ arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
+ if (arg == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("%s: transmitter url not specified\n", progname));
+ return 1;
+ }
+ url = arg;
+ mode = 1;
+ break;
+ case 'v':
+ arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
+ if (arg == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("%s: verbose level not specified\n", progname));
+ return 1;
+ }
+ CHANGE_VERBOSE_LEVEL (mapec, atoi (arg));
+ break;
+ case 'h':
+ default:
+ printf ("usage: %s [-h] [-n] [-l log] [-r url] [-s serv] [-t url] [-v level] [file]\n", progname);
+ return (c != 'h');
+ }
+
+ /* store service info */
+ if (mode != -1) {
+ int id = -1;
+ comm_t *comm = NULL;
+ for (int i = 0; i < MAXCOMMS; i++) {
+ comm = comm_list + i;
+ if ((comm->serv) && (strcmp (serv, comm->serv) == 0)) {
+ id = i;
+ break;
+ }
+ }
+ if ((id == -1) && (nbcomms < MAXCOMMS)) {
+ id = nbcomms++;
+ comm = comm_list + id;
+ }
+ if (id == -1) {
+ VERBOSE (mapec, ERROR, PRINTF ("can't connect on url '%s'\n", url));
+ return -1;
+ }
+ if (comm->serv == NULL) {
+ comm->serv = strdup (serv);
+ }
+ if (mode == reverse) {
+ free (comm->rxurl);
+ comm->rxurl = strdup (url);
+ } else {
+ free (comm->txurl);
+ comm->txurl = strdup (url);
+ }
+ mode = -1;
+ }
+ }
+
+ /* checks */
+ if (nbcomms == 0) {
+ VERBOSE (mapec, ERROR, PRINTF ("no communication channel\n"));
+ return -1;
+ }
+
+ /* init communication channel */
+ for (int i = 0; i < nbcomms; i++) {
+ comm_t *comm = comm_list + i;
+ if (comm->serv) {
+ if ((comm->rxurl == NULL) || (comm->txurl == NULL)) {
+ VERBOSE (mapec, ERROR, PRINTF ("missing an url (%s|%s)\n", comm->rxurl, comm->txurl));
+ return -1;
+ }
+ comm->fid = MAPEC_Connect (comm->rxurl, comm->txurl);
+ if (comm->fid < 0) {
+ VERBOSE (mapec, ERROR, PRINTF ("can't open communication for %s %s %s\n", comm->serv, comm->rxurl, comm->txurl));
+ return -1;
+ }
+ }
+ }
+
+ /* open script file */
+ FILE *fid = stdin;
+ if (filename != NULL) {
+ fid = fopen (filename, "r");
+ if (fid == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("can't open script file '%s' for reading\n", filename));
+ return -1;
+ }
+ }
+ char *script = read_stream (fid, NULL);
+ if (fid != stdin) {
+ fclose (fid);
+ }
+ if (script == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("no script read\n"));
+ return -1;
+ }
+
+ /* open log file */
+ FILE *log = NULL;
+ if (logname != NULL) {
+ if (strcmp (logname, "-") == 0) {
+ log = stdout;
+ } else {
+ log = fopen (logname, "w");
+ if (log == NULL) {
+ VERBOSE (mapec, ERROR, PRINTF ("can't open log file '%s' for writing\n", logname));
+ return -1;
+ }
+ }
+ }
+
+ /* signals */
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ /* main loop */
+ int rc = 0;
+ ptr = script;
+ while (*ptr != '\0') {
+
+ /* read line */
+ char *line = ptr;
+ TEST_CHARS (ptr, "\n\r", 1);
+ *ptr++ = '\0';
+
+ /* clean line */
+ TEST_CHARS (line, " \t", 0);
+ if ((*line == '\0') || (*line == '#')) {
+ continue;
+ }
+
+ /* analyse line */
+ mode = -1;
+ if (*line == 'R') {
+ mode = 0 ^ reverse;
+ } else if (*line == 'T') {
+ mode = 1 ^ reverse;
+ } else if (strncmp (line, "SLEEP", 5) == 0) {
+ int duration = atoi (line + 5);
+ VERBOSE (mapec, INFO, PRINTF ("sleep %dms\n", duration));
+ usleep (duration * 1000);
+ continue;
+ }
+ if (mode == -1) {
+ VERBOSE (mapec, WARNING, PRINTF ("unrecognize line '%s'\n", line));
+ continue;
+ }
+
+ /* find service */
+ comm_t *comm = NULL;
+ int offset = 1;
+ int i;
+ for (i = 0; i < nbcomms; i++) {
+ comm_t *c = comm_list + i;
+ VERBOSE (mapec, TRACE, PRINTF ("test %s\n", c->serv));
+ if (strncmp (line + offset, c->serv, strlen (c->serv)) == 0) {
+ comm = c;
+ offset += strlen (c->serv);
+ break;
+ }
+ }
+ if (comm == NULL) {
+ VERBOSE (mapec, TRACE, PRINTF ("no MAPEC found '%s'\n", line));
+ continue;
+ }
+ VERBOSE (mapec, DEBUG, PRINTF ("work with %c[%s]\n", mode ? 'T' : 'R', comm->serv));
+
+ /* get values */
+ char *tmp = line + offset;
+ TEST_CHARS (tmp, " \t", 0);
+ if (strncmp (tmp, "PAYLOAD", 3) != 0) {
+ VERBOSE (mapec, WARNING, PRINTF ("can't parse line '%s' (%s)\n", line, tmp));
+ continue;
+ }
+ tmp += 7;
+ TEST_CHARS (tmp, " \t=", 0);
+ uint8_t payload[MAXPAYLOAD] = {0};
+ int len = parse_array (tmp, payload, MAXPAYLOAD);
+ if (len == 0) {
+ VERBOSE (mapec, WARNING, PRINTF ("can't parse line '%s'\n", line));
+ continue;
+ }
+
+ VERBOSE (mapec, TRACE, PRINTF ("payload length: %d\n", len));
+
+ /* transmit */
+ if (mode == 1) {
+ int txlen = MAPEC_Send (comm->fid, payload, len);
+ /* check payload */
+ if (txlen != len) {
+ VERBOSE (mapec, WARNING, PRINTF ("T%s: payloads differed %d/%d\n", comm->serv, len, txlen));
+ } else {
+ VERBOSE (mapec, INFO, PRINTF ("T%s: payloads matched [%d]\n", comm->serv, txlen));
+ }
+
+ if (log) {
+ print_message (log, comm->serv, 1, payload, txlen);
+ }
+ } else { /* receive */
+ uint8_t rxpayload[MAXPAYLOAD] = {0};
+ int rxlen = MAPEC_Receive (comm->fid, rxpayload, MAXPAYLOAD);
+
+ /* check payload */
+ if ((rxlen != len) || ((memcmp (rxpayload, payload, rxlen) != 0))) {
+ VERBOSE (mapec, WARNING, PRINTF ("R%s: payloads differed %d/%d\n", comm->serv, len, rxlen));
+ } else {
+ VERBOSE (mapec, INFO, PRINTF ("R%s: payloads matched [%d]\n", comm->serv, rxlen));
+ }
+
+ if (log) {
+ print_message (log, comm->serv, 0, rxpayload, rxlen);
+ }
+ }
+ }
+
+ /* cleaning */
+ free (script);
+ while (nbcomms) {
+ comm_t *comm = comm_list + --nbcomms;
+ MAPEC_Close (comm->fid);
+ free (comm->serv);
+ free (comm->rxurl);
+ free (comm->txurl);
+ }
+ if (log) {
+ fclose (log);
+ }
+
+ return rc + GET_VERBOSE_ERRORS (mapec);
+}
+
+/* test: mapec_valid.exe -h | grep usage */
+/* test: mapec_valid.exe -l 2>&1 | grep 'log file not specified' */
+/* test: mapec_valid.exe -r 2>&1 | grep 'receiver url not specified' */
+/* test: mapec_valid.exe -s 2>&1 | grep 'service name not specified' */
+/* test: mapec_valid.exe -t 2>&1 | grep 'transmitter url not specified' */
+/* test: mapec_valid.exe -v 2>&1 | grep 'verbose level url not specified' */
+
+/* test: mapec_valid.exe -s UDP o -r udp://localhost:1234 2>&1 | grep 'missing an url' */
+/* test: echo | mapec_valid.exe -s UDP -r udp://localhost:1234 -t udp://localhost:1235 */
+/* test: mapec_valid.exe -s UDP -r udp://localhost:1234 -t udp://localhost:1235 script-udp.marep & mapec_valid.exe -n -s UDP -r udp://localhost:1234 -t udp://localhost:1235 script-udp.marep */
+/* test: mapec_valid.exe -s TUN0 -r tun://tun0 2>&1 | grep 'missing an url' */
+/* test: echo | mapec_valid.exe -s TUN0 -r tun://tun0 -t tun://1.2.3.4 */
+
+/* vim: set ts=4 sw=4 si et: */