mirror of
				https://github.com/alexandrebobkov/ESP-Nodes.git
				synced 2025-10-31 10:02:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			929 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			929 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-FileCopyrightText: 2024 Cesanta Software Limited
 | |
| // SPDX-License-Identifier: GPL-2.0-only or commercial
 | |
| // Generated by Mongoose Wizard, https://mongoose.ws/wizard/
 | |
| 
 | |
| #include "mongoose.h"
 | |
| #include "mongoose_glue.h"
 | |
| 
 | |
| #if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
 | |
| #define HTTP_URL "http://0.0.0.0:8080"
 | |
| #define HTTPS_URL "https://0.0.0.0:8443"
 | |
| #else
 | |
| #define HTTP_URL "http://0.0.0.0:80"
 | |
| #define HTTPS_URL "https://0.0.0.0:443"
 | |
| #endif
 | |
| 
 | |
| #ifndef offsetof
 | |
| #define offsetof(st, m) ((size_t) ((char *) &((st *) 0)->m - (char *) 0))
 | |
| #endif
 | |
| 
 | |
| #define NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
 | |
| #define JSON_HEADERS "Content-Type: application/json\r\n" NO_CACHE_HEADERS
 | |
| 
 | |
| // How to create a self signed Elliptic Curve certificate, see
 | |
| // https://github.com/cesanta/mongoose/blob/master/test/certs/generate.sh
 | |
| #define TLS_CERT                                                       \
 | |
|   "-----BEGIN CERTIFICATE-----\n"                                      \
 | |
|   "MIIBMTCB2aADAgECAgkAluqkgeuV/zUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAwwI\n" \
 | |
|   "TW9uZ29vc2UwHhcNMjQwNTA3MTQzNzM2WhcNMzQwNTA1MTQzNzM2WjARMQ8wDQYD\n" \
 | |
|   "VQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASo3oEiG+BuTt5y\n" \
 | |
|   "ZRyfwNr0C+SP+4M0RG2pYkb2v+ivbpfi72NHkmXiF/kbHXtgmSrn/PeTqiA8M+mg\n" \
 | |
|   "BhYjDX+zoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAw\n" \
 | |
|   "RAIgTXW9MITQSwzqbNTxUUdt9DcB+8pPUTbWZpiXcA26GMYCIBiYw+DSFMLHmkHF\n" \
 | |
|   "+5U3NXW3gVCLN9ntD5DAx8LTG8sB\n"                                     \
 | |
|   "-----END CERTIFICATE-----\n"
 | |
| 
 | |
| #define TLS_KEY                                                        \
 | |
|   "-----BEGIN EC PRIVATE KEY-----\n"                                   \
 | |
|   "MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n" \
 | |
|   "AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n" \
 | |
|   "4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"                             \
 | |
|   "-----END EC PRIVATE KEY-----\n"
 | |
| 
 | |
| struct mg_mgr g_mgr;  // Mongoose event manager
 | |
| 
 | |
| #if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
 | |
| // Every time device state changes, this counter increments.
 | |
| // Used by the heartbeat endpoint, to signal the UI when to refresh
 | |
| static unsigned long s_device_change_version = 0;
 | |
| 
 | |
| struct attribute {
 | |
|   const char *name;
 | |
|   const char *type;
 | |
|   const char *format;
 | |
|   size_t offset;
 | |
|   size_t size;
 | |
|   bool readonly;
 | |
| };
 | |
| 
 | |
| struct apihandler {
 | |
|   const char *name;
 | |
|   const char *type;
 | |
|   bool readonly;
 | |
|   int read_level;
 | |
|   int write_level;
 | |
|   unsigned long version;
 | |
| };
 | |
| 
 | |
| struct apihandler_custom {
 | |
|   struct apihandler common;
 | |
|   void (*reply)(struct mg_connection *, struct mg_http_message *);
 | |
| };
 | |
| 
 | |
| struct apihandler_upload {
 | |
|   struct apihandler common;
 | |
|   void *(*opener)(char *, size_t);         // Open function (OTA and upload)
 | |
|   bool (*closer)(void *);                  // Closer function (OTA and upload)
 | |
|   bool (*writer)(void *, void *, size_t);  // Writer function (OTA and upload)
 | |
| };
 | |
| 
 | |
| struct apihandler_ota {
 | |
|   struct apihandler common;
 | |
|   void *(*opener)(char *, size_t);         // Open function (OTA and upload)
 | |
|   bool (*closer)(void *);                  // Closer function (OTA and upload)
 | |
|   bool (*writer)(void *, void *, size_t);  // Writer function (OTA and upload)
 | |
| };
 | |
| 
 | |
| struct apihandler_action {
 | |
|   struct apihandler common;
 | |
|   bool (*checker)(void);           // Checker function for actions
 | |
|   void (*starter)(struct mg_str);  // Starter function for actions
 | |
| };
 | |
| 
 | |
| struct apihandler_data {
 | |
|   struct apihandler common;
 | |
|   const struct attribute *attributes;  // Points to the strucure descriptor
 | |
|   size_t data_size;                    // Size of C structure
 | |
|   void (*getter)(void *);              // Getter/check/begin function
 | |
|   void (*setter)(void *);              // Setter/start/end function
 | |
| };
 | |
| 
 | |
| struct apihandler_array {
 | |
|   struct apihandler common;
 | |
|   const struct attribute *attributes;  // Points to the strucure descriptor
 | |
|   size_t data_size;                    // Size of C structure
 | |
|   void (*getter)(uint64_t, void *);    // Getter/check/begin function
 | |
|   void (*setter)(uint64_t, void *);    // Setter/start/end function
 | |
|   uint64_t (*sizer)(void);             // Array size, for data handlers only
 | |
| };
 | |
| 
 | |
| struct attribute s_state_attributes[] = {
 | |
|   {"speed", "int", NULL, offsetof(struct state, speed), 0, false},
 | |
|   {"temperature", "int", NULL, offsetof(struct state, temperature), 0, false},
 | |
|   {"humidity", "int", NULL, offsetof(struct state, humidity), 0, false},
 | |
|   {"uptime", "int", NULL, offsetof(struct state, uptime), 0, false},
 | |
|   {"version", "string", NULL, offsetof(struct state, version), 20, false},
 | |
|   {"online", "bool", NULL, offsetof(struct state, online), 0, false},
 | |
|   {"lights", "bool", NULL, offsetof(struct state, lights), 0, false},
 | |
|   {"level", "int", NULL, offsetof(struct state, level), 0, false},
 | |
|   {NULL, NULL, NULL, 0, 0, false}
 | |
| };
 | |
