From b5bbefe0fcc8abcbbed7db2deecdf1705f4190ea Mon Sep 17 00:00:00 2001 From: Mazet Laurent Date: Fri, 23 May 2025 17:16:58 +0200 Subject: [PATCH] add a validation program --- mapec_valid.c | 504 +++++++++++++++++++++++++++++++++++++++++++++++ plaintext.txt | 3 + script-udp.marep | 10 + 3 files changed, 517 insertions(+) create mode 100644 mapec_valid.c create mode 100644 plaintext.txt create mode 100644 script-udp.marep diff --git a/mapec_valid.c b/mapec_valid.c new file mode 100644 index 0000000..14706a2 --- /dev/null +++ b/mapec_valid.c @@ -0,0 +1,504 @@ +/* + File name : mapec_valid.c + Projet : MERLIN + Date of creation : 2025/05/23 + Version : 1.0 + Copyright : Thales SIX + Author : Laurent Mazet + + Description : Minimal API for Packet Exchange Commmunication validation + program + + History : + - initial version +*/ + +/* depend: */ +/* cflags: */ +/* linker: mapec.o */ + +#include +#include +#include +#include +#include +#include +#include + +#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: */ diff --git a/plaintext.txt b/plaintext.txt new file mode 100644 index 0000000..8a1ef7c --- /dev/null +++ b/plaintext.txt @@ -0,0 +1,3 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque maximus euismod condimentum. Duis tempus elit sit amet justo molestie sodales. Quisque malesuada sodales tortor. Nunc a ornare justo. Vestibulum eu bibendum erat, at rutrum justo. Quisque pretium luctus pellentesque. Maecenas lectus nibh, dignissim sit amet justo non, sagittis auctor orci. + +Integer fringilla ipsum in sodales auctor. Phasellus magna quam, aliquet vitae nisl sit amet, gravida venenatis diam. Quisque at metus ornare, consectetur enim in, luctus neque. Quisque dignissim diam sit amet massa eleifend euismod. Phasellus nulla sem, feugiat ut bibendum nec, bibendum non odio. In pharetra, nibh vitae porta pharetra, dolor nunc fermentum neque, et lacinia tellus metus a ligula. Quisque aliquet ac leo a fringilla. Nunc porttitor vehicula ullamcorper. Ut vehicula lectus convallis neque mollis, et malesuada libero porta. diff --git a/script-udp.marep b/script-udp.marep new file mode 100644 index 0000000..9836487 --- /dev/null +++ b/script-udp.marep @@ -0,0 +1,10 @@ +# Test script + +TUDP PAYLOAD=@plaintext.txt +RUDP PAYLOAD=@plaintext.txt + +TUDP PAYLAOD="This\ is\ a\ text" +RUDP PAYLAOD="This\ is\ a\ text" + +TUDP PAYLAOD=01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10 +RUDP PAYLAOD=01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10 -- 2.30.2