mirror of
https://github.com/merbanan/rtl_433.git
synced 2026-04-23 19:17:03 -04:00
570 lines
16 KiB
C
570 lines
16 KiB
C
/*
|
|
* A general structure for extracting hierarchical data from the devices;
|
|
* typically key-value pairs, but allows for more rich data as well.
|
|
*
|
|
* Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "data.h"
|
|
|
|
typedef void* (*array_elementwise_import_fn)(void*);
|
|
typedef void* (*array_element_release_fn)(void*);
|
|
typedef void* (*value_release_fn)(void*);
|
|
|
|
typedef struct {
|
|
/* what is the element size when put inside an array? */
|
|
int array_element_size;
|
|
|
|
/* is the element boxed (ie. behind a pointer) when inside an array?
|
|
if it's not boxed ("unboxed"), json dumping function needs to make
|
|
a copy of the value beforehand, because the dumping function only
|
|
deals with boxed values.
|
|
*/
|
|
_Bool array_is_boxed;
|
|
|
|
/* function for importing arrays. strings are specially handled (as they
|
|
are copied deeply), whereas other arrays are just copied shallowly
|
|
(but copied nevertheles) */
|
|
array_elementwise_import_fn array_elementwise_import;
|
|
|
|
/* a function for releasing an element when put in an array; integers
|
|
* don't need to be released, while ie. strings and arrays do. */
|
|
array_element_release_fn array_element_release;
|
|
|
|
/* a function for releasing a value. everything needs to be released. */
|
|
value_release_fn value_release;
|
|
} data_meta_type_t;
|
|
|
|
struct data_printer_context;
|
|
|
|
typedef struct data_printer {
|
|
void (*print_data)(struct data_printer_context *printer_ctx, data_t *data, char *format, FILE *file);
|
|
void (*print_array)(struct data_printer_context *printer_ctx, data_array_t *data, char *format, FILE *file);
|
|
void (*print_string)(struct data_printer_context *printer_ctx, const char *data, char *format, FILE *file);
|
|
void (*print_double)(struct data_printer_context *printer_ctx, double data, char *format, FILE *file);
|
|
void (*print_int)(struct data_printer_context *printer_ctx, int data, char *format, FILE *file);
|
|
} data_printer_t;
|
|
|
|
typedef struct data_printer_context {
|
|
data_printer_t *printer;
|
|
void *aux;
|
|
} data_printer_context_t;
|
|
|
|
static data_meta_type_t dmt[DATA_COUNT] = {
|
|
// DATA_DATA
|
|
{ .array_element_size = sizeof(data_t*),
|
|
.array_is_boxed = true,
|
|
.array_elementwise_import = NULL,
|
|
.array_element_release = (array_element_release_fn) data_free,
|
|
.value_release = (value_release_fn) data_free },
|
|
|
|
// DATA_INT
|
|
{ .array_element_size = sizeof(int),
|
|
.array_is_boxed = false,
|
|
.array_elementwise_import = NULL,
|
|
.array_element_release = NULL,
|
|
.value_release = (value_release_fn) free },
|
|
|
|
// DATA_DOUBLE
|
|
{ .array_element_size = sizeof(double),
|
|
.array_is_boxed = false,
|
|
.array_elementwise_import = NULL,
|
|
.array_element_release = NULL,
|
|
.value_release = (value_release_fn) free },
|
|
|
|
// DATA_STRING
|
|
{ .array_element_size = sizeof(char*),
|
|
.array_is_boxed = true,
|
|
.array_elementwise_import = (array_elementwise_import_fn) strdup,
|
|
.array_element_release = (array_element_release_fn) free,
|
|
.value_release = (value_release_fn) free },
|
|
|
|
// DATA_ARRAY
|
|
{ .array_element_size = sizeof(data_array_t*),
|
|
.array_is_boxed = true,
|
|
.array_elementwise_import = NULL,
|
|
.array_element_release = (array_element_release_fn) data_array_free ,
|
|
.value_release = (value_release_fn) data_array_free },
|
|
};
|
|
|
|
static void print_json_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file);
|
|
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *data, char *format, FILE *file);
|
|
static void print_json_string(data_printer_context_t *printer_ctx, const char *data, char *format, FILE *file);
|
|
static void print_json_double(data_printer_context_t *printer_ctx, double data, char *format, FILE *file);
|
|
static void print_json_int(data_printer_context_t *printer_ctx, int data, char *format, FILE *file);
|
|
|
|
static void print_kv_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file);
|
|
static void print_kv_string(data_printer_context_t *printer_ctx, const char *data, char *format, FILE *file);
|
|
static void print_kv_double(data_printer_context_t *printer_ctx, double data, char *format, FILE *file);
|
|
static void print_kv_int(data_printer_context_t *printer_ctx, int data, char *format, FILE *file);
|
|
|
|
typedef struct {
|
|
const char **fields;
|
|
int data_recursion;
|
|
const char* separator;
|
|
} data_csv_aux_t;
|
|
|
|
static void print_csv_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file);
|
|
static void print_csv_string(data_printer_context_t *printer_ctx, const char *data, char *format, FILE *file);
|
|
|
|
data_printer_t data_json_printer = {
|
|
.print_data = print_json_data,
|
|
.print_array = print_json_array,
|
|
.print_string = print_json_string,
|
|
.print_double = print_json_double,
|
|
.print_int = print_json_int
|
|
};
|
|
|
|
data_printer_t data_kv_printer = {
|
|
.print_data = print_kv_data,
|
|
.print_array = print_json_array,
|
|
.print_string = print_kv_string,
|
|
.print_double = print_kv_double,
|
|
.print_int = print_kv_int
|
|
};
|
|
|
|
data_printer_t data_csv_printer = {
|
|
.print_data = print_csv_data,
|
|
.print_array = print_json_array,
|
|
.print_string = print_csv_string,
|
|
.print_double = print_json_double,
|
|
.print_int = print_json_int
|
|
};
|
|
|
|
static _Bool import_values(void* dst, void* src, int num_values, data_type_t type) {
|
|
int element_size = dmt[type].array_element_size;
|
|
array_elementwise_import_fn import = dmt[type].array_elementwise_import;
|
|
if (import) {
|
|
for (int i = 0; i < num_values; ++i) {
|
|
void *copy = import(*(void**)(src + element_size * i));
|
|
if (!copy) {
|
|
--i;
|
|
while (i >= 0) {
|
|
free(*(void**)(dst + element_size * i));
|
|
--i;
|
|
}
|
|
return false;
|
|
} else {
|
|
*((char**) dst + i) = copy;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
memcpy(dst, src, element_size * num_values);
|
|
}
|
|
return true; // error is returned early
|
|
}
|
|
|
|
data_array_t *data_array(int num_values, data_type_t type, void *values) {
|
|
data_array_t *array = calloc(1, sizeof(data_array_t));
|
|
if (array) {
|
|
int element_size = dmt[type].array_element_size;
|
|
array->values = calloc(num_values, element_size);
|
|
if (!array->values)
|
|
goto alloc_error;
|
|
if (!import_values(array->values, values, num_values, type))
|
|
goto alloc_error;
|
|
|
|
array->num_values = num_values;
|
|
array->type = type;
|
|
}
|
|
return array;
|
|
|
|
alloc_error:
|
|
if (array)
|
|
free(array->values);
|
|
free(array);
|
|
return NULL;
|
|
}
|
|
|
|
data_t *data_make(const char *key, const char *pretty_key, ...) {
|
|
va_list ap;
|
|
data_type_t type;
|
|
va_start(ap, pretty_key);
|
|
|
|
data_t *first = NULL;
|
|
data_t *prev = NULL;
|
|
char* format = false;
|
|
type = va_arg(ap, data_type_t);
|
|
do {
|
|
data_t *current;
|
|
void *value = NULL;
|
|
|
|
switch (type) {
|
|
case DATA_FORMAT : {
|
|
format = strdup(va_arg(ap, char*));
|
|
if (!format)
|
|
goto alloc_error;
|
|
type = va_arg(ap, data_type_t);
|
|
continue;
|
|
} break;
|
|
case DATA_COUNT : {
|
|
assert(0);
|
|
} break;
|
|
case DATA_DATA : {
|
|
value = va_arg(ap, data_t*);
|
|
} break;
|
|
case DATA_INT : {
|
|
value = malloc(sizeof(int));
|
|
if (value)
|
|
*(int*) value = va_arg(ap, int);
|
|
} break;
|
|
case DATA_DOUBLE : {
|
|
value = malloc(sizeof(double));
|
|
if (value)
|
|
*(double*) value = va_arg(ap, double);
|
|
} break;
|
|
case DATA_STRING : {
|
|
value = strdup(va_arg(ap, char*));
|
|
} break;
|
|
case DATA_ARRAY : {
|
|
value = va_arg(ap, data_t*);
|
|
} break;
|
|
}
|
|
|
|
// also some null arguments are mapped to an alloc error;
|
|
// that's ok, because they originate (typically..) from
|
|
// an alloc error anyway
|
|
if (!value)
|
|
goto alloc_error;
|
|
|
|
current = calloc(1, sizeof(*current));
|
|
if (!current)
|
|
goto alloc_error;
|
|
if (prev)
|
|
prev->next = current;
|
|
|
|
current->key = strdup(key);
|
|
if (!current->key)
|
|
goto alloc_error;
|
|
current->pretty_key = strdup(pretty_key ? pretty_key : key);
|
|
if (!current->pretty_key)
|
|
goto alloc_error;
|
|
current->type = type;
|
|
current->format = format;
|
|
current->value = value;
|
|
current->next = NULL;
|
|
|
|
prev = current;
|
|
if (!first)
|
|
first = current;
|
|
|
|
key = va_arg(ap, const char*);
|
|
if (key) {
|
|
pretty_key = va_arg(ap, const char*);
|
|
type = va_arg(ap, data_type_t);
|
|
format = NULL;
|
|
}
|
|
} while (key);
|
|
va_end(ap);
|
|
|
|
return first;
|
|
|
|
alloc_error:
|
|
data_free(first);
|
|
va_end(ap);
|
|
return NULL;
|
|
}
|
|
|
|
void data_array_free(data_array_t *array) {
|
|
array_element_release_fn release = dmt[array->type].array_element_release;
|
|
if (release) {
|
|
int element_size = dmt[array->type].array_element_size;
|
|
for (int i = 0; i < array->num_values; ++i)
|
|
release(*(void**)(array->values + element_size * i));
|
|
}
|
|
free(array->values);
|
|
free(array);
|
|
}
|
|
|
|
void data_free(data_t *data) {
|
|
while (data) {
|
|
data_t *prev_data = data;
|
|
if (dmt[data->type].value_release)
|
|
dmt[data->type].value_release(data->value);
|
|
free(data->pretty_key);
|
|
free(data->key);
|
|
data = data->next;
|
|
free(prev_data);
|
|
}
|
|
}
|
|
|
|
void data_print(data_t* data, FILE *file, data_printer_t *printer, void *aux)
|
|
{
|
|
data_printer_context_t ctx = {
|
|
.printer = printer,
|
|
.aux = aux
|
|
};
|
|
ctx.printer->print_data(&ctx, data, NULL, file);
|
|
fputc('\n', file);
|
|
}
|
|
|
|
/* JSON printer */
|
|
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *array, char *format, FILE *file);
|
|
|
|
static void print_value(data_printer_context_t *printer_ctx, FILE *file, data_type_t type, void *value, char *format) {
|
|
switch (type) {
|
|
case DATA_FORMAT :
|
|
case DATA_COUNT : {
|
|
assert(0);
|
|
} break;
|
|
case DATA_DATA : {
|
|
printer_ctx->printer->print_data(printer_ctx, value, format, file);
|
|
} break;
|
|
case DATA_INT : {
|
|
printer_ctx->printer->print_int(printer_ctx, *(int*) value, format, file);
|
|
} break;
|
|
case DATA_DOUBLE : {
|
|
printer_ctx->printer->print_double(printer_ctx, *(double*) value, format, file);
|
|
} break;
|
|
case DATA_STRING : {
|
|
printer_ctx->printer->print_string(printer_ctx, value, format, file);
|
|
} break;
|
|
case DATA_ARRAY : {
|
|
printer_ctx->printer->print_array(printer_ctx, value, format, file);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void print_json_array(data_printer_context_t *printer_ctx, data_array_t *array, char *format, FILE *file) {
|
|
int element_size = dmt[array->type].array_element_size;
|
|
char buffer[element_size];
|
|
fprintf(file, "[");
|
|
for (int c = 0; c < array->num_values; ++c) {
|
|
if (c)
|
|
fprintf(file, ", ");
|
|
if (!dmt[array->type].array_is_boxed) {
|
|
memcpy(buffer, (void**)(array->values + element_size * c), element_size);
|
|
print_value(printer_ctx, file, array->type, buffer, format);
|
|
} else {
|
|
print_value(printer_ctx, file, array->type, *(void**)(array->values + element_size * c), format);
|
|
}
|
|
}
|
|
fprintf(file, "]");
|
|
}
|
|
|
|
static void print_json_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file)
|
|
{
|
|
_Bool separator = false;
|
|
fputc('{', file);
|
|
while (data) {
|
|
if (separator)
|
|
fprintf(file, ", ");
|
|
printer_ctx->printer->print_string(printer_ctx, data->key, NULL, file);
|
|
fprintf(file, " : ");
|
|
print_value(printer_ctx, file, data->type, data->value, data->format);
|
|
separator = true;
|
|
data = data->next;
|
|
}
|
|
fputc('}', file);
|
|
}
|
|
|
|
static void print_json_string(data_printer_context_t *printer_ctx, const char *str, char *format, FILE *file) {
|
|
fprintf(file, "\"");
|
|
while (*str) {
|
|
if (*str == '"')
|
|
fputc('\\', file);
|
|
fputc(*str, file);
|
|
++str;
|
|
}
|
|
fprintf(file, "\"");
|
|
}
|
|
|
|
static void print_json_double(data_printer_context_t *printer_ctx, double data, char *format, FILE *file)
|
|
{
|
|
fprintf(file, "%f", data);
|
|
}
|
|
|
|
static void print_json_int(data_printer_context_t *printer_ctx, int data, char *format, FILE *file)
|
|
{
|
|
fprintf(file, "%d", data);
|
|
}
|
|
|
|
/* Key-Value printer */
|
|
static void print_kv_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file)
|
|
{
|
|
_Bool separator = false;
|
|
_Bool was_labeled = false;
|
|
_Bool written_title = false;
|
|
while (data) {
|
|
_Bool labeled = data->pretty_key[0];
|
|
/* put a : between the first non-labeled and labeled */
|
|
if (separator) {
|
|
if (labeled && !was_labeled && !written_title) {
|
|
fprintf(file, ": ");
|
|
written_title = true;
|
|
separator = false;
|
|
} else {
|
|
if (was_labeled)
|
|
fprintf(file, ", ");
|
|
else
|
|
fprintf(file, " ");
|
|
}
|
|
}
|
|
fprintf(file, "%s", data->pretty_key);
|
|
if (labeled)
|
|
fputc(' ', file);
|
|
print_value(printer_ctx, file, data->type, data->value, data->format);
|
|
separator = true;
|
|
was_labeled = labeled;
|
|
data = data->next;
|
|
}
|
|
}
|
|
|
|
static void print_kv_double(data_printer_context_t *printer_ctx, double data, char *format, FILE *file)
|
|
{
|
|
fprintf(file, format ? format : "%f", data);
|
|
}
|
|
|
|
static void print_kv_int(data_printer_context_t *printer_ctx, int data, char *format, FILE *file)
|
|
{
|
|
fprintf(file, format ? format : "%d", data);
|
|
}
|
|
|
|
|
|
static void print_kv_string(data_printer_context_t *printer_ctx, const char *data, char *format, FILE *file)
|
|
{
|
|
fprintf(file, format ? format : "%s", data);
|
|
}
|
|
|
|
/* CSV printer; doesn't really support recursive data objects yes */
|
|
static void print_csv_data(data_printer_context_t *printer_ctx, data_t *data, char *format, FILE *file)
|
|
{
|
|
data_csv_aux_t *csv = printer_ctx->aux;
|
|
const char **fields = csv->fields;
|
|
int i;
|
|
|
|
if (csv->data_recursion)
|
|
return;
|
|
|
|
++csv->data_recursion;
|
|
for (i = 0; fields[i]; ++i) {
|
|
const char *key = fields[i];
|
|
data_t *iter, *found = NULL;
|
|
if (i) fprintf(file, "%s", csv->separator);
|
|
iter = data;
|
|
for (iter = data, found; !found && iter; iter = iter->next)
|
|
if (strcmp(iter->key, key) == 0)
|
|
found = iter;
|
|
|
|
if (found)
|
|
print_value(printer_ctx, file, found->type, found->value, found->format);
|
|
}
|
|
--csv->data_recursion;
|
|
}
|
|
|
|
static void print_csv_string(data_printer_context_t *printer_ctx, const char *str, char *format, FILE *file)
|
|
{
|
|
data_csv_aux_t *csv = printer_ctx->aux;
|
|
while (*str) {
|
|
if (strncmp(str, csv->separator, strlen(csv->separator)) == 0)
|
|
fputc('\\', file);
|
|
fputc(*str, file);
|
|
++str;
|
|
}
|
|
}
|
|
|
|
static int compare_strings(char** a, char** b)
|
|
{
|
|
return strcmp(*a, *b);
|
|
}
|
|
|
|
void *data_csv_init(const char **fields, int num_fields)
|
|
{
|
|
data_csv_aux_t *csv = calloc(1, sizeof(data_csv_aux_t));
|
|
int csv_fields = 0;
|
|
int i, j;
|
|
const char **allowed = NULL;
|
|
int *use_count = NULL;
|
|
int num_unique_fields;
|
|
if (!csv)
|
|
goto alloc_error;
|
|
|
|
csv->separator = ",";
|
|
|
|
allowed = calloc(num_fields, sizeof(const char*));
|
|
memcpy(allowed, fields, sizeof(const char*) * num_fields);
|
|
|
|
qsort(allowed, num_fields, sizeof(char*), (void*) compare_strings);
|
|
|
|
// overwrite duplicates
|
|
i = 0;
|
|
j = 0;
|
|
while (j < num_fields) {
|
|
while (j > 0 && j < num_fields &&
|
|
strcmp(allowed[j - 1], allowed[j]) == 0)
|
|
++j;
|
|
|
|
if (j < num_fields) {
|
|
allowed[i] = allowed[j];
|
|
++i;
|
|
++j;
|
|
|
|
}
|
|
}
|
|
num_unique_fields = i;
|
|
|
|
csv->fields = calloc(num_unique_fields + 1, sizeof(const char**));
|
|
if (!csv->fields)
|
|
goto alloc_error;
|
|
|
|
use_count = calloc(num_unique_fields, sizeof(*use_count));
|
|
if (!use_count)
|
|
goto alloc_error;
|
|
|
|
for (i = 0; i < num_fields; ++i) {
|
|
const char **field = bsearch(&fields[i], allowed, num_unique_fields, sizeof(const char*),
|
|
(void*) compare_strings);
|
|
int *field_use_count = use_count + (field - allowed);
|
|
if (field && !*field_use_count) {
|
|
csv->fields[csv_fields] = fields[i];
|
|
++csv_fields;
|
|
++*field_use_count;
|
|
}
|
|
}
|
|
csv->fields[csv_fields] = NULL;
|
|
free(allowed);
|
|
free(use_count);
|
|
|
|
// Output the CSV header
|
|
for (i = 0; csv->fields[i]; ++i) {
|
|
printf("%s%s", i > 0 ? csv->separator : "", csv->fields[i]);
|
|
}
|
|
printf("\n");
|
|
return csv;
|
|
|
|
alloc_error:
|
|
free(use_count);
|
|
free(allowed);
|
|
if (csv) free(csv->fields);
|
|
free(csv);
|
|
return NULL;
|
|
}
|
|
|
|
void data_csv_free(void *aux)
|
|
{
|
|
data_csv_aux_t *csv = aux;
|
|
free(csv->fields);
|
|
free(csv);
|
|
}
|