| struct attribute s_leds_attributes[] = {
 | |
|   {"led1", "bool", NULL, offsetof(struct leds, led1), 0, false},
 | |
|   {"led2", "bool", NULL, offsetof(struct leds, led2), 0, false},
 | |
|   {"led3", "bool", NULL, offsetof(struct leds, led3), 0, false},
 | |
|   {NULL, NULL, NULL, 0, 0, false}
 | |
| };
 | |
| struct attribute s_network_settings_attributes[] = {
 | |
|   {"ip_address", "string", NULL, offsetof(struct network_settings, ip_address), 20, false},
 | |
|   {"gw_address", "string", NULL, offsetof(struct network_settings, gw_address), 20, false},
 | |
|   {"netmask", "string", NULL, offsetof(struct network_settings, netmask), 20, false},
 | |
|   {"dhcp", "bool", NULL, offsetof(struct network_settings, dhcp), 0, false},
 | |
|   {NULL, NULL, NULL, 0, 0, false}
 | |
| };
 | |
| struct attribute s_settings_attributes[] = {
 | |
|   {"string_val", "string", NULL, offsetof(struct settings, string_val), 40, false},
 | |
|   {"log_level", "string", NULL, offsetof(struct settings, log_level), 10, false},
 | |
|   {"double_val", "double", "%.5f", offsetof(struct settings, double_val), 0, false},
 | |
|   {"int_val", "int", NULL, offsetof(struct settings, int_val), 0, false},
 | |
|   {"bool_val", "bool", NULL, offsetof(struct settings, bool_val), 0, false},
 | |
|   {NULL, NULL, NULL, 0, 0, false}
 | |
| };
 | |
| struct attribute s_security_attributes[] = {
 | |
|   {"admin_password", "string", NULL, offsetof(struct security, admin_password), 40, false},
 | |
|   {"user_password", "string", NULL, offsetof(struct security, user_password), 40, false},
 | |
|   {NULL, NULL, NULL, 0, 0, false}
 | |
| };
 | |
| 
 | |
| struct apihandler_action s_apihandler_reboot = {{"reboot", "action", false, 3, 7, 0UL}, glue_check_reboot, glue_start_reboot};
 | |
| struct apihandler_action s_apihandler_reformat = {{"reformat", "action", false, 3, 7, 0UL}, glue_check_reformat, glue_start_reformat};
 | |
| struct apihandler_ota s_apihandler_firmware_update = {{"firmware_update", "ota", false, 3, 7, 0UL}, glue_ota_begin_firmware_update, glue_ota_end_firmware_update, glue_ota_write_firmware_update};
 | |
| struct apihandler_upload s_apihandler_file_upload = {{"file_upload", "upload", false, 3, 7, 0UL}, glue_file_open_file_upload, glue_file_close_file_upload, glue_file_write_file_upload};
 | |
| struct apihandler_custom s_apihandler_graph_data = {{"graph_data", "custom", false, 3, 3, 0UL}, glue_reply_graph_data};
 | |
| struct apihandler_data s_apihandler_state = {{"state", "data", true, 0, 0, 0UL}, s_state_attributes, sizeof(struct state), (void (*)(void *)) glue_get_state, NULL};
 | |
| struct apihandler_data s_apihandler_leds = {{"leds", "data", false, 3, 3, 0UL}, s_leds_attributes, sizeof(struct leds), (void (*)(void *)) glue_get_leds, (void (*)(void *)) glue_set_leds};
 | |
| struct apihandler_data s_apihandler_network_settings = {{"network_settings", "data", false, 3, 7, 0UL}, s_network_settings_attributes, sizeof(struct network_settings), (void (*)(void *)) glue_get_network_settings, (void (*)(void *)) glue_set_network_settings};
 | |
| struct apihandler_data s_apihandler_settings = {{"settings", "data", false, 3, 7, 0UL}, s_settings_attributes, sizeof(struct settings), (void (*)(void *)) glue_get_settings, (void (*)(void *)) glue_set_settings};
 | |
| struct apihandler_data s_apihandler_security = {{"security", "data", false, 7, 7, 0UL}, s_security_attributes, sizeof(struct security), (void (*)(void *)) glue_get_security, (void (*)(void *)) glue_set_security};
 | |
| struct apihandler_custom s_apihandler_loglevels = {{"loglevels", "custom", false, 0, 0, 0UL}, glue_reply_loglevels};
 | |
| struct apihandler_custom s_apihandler_events = {{"events", "custom", false, 0, 0, 0UL}, glue_reply_events};
 | |
| 
 | |
| static struct apihandler *s_apihandlers[] = {
 | |
|   (struct apihandler *) &s_apihandler_reboot,
 | |
|   (struct apihandler *) &s_apihandler_reformat,
 | |
|   (struct apihandler *) &s_apihandler_firmware_update,
 | |
|   (struct apihandler *) &s_apihandler_file_upload,
 | |
|   (struct apihandler *) &s_apihandler_graph_data,
 | |
|   (struct apihandler *) &s_apihandler_state,
 | |
|   (struct apihandler *) &s_apihandler_leds,
 | |
|   (struct apihandler *) &s_apihandler_network_settings,
 | |
|   (struct apihandler *) &s_apihandler_settings,
 | |
|   (struct apihandler *) &s_apihandler_security,
 | |
|   (struct apihandler *) &s_apihandler_loglevels,
 | |
|   (struct apihandler *) &s_apihandler_events
 | |
| };
 | |
| 
 | |
