mirror of
https://github.com/merbanan/rtl_433.git
synced 2026-04-23 02:57:07 -04:00
1011 lines
27 KiB
C
1011 lines
27 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 "limits.h"
|
|
// gethostname() needs _XOPEN_SOURCE 500 on unistd.h
|
|
#define _XOPEN_SOURCE 500
|
|
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
|
|
#undef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
|
|
#endif
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
|
|
#define SOCKET int
|
|
#define INVALID_SOCKET -1
|
|
#endif
|
|
|
|
#include <time.h>
|
|
|
|
#include "data.h"
|
|
|
|
#ifdef _WIN32
|
|
#define _POSIX_HOST_NAME_MAX 128
|
|
#undef close /* We only work with sockets here */
|
|
#define close(s) closesocket (s)
|
|
#define perror(str) ws2_perror (str)
|
|
|
|
static void ws2_perror (const char *str)
|
|
{
|
|
if (str && *str)
|
|
fprintf (stderr, "%s: ", str);
|
|
fprintf (stderr, "Winsock error %d.\n", WSAGetLastError());
|
|
}
|
|
#endif
|
|
|
|
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 nevertheless) */
|
|
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_output;
|
|
|
|
typedef struct data_output {
|
|
void (*print_data)(struct data_output *output, data_t *data, char *format);
|
|
void (*print_array)(struct data_output *output, data_array_t *data, char *format);
|
|
void (*print_string)(struct data_output *output, const char *data, char *format);
|
|
void (*print_double)(struct data_output *output, double data, char *format);
|
|
void (*print_int)(struct data_output *output, int data, char *format);
|
|
void (*output_free)(struct data_output *output);
|
|
FILE *file;
|
|
} data_output_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 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 **)((char *)src + element_size * i));
|
|
if (!copy) {
|
|
--i;
|
|
while (i >= 0) {
|
|
free(*(void **)((char *)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;
|
|
}
|
|
|
|
static data_t *vdata_make(data_t *first, const char *key, const char *pretty_key, va_list ap)
|
|
{
|
|
data_type_t type;
|
|
data_t *prev = first;
|
|
while (prev && prev->next)
|
|
prev = prev->next;
|
|
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);
|
|
return NULL;
|
|
}
|
|
|
|
data_t *data_make(const char *key, const char *pretty_key, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, pretty_key);
|
|
data_t *result = vdata_make(NULL, key, pretty_key, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
data_t *data_append(data_t *first, const char *key, const char *pretty_key, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, pretty_key);
|
|
data_t *result = vdata_make(first, key, pretty_key, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
data_t *data_prepend(data_t *first, const char *key, const char *pretty_key, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, pretty_key);
|
|
data_t *result = vdata_make(NULL, key, pretty_key, ap);
|
|
va_end(ap);
|
|
|
|
if (!result)
|
|
return first;
|
|
|
|
data_t *prev = result;
|
|
while (prev && prev->next)
|
|
prev = prev->next;
|
|
prev->next = first;
|
|
|
|
return result;
|
|
}
|
|
|
|
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 **)((char *)array->values + element_size * i));
|
|
}
|
|
free(array->values);
|
|
free(array);
|
|
}
|
|
|
|
data_t *data_retain(data_t *data)
|
|
{
|
|
if (data)
|
|
++data->retain;
|
|
return data;
|
|
}
|
|
|
|
void data_free(data_t *data)
|
|
{
|
|
if (data && data->retain) {
|
|
--data->retain;
|
|
return;
|
|
}
|
|
while (data) {
|
|
data_t *prev_data = data;
|
|
if (dmt[data->type].value_release)
|
|
dmt[data->type].value_release(data->value);
|
|
free(data->format);
|
|
free(data->pretty_key);
|
|
free(data->key);
|
|
data = data->next;
|
|
free(prev_data);
|
|
}
|
|
}
|
|
|
|
void data_output_print(data_output_t *output, data_t *data)
|
|
{
|
|
output->print_data(output, data, NULL);
|
|
if (output->file) {
|
|
fputc('\n', output->file);
|
|
fflush(output->file);
|
|
}
|
|
}
|
|
|
|
void data_output_free(data_output_t *output)
|
|
{
|
|
if (!output)
|
|
return;
|
|
output->output_free(output);
|
|
}
|
|
|
|
static void print_value(data_output_t *output, data_type_t type, void *value, char *format)
|
|
{
|
|
switch (type) {
|
|
case DATA_FORMAT:
|
|
case DATA_COUNT:
|
|
assert(0);
|
|
break;
|
|
case DATA_DATA:
|
|
output->print_data(output, value, format);
|
|
break;
|
|
case DATA_INT:
|
|
output->print_int(output, *(int *)value, format);
|
|
break;
|
|
case DATA_DOUBLE:
|
|
output->print_double(output, *(double *)value, format);
|
|
break;
|
|
case DATA_STRING:
|
|
output->print_string(output, value, format);
|
|
break;
|
|
case DATA_ARRAY:
|
|
output->print_array(output, value, format);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void print_array_value(data_output_t *output, data_array_t *array, char *format, int idx)
|
|
{
|
|
int element_size = dmt[array->type].array_element_size;
|
|
#ifdef RTL_433_NO_VLAs
|
|
char *buffer = alloca (element_size);
|
|
#else
|
|
char buffer[element_size];
|
|
#endif
|
|
|
|
if (!dmt[array->type].array_is_boxed) {
|
|
memcpy(buffer, (void **)((char *)array->values + element_size * idx), element_size);
|
|
print_value(output, array->type, buffer, format);
|
|
} else {
|
|
print_value(output, array->type, *(void **)((char *)array->values + element_size * idx), format);
|
|
}
|
|
}
|
|
|
|
/* JSON printer */
|
|
|
|
static void print_json_array(data_output_t *output, data_array_t *array, char *format)
|
|
{
|
|
fprintf(output->file, "[");
|
|
for (int c = 0; c < array->num_values; ++c) {
|
|
if (c)
|
|
fprintf(output->file, ", ");
|
|
print_array_value(output, array, format, c);
|
|
}
|
|
fprintf(output->file, "]");
|
|
}
|
|
|
|
static void print_json_data(data_output_t *output, data_t *data, char *format)
|
|
{
|
|
bool separator = false;
|
|
fputc('{', output->file);
|
|
while (data) {
|
|
if (separator)
|
|
fprintf(output->file, ", ");
|
|
output->print_string(output, data->key, NULL);
|
|
fprintf(output->file, " : ");
|
|
print_value(output, data->type, data->value, data->format);
|
|
separator = true;
|
|
data = data->next;
|
|
}
|
|
fputc('}', output->file);
|
|
}
|
|
|
|
static void print_json_string(data_output_t *output, const char *str, char *format)
|
|
{
|
|
fprintf(output->file, "\"");
|
|
while (*str) {
|
|
if (*str == '"')
|
|
fputc('\\', output->file);
|
|
fputc(*str, output->file);
|
|
++str;
|
|
}
|
|
fprintf(output->file, "\"");
|
|
}
|
|
|
|
static void print_json_double(data_output_t *output, double data, char *format)
|
|
{
|
|
fprintf(output->file, "%.3f", data);
|
|
}
|
|
|
|
static void print_json_int(data_output_t *output, int data, char *format)
|
|
{
|
|
fprintf(output->file, "%d", data);
|
|
}
|
|
|
|
static void data_output_json_free(data_output_t *output)
|
|
{
|
|
if (!output)
|
|
return;
|
|
|
|
free(output);
|
|
}
|
|
|
|
struct data_output *data_output_json_create(FILE *file)
|
|
{
|
|
data_output_t *output = calloc(1, sizeof(data_output_t));
|
|
if (!output) {
|
|
fprintf(stderr, "calloc() failed");
|
|
return NULL;
|
|
}
|
|
|
|
output->print_data = print_json_data;
|
|
output->print_array = print_json_array;
|
|
output->print_string = print_json_string;
|
|
output->print_double = print_json_double;
|
|
output->print_int = print_json_int;
|
|
output->output_free = data_output_json_free;
|
|
output->file = file;
|
|
|
|
return output;
|
|
}
|
|
|
|
/* Key-Value printer */
|
|
|
|
static void print_kv_data(data_output_t *output, data_t *data, char *format)
|
|
{
|
|
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(output->file, "\n");
|
|
written_title = true;
|
|
separator = false;
|
|
} else {
|
|
if (was_labeled)
|
|
fprintf(output->file, "\n");
|
|
else
|
|
fprintf(output->file, " ");
|
|
}
|
|
}
|
|
if (!strcmp(data->key, "time"))
|
|
/* fprintf(output->file, "") */;
|
|
else if (!strcmp(data->key, "model"))
|
|
fprintf(output->file, ":\t");
|
|
else
|
|
fprintf(output->file, "\t%s:\t", data->pretty_key);
|
|
if (labeled)
|
|
fputc(' ', output->file);
|
|
print_value(output, data->type, data->value, data->format);
|
|
separator = true;
|
|
was_labeled = labeled;
|
|
data = data->next;
|
|
}
|
|
}
|
|
|
|
static void print_kv_double(data_output_t *output, double data, char *format)
|
|
{
|
|
fprintf(output->file, format ? format : "%.3f", data);
|
|
}
|
|
|
|
static void print_kv_int(data_output_t *output, int data, char *format)
|
|
{
|
|
fprintf(output->file, format ? format : "%d", data);
|
|
}
|
|
|
|
static void print_kv_string(data_output_t *output, const char *data, char *format)
|
|
{
|
|
fprintf(output->file, format ? format : "%s", data);
|
|
}
|
|
|
|
struct data_output *data_output_kv_create(FILE *file)
|
|
{
|
|
data_output_t *output = calloc(1, sizeof(data_output_t));
|
|
if (!output) {
|
|
fprintf(stderr, "calloc() failed");
|
|
return NULL;
|
|
}
|
|
|
|
output->print_data = print_kv_data;
|
|
output->print_array = print_json_array;
|
|
output->print_string = print_kv_string;
|
|
output->print_double = print_kv_double;
|
|
output->print_int = print_kv_int;
|
|
output->output_free = data_output_json_free;
|
|
output->file = file;
|
|
|
|
return output;
|
|
}
|
|
|
|
/* CSV printer; doesn't really support recursive data objects yet */
|
|
|
|
typedef struct {
|
|
struct data_output output;
|
|
const char **fields;
|
|
int data_recursion;
|
|
const char *separator;
|
|
} data_output_csv_t;
|
|
|
|
static void print_csv_data(data_output_t *output, data_t *data, char *format)
|
|
{
|
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
|
|
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 *found = NULL;
|
|
if (i)
|
|
fprintf(output->file, "%s", csv->separator);
|
|
for (data_t *iter = data; !found && iter; iter = iter->next)
|
|
if (strcmp(iter->key, key) == 0)
|
|
found = iter;
|
|
|
|
if (found)
|
|
print_value(output, found->type, found->value, found->format);
|
|
}
|
|
--csv->data_recursion;
|
|
}
|
|
|
|
static void print_csv_array(data_output_t *output, data_array_t *array, char *format)
|
|
{
|
|
for (int c = 0; c < array->num_values; ++c) {
|
|
if (c)
|
|
fprintf(output->file, ";");
|
|
print_array_value(output, array, format, c);
|
|
}
|
|
}
|
|
|
|
static void print_csv_string(data_output_t *output, const char *str, char *format)
|
|
{
|
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
|
|
while (*str) {
|
|
if (strncmp(str, csv->separator, strlen(csv->separator)) == 0)
|
|
fputc('\\', output->file);
|
|
fputc(*str, output->file);
|
|
++str;
|
|
}
|
|
}
|
|
|
|
static int compare_strings(const void *a, const void *b)
|
|
{
|
|
return strcmp(*(char **)a, *(char **)b);
|
|
}
|
|
|
|
void data_output_csv_init(struct data_output *output, const char **fields, int num_fields)
|
|
{
|
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
|
|
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 *), 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 *),
|
|
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) {
|
|
fprintf(csv->output.file, "%s%s", i > 0 ? csv->separator : "", csv->fields[i]);
|
|
}
|
|
fprintf(csv->output.file, "\n");
|
|
return;
|
|
|
|
alloc_error:
|
|
free(use_count);
|
|
free(allowed);
|
|
if (csv)
|
|
free(csv->fields);
|
|
free(csv);
|
|
}
|
|
|
|
static void data_output_csv_free(data_output_t *output)
|
|
{
|
|
data_output_csv_t *csv = (data_output_csv_t *)output;
|
|
|
|
free(csv->fields);
|
|
free(csv);
|
|
}
|
|
|
|
struct data_output *data_output_csv_create(FILE *file)
|
|
{
|
|
data_output_csv_t *csv = calloc(1, sizeof(data_output_csv_t));
|
|
if (!csv) {
|
|
fprintf(stderr, "calloc() failed");
|
|
return NULL;
|
|
}
|
|
|
|
csv->output.print_data = print_csv_data;
|
|
csv->output.print_array = print_csv_array;
|
|
csv->output.print_string = print_csv_string;
|
|
csv->output.print_double = print_json_double;
|
|
csv->output.print_int = print_json_int;
|
|
csv->output.output_free = data_output_csv_free;
|
|
csv->output.file = file;
|
|
|
|
return &csv->output;
|
|
}
|
|
|
|
/* Datagram (UDP) client */
|
|
|
|
typedef struct {
|
|
struct sockaddr_storage addr;
|
|
socklen_t addr_len;
|
|
SOCKET sock;
|
|
} datagram_client_t;
|
|
|
|
static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
|
|
{
|
|
if (!host || !port)
|
|
return -1;
|
|
|
|
struct addrinfo hints, *res, *res0;
|
|
int error;
|
|
SOCKET sock;
|
|
const char *cause = NULL;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
error = getaddrinfo(host, port, &hints, &res0);
|
|
if (error) {
|
|
fprintf(stderr, "%s\n", gai_strerror(error));
|
|
return -1;
|
|
}
|
|
sock = INVALID_SOCKET;
|
|
for (res = res0; res; res = res->ai_next) {
|
|
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
if (sock >= 0) {
|
|
client->sock = sock;
|
|
memset(&client->addr, 0, sizeof(client->addr));
|
|
memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
|
|
client->addr_len = res->ai_addrlen;
|
|
break; // success
|
|
}
|
|
}
|
|
freeaddrinfo(res0);
|
|
if (sock == INVALID_SOCKET) {
|
|
perror("socket");
|
|
return -1;
|
|
}
|
|
|
|
//int broadcast = 1;
|
|
//int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void datagram_client_close(datagram_client_t *client)
|
|
{
|
|
if (!client)
|
|
return;
|
|
|
|
if (client->sock != INVALID_SOCKET) {
|
|
close(client->sock);
|
|
client->sock = INVALID_SOCKET;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
|
|
{
|
|
int r = sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
|
|
if (r == -1) {
|
|
perror("sendto");
|
|
}
|
|
}
|
|
|
|
/* array buffer (string builder) */
|
|
|
|
typedef struct {
|
|
char *head;
|
|
char *tail;
|
|
size_t left;
|
|
} abuf_t;
|
|
|
|
static void abuf_init(abuf_t *buf, char *dst, size_t len)
|
|
{
|
|
buf->head = dst;
|
|
buf->tail = dst;
|
|
buf->left = len;
|
|
}
|
|
|
|
static void abuf_setnull(abuf_t *buf)
|
|
{
|
|
buf->head = NULL;
|
|
buf->tail = NULL;
|
|
buf->left = 0;
|
|
}
|
|
|
|
static char *abuf_push(abuf_t *buf)
|
|
{
|
|
return buf->tail;
|
|
}
|
|
|
|
static void abuf_pop(abuf_t *buf, char *end)
|
|
{
|
|
buf->left += buf->tail - end;
|
|
buf->tail = end;
|
|
}
|
|
|
|
static void abuf_cat(abuf_t *buf, const char *str)
|
|
{
|
|
size_t len = strlen(str);
|
|
if (buf->left >= len + 1) {
|
|
strcpy(buf->tail, str);
|
|
buf->tail += len;
|
|
buf->left -= len;
|
|
}
|
|
}
|
|
|
|
static int abuf_printf(abuf_t *buf, const char *restrict format, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
|
|
int n = vsnprintf(buf->tail, buf->left, format, ap);
|
|
size_t len = 0;
|
|
if (n > 0) {
|
|
len = (size_t)n < buf->left ? (size_t)n : buf->left;
|
|
buf->tail += len;
|
|
buf->left -= len;
|
|
}
|
|
va_end(ap);
|
|
return n;
|
|
}
|
|
|
|
/* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
|
|
|
|
typedef struct {
|
|
struct data_output output;
|
|
datagram_client_t client;
|
|
int pri;
|
|
char hostname[_POSIX_HOST_NAME_MAX + 1];
|
|
abuf_t msg;
|
|
} data_output_syslog_t;
|
|
|
|
static void print_syslog_array(data_output_t *output, data_array_t *array, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
|
|
abuf_cat(&syslog->msg, "[");
|
|
for (int c = 0; c < array->num_values; ++c) {
|
|
if (c)
|
|
abuf_cat(&syslog->msg, ",");
|
|
print_array_value(output, array, format, c);
|
|
}
|
|
abuf_cat(&syslog->msg, "]");
|
|
}
|
|
|
|
static void print_syslog_object(data_output_t *output, data_t *data, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
|
|
bool separator = false;
|
|
abuf_cat(&syslog->msg, "{");
|
|
while (data) {
|
|
if (separator)
|
|
abuf_cat(&syslog->msg, ",");
|
|
output->print_string(output, data->key, NULL);
|
|
abuf_cat(&syslog->msg, ":");
|
|
print_value(output, data->type, data->value, data->format);
|
|
separator = true;
|
|
data = data->next;
|
|
}
|
|
abuf_cat(&syslog->msg, "}");
|
|
}
|
|
|
|
static void print_syslog_data(data_output_t *output, data_t *data, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
|
|
if (syslog->msg.tail) {
|
|
print_syslog_object(output, data, format);
|
|
return;
|
|
}
|
|
|
|
char message[1024];
|
|
abuf_init(&syslog->msg, message, 1024);
|
|
|
|
time_t now;
|
|
struct tm tm_info;
|
|
time(&now);
|
|
#ifdef _MSC_VER
|
|
gmtime_s(&tm_info, &now);
|
|
#else
|
|
gmtime_r(&now, &tm_info);
|
|
#endif
|
|
char timestamp[21];
|
|
strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
|
|
|
|
abuf_printf(&syslog->msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
|
|
|
|
print_syslog_object(output, data, format);
|
|
|
|
datagram_client_send(&syslog->client, message, strlen(message));
|
|
|
|
abuf_setnull(&syslog->msg);
|
|
}
|
|
|
|
static void print_syslog_string(data_output_t *output, const char *str, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
|
|
char *buf = syslog->msg.tail;
|
|
size_t size = syslog->msg.left;
|
|
|
|
if (size < strlen(str) + 3) {
|
|
return;
|
|
}
|
|
|
|
*buf++ = '"';
|
|
size--;
|
|
for (; *str && size >= 3; ++str) {
|
|
if (*str == '"' || *str == '\\') {
|
|
*buf++ = '\\';
|
|
size--;
|
|
}
|
|
*buf++ = *str;
|
|
size--;
|
|
}
|
|
if (size >= 2) {
|
|
*buf++ = '"';
|
|
size--;
|
|
}
|
|
*buf = '\0';
|
|
|
|
syslog->msg.tail = buf;
|
|
syslog->msg.left = size;
|
|
}
|
|
|
|
static void print_syslog_double(data_output_t *output, double data, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
abuf_printf(&syslog->msg, "%f", data);
|
|
}
|
|
|
|
static void print_syslog_int(data_output_t *output, int data, char *format)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
abuf_printf(&syslog->msg, "%d", data);
|
|
}
|
|
|
|
static void data_output_syslog_free(data_output_t *output)
|
|
{
|
|
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
|
|
|
|
if (!syslog)
|
|
return;
|
|
|
|
datagram_client_close(&syslog->client);
|
|
|
|
free(syslog);
|
|
}
|
|
|
|
struct data_output *data_output_syslog_create(const char *host, const char *port)
|
|
{
|
|
data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
|
|
if (!syslog) {
|
|
fprintf(stderr, "calloc() failed");
|
|
return NULL;
|
|
}
|
|
#ifdef _WIN32
|
|
WSADATA wsa;
|
|
|
|
if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
|
|
perror("WSAStartup()");
|
|
free(syslog);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
syslog->output.print_data = print_syslog_data;
|
|
syslog->output.print_array = print_syslog_array;
|
|
syslog->output.print_string = print_syslog_string;
|
|
syslog->output.print_double = print_syslog_double;
|
|
syslog->output.print_int = print_syslog_int;
|
|
syslog->output.output_free = data_output_syslog_free;
|
|
// Severity 5 "Notice", Facility 20 "local use 4"
|
|
syslog->pri = 20 * 8 + 5;
|
|
gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
|
|
syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
|
|
datagram_client_open(&syslog->client, host, port);
|
|
|
|
return &syslog->output;
|
|
}
|