From 7a0ed097e72100efe3a513844af09ed6ebc64d8f Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Wed, 20 Sep 2023 18:50:50 +0200 Subject: [PATCH 1/3] Add SigMF file format support --- include/fileformat.h | 16 ++ include/microtar.h | 95 +++++++++ include/samp_grab.h | 4 +- include/sigmf.h | 67 ++++++ src/CMakeLists.txt | 2 + src/fileformat.c | 57 ++++- src/microtar.c | 426 ++++++++++++++++++++++++++++++++++++++ src/r_api.c | 32 ++- src/rtl_433.c | 21 +- src/samp_grab.c | 103 ++++++--- src/sigmf.c | 482 +++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1274 insertions(+), 31 deletions(-) create mode 100644 include/microtar.h create mode 100644 include/sigmf.h create mode 100644 src/microtar.c create mode 100644 src/sigmf.c diff --git a/include/fileformat.h b/include/fileformat.h index 4911de293..cc9af50ec 100644 --- a/include/fileformat.h +++ b/include/fileformat.h @@ -27,6 +27,7 @@ enum file_type { F_FLOAT = 1 << 1, F_1CH = 1 << 4, F_2CH = 2 << 4, + F_W4 = 4 << 8, F_W8 = 8 << 8, F_W12 = 12 << 8, F_W16 = 16 << 8, @@ -56,6 +57,8 @@ enum file_type { F_CS32 = F_2CH | F_SIGNED | F_INT | F_W32, F_F32 = F_1CH | F_SIGNED | F_FLOAT | F_W32, F_CF32 = F_2CH | F_SIGNED | F_FLOAT | F_W32, + F_F64 = F_1CH | F_SIGNED | F_FLOAT | F_W64, + F_CF64 = F_2CH | F_SIGNED | F_FLOAT | F_W64, // compound types CU8_IQ = F_CU8 | F_IQ, CS8_IQ = F_CS8 | F_IQ, @@ -72,14 +75,21 @@ enum file_type { PULSE_OOK = F_OOK, }; +enum container_type { + FILEFMT_RAW = 0, + FILEFMT_SIGMF = 1, +}; + typedef struct { uint32_t format; uint32_t raw_format; + uint32_t container; uint32_t center_frequency; uint32_t sample_rate; char const *spec; char const *path; FILE *file; + void *file_aux; } file_info_t; /// Clear all file info. @@ -135,4 +145,10 @@ void file_info_check_write(file_info_t *info); /// @return a string describing the format char const *file_info_string(file_info_t *info); +/// Return a SigMF datset type string for the format. +char const *file_info_to_sigmf_type(file_info_t *info); + +/// Parse a SigMF datset type string to a format. +uint32_t file_info_from_sigmf_type(char const *sigmf_datatype); + #endif /* INCLUDE_FILEFORMAT_H_ */ diff --git a/include/microtar.h b/include/microtar.h new file mode 100644 index 000000000..98c1a52f3 --- /dev/null +++ b/include/microtar.h @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2017 rxi + * modified 2023 by Christian Zuckschwerdt + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See `microtar.c` for details. + */ + +#ifndef MICROTAR_H +#define MICROTAR_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#define MTAR_VERSION "0.1.0" + +enum { + MTAR_ESUCCESS = 0, + MTAR_EFAILURE = -1, + MTAR_EOPENFAIL = -2, + MTAR_EREADFAIL = -3, + MTAR_EWRITEFAIL = -4, + MTAR_ESEEKFAIL = -5, + MTAR_EBADCHKSUM = -6, + MTAR_ENULLRECORD = -7, + MTAR_ENOTFOUND = -8 +}; + +enum { + MTAR_TREG = '0', + MTAR_TLNK = '1', + MTAR_TSYM = '2', + MTAR_TCHR = '3', + MTAR_TBLK = '4', + MTAR_TDIR = '5', + MTAR_TFIFO = '6', + MTAR_TCONT = '7', /* reserved */ + MTAR_TXHD = 'x', /* Extended header referring to the next file in the archive */ + MTAR_TXGL = 'g' /* Global extended header */ +}; + +typedef struct { + unsigned mode; + unsigned owner; + unsigned group; + unsigned size; + unsigned mtime; + unsigned type; + unsigned devmajor; /* USTAR ext. */ + unsigned devminor; /* USTAR ext. */ + char name[101]; + char linkname[101]; + char uname[33]; /* USTAR ext. */ + char gname[33]; /* USTAR ext. */ +} mtar_header_t; + + +typedef struct mtar_t mtar_t; + +struct mtar_t { + void *stream; + unsigned pos; + unsigned remaining_data; + unsigned last_header; +}; + + +const char* mtar_strerror(int err); + +int mtar_open(mtar_t *tar, const char *filename, const char *mode); +int mtar_close(mtar_t *tar); + +int mtar_seek(mtar_t *tar, unsigned pos); +int mtar_rewind(mtar_t *tar); +int mtar_next(mtar_t *tar); +int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h); +int mtar_read_header(mtar_t *tar, mtar_header_t *h); +int mtar_read_data(mtar_t *tar, void *ptr, unsigned size); + +int mtar_write_header(mtar_t *tar, const mtar_header_t *h); +int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size); +int mtar_write_dir_header(mtar_t *tar, const char *name); +int mtar_write_data(mtar_t *tar, const void *data, unsigned size); +int mtar_finalize(mtar_t *tar); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/samp_grab.h b/include/samp_grab.h index 4e0d21523..43913e927 100644 --- a/include/samp_grab.h +++ b/include/samp_grab.h @@ -15,6 +15,8 @@ #include typedef struct samp_grab { + int sg_fileformat; ///< Signal grabber format: 0=raw, 1=SigMF + uint32_t *frequency; uint32_t *samp_rate; int *sample_size; @@ -26,7 +28,7 @@ typedef struct samp_grab { unsigned sg_len; } samp_grab_t; -samp_grab_t *samp_grab_create(unsigned size); +samp_grab_t *samp_grab_create(unsigned size, int sg_fileformat); void samp_grab_free(samp_grab_t *g); diff --git a/include/sigmf.h b/include/sigmf.h new file mode 100644 index 000000000..e584e3462 --- /dev/null +++ b/include/sigmf.h @@ -0,0 +1,67 @@ +/** @file + SigMF file read and write support. + + Copyright (C) 2023 Christian Zuckschwerdt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#ifndef INCLUDE_SIGMF_H_ +#define INCLUDE_SIGMF_H_ + +#include +#include + +#include "microtar.h" + +/** A data structure for SigMF reader/writer related fields. +*/ +typedef struct sigmf { + char *datatype; ///< meta data + uint32_t sample_rate; ///< meta data + char *recorder; ///< meta data + char *description; ///< meta data + uint32_t first_sample_start; ///< meta data + uint32_t first_frequency; ///< meta data + + //FILE *file; ///< data stream output + uint32_t data_len; ///< data size, if known beforehand + uint32_t data_offset; ///< data offset in the file + mtar_t mtar; ///< base tar file +} sigmf_t; + +/** Check if the given path is a valid .sigmf file name. +*/ +int sigmf_valid_filename(char const *p); + +/** A simple SigMF reader. + + Opens the file at @p path, reads the meta data and seeks to the data offset. + + Collections are not supported and a warning will be reported if encountered. + Multiple streams per file are not supported and a warning will be reported if encountered. +*/ +int sigmf_reader_open(sigmf_t *sigmf, char const *path); + +/** Closes the SigMF reader. +*/ +int sigmf_reader_close(sigmf_t *sigmf); + +/** A simple SigMF writer. + + Creates the file at @p path, writes the meta data and seeks to the data offset. +*/ +int sigmf_writer_open(sigmf_t *sigmf, char const *path, int overwrite); + +/** Finalizes writing and closes the SigMF writer. + */ +int sigmf_writer_close(sigmf_t *sigmf); + +/** Frees SigMF data items, but not the struct itself. + */ +int sigmf_free_items(sigmf_t *sigmf); + +#endif /* INCLUDE_SIGMF_H_ */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index adfafa8e5..cade6325f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(r_433 STATIC jsmn.c list.c logger.c + microtar.c mongoose.c optparse.c output_file.c @@ -40,6 +41,7 @@ add_library(r_433 STATIC rfraw.c samp_grab.c sdr.c + sigmf.c term_ctl.c util.c write_sigrok.c diff --git a/src/fileformat.c b/src/fileformat.c index 4656705f3..d522dd775 100644 --- a/src/fileformat.c +++ b/src/fileformat.c @@ -94,6 +94,51 @@ char const *file_info_string(file_info_t *info) } } +char const *file_info_to_sigmf_type(file_info_t *info) +{ + switch (info->format) { + case CU8_IQ: return "cu8"; + case CS8_IQ: return "ci8"; + //case CU16_IQ: return "cu16_le"; + case CS16_IQ: return "ci16_le"; + //case CU32_IQ: return "cu32_le"; + //case CS32_IQ: return "ci32_le"; + case CF32_IQ: return "cf32_le"; + //case CF64_IQ: return "cf64_le"; + //case U8_IQ: return "ru8"; + //case S8_IQ: return "ri8"; + //case U16_IQ: return "ru16_le"; + //case S16_IQ: return "ri16_le"; + //case U32_IQ: return "ru32_le"; + //case S32_IQ: return "ri32_le"; + //case F32_IQ: return "rf32_le"; + //case F64_IQ: return "rf64_le"; + default: return "Unknown"; + } +} + +uint32_t file_info_from_sigmf_type(char const *sigmf_datatype) +{ + if (!sigmf_datatype) return 0; + else if (!strcmp("cu8", sigmf_datatype)) return F_CU8; + else if (!strcmp("ci8", sigmf_datatype)) return F_CS8; + else if (!strcmp("cu16_le", sigmf_datatype)) return F_CU16; + else if (!strcmp("ci16_le", sigmf_datatype)) return F_CS16; + else if (!strcmp("cu32_le", sigmf_datatype)) return F_CU32; + else if (!strcmp("ci32_le", sigmf_datatype)) return F_CS32; + else if (!strcmp("cf32_le", sigmf_datatype)) return F_CF32; + else if (!strcmp("cf64_le", sigmf_datatype)) return F_CF64; + else if (!strcmp("ru8", sigmf_datatype)) return F_U8; + else if (!strcmp("ri8", sigmf_datatype)) return F_S8; + else if (!strcmp("ru16_le", sigmf_datatype)) return F_U16; + else if (!strcmp("ri16_le", sigmf_datatype)) return F_S16; + else if (!strcmp("ru32_le", sigmf_datatype)) return F_U32; + else if (!strcmp("ri32_le", sigmf_datatype)) return F_S32; + else if (!strcmp("rf32_le", sigmf_datatype)) return F_F32; + else if (!strcmp("rf64_le", sigmf_datatype)) return F_F64; + else return 0; +} + static void file_type_set_format(uint32_t *type, uint32_t val) { *type = (*type & 0xffff0000) | val; @@ -203,9 +248,10 @@ static void file_type(char const *filename, file_info_t *info) else if (len == 4 && !strncasecmp("cf32", t, 4)) file_type_set_format(&info->format, F_CF32); else if (len == 5 && !strncasecmp("cfile", t, 5)) file_type_set_format(&info->format, F_CF32); // compat else if (len == 5 && !strncasecmp("logic", t, 5)) file_type_set_content(&info->format, F_LOGIC); - else if (len == 3 && !strncasecmp("complex16u", t, 10)) file_type_set_format(&info->format, F_CU8); // compat - else if (len == 3 && !strncasecmp("complex16s", t, 10)) file_type_set_format(&info->format, F_CS8); // compat - else if (len == 4 && !strncasecmp("complex", t, 7)) file_type_set_format(&info->format, F_CF32); // compat + else if (len == 10 && !strncasecmp("complex16u", t, 10)) file_type_set_format(&info->format, F_CU8); // compat + else if (len == 10 && !strncasecmp("complex16s", t, 10)) file_type_set_format(&info->format, F_CS8); // compat + else if (len == 7 && !strncasecmp("complex", t, 7)) file_type_set_format(&info->format, F_CF32); // compat + else if (len == 5 && !strncasecmp("sigmf", t, 5)) info->container = FILEFMT_SIGMF; //else fprintf(stderr, "Skipping type (len %ld) %s\n", len, t); } else { p++; // skip non-alphanum char otherwise @@ -255,6 +301,11 @@ int file_info_parse_filename(file_info_t *info, char const *filename) return 0; } + //if (sigmf_valid_filename(filename)) { + // // just set the container type + // // other info needs to be read later + //} + info->spec = filename; char const *p = last_plain_colon(filename); diff --git a/src/microtar.c b/src/microtar.c new file mode 100644 index 000000000..59b6920f5 --- /dev/null +++ b/src/microtar.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2017 rxi + * modified 2023 by Christian Zuckschwerdt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "microtar.h" + +typedef struct { + char name[100]; + char mode[8]; + char owner[8]; + char group[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char type; + char linkname[100]; + char magic[6]; /* USTAR */ + char version[2]; /* USTAR */ + char uname[32]; /* USTAR */ + char gname[32]; /* USTAR */ + char devmajor[8]; /* USTAR */ + char devminor[8]; /* USTAR */ + char prefix[155]; /* USTAR */ + char _padding[12]; +} mtar_raw_header_t; + +#define TMAGIC "ustar" /* ustar and a null */ +#define TMAGLEN 6 +#define TVERSION "00" /* 00 and no null */ +#define TVERSLEN 2 +#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */ + + +/* A safe variant of strncpy. */ +/* Copies only len-1 chars but always zero terminates. */ +static char * safe_strcpy(char *dst, const char *src, size_t len) { + dst[len - 1] = '\0'; + return strncpy(dst, src, len - 1); +} + + +// Tar represents all its numbers as octal +static unsigned parse_octal(char const *str, unsigned maxlen, unsigned *num) { + unsigned val = 0; + unsigned i = 0; + for (; i < maxlen && str[i] == ' '; ++i); + for (; i < maxlen && str[i] >= '0' && str[i] <= '7'; ++i) { + val *= 8; + val += str[i] - '0'; + } + if (num) { + *num = val; + } + return 0; +} + + +static unsigned round_up(unsigned n, unsigned incr) { + return n + (incr - n % incr) % incr; +} + + +static unsigned checksum(const mtar_raw_header_t* rh) { + unsigned i; + unsigned char *p = (unsigned char*) rh; + unsigned res = 256; /* 8 chars of value 32 (space char) */ + for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) { + res += p[i]; + } + for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) { + res += p[i]; + } + return res; +} + + +static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) { + unsigned chksum1, chksum2; + + /* If the checksum starts with a null byte we assume the record is NULL */ + if (*rh->checksum == '\0') { + return MTAR_ENULLRECORD; + } + + /* Build and compare checksum */ + chksum1 = checksum(rh); + parse_octal(rh->checksum, sizeof(rh->checksum), &chksum2); + if (chksum1 != chksum2) { + return MTAR_EBADCHKSUM; + } + + /* Load raw header into header */ + parse_octal(rh->mode, sizeof(rh->mode), &h->mode); + parse_octal(rh->owner, sizeof(rh->owner), &h->owner); + parse_octal(rh->group, sizeof(rh->group), &h->group); + parse_octal(rh->devmajor, sizeof(rh->devmajor), &h->devmajor); + parse_octal(rh->devminor, sizeof(rh->devminor), &h->devminor); + parse_octal(rh->size, sizeof(rh->size), &h->size); + parse_octal(rh->mtime, sizeof(rh->mtime), &h->mtime); + h->type = rh->type; + h->name[sizeof(h->name) - 1] = '\0'; + memcpy(h->name, rh->name, sizeof(rh->name)); + h->linkname[sizeof(h->linkname) - 1] = '\0'; + memcpy(h->linkname, rh->linkname, sizeof(rh->linkname)); + h->uname[sizeof(h->uname) - 1] = '\0'; + memcpy(h->uname, rh->uname, sizeof(rh->uname)); + h->gname[sizeof(h->gname) - 1] = '\0'; + memcpy(h->gname, rh->gname, sizeof(rh->gname)); + + return MTAR_ESUCCESS; +} + + +static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) { + unsigned chksum; + + /* Load header into raw header */ + memset(rh, 0, sizeof(*rh)); + sprintf(rh->mode, "%07o", h->mode & 07777777); + sprintf(rh->owner, "%07o", h->owner & 07777777); + sprintf(rh->group, "%07o", h->group & 07777777); + sprintf(rh->devmajor, "%07o", h->devmajor & 07777777); + sprintf(rh->devminor, "%07o", h->devminor & 07777777); + sprintf(rh->size, "%011o", h->size); + sprintf(rh->mtime, "%011o", h->mtime); + rh->type = h->type ? h->type : MTAR_TREG; + strncpy(rh->name, h->name, sizeof(rh->name)); + strncpy(rh->linkname, h->linkname, sizeof(rh->linkname)); + strncpy(rh->uname, h->uname, sizeof(rh->uname)); + strncpy(rh->gname, h->gname, sizeof(rh->gname)); + + memcpy(rh->magic, TMAGIC, TMAGLEN); + memcpy(rh->version, TVERSION, TVERSLEN); + + /* Calculate and write checksum */ + chksum = checksum(rh); + sprintf(rh->checksum, "%07o", chksum); + rh->checksum[7] = ' '; + + return MTAR_ESUCCESS; +} + + +const char* mtar_strerror(int err) { + switch (err) { + case MTAR_ESUCCESS : return "success"; + case MTAR_EFAILURE : return "failure"; + case MTAR_EOPENFAIL : return "could not open"; + case MTAR_EREADFAIL : return "could not read"; + case MTAR_EWRITEFAIL : return "could not write"; + case MTAR_ESEEKFAIL : return "could not seek"; + case MTAR_EBADCHKSUM : return "bad checksum"; + case MTAR_ENULLRECORD : return "null record"; + case MTAR_ENOTFOUND : return "file not found"; + } + return "unknown error"; +} + + +static int file_write(mtar_t *tar, const void *data, unsigned size) { + unsigned res = fwrite(data, 1, size, tar->stream); + return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL; +} + +static int file_read(mtar_t *tar, void *data, unsigned size) { + unsigned res = fread(data, 1, size, tar->stream); + return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL; +} + +static int file_seek(mtar_t *tar, unsigned offset) { + int res = fseek(tar->stream, offset, SEEK_SET); + return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL; +} + +static int file_close(mtar_t *tar) { + fclose(tar->stream); + return MTAR_ESUCCESS; +} + + +static int tread(mtar_t *tar, void *data, unsigned size) { + int err = file_read(tar, data, size); + tar->pos += size; + return err; +} + + +static int twrite(mtar_t *tar, const void *data, unsigned size) { + int err = file_write(tar, data, size); + tar->pos += size; + return err; +} + + +static int write_null_bytes(mtar_t *tar, int n) +{ + char nul = '\0'; + for (int i = 0; i < n; i++) { + int err = twrite(tar, &nul, 1); + if (err) { + return err; + } + } + return MTAR_ESUCCESS; +} + + +int mtar_open(mtar_t *tar, const char *filename, const char *mode) { + mtar_header_t h; + + /* Init tar struct and functions */ + memset(tar, 0, sizeof(*tar)); + + /* Assure mode is always binary */ + if ( strchr(mode, 'r') ) mode = "rb"; + if ( strchr(mode, 'w') ) mode = "wb"; + if ( strchr(mode, 'a') ) mode = "ab"; + /* Open file */ + tar->stream = fopen(filename, mode); + if (!tar->stream) { + return MTAR_EOPENFAIL; + } + /* Read first header to check it is valid if mode is `r` */ + if (*mode == 'r') { + int err = mtar_read_header(tar, &h); + if (err != MTAR_ESUCCESS) { + mtar_close(tar); + return err; + } + } + + /* Return ok */ + return MTAR_ESUCCESS; +} + + +int mtar_close(mtar_t *tar) { + return file_close(tar); +} + + +int mtar_seek(mtar_t *tar, unsigned pos) { + int err = file_seek(tar, pos); + tar->pos = pos; + return err; +} + + +int mtar_rewind(mtar_t *tar) { + tar->remaining_data = 0; + tar->last_header = 0; + return mtar_seek(tar, 0); +} + + +int mtar_next(mtar_t *tar) { + int err, n; + mtar_header_t h; + /* Load header */ + err = mtar_read_header(tar, &h); + if (err) { + return err; + } + /* Seek to next record */ + n = round_up(h.size, 512) + sizeof(mtar_raw_header_t); + return mtar_seek(tar, tar->pos + n); +} + + +int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) { + int err; + mtar_header_t header; + /* Start at beginning */ + err = mtar_rewind(tar); + if (err) { + return err; + } + /* Iterate all files until we hit an error or find the file */ + while ( (err = mtar_read_header(tar, &header)) == MTAR_ESUCCESS ) { + if ( !strcmp(header.name, name) ) { + if (h) { + *h = header; + } + return MTAR_ESUCCESS; + } + mtar_next(tar); + } + /* Return error */ + if (err == MTAR_ENULLRECORD) { + err = MTAR_ENOTFOUND; + } + return err; +} + + +int mtar_read_header(mtar_t *tar, mtar_header_t *h) { + int err; + mtar_raw_header_t rh; + /* Save header position */ + tar->last_header = tar->pos; + /* Read raw header */ + err = tread(tar, &rh, sizeof(rh)); + if (err) { + return err; + } + /* Seek back to start of header */ + err = mtar_seek(tar, tar->last_header); + if (err) { + return err; + } + /* Load raw header into header struct and return */ + return raw_to_header(h, &rh); +} + + +int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) { + int err; + /* If we have no remaining data then this is the first read, we get the size, + * set the remaining data and seek to the beginning of the data */ + if (tar->remaining_data == 0) { + mtar_header_t h; + /* Read header */ + err = mtar_read_header(tar, &h); + if (err) { + return err; + } + /* Seek past header and init remaining data */ + err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t)); + if (err) { + return err; + } + tar->remaining_data = h.size; + } + /* Read data */ + err = tread(tar, ptr, size); + if (err) { + return err; + } + tar->remaining_data -= size; + /* If there is no remaining data we've finished reading and seek back to the + * header */ + if (tar->remaining_data == 0) { + return mtar_seek(tar, tar->last_header); + } + return MTAR_ESUCCESS; +} + + +int mtar_write_header(mtar_t *tar, const mtar_header_t *h) { + mtar_raw_header_t rh; + /* Build raw header and write */ + header_to_raw(&rh, h); + tar->remaining_data = h->size; + return twrite(tar, &rh, sizeof(rh)); +} + + +int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) { + mtar_header_t h; + /* Build header */ + memset(&h, 0, sizeof(h)); + safe_strcpy(h.name, name, sizeof(h.name)); + h.size = size; + h.type = MTAR_TREG; + h.mode = 0664; + /* Write header */ + return mtar_write_header(tar, &h); +} + + +int mtar_write_dir_header(mtar_t *tar, const char *name) { + mtar_header_t h; + /* Build header */ + memset(&h, 0, sizeof(h)); + safe_strcpy(h.name, name, sizeof(h.name)); + h.type = MTAR_TDIR; + h.mode = 0775; + /* Write header */ + return mtar_write_header(tar, &h); +} + + +int mtar_write_data(mtar_t *tar, const void *data, unsigned size) { + int err; + /* Write data */ + err = twrite(tar, data, size); + if (err) { + return err; + } + tar->remaining_data -= size; + /* Write padding if we've written all the data for this file */ + if (tar->remaining_data == 0) { + return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos); + } + return MTAR_ESUCCESS; +} + + +int mtar_finalize(mtar_t *tar) { + /* Write two NULL records */ + return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2); +} diff --git a/src/r_api.c b/src/r_api.c index fe49b94c4..f718d0521 100644 --- a/src/r_api.c +++ b/src/r_api.c @@ -37,6 +37,7 @@ #include "output_trigger.h" #include "output_rtltcp.h" #include "write_sigrok.h" +#include "sigmf.h" #include "mongoose.h" #include "compat_time.h" #include "logger.h" @@ -1171,6 +1172,13 @@ void close_dumpers(struct r_cfg *cfg) { for (void **iter = cfg->demod->dumper.elems; iter && *iter; ++iter) { file_info_t *dumper = *iter; + + if (dumper->container == FILEFMT_SIGMF) { + sigmf_writer_close((sigmf_t *)dumper->file_aux); + sigmf_free_items((sigmf_t *)dumper->file_aux); + dumper->file = NULL; + } + if (dumper->file && (dumper->file != stdout)) { fclose(dumper->file); dumper->file = NULL; @@ -1208,7 +1216,29 @@ void add_dumper(r_cfg_t *cfg, char const *spec, int overwrite) list_push(&cfg->demod->dumper, dumper); file_info_parse_filename(dumper, spec); - if (strcmp(dumper->path, "-") == 0) { /* Write samples to stdout */ + // Open the output + if (dumper->container == FILEFMT_SIGMF) { + sigmf_t *sigmf = calloc(1, sizeof(*sigmf)); + if (!sigmf) + FATAL_CALLOC("add_dumper()"); + + sigmf->datatype = strdup(file_info_to_sigmf_type(dumper)); + sigmf->sample_rate = dumper->sample_rate; + sigmf->recorder = strdup("rtl_433"); + sigmf->description = strdup("Sample written by rtl_433"); + sigmf->first_sample_start = 0; + sigmf->first_frequency = dumper->center_frequency; + sigmf->data_len = 0; // Unknown length + + int r = sigmf_writer_open(sigmf, dumper->path, overwrite); + if (r) { + fprintf(stderr, "Failed to open %s\n", dumper->path); + return; + } + dumper->file = sigmf->mtar.stream; + dumper->file_aux = sigmf; + } + else if (strcmp(dumper->path, "-") == 0) { /* Write samples to stdout */ dumper->file = stdout; #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); diff --git a/src/rtl_433.c b/src/rtl_433.c index 5a7294087..150c5734c 100644 --- a/src/rtl_433.c +++ b/src/rtl_433.c @@ -53,6 +53,7 @@ #include "logger.h" #include "fatal.h" #include "write_sigrok.h" +#include "sigmf.h" #include "mongoose.h" #ifdef _WIN32 @@ -999,6 +1000,11 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg) case 'S': if (!arg) usage(1); + int grab_fileformat = 0; + if (!strncasecmp(arg, "sigmf", 5)) { + grab_fileformat = 1; + arg = arg_param(arg); + } if (strcasecmp(arg, "all") == 0) cfg->grab_mode = 1; else if (strcasecmp(arg, "unknown") == 0) @@ -1008,7 +1014,7 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg) else cfg->grab_mode = atobv(arg, 1); if (cfg->grab_mode && !cfg->demod->samp_grab) - cfg->demod->samp_grab = samp_grab_create(SIGNAL_GRABBER_BUFFER); + cfg->demod->samp_grab = samp_grab_create(SIGNAL_GRABBER_BUFFER, grab_fileformat); break; case 'm': fprintf(stderr, "sample mode option is deprecated.\n"); @@ -1804,7 +1810,18 @@ int main(int argc, char **argv) { cfg->center_frequency = demod->load_info.center_frequency ? demod->load_info.center_frequency : cfg->frequency[0]; FILE *in_file; - if (strcmp(demod->load_info.path, "-") == 0) { // read samples from stdin + if (demod->load_info.container == FILEFMT_SIGMF) { // unpack tar + sigmf_t sigmf = {0}; + int rc = sigmf_reader_open(&sigmf, cfg->in_filename); + // handle errors + print_logf(LOG_INFO, "Input", "Opening returned \"%d\"", rc); + // copy meta + demod->load_info.format = CU8_IQ; + cfg->samp_rate = sigmf.sample_rate; + cfg->center_frequency = sigmf.first_frequency; + in_file = sigmf.mtar.stream; + } + else if (strcmp(demod->load_info.path, "-") == 0) { // read samples from stdin in_file = stdin; cfg->in_filename = ""; } else { diff --git a/src/samp_grab.c b/src/samp_grab.c index 0f4b7baeb..83591934e 100644 --- a/src/samp_grab.c +++ b/src/samp_grab.c @@ -9,6 +9,8 @@ (at your option) any later version. */ +#include "samp_grab.h" + #include #include #include @@ -24,10 +26,10 @@ #include #endif -#include "samp_grab.h" +#include "sigmf.h" #include "fatal.h" -samp_grab_t *samp_grab_create(unsigned size) +samp_grab_t *samp_grab_create(unsigned size, int fileformat) { samp_grab_t *g; g = calloc(1, sizeof(*g)); @@ -36,6 +38,7 @@ samp_grab_t *samp_grab_create(unsigned size) return NULL; // NOTE: returns NULL on alloc failure. } + g->sg_fileformat = fileformat; g->sg_size = size; g->sg_counter = 1; @@ -93,22 +96,10 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) if (!g->sg_buf) return; - unsigned end_pos, start_pos, signal_bsize, wlen, wrest; - char f_name[64] = {0}; - FILE *fp; - - char *format = *g->sample_size == 2 ? "cu8" : "cs16"; double freq_mhz = *g->frequency / 1000000.0; double rate_khz = *g->samp_rate / 1000.0; - while (1) { - sprintf(f_name, "g%03u_%gM_%gk.%s", g->sg_counter, freq_mhz, rate_khz, format); - g->sg_counter++; - if (access(f_name, F_OK) == -1) { - break; - } - } - signal_bsize = *g->sample_size * grab_len; + unsigned signal_bsize = *g->sample_size * grab_len; signal_bsize += BLOCK_SIZE - (signal_bsize % BLOCK_SIZE); if (signal_bsize > g->sg_len) { @@ -117,13 +108,14 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) } // relative end in bytes from current sg_index down - end_pos = *g->sample_size * grab_end; + unsigned end_pos = *g->sample_size * grab_end; if (g->sg_index >= end_pos) end_pos = g->sg_index - end_pos; else end_pos = g->sg_size - end_pos + g->sg_index; // end_pos is now absolute in sg_buf + unsigned start_pos; if (end_pos >= signal_bsize) start_pos = end_pos - signal_bsize; else @@ -132,19 +124,31 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) //fprintf(stderr, "signal_bsize = %d - sg_index = %d\n", signal_bsize, g->sg_index); //fprintf(stderr, "start_pos = %d - buffer_size = %d\n", start_pos, g->sg_size); + unsigned wlen = signal_bsize; + unsigned wrest = 0; + if (start_pos + signal_bsize > g->sg_size) { + wlen = g->sg_size - start_pos; + wrest = signal_bsize - wlen; + } + + if (!g->sg_fileformat) { + char *datatype = *g->sample_size == 2 ? "cu8" : "cs16"; + + char f_name[64] = {0}; + while (1) { + snprintf(f_name, sizeof(f_name), "g%03u_%gM_%gk.%s", g->sg_counter, freq_mhz, rate_khz, datatype); + g->sg_counter++; + if (access(f_name, F_OK) == -1) { + break; + } + } + fprintf(stderr, "*** Saving signal to file %s (%u samples, %u bytes)\n", f_name, grab_len, signal_bsize); - fp = fopen(f_name, "wb"); + FILE *fp = fopen(f_name, "wb"); if (!fp) { fprintf(stderr, "Failed to open %s\n", f_name); return; } - - wlen = signal_bsize; - wrest = 0; - if (start_pos + signal_bsize > g->sg_size) { - wlen = g->sg_size - start_pos; - wrest = signal_bsize - wlen; - } //fprintf(stderr, "*** Writing data from %d, len %d\n", start_pos, wlen); fwrite(&g->sg_buf[start_pos], 1, wlen, fp); @@ -154,4 +158,55 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) } fclose(fp); + } + + if (g->sg_fileformat) { + char *datatype = *g->sample_size == 2 ? "cu8" : "ci16_le"; + + char f_name[64] = {0}; + while (1) { + snprintf(f_name, sizeof(f_name), "g%03u_%gM_%gk.%s", g->sg_counter, freq_mhz, rate_khz, "sigmf"); + g->sg_counter++; + if (access(f_name, F_OK) == -1) { + break; + } + } + fprintf(stderr, "*** Saving signal to file %s (%u samples, %u bytes)\n", f_name, grab_len, signal_bsize); + + sigmf_t sigmf = { + .datatype = strdup(datatype), + .sample_rate = *g->samp_rate, + .recorder = strdup("rtl_433"), + .description = strdup("Sample grabbed by rtl_433"), + .first_sample_start = 0, + .first_frequency = *g->frequency, + .data_len = signal_bsize, + }; + + int r = sigmf_writer_open(&sigmf, f_name, 0); + if (r) { + fprintf(stderr, "Failed to open %s\n", f_name); + return; + } + + // fprintf(stderr, "*** Writing data from %d, len %d\n", start_pos, wlen); + fwrite(&g->sg_buf[start_pos], 1, wlen, sigmf.mtar.stream); + + if (wrest) { + // fprintf(stderr, "*** Writing data from %d, len %d\n", 0, wrest); + fwrite(&g->sg_buf[0], 1, wrest, sigmf.mtar.stream); + } + + r = sigmf_writer_close(&sigmf); + if (r) { + fprintf(stderr, "Failed to close %s\n", f_name); + return; + } + + r = sigmf_free_items(&sigmf); + if (r) { + fprintf(stderr, "Failed to free SigMF writer %s\n", f_name); + return; + } + } } diff --git a/src/sigmf.c b/src/sigmf.c new file mode 100644 index 000000000..919553115 --- /dev/null +++ b/src/sigmf.c @@ -0,0 +1,482 @@ +/** @file + SigMF basic file read and write support. + + Copyright (C) 2023 Christian Zuckschwerdt + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +/* +NOTES: +V7, USTAR, PAX, GNU, OLDGNU, PAX +ignore pax files of object types: 'x' and 'g' + +https://mort.coffee/home/tar/ +https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html + +*/ + +/* +00000000 62 35 31 32 2e 62 69 6e 00 00 00 00 00 00 00 00 |b512.bin........| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00000060 00 00 00 00 30 30 30 36 34 34 20 00 30 30 30 37 |....000644 .0007| +00000070 36 35 20 00 30 30 30 30 32 34 20 00 30 30 30 30 |65 .000024 .0000| +00000080 30 30 30 31 30 30 30 20 31 34 35 30 31 35 36 30 |0001000 14501560| +00000090 35 32 33 20 30 31 32 33 33 35 00 20 30 00 00 00 |523 012335. 0...| +000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00000100 00 75 73 74 61 72 00 30 30 7a 61 6e 79 00 00 00 |.ustar.00zany...| +00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000120 00 00 00 00 00 00 00 00 00 73 74 61 66 66 00 00 |.........staff..| +00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000140 00 00 00 00 00 00 00 00 00 30 30 30 30 30 30 20 |.........000000 | +00000150 00 30 30 30 30 30 30 20 00 00 00 00 00 00 00 00 |.000000 ........| +00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00000800 +*/ + +#include "sigmf.h" + +#include +#include + +#ifdef _WIN32 + #include + #include + #ifdef _MSC_VER + #define F_OK 0 + #endif +#endif +#ifndef _MSC_VER + #include +#endif + +#ifdef _MSC_VER + #ifndef strncasecmp // Microsoft Visual Studio + #define strncasecmp _strnicmp + #endif +#else + #include +#endif + +#include "logger.h" +#include "fatal.h" +#include "microtar.h" +#include "jsmn.h" + +// Helper + +static int path_has_extension(char const *path, char const *ext) +{ + if (!path || !ext) { + return 0; + } + size_t path_len = strlen(path); + size_t ext_len = strlen(ext); + return path_len >= ext_len && !strncasecmp(ext, path + path_len - ext_len, ext_len); +} + +static int jsoneq(char const *json, jsmntok_t const *tok, char const *s) +{ + if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; +} + +static char *jsondup(char const *json, jsmntok_t const *tok) +{ + int len = tok->end - tok->start; + char *p = malloc(len + 1); + if (!p) { + WARN_MALLOC("jsondup()"); + return NULL; + } + p[len] = '\0'; + return memcpy(p, json + tok->start, len); +} + +/* +{ + "global": { + "core:datatype": "cu8", + "core:sample_rate": 250000, + "core:recorder": "rtl_433" + "core:description": "bresser_3ch/gfile001.cu8" + "core:version": "1.0.0" + }, + "captures": [ + { + "core:sample_start": 0, + "core:frequency": 433920000 + } + ], + "annotations": [] +} +*/ +static int json_parse(sigmf_t *sigmf, char const *json) +{ + int i = 0; + int r; + jsmn_parser p; + jsmntok_t t[128]; // We expect around 20 tokens + + jsmn_init(&p); + r = jsmn_parse(&p, json, strlen(json), t, sizeof(t) / sizeof(t[0])); + if (r < 0) { + print_logf(LOG_WARNING, __func__, "Failed to parse JSON: %d", r); + return -1; + } + + // Expect the top-level element to be an object + if (r < 1 || t[0].type != JSMN_OBJECT) { + print_log(LOG_WARNING, __func__, "Object expected"); + return -1; + } + + //for (int j = 0; j < r; j++) { + // print_logf(LOG_WARNING, __func__, "token (%d): %.*s", t[j].size, t[j].end - t[j].start, json + t[j].start); + //} + + // Loop over all keys of the root object + for (int obj0_items = t[i].size; obj0_items > 0; obj0_items--) { + i++; + //print_logf(LOG_WARNING, __func__, "0 PARSING: %.*s", t[i].end - t[i].start, json + t[i].start); + if (jsoneq(json, &t[i], "global") == 0) { + i++; + // Expect the global element to be an object + if (i >= r || t[i].type != JSMN_OBJECT) { + print_log(LOG_WARNING, __func__, "Object expected"); + return -1; + } + // Loop over all keys of the global object + for (int obj1_items = t[i].size; obj1_items > 0; obj1_items--) { + i++; + //print_logf(LOG_WARNING, __func__, "1 PARSING: %.*s", t[i].end - t[i].start, json + t[i].start); + if (jsoneq(json, &t[i], "core:datatype") == 0) { + i++; + free(sigmf->datatype); + sigmf->datatype = jsondup(json, &t[i]); + printf("SigMF datatype: %s\n", sigmf->datatype); + } + else if (jsoneq(json, &t[i], "core:sample_rate") == 0) { + i++; + char *endptr = NULL; + double val = strtod(json + t[i].start, &endptr); + // compare endptr to t[i].end + sigmf->sample_rate = (uint32_t)val; + printf("SigMF sample_rate: %u\n", sigmf->sample_rate); + } + else if (jsoneq(json, &t[i], "core:recorder") == 0) { + i++; + free(sigmf->recorder); + sigmf->recorder = jsondup(json, &t[i]); + printf("SigMF recorder: %s\n", sigmf->recorder); + } + else if (jsoneq(json, &t[i], "core:description") == 0) { + i++; + free(sigmf->description); + sigmf->description = jsondup(json, &t[i]); + printf("SigMF description: %s\n", sigmf->description); + } + else if (jsoneq(json, &t[i], "core:version") == 0) { + i++; + if (jsoneq(json, &t[i], "1.0.0") != 0) { + print_logf(LOG_WARNING, __func__, "Expected version 1.0.0 but found: %.*s", t[i].end - t[i].start, json + t[i].start); + } + } + else { + print_logf(LOG_WARNING, __func__, "Unexpected key: %.*s", t[i].end - t[i].start, json + t[i].start); + } + } + } + else if (jsoneq(json, &t[i], "captures") == 0) { + i++; + // Expect the captures element to be an array + if (i >= r || t[i].type != JSMN_ARRAY) { + print_log(LOG_WARNING, __func__, "Array expected"); + return -1; + } + for (int obj1_items = t[i].size; obj1_items > 0; obj1_items--) { + i++; + + // Expect the capture elements to be an object + if (i >= r || t[i].type != JSMN_OBJECT) { + print_log(LOG_WARNING, __func__, "Object expected"); + return -1; + } + // Loop over all keys of a capture object + for (int obj2_items = t[i].size; obj2_items > 0; obj2_items--) { + i++; + if (jsoneq(json, &t[i], "core:sample_start") == 0) { + i++; + char *endptr = NULL; + uint32_t val = strtoul(json + t[i].start, &endptr, 10); + // compare endptr to t[i].end + sigmf->first_sample_start = val; + printf("SigMF first_sample_start: %u\n", sigmf->first_sample_start); + } + else if (jsoneq(json, &t[i], "core:global_index") == 0) { + i++; // skip value + } + else if (jsoneq(json, &t[i], "core:frequency") == 0) { + i++; + char *endptr = NULL; + double val = strtod(json + t[i].start, &endptr); + // compare endptr to t[i].end + sigmf->first_frequency = (uint32_t)val; + printf("SigMF first_frequency: %u\n", sigmf->first_frequency); + } + else if (jsoneq(json, &t[i], "core:datetime") == 0) { + i++; // skip value + } + else if (jsoneq(json, &t[i], "core:header_bytes") == 0) { + i++; // skip value + } + else { + print_logf(LOG_WARNING, __func__, "Unexpected key: %.*s", t[i].end - t[i].start, json + t[i].start); + } + } + } + } + else if (jsoneq(json, &t[i], "annotations") == 0) { + i++; + // Expect the annotations element to be an array + if (i >= r || t[i].type != JSMN_ARRAY) { + print_log(LOG_WARNING, __func__, "Array expected"); + return -1; + } + for (int obj1_items = t[i].size; obj1_items > 0; obj1_items--) { + i++; + + // Expect the annotation elements to be an object + if (i >= r || t[i].type != JSMN_OBJECT) { + print_log(LOG_WARNING, __func__, "Object expected"); + return -1; + } + // Loop over all keys of a annotation object + for (int obj2_items = t[i].size; obj2_items > 0; obj2_items--) { + i++; + // core:sample_start + // core:sample_count + // core:freq_lower_edge + // core:freq_upper_edge + // core:label + // core:generator + // core:comment + // core:uuid + i++; // TODO: implement value reader... + } + } + } + else { + print_logf(LOG_WARNING, __func__, "Unexpected key: %.*s", t[i].end - t[i].start, json + t[i].start); + } + } + + return 0; +} + +static int sigmf_write_meta(sigmf_t *sigmf, mtar_t *tar) +{ + (void)sigmf; + + // NOTE: we need e.g. "core:dataset": "samples.cu12" for uncommon formats + char json[1024] = {0}; + snprintf(json, sizeof(json), "{\ + \"global\" : {\ + \"core:datatype\" : \"%s\",\ + \"core:sample_rate\" : %u,\ + \"core:recorder\" : \"%s\",\ + \"core:version\" : \"1.0.0\"\ + },\ + \"captures\" : [\ + {\ + \"core:sample_start\" : %u,\ + \"core:frequency\" : %u\ + }\ + ],\ + \"annotations\" : []\ +}\ +", + sigmf->datatype, + sigmf->sample_rate, + sigmf->recorder, + sigmf->first_sample_start, + sigmf->first_frequency); + + size_t json_len = strlen(json); + mtar_write_file_header(tar, "foobar.sigmf-meta", json_len); + mtar_write_data(tar, json, json_len); + + return 0; +} + +// API + +int sigmf_valid_filename(char const *p) +{ + if (!p) { + return 0; + } + int len = strlen(p); + return len >= 6 && !strncasecmp(".sigmf", p + len - 6, 6); +} + +int sigmf_reader_open(sigmf_t *sigmf, char const *path) +{ + int is_sigmf = sigmf_valid_filename(path); + if (!is_sigmf){ + print_logf(LOG_WARNING, "Input", "SigMF input file must have .sigmf extension"); + // return -1; + } + + mtar_header_t h; + char *stream_name = NULL; + + // Open archive for reading + mtar_open(&sigmf->mtar, path, "r"); + + // Check all file names + while ((mtar_read_header(&sigmf->mtar, &h)) != MTAR_ENULLRECORD) { + print_logf(LOG_DEBUG, "Input", "SigMF input cotains: %s (%u bytes)", h.name, h.size); + + // Skip all non-regular files + if (h.type != MTAR_TREG) { + print_logf(LOG_WARNING, "Input", "SigMF input file contains a non-regular file"); + mtar_next(&sigmf->mtar); + continue; + } + + // Warn if collection is present + if (path_has_extension(h.name, ".sigmf-collection")) { + print_logf(LOG_WARNING, "Input", "SigMF input file contains a collection which is no supported"); + } + + // Grab the first stream + if (path_has_extension(h.name, ".sigmf-meta")) { + if (stream_name && strcmp(h.name, stream_name)) { + print_logf(LOG_NOTICE, "Input", "SigMF input file contains updated meta file"); + free(stream_name); + stream_name = NULL; + // free(meta_json); + } + if (stream_name) { + print_logf(LOG_WARNING, "Input", "SigMF input file contains multiple streams"); + } + else { + stream_name = strdup(h.name); + // read meta + char *p = calloc(1, h.size + 1); + mtar_read_data(&sigmf->mtar, p, h.size); + // printf("%s", p); + // decode JSON and copy meta + json_parse(sigmf, p); + free(p); + } + + // Continue to read through all tar entries to check for errors + } + + mtar_next(&sigmf->mtar); + } + + if (!stream_name) { + print_logf(LOG_ERROR, "Input", "SigMF input file with no streams"); + return -1; + } + + // Mangle stream name + size_t name_len = strlen(stream_name); + strncpy(stream_name + name_len - 4, "data", 4); + + // Load and print contents of file "foo.sigmf-data" + // NOTE: finds the first instance but should theoretically use the last one + int r = mtar_find(&sigmf->mtar, stream_name, &h); + if (r) { + print_logf(LOG_ERROR, "Input", "SigMF input file with no stream data"); + return -1; + } + + // Seek past header + size_t sizeof_mtar_raw_header_t = 512; // sizeof(mtar_raw_header_t) + int err = mtar_seek(&sigmf->mtar, sigmf->mtar.pos + sizeof_mtar_raw_header_t); + if (err) { + return err; + } + + return 0; +} + +int sigmf_reader_close(sigmf_t *sigmf) +{ + return fclose(sigmf->mtar.stream); +} + +int sigmf_writer_open(sigmf_t *sigmf, char const *path, int overwrite) +{ + if (access(path, F_OK) == 0) { + if (!overwrite) { + print_logf(LOG_FATAL, "Input", "SigMF output file %s already exists, exiting", path); + exit(1); + } + else { + print_logf(LOG_NOTICE, "Input", "SigMF output file %s already exists, overwriting", path); + } + } + + // Open archive for writing + mtar_open(&sigmf->mtar, path, "w"); + + sigmf_write_meta(sigmf, &sigmf->mtar); + + mtar_write_file_header(&sigmf->mtar, "foobar.sigmf-data", sigmf->data_len); + //mtar_write_data(&sigmf->mtar, data, size); + + sigmf->data_offset = ftell(sigmf->mtar.stream); + + return 0; + } + +int sigmf_writer_close(sigmf_t *sigmf) +{ + char const padding[512] = {0}; + long f_pos = ftell(sigmf->mtar.stream); + long d_len = f_pos - sigmf->data_offset; + /* Write padding if needed */ + if (f_pos % 512) { + fwrite(padding, 1, 512 - f_pos % 512, sigmf->mtar.stream); + } + /* Write two NULL records */ + fwrite(padding, 1, 512, sigmf->mtar.stream); + fwrite(padding, 1, 512, sigmf->mtar.stream); + + // Set the file length header if needed + if (sigmf->data_len != d_len) { + // Write header again + mtar_t tar; + int res = fseek(sigmf->mtar.stream, sigmf->data_offset - 512, SEEK_SET); + mtar_write_file_header(&sigmf->mtar, "foobar.sigmf-data", d_len); + } + // return mtar_close(&sigmf->mtar); + return fclose(sigmf->mtar.stream); +} + +int sigmf_free_items(sigmf_t *sigmf) +{ + // TODO: check if base tar file is free + //sigmf->mtar... + free(sigmf->datatype); + sigmf->datatype = NULL; + free(sigmf->recorder); + sigmf->recorder = NULL; + free(sigmf->description); + sigmf->description = NULL; + return 0; +} From 997d8c67061f4d5904f6614251b7c7580f0ca967 Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Fri, 22 Sep 2023 20:15:16 +0200 Subject: [PATCH 2/3] Fix alloc checks --- src/output_rtltcp.c | 2 +- src/r_api.c | 19 ++++++++++++++++--- src/samp_grab.c | 21 +++++++++++++++++---- src/sigmf.c | 25 ++++++++++++++++++++----- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/output_rtltcp.c b/src/output_rtltcp.c index 60459400d..360204f67 100644 --- a/src/output_rtltcp.c +++ b/src/output_rtltcp.c @@ -536,7 +536,7 @@ struct raw_output *raw_output_rtltcp_create(const char *host, const char *port, #else -struct raw_output *raw_output_rtltcp_create(const char *host, const char *port, r_cfg_t *cfg) +struct raw_output *raw_output_rtltcp_create(const char *host, const char *port, char const *opts, r_cfg_t *cfg) { UNUSED(host); UNUSED(port); diff --git a/src/r_api.c b/src/r_api.c index f718d0521..b4e636c9b 100644 --- a/src/r_api.c +++ b/src/r_api.c @@ -1222,10 +1222,23 @@ void add_dumper(r_cfg_t *cfg, char const *spec, int overwrite) if (!sigmf) FATAL_CALLOC("add_dumper()"); - sigmf->datatype = strdup(file_info_to_sigmf_type(dumper)); + char *datatype = strdup(file_info_to_sigmf_type(dumper)); + if (!datatype) { + WARN_STRDUP("add_dumper()"); + } + char *recorder = strdup("rtl_433"); + if (!recorder) { + WARN_STRDUP("add_dumper()"); + } + char *description = strdup("Sample written by rtl_433"); + if (!description) { + WARN_STRDUP("add_dumper()"); + } + + sigmf->datatype = datatype; sigmf->sample_rate = dumper->sample_rate; - sigmf->recorder = strdup("rtl_433"); - sigmf->description = strdup("Sample written by rtl_433"); + sigmf->recorder = recorder; + sigmf->description = description; sigmf->first_sample_start = 0; sigmf->first_frequency = dumper->center_frequency; sigmf->data_len = 0; // Unknown length diff --git a/src/samp_grab.c b/src/samp_grab.c index 83591934e..be052a815 100644 --- a/src/samp_grab.c +++ b/src/samp_grab.c @@ -161,7 +161,7 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) } if (g->sg_fileformat) { - char *datatype = *g->sample_size == 2 ? "cu8" : "ci16_le"; + char const *datatype_s = *g->sample_size == 2 ? "cu8" : "ci16_le"; char f_name[64] = {0}; while (1) { @@ -173,11 +173,24 @@ void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end) } fprintf(stderr, "*** Saving signal to file %s (%u samples, %u bytes)\n", f_name, grab_len, signal_bsize); + char *datatype = strdup(datatype_s); + if (!datatype) { + WARN_STRDUP("add_dumper()"); + } + char *recorder = strdup("rtl_433"); + if (!recorder) { + WARN_STRDUP("add_dumper()"); + } + char *description = strdup("Sample grabbed by rtl_433"); + if (!description) { + WARN_STRDUP("add_dumper()"); + } + sigmf_t sigmf = { - .datatype = strdup(datatype), + .datatype = datatype, .sample_rate = *g->samp_rate, - .recorder = strdup("rtl_433"), - .description = strdup("Sample grabbed by rtl_433"), + .recorder = recorder, + .description = description, .first_sample_start = 0, .first_frequency = *g->frequency, .data_len = signal_bsize, diff --git a/src/sigmf.c b/src/sigmf.c index 919553115..934cfceab 100644 --- a/src/sigmf.c +++ b/src/sigmf.c @@ -372,8 +372,16 @@ int sigmf_reader_open(sigmf_t *sigmf, char const *path) } else { stream_name = strdup(h.name); + if (!stream_name) { + WARN_STRDUP("add_dumper()"); + } // read meta char *p = calloc(1, h.size + 1); + if (!p) { + WARN_MALLOC("sigmf_reader_open()"); + free(stream_name); + return -1; + } mtar_read_data(&sigmf->mtar, p, h.size); // printf("%s", p); // decode JSON and copy meta @@ -383,6 +391,7 @@ int sigmf_reader_open(sigmf_t *sigmf, char const *path) // Continue to read through all tar entries to check for errors } + free(stream_name); mtar_next(&sigmf->mtar); } @@ -423,11 +432,11 @@ int sigmf_writer_open(sigmf_t *sigmf, char const *path, int overwrite) { if (access(path, F_OK) == 0) { if (!overwrite) { - print_logf(LOG_FATAL, "Input", "SigMF output file %s already exists, exiting", path); + print_logf(LOG_FATAL, "Output", "SigMF output file %s already exists, exiting", path); exit(1); } else { - print_logf(LOG_NOTICE, "Input", "SigMF output file %s already exists, overwriting", path); + print_logf(LOG_NOTICE, "Output", "SigMF output file %s already exists, overwriting", path); } } @@ -460,9 +469,15 @@ int sigmf_writer_close(sigmf_t *sigmf) // Set the file length header if needed if (sigmf->data_len != d_len) { // Write header again - mtar_t tar; - int res = fseek(sigmf->mtar.stream, sigmf->data_offset - 512, SEEK_SET); - mtar_write_file_header(&sigmf->mtar, "foobar.sigmf-data", d_len); + int r = fseek(sigmf->mtar.stream, sigmf->data_offset - 512, SEEK_SET); + if (r) { + print_logf(LOG_ERROR, "Output", "SigMF output file seek failed"); + } else { + r = mtar_write_file_header(&sigmf->mtar, "foobar.sigmf-data", d_len); + if (r) { + print_logf(LOG_ERROR, "Output", "SigMF output file header rewrite failed"); + } + } } // return mtar_close(&sigmf->mtar); return fclose(sigmf->mtar.stream); From 172c0f62d4b1c9e8f993deea3ff378e954ed1990 Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Fri, 22 Sep 2023 20:26:19 +0200 Subject: [PATCH 3/3] Fix sticky brace warning --- src/sigmf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sigmf.c b/src/sigmf.c index 934cfceab..7e1d9665e 100644 --- a/src/sigmf.c +++ b/src/sigmf.c @@ -332,7 +332,7 @@ int sigmf_valid_filename(char const *p) int sigmf_reader_open(sigmf_t *sigmf, char const *path) { int is_sigmf = sigmf_valid_filename(path); - if (!is_sigmf){ + if (!is_sigmf) { print_logf(LOG_WARNING, "Input", "SigMF input file must have .sigmf extension"); // return -1; }