| static struct apihandler *get_api_handler(struct mg_str name) {
 | |
|   size_t num_handlers = sizeof(s_apihandlers) / sizeof(s_apihandlers[0]);
 | |
|   size_t i;
 | |
|   if (name.len == 0) return NULL;
 | |
|   if (num_handlers == 0) return NULL;
 | |
|   for (i = 0; i < num_handlers; i++) {
 | |
|     struct apihandler *h = s_apihandlers[i];
 | |
|     size_t n = strlen(h->name);
 | |
|     if (n > name.len) continue;
 | |
|     if (strncmp(name.buf, h->name, n) != 0) continue;
 | |
|     if (name.len > n && name.buf[n] != '/') continue;
 | |
|     return h;
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static struct apihandler *find_handler(struct mg_http_message *hm) {
 | |
|   if (hm->uri.len < 6 || strncmp(hm->uri.buf, "/api/", 5) != 0) return NULL;
 | |
|   return get_api_handler(mg_str_n(hm->uri.buf + 5, hm->uri.len - 5));
 | |
| }
 | |
| 
 | |
| void mg_json_get_str2(struct mg_str json, const char *path, char *buf,
 | |
|                       size_t len) {
 | |
|   struct mg_str s = mg_json_get_tok(json, path);
 | |
|   if (s.len > 1 && s.buf[0] == '"') {
 | |
|     mg_json_unescape(mg_str_n(s.buf + 1, s.len - 2), buf, len);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void mongoose_set_http_handlers(const char *name, ...) {
 | |
|   struct apihandler *h = get_api_handler(mg_str(name));
 | |
|   va_list ap;
 | |
|   va_start(ap, name);
 | |
|   if (h == NULL) {
 | |
|     MG_ERROR(("No API with name [%s]", name));
 | |
|   } else if (strcmp(h->type, "data") == 0) {
 | |
|     ((struct apihandler_data *) h)->getter = va_arg(ap, void (*)(void *));
 | |
|     ((struct apihandler_data *) h)->setter = va_arg(ap, void (*)(void *));
 | |
|   } else if (strcmp(h->type, "action") == 0) {
 | |
|     ((struct apihandler_action *) h)->checker = va_arg(ap, bool (*)(void));
 | |
|     ((struct apihandler_action *) h)->starter =
 | |
|         va_arg(ap, void (*)(struct mg_str));
 | |
|   } else if (strcmp(h->type, "ota") == 0 || strcmp(h->type, "upload") == 0) {
 | |
|     ((struct apihandler_ota *) h)->opener =
 | |
|         va_arg(ap, void *(*) (char *, size_t));
 | |
|     ((struct apihandler_ota *) h)->closer = va_arg(ap, bool (*)(void *));
 | |
|     ((struct apihandler_ota *) h)->writer =
 | |
|         va_arg(ap, bool (*)(void *, void *, size_t));
 | |
|   } else if (strcmp(h->type, "custom") == 0) {
 | |
|     ((struct apihandler_custom *) h)->reply =
 | |
|         va_arg(ap, void (*)(struct mg_connection *, struct mg_http_message *));
 | |
|   } else {
 | |
|     MG_ERROR(("Setting [%s] failed: not implemented", name));
 | |
|   }
 | |
|   va_end(ap);
 | |
| }
 | |
| 
 | |
| #if WIZARD_ENABLE_HTTP_UI_LOGIN
 | |
| 
 | |
| struct user {
 | |
|   struct user *next;
 | |
|   char name[32];   // User name
 | |
|   char token[21];  // Login token
 | |
|   int level;       // Access level
 | |
| };
 | |
| 
 | |
| static struct user *s_users;  // List of authenticated users
 | |
| 
 | |
| // Parse HTTP requests, return authenticated user or NULL
 | |
| static struct user *authenticate(struct mg_http_message *hm) {
 | |
|   char user[100], pass[100];
 | |
|   struct user *u, *result = NULL;
 | |
|   mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
 | |
| 
 | |
|   if (user[0] != '\0' && pass[0] != '\0') {
 | |
|     // Both user and password is set, auth by user/password via glue API
 | |
|     int level = glue_authenticate(user, pass);
 | |
|     MG_DEBUG(("user %s, level: %d", user, level));
 | |
|     if (level > 0) {  // Proceed only if the firmware authenticated us
 | |
|       // uint64_t uid = hash(3, mg_str(user), mg_str(":"), mg_str(pass));
 | |
|       for (u = s_users; u != NULL && result == NULL; u = u->next) {
 | |
|         if (strcmp(user, u->name) == 0) result = u;
 | |
|       }
 | |
|       // Not yet authenticated, add to the list
 | |
|       if (result == NULL) {
 | |
|         result = (struct user *) calloc(1, sizeof(*result));
 | |
|         mg_snprintf(result->name, sizeof(result->name), "%s", user);
 | |
|         mg_random_str(result->token, sizeof(result->token) - 1);
 | |
|         result->level = level, result->next = s_users, s_users = result;
 | |
|       }
 | |
|     }
 | |
|   } else if (user[0] == '\0' && pass[0] != '\0') {
 | |
|     for (u = s_users; u != NULL && result == NULL; u = u->next) {
 | |
|       if (strcmp(u->token, pass) == 0) result = u;
 | |
|     }
 | |
|   }
 | |
|   MG_VERBOSE(("[%s/%s] -> %s", user, pass, result ? "OK" : "FAIL"));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static void handle_login(struct mg_connection *c, struct user *u) {
 | |
|   char cookie[256];
 | |
|   mg_snprintf(cookie, sizeof(cookie),
 | |
|               "Set-Cookie: access_token=%s; Path=/; "
 | |
|               "%sHttpOnly; SameSite=Lax; Max-Age=%d\r\n",
 | |
|               u->token, c->is_tls ? "Secure; " : "", 3600 * 24);
 | |
|   mg_http_reply(c, 200, cookie, "{%m:%m,%m:%d}",  //
 | |
|                 MG_ESC("user"), MG_ESC(u->name),  //
 | |
|                 MG_ESC("level"), u->level);
 | |
| }
 | |
| 
 | |
| static void handle_logout(struct mg_connection *c) {
 | |
|   char cookie[256];
 | |
|   mg_snprintf(cookie, sizeof(cookie),
 | |
|               "Set-Cookie: access_token=; Path=/; "
 | |
|               "Expires=Thu, 01 Jan 1970 00:00:00 UTC; "
 | |
|               "%sHttpOnly; Max-Age=0; \r\n",
 | |
|               c->is_tls ? "Secure; " : "");
 | |
|   mg_http_reply(c, 401, cookie, "Unauthorized\n");
 | |
| }
 | |
| #endif  // WIZARD_ENABLE_HTTP_UI_LOGIN
 | |
| 
 | |
| struct upload_state {
 | |
|   char marker;               // Tells that we're a file upload connection
 | |
|   size_t expected;           // POST data length, bytes
 | |
|   size_t received;           // Already received bytes
 | |
|   void *fp;                  // Opened file
 | |
|   bool (*fn_close)(void *);  // Close function
 | |
|   bool (*fn_write)(void *, void *, size_t);  // Write function
 | |
| };
 | |
| 
 | |
| struct action_state {
 | |
|   char marker;       // Tells that we're an action connection
 | |
|   bool (*fn)(void);  // Action status function
 | |
| };
 | |
| 
 | |
| static void close_uploaded_file(struct upload_state *us) {
 | |
|   us->marker = 0;
 | |
|   if (us->fn_close != NULL && us->fp != NULL) {
 | |
|     us->fn_close(us->fp);
 | |
|     us->fp = NULL;
 | |
|   }
 | |
|   memset(us, 0, sizeof(*us));
 | |
| }
 | |
| 
 | |
| static void upload_handler(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   struct upload_state *us = (struct upload_state *) c->data;
 | |
|   if (sizeof(*us) > sizeof(c->data)) {
 | |
|     mg_error(
 | |
|         c, "FAILURE: sizeof(c->data) == %lu, need %lu. Set -DMG_DATA_SIZE=XXX",
 | |
|         sizeof(c->data), sizeof(*us));
 | |
|     return;
 | |
|   }
 | |
|   // Catch uploaded file data for both MG_EV_READ and MG_EV_HTTP_HDRS
 | |
|   if (us->marker == 'U' && ev == MG_EV_READ && us->expected > 0 &&
 | |
|       c->recv.len > 0) {
 | |
|     size_t alignment = 512;  // Maximum flash write granularity (iMXRT, Pico)
 | |
|     size_t aligned = (us->received + c->recv.len < us->expected)
 | |
|                          ? aligned = MG_ROUND_DOWN(c->recv.len, alignment)
 | |
|                          : c->recv.len;  // Last write can be unaligned
 | |
|     bool ok = aligned > 0 ? us->fn_write(us->fp, c->recv.buf, aligned) : true;
 | |
|     us->received += aligned;
 | |
|     MG_DEBUG(("%lu chunk: %lu/%lu, %lu/%lu, ok: %d", c->id, aligned,
 | |
|               c->recv.len, us->received, us->expected, ok));
 | |
|     mg_iobuf_del(&c->recv, 0, aligned);  // Delete received data
 | |
|     if (ok == false) {
 | |
|       mg_http_reply(c, 400, "", "Upload error\n");
 | |
|       close_uploaded_file(us);
 | |
|       c->is_draining = 1;  // Close connection when response it sent
 | |
|     } else if (us->received >= us->expected) {
 | |
|       // Uploaded everything. Send response back
 | |
|       MG_INFO(("%lu done, %lu bytes", c->id, us->received));
 | |
|       mg_http_reply(c, 200, NULL, "%lu ok\n", us->received);
 | |
|       close_uploaded_file(us);
 | |
|       c->is_draining = 1;  // Close connection when response it sent
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Close uploading file descriptor
 | |
|   if (us->marker == 'U' && ev == MG_EV_CLOSE) close_uploaded_file(us);
 | |
|   (void) ev_data;
 | |
| }
 | |
| 
 | |
| static void prep_upload(struct mg_connection *c, struct mg_http_message *hm,
 | |
|                         void *(*fn_open)(char *, size_t),
 | |
|                         bool (*fn_close)(void *),
 | |
|                         bool (*fn_write)(void *, void *, size_t)) {
 | |
|   struct upload_state *us = (struct upload_state *) c->data;
 | |
|   struct mg_str parts[3];
 | |
|   char path[MG_PATH_MAX];
 | |
|   memset(us, 0, sizeof(*us));                    // Cleanup upload state
 | |
|   memset(parts, 0, sizeof(parts));               // Init match parts
 | |
|   mg_match(hm->uri, mg_str("/api/*/#"), parts);  // Fetch file name
 | |
|   mg_url_decode(parts[1].buf, parts[1].len, path, sizeof(path), 0);
 | |
|   us->fp = fn_open(path, hm->body.len);
 | |
|   MG_DEBUG(("file: [%s] size: %lu fp: %p", path, hm->body.len, us->fp));
 | |
|   us->marker = 'U';  // Mark us as an upload connection
 | |
|   if (us->fp == NULL) {
 | |
|     mg_http_reply(c, 400, JSON_HEADERS, "File open error\n");
 | |
|     c->is_draining = 1;
 | |
|   } else {
 | |
|     us->expected = hm->body.len;              // Store number of bytes we expect
 | |
|     us->fn_close = fn_close;                  // Store closing function
 | |
|     us->fn_write = fn_write;                  // Store writing function
 | |
|     mg_iobuf_del(&c->recv, 0, hm->head.len);  // Delete HTTP headers
 | |
|     c->fn = upload_handler;                   // Change event handler function
 | |
|     c->pfn = NULL;                            // Detach HTTP handler
 | |
|     mg_call(c, MG_EV_READ, &c->recv.len);     // Write initial data
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void handle_uploads(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   struct upload_state *us = (struct upload_state *) c->data;
 | |
| 
 | |
|   // Catch /upload requests early, without buffering whole body
 | |
|   // When we receive MG_EV_HTTP_HDRS event, that means we've received all
 | |
|   // HTTP headers but not necessarily full HTTP body
 | |
|   if (ev == MG_EV_HTTP_HDRS && us->marker == 0) {
 | |
|     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
 | |
|     struct apihandler *h = find_handler(hm);
 | |
|     if (h != NULL &&
 | |
|         (strcmp(h->type, "upload") == 0 || strcmp(h->type, "ota") == 0)) {
 | |
|       struct apihandler_upload *hu = (struct apihandler_upload *) h;
 | |
|       prep_upload(c, hm, hu->opener, hu->closer, hu->writer);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void handle_action(struct mg_connection *c, struct mg_http_message *hm,
 | |
|                           bool (*check_fn)(void),
 | |
|                           void (*start_fn)(struct mg_str)) {
 | |
|   if (hm->body.len > 0) {
 | |
|     start_fn(hm->body);
 | |
|     if (check_fn()) {
 | |
|       struct action_state *as = (struct action_state *) c->data;
 | |
|       as->marker = 'A';
 | |
|       as->fn = check_fn;
 | |
|     } else {
 | |
|       mg_http_reply(c, 200, JSON_HEADERS, "false");
 | |
|     }
 | |
|   } else {
 | |
|     mg_http_reply(c, 200, JSON_HEADERS, "%s", check_fn() ? "true" : "false");
 | |
|   }
 | |
| }
 | |
| 
 | |
| size_t print_struct(void (*out)(char, void *), void *ptr, va_list *ap) {
 | |
|   const struct attribute *a = va_arg(*ap, struct attribute *);
 | |
|   char *data = va_arg(*ap, char *);
 | |
|   size_t i, len = 0;
 | |
|   for (i = 0; a[i].name != NULL; i++) {
 | |
|     char *attrptr = data + a[i].offset;
 | |
|     len += mg_xprintf(out, ptr, "%s%m:", i == 0 ? "" : ",", MG_ESC(a[i].name));
 | |
|     if (strcmp(a[i].type, "int") == 0) {
 | |
|       len += mg_xprintf(out, ptr, "%d", *(int *) attrptr);
 | |
|     } else if (strcmp(a[i].type, "double") == 0) {
 | |
|       const char *fmt = a[i].format;
 | |
|       if (fmt == NULL) fmt = "%g";
 | |
|       len += mg_xprintf(out, ptr, fmt, *(double *) attrptr);
 | |
|     } else if (strcmp(a[i].type, "bool") == 0) {
 | |
|       len += mg_xprintf(out, ptr, "%s", *(bool *) attrptr ? "true" : "false");
 | |
|     } else if (strcmp(a[i].type, "string") == 0) {
 | |
|       len += mg_xprintf(out, ptr, "%m", MG_ESC(attrptr));
 | |
|     } else {
 | |
|       len += mg_xprintf(out, ptr, "null");
 | |
|     }
 | |
|   }
 | |
|   return len;
 | |
| }
 | |
| 
 | |
| static void handle_object(struct mg_connection *c, struct mg_http_message *hm,
 | |
|                           struct apihandler_data *h) {
 | |
|   void *data = calloc(1, h->data_size);
 | |
|   h->getter(data);
 | |
|   if (hm->body.len > 0 && h->data_size > 0) {
 | |
|     char *tmp = calloc(1, h->data_size);
 | |
|     size_t i;
 | |
|     memcpy(tmp, data, h->data_size);
 | |
|     for (i = 0; h->attributes[i].name != NULL; i++) {
 | |
|       const struct attribute *a = &h->attributes[i];
 | |
|       char jpath[100];
 | |
|       mg_snprintf(jpath, sizeof(jpath), "$.%s", a->name);
 | |
|       if (strcmp(a->type, "int") == 0) {
 | |
|         double d;
 | |
|         if (mg_json_get_num(hm->body, jpath, &d)) {
 | |
|           int v = (int) d;
 | |
|           memcpy(tmp + a->offset, &v, sizeof(v));
 | |
|         }
 | |
|       } else if (strcmp(a->type, "bool") == 0) {
 | |
|         mg_json_get_bool(hm->body, jpath, (bool *) (tmp + a->offset));
 | |
|       } else if (strcmp(a->type, "double") == 0) {
 | |
|         mg_json_get_num(hm->body, jpath, (double *) (tmp + a->offset));
 | |
|       } else if (strcmp(a->type, "string") == 0) {
 | |
|         mg_json_get_str2(hm->body, jpath, tmp + a->offset, a->size);
 | |
|       }
 | |
|     }
 | |
|     // If structure changes, increment version
 | |
|     if (memcmp(data, tmp, h->data_size) != 0) s_device_change_version++;
 | |
|     if (h->setter != NULL) h->setter(tmp);  // Can be NULL if readonly
 | |
|     free(tmp);
 | |
|     h->getter(data);  // Re-sync again after setting
 | |
|   }
 | |
|   mg_http_reply(c, 200, JSON_HEADERS, "{%M}\n", print_struct, h->attributes,
 | |
|                 data);
 | |
|   free(data);
 | |
| }
 | |
| 
 | |
| static size_t print_array(void (*out)(char, void *), void *ptr, va_list *ap) {
 | |
|   struct apihandler_array *ha = va_arg(*ap, struct apihandler_array *);
 | |
|   uint64_t size = *va_arg(*ap, uint64_t *);
 | |
|   uint64_t start = *va_arg(*ap, uint64_t *);
 | |
|   size_t i, max = 20, len = 0;
 | |
|   void *data = calloc(1, ha->data_size);
 | |
|   for (i = 0; i < max && start + i < size; i++) {
 | |
|     ha->getter(start + i, data);
 | |
|     if (i > 0) len += mg_xprintf(out, ptr, ",");
 | |
|     len += mg_xprintf(out, ptr, "{%M}", print_struct, ha->attributes, data);
 | |
|   }
 | |
|   free(data);
 | |
|   return len;
 | |
| }
 | |
| 
 | |
| static void handle_array(struct mg_connection *c, struct mg_http_message *hm,
 | |
|                          struct apihandler_array *h) {
 | |
|   char buf[40] = "";
 | |
|   uint64_t size = h->sizer();
 | |
|   uint64_t start = 0;
 | |
|   mg_http_get_var(&hm->query, "start", buf, sizeof(buf));
 | |
|   if (!mg_str_to_num(mg_str(buf), 10, &start, sizeof(start))) start = 0;
 | |
|   mg_http_reply(c, 200, JSON_HEADERS, "{%m:%llu, %m:%llu, %m:[%M]}\n",
 | |
|                 MG_ESC("size"), size, MG_ESC("start"), start, MG_ESC("data"),
 | |
|                 print_array, h, &size, &start);
 | |
| }
 | |
| 
 | |
| size_t print_timeseries(void (*out)(char, void *), void *ptr, va_list *ap) {
 | |
|   uint32_t *timestamps = va_arg(*ap, uint32_t *);
 | |
|   double *values = va_arg(*ap, double *);
 | |
|   size_t count = va_arg(*ap, size_t);
 | |
|   size_t i, len = 0;
 | |
|   for (i = 0; i < count; i++) {
 | |
|     const char *comma = i == 0 ? "" : ",";
 | |
|     len += mg_xprintf(out, ptr, "%s[%lu,%g]", comma, timestamps[i], values[i]);
 | |
|   }
 | |
|   return len;
 | |
| }
 | |
| 
 | |
| static void handle_api_call(struct mg_connection *c, struct mg_http_message *hm,
 | |
|                             struct apihandler *h) {
 | |
|   if (strcmp(h->type, "object") == 0 || strcmp(h->type, "data") == 0) {
 | |
|     handle_object(c, hm, (struct apihandler_data *) h);
 | |
|   } else if (strcmp(h->type, "array") == 0) {
 | |
|     handle_array(c, hm, (struct apihandler_array *) h);
 | |
|   } else if (strcmp(h->type, "action") == 0) {
 | |
|     struct apihandler_action *ha = (struct apihandler_action *) h;
 | |
|     handle_action(c, hm, ha->checker, ha->starter);
 | |
|     //  } else if (strcmp(h->type, "graph") == 0) {
 | |
|     //    handle_graph(c, hm, (struct apihandler_graph *) h);
 | |
|   } else if (strcmp(h->type, "custom") == 0) {
 | |
|     ((struct apihandler_custom *) h)->reply(c, hm);
 | |
|   } else {
 | |
|     mg_http_reply(c, 500, JSON_HEADERS, "API type %s unknown\n", h->type);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void glue_update_state(void) {
 | |
|   s_device_change_version++;
 | |
| }
 | |
| 
 | |
| // Mongoose event handler function, gets called by the mg_mgr_poll()
 | |
| static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   if (ev == MG_EV_HTTP_HDRS && c->data[0] == 0) {
 | |
| #if WIZARD_ENABLE_HTTP_UI_LOGIN
 | |
|     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
 | |
|     if (mg_match(hm->uri, mg_str("/api/#"), NULL) ||
 | |
|         mg_match(hm->uri, mg_str("/websocket"), NULL)) {
 | |
|       struct apihandler *h = find_handler(hm);
 | |
|       struct user *u = authenticate(hm);
 | |
|       if ((u == NULL ||
 | |
|            (h != NULL && (u->level < h->read_level ||
 | |
|                           (hm->body.len > 0 && u->level < h->write_level))))) {
 | |
|         mg_http_reply(c, 403, JSON_HEADERS, "Not Authorised\n");
 | |
|         c->data[0] = 'Z';  // Mark this connection as handled
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   // We're checking c->is_websocket cause WS connection use c->data
 | |
|   if (c->is_websocket == 0) handle_uploads(c, ev, ev_data);
 | |
|   if (ev == MG_EV_POLL && c->is_websocket == 0 && c->data[0] == 'A') {
 | |
|     // Check if action in progress is complete
 | |
|     struct action_state *as = (struct action_state *) c->data;
 | |
|     if (as->fn() == false) {
 | |
|       mg_http_reply(c, 200, JSON_HEADERS, "true");
 | |
|       memset(as, 0, sizeof(*as));
 | |
|     }
 | |
|   } else if (ev == MG_EV_HTTP_MSG && c->is_websocket == 0 && c->data[0] == 0) {
 | |
|     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
 | |
| #if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
 | |
|     struct apihandler *h = find_handler(hm);
 | |
| #if WIZARD_ENABLE_HTTP_UI_LOGIN
 | |
|     struct user *u = authenticate(hm);
 | |
|     if (mg_match(hm->uri, mg_str("/api/login"), NULL)) {
 | |
|       handle_login(c, u);
 | |
|     } else if (mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
 | |
|       handle_logout(c);
 | |
|     } else
 | |
| #endif
 | |
|         if (mg_match(hm->uri, mg_str("/api/ok"), NULL)) {
 | |
|       mg_http_reply(c, 200, JSON_HEADERS, "true\n");
 | |
|     } else if (mg_match(hm->uri, mg_str("/websocket"), NULL)) {
 | |
|       mg_ws_upgrade(c, hm, NULL);
 | |
|     } else if (mg_match(hm->uri, mg_str("/api/heartbeat"), NULL)) {
 | |
|       mg_http_reply(c, 200, JSON_HEADERS, "{%m:%lu}\n", MG_ESC("version"),
 | |
|                     s_device_change_version);
 | |
|     } else if (h != NULL) {
 | |
|       handle_api_call(c, hm, h);
 | |
|     } else if (c->data[0] == 0)
 | |
| #endif  // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
 | |
|     {
 | |
| #if WIZARD_ENABLE_HTTP_UI
 | |
|       struct mg_http_serve_opts opts;
 | |
|       memset(&opts, 0, sizeof(opts));
 | |
|       opts.root_dir = "/web_root/";
 | |
|       opts.fs = &mg_fs_packed;
 | |
|       opts.extra_headers = NO_CACHE_HEADERS;
 | |
|       mg_http_serve_dir(c, hm, &opts);
 | |
| #else
 | |
|       mg_http_reply(c, 200, "", ":)\n");
 | |
| #endif  // WIZARD_ENABLE_HTTP_UI
 | |
|     }
 | |
|     // Show this request
 | |
|     MG_DEBUG(("%lu %.*s %.*s %lu -> %.*s %lu", c->id, hm->method.len,
 | |
|               hm->method.buf, hm->uri.len, hm->uri.buf, hm->body.len,
 | |
|               c->send.len > 15 ? 3 : 0, &c->send.buf[9], c->send.len));
 | |
|   } else if (ev == MG_EV_WS_MSG) {
 | |
|     // Got websocket frame. Received data is wm->data
 | |
|     struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
 | |
|     mg_ws_send(c, wm->data.buf, wm->data.len, WEBSOCKET_OP_TEXT);
 | |
|   } else if (ev == MG_EV_WS_MSG || ev == MG_EV_WS_CTL) {
 | |
|     // Ignore received data
 | |
|   } else if (ev == MG_EV_ACCEPT) {
 | |
|     if (c->fn_data != NULL) {  // TLS listener
 | |
|       struct mg_tls_opts opts;
 | |
|       memset(&opts, 0, sizeof(opts));
 | |
|       opts.cert = mg_str(TLS_CERT);
 | |
|       opts.key = mg_str(TLS_KEY);
 | |
|       mg_tls_init(c, &opts);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| #if WIZARD_ENABLE_WEBSOCKET
 | |
| struct ws_handler {
 | |
|   unsigned timeout_ms;
 | |
|   void (*fn)(struct mg_connection *);
 | |
| };
 | |
| static struct ws_handler
 | |
|     s_ws_handlers[sizeof(((struct mg_connection *) 0)->data) /
 | |
|                   sizeof(struct ws_handler)];
 | |
| static size_t s_ws_handlers_count;
 | |
| 
 | |
| void mongoose_add_ws_handler(unsigned ms, void (*fn)(struct mg_connection *)) {
 | |
|   size_t max = sizeof(s_ws_handlers) / sizeof(s_ws_handlers[0]);
 | |
|   if (s_ws_handlers_count >= max) {
 | |
|     MG_ERROR(("WS handlers limit exceeded, max %lu", max));
 | |
|   } else {
 | |
|     s_ws_handlers[s_ws_handlers_count].timeout_ms = ms;
 | |
|     s_ws_handlers[s_ws_handlers_count].fn = fn;
 | |
|     s_ws_handlers_count++;
 | |
|   }
 | |
| };
 | |
| 
 | |
| static void send_websocket_data(void) {
 | |
|   struct mg_connection *c;
 | |
|   uint64_t now = mg_millis();
 | |
| 
 | |
|   for (c = g_mgr.conns; c != NULL; c = c->next) {
 | |
|     uint64_t *timers = (uint64_t *) &c->data[0];
 | |
|     size_t i;
 | |
| 
 | |
|     if (c->is_websocket == 0) continue;  // Not a websocket connection? Skip
 | |
|     if (c->send.len > 2048) continue;    // Too much data already? Skip
 | |
| 
 | |
|     for (i = 0; i < s_ws_handlers_count; i++) {
 | |
|       if (c->pfn_data == NULL ||
 | |
|           mg_timer_expired(&timers[i], s_ws_handlers[i].timeout_ms, now)) {
 | |
|         s_ws_handlers[i].fn(c);
 | |
|         c->pfn_data = (void *) 1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| #else
 | |
| void mongoose_add_ws_handler(unsigned ms, void (*fn)(struct mg_connection *)) {
 | |
|   (void) ms, (void) fn;
 | |
|   MG_ERROR(("Websocket support is not enabled!"));
 | |
| }
 | |
| #endif  // WIZARD_ENABLE_WEBSOCKET
 | |
| 
 | |
| #endif  // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
 | |
| 
 | |
| #if WIZARD_ENABLE_SNTP
 | |
| static uint64_t s_sntp_timer = 0;
 | |
| bool s_sntp_response_received = false;
 | |
| static void sntp_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   if (ev == MG_EV_SNTP_TIME) {
 | |
|     uint64_t t = *(uint64_t *) ev_data;
 | |
|     glue_sntp_on_time(t);
 | |
|     s_sntp_response_received = true;
 | |
|     s_sntp_timer += (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
 | |
|   }
 | |
|   (void) c;
 | |
| }
 | |
| 
 | |
| static void sntp_timer(void *param) {
 | |
|   // uint64_t t1 = mg_now(), t2 = mg_millis();
 | |
|   uint64_t timeout = (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
 | |
|   if (s_sntp_response_received == false) timeout = 1000;
 | |
|   // This function is called every second. Once we received a response,
 | |
|   // trigger SNTP sync less frequently, as set by the define
 | |
|   if (mg_timer_expired(&s_sntp_timer, timeout, mg_millis())) {
 | |
|     mg_sntp_connect(param, WIZARD_SNTP_URL, sntp_ev_handler, NULL);
 | |
|   }
 | |
| }
 | |
| #endif  // WIZARD_ENABLE_SNTP
 | |
| 
 | |
| #if WIZARD_ENABLE_MQTT
 | |
| static struct mongoose_mqtt_handlers s_mqtt_handlers = {
 | |
|     glue_mqtt_connect, glue_mqtt_tls_init, glue_mqtt_on_connect,
 | |
|     glue_mqtt_on_message, glue_mqtt_on_cmd};
 | |
| 
 | |
| struct mg_connection *g_mqtt_conn;  // MQTT client connection
 | |
| 
 | |
| static void mqtt_event_handler(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   if (ev == MG_EV_CONNECT) {
 | |
|     s_mqtt_handlers.tls_init_fn(c);
 | |
|   } else if (ev == MG_EV_MQTT_OPEN) {
 | |
|     s_mqtt_handlers.on_connect_fn(c, *(int *) ev_data);
 | |
|   } else if (ev == MG_EV_MQTT_CMD) {
 | |
|     s_mqtt_handlers.on_cmd_fn(c, ev_data);
 | |
|   } else if (ev == MG_EV_MQTT_MSG) {
 | |
|     struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
 | |
|     s_mqtt_handlers.on_message_fn(c, mm->topic, mm->data);
 | |
|   } else if (ev == MG_EV_CLOSE) {
 | |
|     g_mqtt_conn = NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void mqtt_timer(void *arg) {
 | |
|   if (g_mqtt_conn == NULL) {
 | |
|     g_mqtt_conn = s_mqtt_handlers.connect_fn(mqtt_event_handler);
 | |
|   }
 | |
|   (void) arg;
 | |
| }
 | |
| 
 | |
| void mongoose_set_mqtt_handlers(struct mongoose_mqtt_handlers *h) {
 | |
|   if (h->connect_fn) s_mqtt_handlers.connect_fn = h->connect_fn;
 | |
|   if (h->tls_init_fn) s_mqtt_handlers.tls_init_fn = h->tls_init_fn;
 | |
|   if (h->on_message_fn) s_mqtt_handlers.on_message_fn = h->on_message_fn;
 | |
|   if (h->on_connect_fn) s_mqtt_handlers.on_connect_fn = h->on_connect_fn;
 | |
|   if (h->on_cmd_fn) s_mqtt_handlers.on_cmd_fn = h->on_cmd_fn;
 | |
| }
 | |
| #endif  // WIZARD_ENABLE_MQTT
 | |
| 
 | |
| #if WIZARD_ENABLE_MODBUS
 | |
| static struct mongoose_modbus_handlers s_modbus_handlers = {
 | |
|     glue_modbus_read_reg, glue_modbus_write_reg};
 | |
| 
 | |
| static void handle_modbus_pdu(struct mg_connection *c, uint8_t *buf,
 | |
|                               size_t len) {
 | |
|   MG_DEBUG(("Received PDU %p len %lu, hexdump:", buf, len));
 | |
|   mg_hexdump(buf, len);
 | |
|   // size_t hdr_size = 8, max_data_size = sizeof(response) - hdr_size;
 | |
|   if (len < 12) {
 | |
|     MG_ERROR(("PDU too small"));
 | |
|   } else {
 | |
|     uint8_t func = buf[7];  // Function
 | |
|     bool success = false;
 | |
|     size_t response_len = 0;
 | |
|     uint8_t response[260];
 | |
|     memcpy(response, buf, 8);
 | |
|     // uint16_t tid = mg_ntohs(*(uint16_t *) &buf[0]);  // Transaction ID
 | |
|     // uint16_t pid = mg_ntohs(*(uint16_t *) &buf[0]);  // Protocol ID
 | |
|     // uint16_t len = mg_ntohs(*(uint16_t *) &buf[4]);  // PDU length
 | |
|     // uint8_t uid = buf[6];                            // Unit identifier
 | |
|     if (func == 6) {  // write single holding register
 | |
|       uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
 | |
|       uint16_t value = mg_ntohs(*(uint16_t *) &buf[10]);
 | |
|       success = s_modbus_handlers.write_reg_fn(start, value);
 | |
|       *(uint16_t *) &response[8] = mg_htons(start);
 | |
|       *(uint16_t *) &response[10] = mg_htons(value);
 | |
|       response_len = 12;
 | |
|       MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
 | |
|     } else if (func == 16) {  // Write multiple
 | |
|       uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
 | |
|       uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
 | |
|       uint16_t i, *data = (uint16_t *) &buf[13];
 | |
|       if ((size_t) (num * 2 + 10) < sizeof(response)) {
 | |
|         for (i = 0; i < num; i++) {
 | |
|           success = s_modbus_handlers.write_reg_fn((uint16_t) (start + i),
 | |
|                                                    mg_htons(data[i]));
 | |
|           if (success == false) break;
 | |
|         }
 | |
|         *(uint16_t *) &response[8] = mg_htons(start);
 | |
|         *(uint16_t *) &response[10] = mg_htons(num);
 | |
|         response_len = 12;
 | |
|         MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
 | |
|       }
 | |
|     } else if (func == 3 || func == 4) {  // Read multiple
 | |
|       uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
 | |
|       uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
 | |
|       if ((size_t) (num * 2 + 9) < sizeof(response)) {
 | |
|         uint16_t i, val, *data = (uint16_t *) &response[9];
 | |
|         for (i = 0; i < num; i++) {
 | |
|           success = s_modbus_handlers.read_reg_fn((uint16_t) (start + i), &val);
 | |
|           if (success == false) break;
 | |
|           data[i] = mg_htons(val);
 | |
|         }
 | |
|         response[8] = (uint8_t) (num * 2);
 | |
|         response_len = 9 + response[8];
 | |
|         MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
 | |
|       }
 | |
|     }
 | |
|     if (success == false) {
 | |
|       response_len = 9;
 | |
|       response[7] |= 0x80;
 | |
|       response[8] = 4;  // Server Device Failure
 | |
|     }
 | |
|     *(uint16_t *) &response[4] = mg_htons((uint16_t) (response_len - 6));
 | |
|     MG_DEBUG(("Sending PDU response %lu:", response_len));
 | |
|     mg_hexdump(response, response_len);
 | |
|     mg_send(c, response, response_len);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void modbus_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   // if (ev == MG_EV_OPEN) c->is_hexdumping = 1;
 | |
|   if (ev == MG_EV_READ) {
 | |
|     uint16_t len;
 | |
|     if (c->recv.len < 7) return;  // Less than minimum length, buffer more
 | |
|     len = mg_ntohs(*(uint16_t *) &c->recv.buf[4]);  // PDU length
 | |
|     MG_INFO(("Got %lu, expecting %lu", c->recv.len, len + 6));
 | |
|     if (c->recv.len < len + 6U) return;          // Partial frame, buffer more
 | |
|     handle_modbus_pdu(c, c->recv.buf, len + 6);  // Parse PDU and call user
 | |
|     mg_iobuf_del(&c->recv, 0, len + 6U);         // Delete received PDU
 | |
|   }
 | |
|   (void) ev_data;
 | |
| }
 | |
| 
 | |
| void mongoose_set_modbus_handlers(struct mongoose_modbus_handlers *h) {
 | |
|   if (h->read_reg_fn) s_modbus_handlers.read_reg_fn = h->read_reg_fn;
 | |
|   if (h->write_reg_fn) s_modbus_handlers.write_reg_fn = h->write_reg_fn;
 | |
| }
 | |
| #endif  // WIZARD_ENABLE_MODBUS
 | |
| 
 | |
| #if WIZARD_CAPTIVE_PORTAL
 | |
| 
 | |
| #if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
 | |
| #define CAPTIVE_PORTAL_URL "udp://0.0.0.0:5533"
 | |
| #else
 | |
| #define CAPTIVE_PORTAL_URL "udp://0.0.0.0:53"
 | |
| #endif
 | |
| 
 | |
| static const uint8_t answer[] = {
 | |
|     0xc0, 0x0c,          // Point to the name in the DNS question
 | |
|     0,    1,             // 2 bytes - record type, A
 | |
|     0,    1,             // 2 bytes - address class, INET
 | |
|     0,    0,    0, 120,  // 4 bytes - TTL
 | |
|     0,    4              // 2 bytes - address length
 | |
| };
 | |
| 
 | |
| static void dns_fn(struct mg_connection *c, int ev, void *ev_data) {
 | |
|   if (ev == MG_EV_READ) {
 | |
|     struct mg_dns_rr rr;  // Parse first question, offset 12 is header size
 | |
|     size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
 | |
|     MG_DEBUG(("DNS request parsed, result=%d", (int) n));
 | |
|     if (n > 0) {
 | |
|       char buf[512];
 | |
|       uint32_t ip;
 | |
|       struct mg_dns_header *h = (struct mg_dns_header *) buf;
 | |
|       memset(buf, 0, sizeof(buf));  // Clear the whole datagram
 | |
|       h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid;  // Copy tnxid
 | |
|       h->num_questions = mg_htons(1);  // We use only the 1st question
 | |
|       h->num_answers = mg_htons(1);    // And only one answer
 | |
|       h->flags = mg_htons(0x8400);     // Authoritative response
 | |
|       memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n);  // Copy question
 | |
|       memcpy(buf + sizeof(*h) + n, answer, sizeof(answer));   // And answer
 | |
| #if MG_ENABLE_TCPIP
 | |
|       ip = MG_TCPIP_IFACE(c->mgr)->ip;
 | |
| #else
 | |
|       ip = MG_TCPIP_IP;
 | |
| #endif
 | |
|       memcpy(buf + sizeof(*h) + n + sizeof(answer), &ip, 4);
 | |
|       mg_send(c, buf, 12 + n + sizeof(answer) + 4);  // And send it!
 | |
|     }
 | |
|     mg_iobuf_del(&c->recv, 0, c->recv.len);
 | |
|   }
 | |
|   (void) ev_data;
 | |
| }
 | |
| #endif  // WIZARD_CAPTIVE_PORTAL
 | |
| 
 | |
| void mongoose_init(void) {
 | |
|   mg_mgr_init(&g_mgr);      // Initialise event manager
 | |
|   mg_log_set(MG_LL_DEBUG);  // Set log level to debug
 | |
| 
 | |
| #if WIZARD_ENABLE_HTTP
 | |
|   MG_INFO(("Starting HTTP listener"));
 | |
|   mg_http_listen(&g_mgr, HTTP_URL, http_ev_handler, NULL);
 | |
| #endif
 | |
| #if WIZARD_ENABLE_HTTPS
 | |
|   MG_INFO(("Starting HTTPS listener"));
 | |
|   mg_http_listen(&g_mgr, HTTPS_URL, http_ev_handler, "");
 | |
| #endif
 | |
| 
 | |
| #if WIZARD_ENABLE_SNTP
 | |
|   MG_INFO(("Starting SNTP timer"));
 | |
|   mg_timer_add(&g_mgr, 1000, MG_TIMER_REPEAT, sntp_timer, &g_mgr);
 | |
| #endif
 | |
| 
 | |
| #if WIZARD_DNS_TYPE == 2
 | |
|   g_mgr.dns4.url = WIZARD_DNS_URL;
 | |
| #endif
 | |
| 
 | |
| #if WIZARD_ENABLE_MQTT
 | |
|   MG_INFO(("Starting MQTT reconnection timer"));
 | |
|   mg_timer_add(&g_mgr, 1000, MG_TIMER_REPEAT, mqtt_timer, &g_mgr);
 | |
| #endif
 | |
| 
 | |
| #if WIZARD_ENABLE_MODBUS
 | |
|   {
 | |
|     char url[100];
 | |
|     mg_snprintf(url, sizeof(url), "tcp://0.0.0.0:%d", WIZARD_MODBUS_PORT);
 | |
|     MG_INFO(("Starting Modbus-TCP server on port %d", WIZARD_MODBUS_PORT));
 | |
|     mg_listen(&g_mgr, url, modbus_ev_handler, NULL);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| #if WIZARD_CAPTIVE_PORTAL
 | |
|   MG_INFO(("Starting captive portal"));
 | |
|   mg_listen(&g_mgr, CAPTIVE_PORTAL_URL, dns_fn, NULL);
 | |
| #endif
 | |
| 
 | |
|   MG_INFO(("Mongoose init complete"));
 | |
| }
 | |
| 
 | |
| void mongoose_poll(void) {
 | |
|   glue_lock();
 | |
|   mg_mgr_poll(&g_mgr, 10);
 | |
| #if WIZARD_ENABLE_WEBSOCKET
 | |
|   send_websocket_data();
 | |
| #endif
 | |
|   glue_unlock();
 | |
| }
 |