1 /**
2  * HTTP control interface
3  *
4  * Copyright (C) 2018 Christian Zuckschwerdt
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11 
12 /*
13 # HTTP control interface
14 
15 A choice of endpoints are available:
16 - "/": serves a user interface (currently redirected to hosted app)
17     (also serves "/favicon.ico", "/app.css", "/app.js", "/vendor.css", "/vendor.js")
18 - "/jsonrpc": JSON-RPC API
19 - "/cmd": simple JSON command API
20 - "/events": HTTP (chunked) streaming API, streams JSON events
21 - "/stream": HTTP (plain) streaming API, streams JSON events
22 - "/api": RESTful API (not implemented)
23 - "ws:": Websocket API (similar to cmd/events API)
24 
25 ## JSON-RPC API
26 
27 S.a. https://www.jsonrpc.org/specification
28 
29 Examples:
30 
31     {"jsonrpc": "2.0", "method": "sample_rate", "params": [1024000], "id": 0}
32     {"jsonrpc": "2.0", "result": "Ok", "id": 0}
33     {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
34 
35 ## JSON command / Websocket API
36 
37 Simplified JSON command and query.
38 
39 Examples:
40 
41     {"cmd": "sample_rate", "val": 1024000}
42     {"result": "Ok"}
43     {"error": "Invalid Request"}}
44 
45 ## HTTP events / streaming / Websocket API
46 
47 You will receive JSON events, one per line terminated with CRLF.
48 On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds.
49 Use e.g. httpie with `http --stream --timeout=70 :8433/events`
50 or `(echo "GET /stream HTTP/1.0\n"; sleep 600) | socat - tcp:127.0.0.1:8433`
51 
52 ## Queries
53 
54 - "registered_protocols"
55 - "enabled_protocols"
56 - "protocol_info"
57     .name
58     .modulation
59     .short_width
60     .long_width
61     .sync_width
62     .tolerance
63     .gap_limit
64     .reset_limit
65     .fields
66 
67 - "device_info"
68     device  0:  Realtek, RTL2838UHIDIR, SN: 00000001
69     Found Rafael Micro R820T tuner
70     Using device 0: Generic RTL2832U OEM
71 
72 - "settings"
73     "device":           0
74     "gain":             0
75     "center_frequency": 433920000
76     "hop_interval":     600
77     "ppm_error":        0
78     "sample_rate":      250000
79     "report_meta":      ["time", "reltime", "notime", "hires", "utc", "protocol", "level"]
80     "convert":          "native"|"si"|"customary"
81 
82 ## Commands
83 
84 - "device":           0
85 - "gain":             0
86 - "center_frequency": 433920000
87 - "hop_interval":     600
88 - "ppm_error":        0
89 - "sample_rate":      250000
90 - "report_meta":      "time"|"reltime"|"notime"|"hires"|"utc"|"protocol"|"level"
91 - "convert":          "native"|"si"|"customary"
92 - "protocol":         1
93 
94 */
95 
96 #include "http_server.h"
97 #include "data.h"
98 #include "rtl_433.h"
99 #include "r_api.h"
100 #include "r_device.h" // used for protocols
101 #include "r_private.h" // used for protocols
102 #include "r_util.h"
103 #include "optparse.h"
104 #include "abuf.h"
105 #include "list.h" // used for protocols
106 #include "jsmn.h"
107 #include "mongoose.h"
108 #include "fatal.h"
109 #include <stdbool.h>
110 
111 // embed index.html so browsers allow access as local
112 #define INDEX_HTML \
113     "<!DOCTYPE html>" \
114     "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" \
115     "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">" \
116     "<link rel=\"icon\" href=\"https://triq.org/rxui/favicon.ico\">" \
117     "<title>rxui</title>" \
118     "<link href=\"https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons\" rel=\"stylesheet\">" \
119     "<link href=\"https://triq.org/rxui/css/app.css\" rel=\"preload\" as=\"style\">" \
120     "<link href=\"https://triq.org/rxui/css/chunk-vendors.css\" rel=\"preload\" as=\"style\">" \
121     "<link href=\"https://triq.org/rxui/js/app.js\" rel=\"preload\" as=\"script\">" \
122     "<link href=\"https://triq.org/rxui/js/chunk-vendors.js\" rel=\"preload\" as=\"script\">" \
123     "<link href=\"https://triq.org/rxui/css/chunk-vendors.css\" rel=\"stylesheet\">" \
124     "<link href=\"https://triq.org/rxui/css/app.css\" rel=\"stylesheet\">" \
125     "<div id=\"app\"></div>" \
126     "<noscript><strong>We're sorry but rxui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>" \
127     "<script src=\"https://triq.org/rxui/js/chunk-vendors.js\"></script>" \
128     "<script src=\"https://triq.org/rxui/js/app.js\"></script>"
129 
130 // generic ring list
131 
132 #define DEFAULT_HISTORY_SIZE 100
133 
134 typedef struct {
135     unsigned size;
136     void **data;
137     void **head;
138     void **tail;
139 } ring_list_t;
140 
ring_list_new(unsigned size)141 static ring_list_t *ring_list_new(unsigned size)
142 {
143     ring_list_t *ring = calloc(1, sizeof(ring_list_t));
144     if (!ring) {
145         WARN_CALLOC("ring_list_new()");
146         return NULL;
147     }
148 
149     ring->data = calloc(size, sizeof(void *));
150     if (!ring->data) {
151         WARN_CALLOC("ring_list_new()");
152         free(ring);
153         return NULL;
154     }
155 
156     ring->size = size;
157     ring->tail = ring->data;
158 
159     return ring;
160 }
161 
162 // the ring needs to be empty before calling this
ring_list_free(ring_list_t * ring)163 static void ring_list_free(ring_list_t *ring)
164 {
165     if (ring) {
166         if (ring->data)
167             free(ring->data);
168         free(ring);
169     }
170 }
171 
172 // free the data returned
ring_list_shift(ring_list_t * ring)173 static void *ring_list_shift(ring_list_t *ring)
174 {
175     if (!ring->head)
176         return NULL;
177 
178     void *ret = *ring->head;
179 
180     ++ring->head;
181     if (ring->head >= ring->data + ring->size)
182         ring->head -= ring->size;
183     if (ring->head == ring->tail)
184         ring->head = NULL;
185 
186     return ret;
187 }
188 
189 // retain data before passing in and free the data returned.
ring_list_push(ring_list_t * ring,void * data)190 static void *ring_list_push(ring_list_t *ring, void *data)
191 {
192     *ring->tail = data;
193 
194     if (!ring->head)
195         ring->head = ring->tail;
196 
197     ++ring->tail;
198     if (ring->tail >= ring->data + ring->size)
199         ring->tail -= ring->size;
200 
201     if (ring->tail == ring->head)
202         return ring_list_shift(ring);
203 
204     return NULL;
205 }
206 
ring_list_iter(ring_list_t * ring)207 static void **ring_list_iter(ring_list_t *ring)
208 {
209     return ring->head;
210 }
211 
ring_list_next(ring_list_t * ring,void ** iter)212 static void **ring_list_next(ring_list_t *ring, void **iter)
213 {
214     if (!iter)
215         return NULL;
216 
217     ++iter;
218     if (iter >= ring->data + ring->size)
219         iter -= ring->size;
220     if (iter == ring->tail)
221         iter = NULL;
222 
223     return iter;
224 }
225 
226 // data helpers that could go into r_api
227 
meta_data(r_cfg_t * cfg)228 static data_t *meta_data(r_cfg_t *cfg)
229 {
230     return data_make(
231             "frequencies", "", DATA_ARRAY, data_array(cfg->frequencies, DATA_INT, cfg->frequency),
232             "hop_times", "", DATA_ARRAY, data_array(cfg->hop_times, DATA_INT, cfg->hop_time),
233             "center_frequency", "", DATA_INT, cfg->center_frequency,
234             "duration", "", DATA_INT, cfg->duration,
235             "samp_rate", "", DATA_INT, cfg->samp_rate,
236             "conversion_mode", "", DATA_INT, cfg->conversion_mode,
237             "fsk_pulse_detect_mode", "", DATA_INT, cfg->fsk_pulse_detect_mode,
238             "after_successful_events_flag", "", DATA_INT, cfg->after_successful_events_flag,
239             "report_meta", "", DATA_INT, cfg->report_meta,
240             "report_protocol", "", DATA_INT, cfg->report_protocol,
241             "report_time", "", DATA_INT, cfg->report_time,
242             "report_time_hires", "", DATA_INT, cfg->report_time_hires,
243             "report_time_tz", "", DATA_INT, cfg->report_time_tz,
244             "report_time_utc", "", DATA_INT, cfg->report_time_utc,
245             "report_description", "", DATA_INT, cfg->report_description,
246             "report_stats", "", DATA_INT, cfg->report_stats,
247             "stats_interval", "", DATA_INT, cfg->stats_interval,
248             NULL);
249 }
250 
protocols_data(r_cfg_t * cfg)251 static data_t *protocols_data(r_cfg_t *cfg)
252 {
253     list_t devs = {0};
254     list_ensure_size(&devs, cfg->num_r_devices);
255 
256     for (int i = 0; i < cfg->num_r_devices; ++i) {
257         r_device *dev = &cfg->devices[i];
258 
259         int enabled = 0;
260         for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {
261             r_device *r_dev = *iter;
262             if (r_dev->protocol_num == dev->protocol_num) {
263                 enabled = 1;
264                 break;
265             }
266         }
267         int fields_len = 0;
268         for (char **iter = dev->fields; iter && *iter; ++iter) {
269             fields_len++;
270         }
271         data_t *data = data_make(
272                 "num", "", DATA_INT, dev->protocol_num,
273                 "name", "", DATA_STRING, dev->name,
274                 "mod", "", DATA_INT, dev->modulation,
275                 "short", "", DATA_DOUBLE, dev->short_width,
276                 "long", "", DATA_DOUBLE, dev->long_width,
277                 "reset", "", DATA_DOUBLE, dev->reset_limit,
278                 "gap", "", DATA_DOUBLE, dev->gap_limit,
279                 "sync", "", DATA_DOUBLE, dev->sync_width,
280                 "tolerance", "", DATA_DOUBLE, dev->tolerance,
281                 "fields", "", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),
282                 "def", "", DATA_INT, dev->disabled == 0,
283                 "en", "", DATA_INT, enabled,
284                 "verbose", "", DATA_INT, dev->verbose,
285                 "verbose_bits", "", DATA_INT, dev->verbose_bits,
286                 NULL);
287         list_push(&devs, data);
288     }
289 
290     for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {
291         r_device *dev = *iter;
292         if (dev->protocol_num > 0) {
293                 continue;
294         }
295         int fields_len = 0;
296         for (char **iter2 = dev->fields; iter2 && *iter2; ++iter2) {
297             fields_len++;
298         }
299         data_t *data = data_make(
300                 "name", "", DATA_STRING, dev->name,
301                 "mod", "", DATA_INT, dev->modulation,
302                 "short", "", DATA_DOUBLE, dev->short_width,
303                 "long", "", DATA_DOUBLE, dev->long_width,
304                 "reset", "", DATA_DOUBLE, dev->reset_limit,
305                 "gap", "", DATA_DOUBLE, dev->gap_limit,
306                 "sync", "", DATA_DOUBLE, dev->sync_width,
307                 "tolerance", "", DATA_DOUBLE, dev->tolerance,
308                 "fields", "", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),
309                 "en", "", DATA_INT, 1,
310                 "verbose", "", DATA_INT, dev->verbose,
311                 "verbose_bits", "", DATA_INT, dev->verbose_bits,
312                 NULL);
313         list_push(&devs, data);
314     }
315 
316     data_t *data = data_make(
317             "protocols", "", DATA_ARRAY, data_array(devs.len, DATA_DATA, devs.elems),
318             NULL);
319     list_free_elems(&devs, NULL);
320     return data;
321 }
322 
323 // very narrowly tailored JSON parsing
324 
325 typedef struct rpc rpc_t;
326 
327 typedef void (*rpc_response_fn)(rpc_t *rpc, int error_code, char const *message, int is_json);
328 
329 struct rpc {
330     struct mg_connection *nc;
331     rpc_response_fn response;
332     int ver;
333     char *method;
334     char *arg;
335     uint32_t val;
336     //list_t params;
337     char *id;
338 };
339 
jsoneq(const char * json,jsmntok_t * tok,const char * s)340 static int jsoneq(const char *json, jsmntok_t *tok, const char *s)
341 {
342     if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
343             strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
344         return 0;
345     }
346     return -1;
347 }
348 
jsondup(const char * json,jsmntok_t * tok)349 static char *jsondup(const char *json, jsmntok_t *tok)
350 {
351     int len = tok->end - tok->start;
352     char *p = malloc(len + 1);
353     if (!p) {
354         WARN_MALLOC("jsondup()");
355         return NULL;
356     }
357     p[len] = '\0';
358     return memcpy(p, json + tok->start, len);
359 }
360 
jsondupq(const char * json,jsmntok_t * tok)361 static char *jsondupq(const char *json, jsmntok_t *tok)
362 {
363     int len = tok->end - tok->start + 2;
364     char *p = malloc(len + 1);
365     if (!p) {
366         WARN_MALLOC("jsondupq()");
367         return NULL;
368     }
369     p[len] = '\0';
370     return memcpy(p, json + tok->start - 1, len);
371 }
372 
373 // {"cmd": "report_meta", "arg": "utc", "val": 1}
json_parse(rpc_t * rpc,struct mg_str const * json)374 static int json_parse(rpc_t *rpc, struct mg_str const *json)
375 {
376     int i;
377     int r;
378     jsmn_parser p;
379     jsmntok_t t[16]; /* We expect no more than 7 tokens */
380 
381     char *cmd    = NULL;
382     char *arg    = NULL;
383     uint32_t val = 0;
384 
385     jsmn_init(&p);
386     r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));
387     if (r < 0) {
388         printf("Failed to parse JSON: %d\n", r);
389         return -1;
390     }
391 
392     /* Assume the top-level element is an object */
393     if (r < 1 || t[0].type != JSMN_OBJECT) {
394         printf("Object expected\n");
395         return -1;
396     }
397 
398     /* Loop over all keys of the root object */
399     for (i = 1; i < r; i++) {
400         if (jsoneq(json->p, &t[i], "cmd") == 0) {
401             i++;
402             free(cmd);
403             cmd = jsondup(json->p, &t[i]);
404         }
405         else if (jsoneq(json->p, &t[i], "arg") == 0) {
406             i++;
407             free(arg);
408             arg = jsondup(json->p, &t[i]);
409         }
410         else if (jsoneq(json->p, &t[i], "val") == 0) {
411             i++;
412             char *endptr = NULL;
413             val = strtol(json->p + t[i].start, &endptr, 10);
414             // compare endptr to t[i].end
415         }
416         else {
417             printf("Unexpected key: %.*s\n", t[i].end - t[i].start, json->p + t[i].start);
418         }
419     }
420 
421     if (!cmd) {
422         free(arg);
423         return -1;
424     }
425     rpc->method     = cmd;
426     rpc->arg        = arg;
427     rpc->val        = val;
428     return 0;
429 }
430 
431 // {"jsonrpc": "2.0", "method": "report_meta", "params": ["utc", 1], "id": 0}
jsonrpc_parse(rpc_t * rpc,struct mg_str const * json)432 static int jsonrpc_parse(rpc_t *rpc, struct mg_str const *json)
433 {
434     int r;
435     jsmn_parser p;
436     jsmntok_t t[16]; /* We expect no more than 11 tokens */
437 
438     char *cmd    = NULL;
439     char *id     = NULL;
440     char *arg    = NULL;
441     uint32_t val = 0;
442 
443     jsmn_init(&p);
444     r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));
445     if (r < 0) {
446         printf("Failed to parse JSON: %d\n", r);
447         return -1;
448     }
449 
450     /* Assume the top-level element is an object */
451     if (r < 1 || t[0].type != JSMN_OBJECT) {
452         printf("Object expected\n");
453         return -1;
454     }
455 
456     /* Loop over all keys of the root object */
457     for (int i = 1; i < r; i++) {
458         if (jsoneq(json->p, &t[i], "jsonrpc") == 0) {
459             i++;
460             // (jsoneq(json->p, &t[i], "2.0") == 0);
461         }
462         else if (jsoneq(json->p, &t[i], "method") == 0) {
463             i++;
464             free(cmd);
465             cmd = jsondup(json->p, &t[i]);
466         }
467         else if (jsoneq(json->p, &t[i], "id") == 0) {
468             i++;
469             if (t[i].type == JSMN_STRING) {
470                 free(id);
471                 id = jsondupq(json->p, &t[i]);
472             }
473             else if (t[i].type == JSMN_PRIMITIVE) {
474                 free(id);
475                 id = jsondup(json->p, &t[i]);
476             }
477         }
478         else if (jsoneq(json->p, &t[i], "params") == 0) {
479             //printf("- Params:\n");
480             if (t[i + 1].type != JSMN_ARRAY) {
481                 continue; /* We expect groups to be an array of strings */
482             }
483             for (int j = 0; j < t[i + 1].size; j++) {
484                 jsmntok_t *g = &t[i + j + 2];
485                 if (g->type == JSMN_STRING) {
486                     free(arg);
487                     arg = jsondup(json->p, g);
488                 }
489                 else if (g->type == JSMN_PRIMITIVE) {
490                     // Number, null/true/false not supported
491                     char *endptr = NULL;
492                     val          = strtol(json->p + g->start, &endptr, 10);
493                 }
494                 //printf("  * %.*s\n", g->end - g->start, json + g->start);
495             }
496             i += t[i + 1].size + 1;
497         }
498         else {
499             printf("Unexpected key: %.*s\n", t[i].end - t[i].start, json->p + t[i].start);
500         }
501     }
502 
503     if (!cmd) {
504         free(id);
505         free(arg);
506         return -1;
507     }
508     rpc->method  = cmd;
509     rpc->arg     = arg;
510     rpc->val     = val;
511     rpc->id      = id;
512     return 0;
513 }
514 
rpc_exec(rpc_t * rpc,r_cfg_t * cfg)515 static void rpc_exec(rpc_t *rpc, r_cfg_t *cfg)
516 {
517     if (!rpc || !rpc->method || !*rpc->method) {
518         rpc->response(rpc, -1, "Method invalid", 0);
519     }
520     // Getter
521     else if (!strcmp(rpc->method, "get_dev_query")) {
522         rpc->response(rpc, 0, cfg->dev_query, 0);
523     }
524     else if (!strcmp(rpc->method, "get_dev_info")) {
525         rpc->response(rpc, 1, cfg->dev_info, 0);
526     }
527     else if (!strcmp(rpc->method, "get_gain")) {
528         rpc->response(rpc, 0, cfg->gain_str, 0);
529     }
530 
531     else if (!strcmp(rpc->method, "get_ppm_error")) {
532         rpc->response(rpc, 2, NULL, cfg->ppm_error);
533     }
534     else if (!strcmp(rpc->method, "get_hop_interval")) {
535         rpc->response(rpc, 2, NULL, cfg->hop_time[0]);
536     }
537     else if (!strcmp(rpc->method, "get_center_frequency")) {
538         rpc->response(rpc, 3, NULL, cfg->center_frequency); // unsigned
539     }
540     else if (!strcmp(rpc->method, "get_sample_rate")) {
541         rpc->response(rpc, 3, NULL, cfg->samp_rate); // unsigned
542     }
543     else if (!strcmp(rpc->method, "get_grab_mode")) {
544         rpc->response(rpc, 2, NULL, cfg->grab_mode);
545     }
546     else if (!strcmp(rpc->method, "get_raw_mode")) {
547         rpc->response(rpc, 2, NULL, cfg->raw_mode);
548     }
549     else if (!strcmp(rpc->method, "get_verbosity")) {
550         rpc->response(rpc, 2, NULL, cfg->verbosity);
551     }
552     else if (!strcmp(rpc->method, "get_verbose_bits")) {
553         rpc->response(rpc, 2, NULL, cfg->verbose_bits);
554     }
555     else if (!strcmp(rpc->method, "get_conversion_mode")) {
556         rpc->response(rpc, 2, NULL, cfg->conversion_mode);
557     }
558     else if (!strcmp(rpc->method, "get_stats")) {
559         char buf[20480]; // we expect the stats string to be around 15k bytes.
560         data_t *data = create_report_data(cfg, 2/*report active devices*/);
561         // flush_report_data(cfg); // snapshot, do not flush
562         data_print_jsons(data, buf, sizeof(buf));
563         rpc->response(rpc, 1, buf, 0);
564         data_free(data);
565     }
566     else if (!strcmp(rpc->method, "get_meta")) {
567         char buf[2048]; // we expect the meta string to be around 500 bytes.
568         data_t *data = meta_data(cfg);
569         data_print_jsons(data, buf, sizeof(buf));
570         rpc->response(rpc, 1, buf, 0);
571         data_free(data);
572     }
573     else if (!strcmp(rpc->method, "get_protocols")) {
574         char buf[65536]; // we expect the protocol string to be around 60k bytes.
575         data_t *data = protocols_data(cfg);
576         data_print_jsons(data, buf, sizeof(buf));
577         rpc->response(rpc, 1, buf, 0);
578         data_free(data);
579     }
580 
581     // Setter
582     else if (!strcmp(rpc->method, "hop_interval")) {
583         cfg->hop_time[0] = rpc->val;
584         rpc->response(rpc, 0, "Ok", 0);
585     }
586     else if (!strcmp(rpc->method, "report_meta")) {
587         if (!rpc->arg)
588             rpc->response(rpc, -1, "Missing arg", 0);
589         else if (!strcasecmp(rpc->arg, "time"))
590             cfg->report_time = REPORT_TIME_DATE;
591         else if (!strcasecmp(rpc->arg, "reltime"))
592             cfg->report_time = REPORT_TIME_SAMPLES;
593         else if (!strcasecmp(rpc->arg, "notime"))
594             cfg->report_time = REPORT_TIME_OFF;
595         else if (!strcasecmp(rpc->arg, "hires"))
596             cfg->report_time_hires = rpc->val;
597         else if (!strcasecmp(rpc->arg, "utc"))
598             cfg->report_time_utc = rpc->val;
599         else if (!strcasecmp(rpc->arg, "protocol"))
600             cfg->report_protocol = rpc->val;
601         else if (!strcasecmp(rpc->arg, "level"))
602             cfg->report_meta = rpc->val;
603         else if (!strcasecmp(rpc->arg, "bits"))
604             cfg->verbose_bits = rpc->val;
605         else if (!strcasecmp(rpc->arg, "description"))
606             cfg->report_description = rpc->val;
607         else
608             cfg->report_meta = rpc->val;
609         rpc->response(rpc, 0, "Ok", 0);
610     }
611     else if (!strcmp(rpc->method, "convert")) {
612         cfg->conversion_mode = rpc->val;
613         rpc->response(rpc, 0, "Ok", 0);
614     }
615     else if (!strcmp(rpc->method, "raw_mode")) {
616         cfg->raw_mode = rpc->val;
617         rpc->response(rpc, 0, "Ok", 0);
618     }
619     else if (!strcmp(rpc->method, "verbosity")) {
620         cfg->verbosity = rpc->val;
621         rpc->response(rpc, 0, "Ok", 0);
622     }
623     else if (!strcmp(rpc->method, "verbose_bits")) {
624         cfg->verbose_bits = rpc->val;
625         rpc->response(rpc, 0, "Ok", 0);
626     }
627     else if (!strcmp(rpc->method, "protocol")) {
628         // set_protocol(rpc->val);
629         rpc->response(rpc, 0, "Ok", 0);
630     }
631 
632     // Apply
633     else if (!strcmp(rpc->method, "device")) {
634         if (!rpc->arg)
635             rpc->response(rpc, -1, "Missing arg", 0);
636         /*
637         if (cfg->set_dev_query)
638             rpc->response(rpc, -1, "Try again later", 0);
639         cfg->set_dev_query = strdup(rpc->arg);
640         if (!cfg->set_dev_query) {
641             WARN_STRDUP("rpc_exec()");
642         }
643         */
644         rpc->response(rpc, -1, "Not implemented", 0);
645     }
646     else if (!strcmp(rpc->method, "gain")) {
647         if (!rpc->arg)
648             rpc->response(rpc, -1, "Missing arg", 0);
649         set_gain_str(cfg, rpc->arg);
650         rpc->response(rpc, 0, "Ok", 0);
651     }
652     else if (!strcmp(rpc->method, "center_frequency")) {
653         set_center_freq(cfg, rpc->val);
654         rpc->response(rpc, 0, "Ok", 0);
655     }
656     else if (!strcmp(rpc->method, "ppm_error")) {
657         set_freq_correction(cfg, rpc->val);
658         rpc->response(rpc, 0, "Ok", 0);
659     }
660     else if (!strcmp(rpc->method, "sample_rate")) {
661         set_sample_rate(cfg, rpc->val);
662         rpc->response(rpc, 0, "Ok", 0);
663     }
664 
665     // Invalid
666     else {
667         rpc->response(rpc, -1, "Unknown method", 0);
668     }
669 }
670 
671 // http server
672 
673 #define KEEP_ALIVE 60 /* seconds */
674 
675 struct http_server_context {
676     struct mg_connection *conn;
677     struct mg_serve_http_opts server_opts;
678     r_cfg_t *cfg;
679     struct data_output *output;
680     ring_list_t *history;
681 };
682 
683 struct nc_context {
684     int is_chunked;
685 };
686 
handle_options(struct mg_connection * nc,struct http_message * hm)687 static void handle_options(struct mg_connection *nc, struct http_message *hm)
688 {
689     UNUSED(hm);
690     mg_printf(nc,
691             "HTTP/1.1 204 No Content\r\n"
692             "Content-Length: 0\r\n"
693             "Cache-Control: max-age=0, private, must-revalidate\r\n"
694             "Access-Control-Allow-Origin: *\r\n"
695             "Access-Control-Expose-Headers:\r\n"
696             "Access-Control-Allow-Credentials: true\r\n"
697             "Access-Control-Max-Age: 1728000\r\n"
698             "Access-Control-Allow-Headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-CSRF-Token\r\n"
699             "Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS\r\n"
700             "\r\n");
701 }
702 
handle_get(struct mg_connection * nc,struct http_message * hm,char const * buf,unsigned int len)703 static void handle_get(struct mg_connection *nc, struct http_message *hm, char const *buf, unsigned int len)
704 {
705     UNUSED(hm);
706     //mg_send_head(nc, 200, -1, NULL);
707     mg_printf(nc,
708             "HTTP/1.1 200 OK\r\n"
709             "Content-Length: %u\r\n"
710             "\r\n", len);
711     mg_send(nc, buf, (size_t)len);
712 }
713 
handle_redirect(struct mg_connection * nc,struct http_message * hm)714 static void handle_redirect(struct mg_connection *nc, struct http_message *hm)
715 {
716     // get the host header
717     struct mg_str host = {0};
718     for (int i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
719         // struct mg_str hn = hm->header_names[i];
720         // struct mg_str hv = hm->header_values[i];
721         // fprintf(stderr, "Header: %.*s: %.*s\n", (int)hn.len, hn.p, (int)hv.len, hv.p);
722         if (mg_vcasecmp(&hm->header_names[i], "Host") == 0) {
723             host = hm->header_values[i];
724             break;
725         }
726     }
727 
728     mg_printf(nc, "%s%s%.*s%s\r\n",
729             "HTTP/1.1 307 Temporary Redirect\r\n",
730             "Location: http://triq.org/rxui/#",
731             (int)host.len, host.p,
732             "\r\n\r\n");
733 }
734 
735 // reply to ws command
rpc_response_ws(rpc_t * rpc,int ret_code,char const * message,int arg)736 static void rpc_response_ws(rpc_t *rpc, int ret_code, char const *message, int arg)
737 {
738     if (ret_code < 0) {
739         mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
740                 "{\"error\": {\"code\": %d, \"message\": \"%s\"}}",
741                 ret_code, message);
742     }
743     else if (ret_code == 0 && message) {
744         mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
745                 "{\"result\": \"%s\"}",
746                 message);
747     }
748     else if (ret_code == 0) {
749         mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
750                 "{\"result\": null}");
751     }
752     else if (ret_code == 1) {
753         mg_send_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT, message, strlen(message));
754     }
755     else if (ret_code == 2) {
756         mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
757                 "{\"result\": %d}",
758                 arg);
759     }
760     else /* if (ret_code == 3) */ {
761         mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
762                 "{\"result\": %u}",
763                 (unsigned)arg);
764     }
765 }
766 
767 // reply to jsonrpc command
rpc_response_jsonrpc(rpc_t * rpc,int ret_code,char const * message,int arg)768 static void rpc_response_jsonrpc(rpc_t *rpc, int ret_code, char const *message, int arg)
769 {
770     char const *id = rpc->id ? rpc->id : "null";
771     if (ret_code < 0) {
772         mg_printf_http_chunk(rpc->nc,
773                 "{\"jsonrpc\": \"2.0\", \"error\": {\"code\": %d, \"message\": \"%s\"}, \"id\": %s}",
774                 ret_code, message, id);
775     }
776     else if (ret_code == 0 && message) {
777         mg_printf_http_chunk(rpc->nc,
778                 "{\"jsonrpc\": \"2.0\", \"result\": \"%s\", \"id\": %s}",
779                 message, id);
780     }
781     else if (ret_code == 0) {
782         mg_printf_http_chunk(rpc->nc,
783                 "{\"jsonrpc\": \"2.0\", \"result\": null, \"id\": %s}",
784                 id);
785     }
786     else if (ret_code == 1) {
787         mg_printf_http_chunk(rpc->nc,
788                 "{\"jsonrpc\": \"2.0\", \"result\": %s, \"id\": %s}",
789                 message, id);
790     }
791     else if (ret_code == 2) {
792         mg_printf_http_chunk(rpc->nc,
793                 "{\"jsonrpc\": \"2.0\", \"result\": %d, \"id\": %s}",
794                 arg, id);
795     }
796     else /* if (ret_code == 3) */ {
797         mg_printf_http_chunk(rpc->nc,
798                 "{\"jsonrpc\": \"2.0\", \"result\": %u, \"id\": %s}",
799                 (unsigned)arg, id);
800     }
801     mg_send_http_chunk(rpc->nc, "", 0); /* Send empty chunk, the end of response */
802 }
803 
804 // reply to json command
rpc_response_jsoncmd(rpc_t * rpc,int ret_code,char const * message,int arg)805 static void rpc_response_jsoncmd(rpc_t *rpc, int ret_code, char const *message, int arg)
806 {
807     if (ret_code < 0) {
808         mg_printf_http_chunk(rpc->nc,
809                 "{\"error\": {\"code\": %d, \"message\": \"%s\"}}",
810                 ret_code, message);
811     }
812     else if (ret_code == 0 &&message) {
813         mg_printf_http_chunk(rpc->nc,
814                 "{\"result\": \"%s\"}",
815                 message);
816     }
817     else if (ret_code == 0) {
818         mg_printf_http_chunk(rpc->nc,
819                 "{\"result\": null}");
820     }
821     else if (ret_code == 1) {
822         mg_printf_http_chunk(rpc->nc,
823                 "{\"result\": %s}",
824                 message);
825     }
826     else if (ret_code == 2) {
827         mg_printf_http_chunk(rpc->nc,
828                 "{\"result\": %d}",
829                 arg);
830     }
831     else /* if (ret_code == 3) */ {
832         mg_printf_http_chunk(rpc->nc,
833                 "{\"result\": %u}",
834                 (unsigned)arg);
835     }
836     mg_send_http_chunk(rpc->nc, "", 0); /* Send empty chunk, the end of response */
837 }
838 
839 // {"cmd":"sample_rate","val":1024000}
840 // http --stream --timeout=70 :8433/events
841 //s.a. https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data.html
handle_json_events(struct mg_connection * nc,struct http_message * hm)842 static void handle_json_events(struct mg_connection *nc, struct http_message *hm)
843 {
844     UNUSED(hm);
845     /* Send headers */
846     mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
847 
848     /* Mark connection */
849     struct nc_context *ctx = calloc(1, sizeof(*ctx));
850     if (!ctx) {
851         WARN_CALLOC("handle_json_events()");
852         return;
853     }
854     ctx->is_chunked = 1;
855     nc->user_data   = ctx;
856 
857     mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer
858 }
859 
860 // (echo "GET /stream HTTP/1.0\n"; sleep 600) | socat - tcp:127.0.0.1:8433
handle_json_stream(struct mg_connection * nc,struct http_message * hm)861 static void handle_json_stream(struct mg_connection *nc, struct http_message *hm)
862 {
863     UNUSED(hm);
864     /* Send headers */
865     mg_printf(nc, "HTTP/1.1 200 OK\r\n\r\n");
866 
867     /* Mark connection */
868     struct nc_context *ctx = calloc(1, sizeof(*ctx));
869     if (!ctx) {
870         WARN_CALLOC("handle_json_stream()");
871         return;
872     }
873     ctx->is_chunked = 0;
874     nc->user_data   = ctx;
875 
876     mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer
877 }
878 
879 // Handles GET with query string and POST with form-encoded body
880 // curl -D - 'http://127.0.0.1:8433/cmd?cmd=report_meta&arg=level'
881 // curl -D - -d "cmd=report_meta&arg=level" -X POST 'http://127.0.0.1:8433/cmd'
882 // http :8433/cmd cmd==center_frequency val==868000000'
883 // http --form POST :8433/cmd cmd=report_meta arg=level val=1
handle_cmd_rpc(struct mg_connection * nc,struct http_message * hm)884 static void handle_cmd_rpc(struct mg_connection *nc, struct http_message *hm)
885 {
886     struct http_server_context *ctx = nc->user_data;
887     char cmd[100], arg[100], val[100];
888     rpc_t rpc = {
889             .nc = nc,
890             .response = rpc_response_jsoncmd,
891             .method = cmd,
892             .arg = arg,
893     };
894 
895     /* Send headers */
896     mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
897 
898     /* Get URL variables */
899     if (mg_vcmp(&hm->method, "GET") == 0) {
900         mg_get_http_var(&hm->query_string, "cmd", cmd, sizeof(cmd));
901         mg_get_http_var(&hm->query_string, "arg", arg, sizeof(arg));
902         mg_get_http_var(&hm->query_string, "val", val, sizeof(val));
903     }
904     /* Get form variables */
905     else {
906         mg_get_http_var(&hm->body, "cmd", cmd, sizeof(cmd));
907         mg_get_http_var(&hm->body, "arg", arg, sizeof(arg));
908         mg_get_http_var(&hm->body, "val", val, sizeof(val));
909     }
910     char *endptr = NULL;
911     rpc.val = strtol(val, &endptr, 10);
912     fprintf(stderr, "POST Got %s, arg %s, val %s (%u)\n", cmd, arg, val, rpc.val);
913 
914     rpc_exec(&rpc, ctx->cfg);
915 }
916 
917 // Handles POST with JSONRPC command
918 // http POST :8433/jsonrpc jsonrpc=2.0 method=sample_rate params:='[1024000]'
handle_json_rpc(struct mg_connection * nc,struct http_message * hm)919 static void handle_json_rpc(struct mg_connection *nc, struct http_message *hm)
920 {
921     struct http_server_context *ctx = nc->user_data;
922 
923     rpc_t rpc = {
924             .nc       = nc,
925             .response = rpc_response_jsonrpc,
926     };
927 
928     /* Send headers */
929     mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
930 
931     /* Parse JSON */
932     int ret = jsonrpc_parse(&rpc, &hm->body);
933     if (!ret) {
934         rpc_exec(&rpc, ctx->cfg);
935     }
936     else {
937         char *error = "{\"error\":\"Invalid command\"}";
938         mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));
939     }
940 
941     free(rpc.method);
942     free(rpc.id);
943     free(rpc.arg);
944 }
945 
946 // Handles WS with JSON command
handle_ws_rpc(struct mg_connection * nc,struct websocket_message * wm)947 static void handle_ws_rpc(struct mg_connection *nc, struct websocket_message *wm)
948 {
949     struct http_server_context *ctx = nc->user_data;
950 
951     rpc_t rpc = {
952             .nc       = nc,
953             .response = rpc_response_ws,
954     };
955 
956     struct mg_str d = {(char *)wm->data, wm->size};
957 
958     /* Parse JSON */
959     int ret = json_parse(&rpc, &d);
960     if (!ret) {
961         rpc_exec(&rpc, ctx->cfg);
962     }
963     else {
964         char *error = "{\"error\":\"Invalid command\"}";
965         mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));
966     }
967 
968     free(rpc.method);
969     free(rpc.id);
970     free(rpc.arg);
971 }
972 
973 static void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
974 
send_keep_alive(struct mg_connection * nc)975 static void send_keep_alive(struct mg_connection *nc)
976 {
977     if (nc->handler != ev_handler)
978         return; // this should not happen
979 
980     struct nc_context *ctx = nc->user_data;
981     if (!ctx)
982         return; // this should not happen
983 
984     if (ctx->is_chunked) {
985         mg_send_http_chunk(nc, "\r\n", 2);
986     }
987     else {
988         mg_send(nc, "\r\n", 2);
989     }
990     mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
991 }
992 
ev_handler(struct mg_connection * nc,int ev,void * ev_data)993 static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
994 {
995     switch (ev) {
996     case MG_EV_TIMER:
997         send_keep_alive(nc);
998         break;
999     case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
1000         struct http_server_context *ctx = nc->user_data;
1001         /* New websocket connection. Send meta. */
1002         data_t *meta = meta_data(ctx->cfg);
1003         data_output_print(ctx->output, meta);
1004         data_free(meta);
1005         /* Send history */
1006         for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))
1007             mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, (char *)*iter, strlen((char *)*iter));
1008         break;
1009     }
1010     case MG_EV_WEBSOCKET_FRAME: {
1011         struct websocket_message *wm = (struct websocket_message *)ev_data;
1012 
1013         handle_ws_rpc(nc, wm);
1014         break;
1015     }
1016     case MG_EV_HTTP_REQUEST: {
1017         struct http_message *hm = (struct http_message *)ev_data;
1018 
1019         if (mg_vcmp(&hm->method, "OPTIONS") == 0) {
1020             handle_options(nc, hm);
1021         }
1022         else if (mg_vcmp(&hm->uri, "/") == 0) {
1023             handle_get(nc, hm, INDEX_HTML, sizeof(INDEX_HTML));
1024             handle_redirect(nc, hm);
1025         }
1026         else if (mg_vcmp(&hm->uri, "/ui") == 0) {
1027             handle_redirect(nc, hm);
1028         }
1029         else if (mg_vcmp(&hm->uri, "/jsonrpc") == 0) {
1030             handle_json_rpc(nc, hm);
1031         }
1032         else if (mg_vcmp(&hm->uri, "/cmd") == 0) {
1033             handle_cmd_rpc(nc, hm);
1034         }
1035         else if (mg_vcmp(&hm->uri, "/events") == 0) {
1036             handle_json_events(nc, hm);
1037         }
1038         else if (mg_vcmp(&hm->uri, "/stream") == 0) {
1039             handle_json_stream(nc, hm);
1040         }
1041         else if (mg_vcmp(&hm->uri, "/api") == 0) {
1042             //handle_api_query(nc, hm);
1043         }
1044 #ifdef SERVE_STATIC
1045         else {
1046             struct http_server_context *ctx = nc->user_data;
1047             mg_serve_http(nc, hm, ctx->server_opts); /* Serve static content */
1048         }
1049 #endif
1050         break;
1051     }
1052     case MG_EV_CLOSE:
1053         //fprintf(stderr, "MG_EV_CLOSE %p %p %p\n", ev_data, nc, nc->user_data);
1054         break;
1055     default:
1056         break;
1057     }
1058 }
1059 
is_websocket(const struct mg_connection * nc)1060 static int is_websocket(const struct mg_connection *nc)
1061 {
1062     return nc->flags & MG_F_IS_WEBSOCKET;
1063 }
1064 
1065 // event handler to broadcast to all our sockets
http_broadcast_send(struct http_server_context * ctx,char const * msg,size_t len)1066 static void http_broadcast_send(struct http_server_context *ctx, char const *msg, size_t len)
1067 {
1068     struct mg_connection *nc;
1069     struct mg_mgr *mgr = ctx->conn->mgr;
1070 
1071     char *dup = strdup(msg);
1072     if (!dup) {
1073         WARN_STRDUP("http_broadcast_send()");
1074     }
1075     else {
1076         free(ring_list_push(ctx->history, dup));
1077     }
1078 
1079     for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
1080         if (nc->handler != ev_handler)
1081             continue;
1082 
1083         struct nc_context *cctx = nc->user_data; // might not be valid
1084         if (is_websocket(nc)) {
1085             mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, msg, len);
1086         }
1087         else if (cctx && cctx->is_chunked) {
1088             mg_send_http_chunk(nc, msg, len);
1089             mg_send_http_chunk(nc, "\r\n", 2);
1090             mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
1091         }
1092         else if (cctx && !cctx->is_chunked) {
1093             mg_send(nc, msg, len);
1094             mg_send(nc, "\r\n", 2);
1095             mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
1096         }
1097     }
1098 }
1099 
http_server_start(struct mg_mgr * mgr,char const * host,char const * port,r_cfg_t * cfg,struct data_output * output)1100 static struct http_server_context *http_server_start(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg, struct data_output *output)
1101 {
1102     struct mg_bind_opts bind_opts;
1103     const char *err_str;
1104 
1105     //struct http_server_context
1106     struct http_server_context *ctx = calloc(1, sizeof(struct http_server_context));
1107     if (!ctx) {
1108         WARN_CALLOC("http_server_start()");
1109         return NULL;
1110     }
1111 
1112     ctx->cfg     = cfg;
1113     ctx->output  = output;
1114     ctx->history = ring_list_new(DEFAULT_HISTORY_SIZE);
1115 
1116     char address[253 + 6 + 1]; // dns max + port
1117     // if the host is an IPv6 address it needs quoting
1118     if (strchr(host, ':'))
1119         snprintf(address, sizeof(address), "[%s]:%s", host, port);
1120     else
1121         snprintf(address, sizeof(address), "%s:%s", host, port);
1122 
1123     /* Set HTTP server options */
1124     memset(&bind_opts, 0, sizeof(bind_opts));
1125     bind_opts.user_data = ctx;
1126     bind_opts.error_string = &err_str;
1127 
1128     ctx->conn = mg_bind_opt(mgr, address, ev_handler, bind_opts);
1129     if (ctx->conn == NULL) {
1130         fprintf(stderr, "Error starting server on address %s: %s\n", address,
1131                 *bind_opts.error_string);
1132         free(ctx);
1133         return NULL;
1134     }
1135 
1136     mg_set_protocol_http_websocket(ctx->conn);
1137     ctx->server_opts.document_root            = "."; // Serve current directory
1138     ctx->server_opts.enable_directory_listing = "yes";
1139 
1140     printf("Starting HTTP server on address %s, serving %s\n", address,
1141             ctx->server_opts.document_root);
1142 
1143     return ctx;
1144 }
1145 
1146 #define SHUTDOWN_JSON "{\"shutdown\":\"goodbye\"}"
1147 
http_server_stop(struct http_server_context * ctx)1148 static int http_server_stop(struct http_server_context *ctx)
1149 {
1150     if (!ctx)
1151         return 0;
1152 
1153     // close the server
1154     ctx->conn->user_data = NULL;
1155     ctx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;
1156 
1157     // close connections with a goodbye
1158     struct mg_mgr *mgr = ctx->conn->mgr;
1159     for (struct mg_connection *nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
1160         if (nc->handler != ev_handler)
1161             continue;
1162 
1163         struct nc_context *cctx = nc->user_data; // might not be valid
1164         if (is_websocket(nc)) {
1165             mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1166         }
1167         else if (cctx && cctx->is_chunked) {
1168             mg_send_http_chunk(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1169             mg_send_http_chunk(nc, "\r\n", 2);
1170             mg_send_http_chunk(nc, "", 0);            /* Send empty chunk, the end of response */
1171         }
1172         else if (cctx && !cctx->is_chunked) {
1173             mg_send(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1174             mg_send(nc, "\r\n", 2);
1175         }
1176     }
1177 
1178     for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))
1179         free((data_t *)*iter);
1180     ring_list_free(ctx->history);
1181 
1182     return 0;
1183 }
1184 
1185 /* HTTP data output */
1186 
1187 typedef struct {
1188     struct data_output output;
1189     struct http_server_context *server;
1190 } data_output_http_t;
1191 
print_http_data(data_output_t * output,data_t * data,char const * format)1192 static void print_http_data(data_output_t *output, data_t *data, char const *format)
1193 {
1194     UNUSED(format);
1195     data_output_http_t *http = (data_output_http_t *)output;
1196 
1197     // collect well-known top level keys
1198     data_t *data_model = NULL;
1199     for (data_t *d = data; d; d = d->next) {
1200         if (!strcmp(d->key, "model"))
1201             data_model = d;
1202     }
1203 
1204     if (data_model) {
1205         // "events"
1206         char buf[2048]; // we expect the biggest strings to be around 500 bytes.
1207         size_t len = data_print_jsons(data, buf, sizeof(buf));
1208         http_broadcast_send(http->server, buf, len);
1209     }
1210     else {
1211         // "states"
1212         size_t buf_size = 20000; // state message need a large buffer
1213         char *buf       = malloc(buf_size);
1214         if (!buf) {
1215             WARN_MALLOC("print_http_data()");
1216             return; // NOTE: skip output on alloc failure.
1217         }
1218         size_t len = data_print_jsons(data, buf, buf_size);
1219         http_broadcast_send(http->server, buf, len);
1220         free(buf);
1221     }
1222 }
1223 
data_output_http_free(data_output_t * output)1224 static void data_output_http_free(data_output_t *output)
1225 {
1226     data_output_http_t *http = (data_output_http_t *)output;
1227 
1228     if (!http)
1229         return;
1230 
1231     http_server_stop(http->server);
1232 
1233     free(http);
1234 }
1235 
data_output_http_create(struct mg_mgr * mgr,char const * host,char const * port,r_cfg_t * cfg)1236 struct data_output *data_output_http_create(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg)
1237 {
1238     data_output_http_t *http = calloc(1, sizeof(data_output_http_t));
1239     if (!http) {
1240         WARN_CALLOC("data_output_http_create()");
1241         return NULL;
1242     }
1243 
1244     http->output.print_data   = print_http_data;
1245     http->output.output_free  = data_output_http_free;
1246 
1247     http->server = http_server_start(mgr, host, port, cfg, &http->output);
1248     if (!http->server) {
1249         exit(1);
1250     }
1251 
1252     return &http->output;
1253 }
1254