├── LICENSE ├── README.md ├── examples ├── BLE │ └── BLE.ino ├── Minimal │ └── Minimal.ino └── RPC │ └── RPC.ino ├── library.properties ├── posix ├── Makefile └── main.c └── src ├── mDash.c ├── mDash.h ├── mongoose.c └── mongoose.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2022 Cesanta Software Limited 2 | All rights reserved 3 | 4 | This software is dual-licensed: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License version 2 as 6 | published by the Free Software Foundation. For the terms of this 7 | license, see . 8 | 9 | You are free to use this software under the terms of the GNU General 10 | Public License, but WITHOUT ANY WARRANTY; without even the implied 11 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | See the GNU General Public License for more details. 13 | 14 | Alternatively, you can license this software under a commercial 15 | license, as set out in . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino / ESP-IDF client library for mdash.net 2 | 3 | See [Documentation](https://mdash.net/docs/) for the quick start guide. 4 | 5 | In order to simulate an mDash device on your Mac/Linux workstation, 6 | register a device on mDash and run the following command from the root of 7 | this repo: 8 | 9 | ```sh 10 | $ make -C posix ARGS="-pass DEVICE_MDASH_PASSWORD" 11 | ``` 12 | 13 | # See also 14 | - [Mongoose Web Server Library](https://mongoose.ws/) - a robust, open-source solution licensed under GPLv2, designed to seamlessly integrate web server functionality into your embedded devices. 15 | - With complementary [Mongoose Wizard](https://mongoose.ws/wizard/) - a no-code visual tool that enables rapid WebUI creation without the need for frontend expertise. 16 | -------------------------------------------------------------------------------- /examples/BLE/BLE.ino: -------------------------------------------------------------------------------- 1 | // This sketch catches BLE advertisements and sends them to MQTT 2 | // 1. In the Arduino IDE, choose Tools / Board / ESP32 Dev module 3 | // 2. In the Arduino IDE, choose Tools / Partition Scheme / Minimal SPIFFS 4 | // 3. Flash this sketch to the ESP32 board 5 | 6 | #define MDASH_APP_NAME "ble-gateway" 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #define WIFI_NETWORK "MyWifiNetworkName" 17 | #define WIFI_PASSWORD "MyWifiPassword" 18 | #define DEVICE_PASSWORD "mDashDeviceToken" 19 | 20 | int scanTime = 3; // In seconds 21 | BLEScan *pBLEScan; 22 | 23 | class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { 24 | void onResult(BLEAdvertisedDevice d) { 25 | char addr[20]; 26 | const unsigned char *p = d.getPayload(); 27 | int plen = d.getPayloadLength(); 28 | 29 | snprintf(addr, sizeof(addr), "%s", d.getAddress().toString().c_str()); 30 | mDashNotify("DB.Save", "{\"mac\":%Q,\"adv\":%H}", addr, plen, p); 31 | Serial.printf("Advertised Device: %s\n", addr); 32 | } 33 | }; 34 | 35 | void setup() { 36 | Serial.begin(115200); 37 | 38 | WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD); 39 | while (WiFi.status() != WL_CONNECTED) delay(500); 40 | 41 | mDashBegin(DEVICE_PASSWORD); 42 | 43 | BLEDevice::init(""); 44 | pBLEScan = BLEDevice::getScan(); // create new scan 45 | pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); 46 | pBLEScan->setActiveScan(true); // active scan uses more power 47 | pBLEScan->setInterval(100); 48 | pBLEScan->setWindow(99); // less or equal setInterval value 49 | } 50 | 51 | void loop() { 52 | BLEScanResults foundDevices = pBLEScan->start(scanTime, false); 53 | pBLEScan->clearResults(); 54 | delay(50); 55 | } 56 | -------------------------------------------------------------------------------- /examples/Minimal/Minimal.ino: -------------------------------------------------------------------------------- 1 | // Minimal Arduino sketch for mdash.net 2 | // 3 | // - Install mDash library: 4 | // - Select "Sketch" → "Include Library" → "Manage Libraries" 5 | // - In the search field, type "mDash" and press Enter 6 | // - Click on "Install" to install the library 7 | // - Select "Tools" → "Board" → "ESP32 Dev Module" 8 | // - Select "Tools" → "Partitioning Scheme" → "Minimal SPIFFS" 9 | // - Select "Tools" → "Port" → your serial port 10 | // - Click on "Upload" button to build and flash the firmware 11 | // 12 | // See https://mdash.net/docs/ for the full IoT product reference impementation 13 | 14 | #define MDASH_APP_NAME "MinimalApp" 15 | #include 16 | 17 | #include 18 | 19 | #define WIFI_NETWORK "MyWifiNetworkName" 20 | #define WIFI_PASSWORD "MyWifiPassword" 21 | #define DEVICE_PASSWORD "mDashDeviceToken" 22 | 23 | void setup() { 24 | Serial.begin(115200); 25 | WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD); 26 | while (WiFi.status() != WL_CONNECTED) { 27 | delay(500); 28 | Serial.print("."); 29 | } 30 | Serial.println("\nWiFi connected, IP address:"); 31 | Serial.println(WiFi.localIP()); 32 | mDashBegin(DEVICE_PASSWORD); 33 | } 34 | 35 | void loop() { 36 | delay(500); 37 | } 38 | -------------------------------------------------------------------------------- /examples/RPC/RPC.ino: -------------------------------------------------------------------------------- 1 | // Custom RPC function example. 2 | // Read more on RPC at https://mdash.net/docs/userguide/remote_control.md 3 | // JSON-RPC API is documented at: https://mongoose.ws/documentation/ 4 | // 5 | // - Install mDash library: 6 | // - Select "Sketch" → "Include Library" → "Manage Libraries" 7 | // - In the search field, type "mDash" and press Enter 8 | // - Click on "Install" to install the library 9 | // - Select "Tools" → "Board" → "ESP32 Dev Module" 10 | // - Select "Tools" → "Partitioning Scheme" → "Minimal SPIFFS" 11 | // - Select "Tools" → "Port" → your serial port 12 | // - Click on "Upload" button to build and flash the firmware 13 | 14 | #define MDASH_APP_NAME "RpcApp" 15 | #include 16 | 17 | #include 18 | 19 | #define WIFI_NETWORK "MyWifiNetworkName" 20 | #define WIFI_PASSWORD "MyWifiPassword" 21 | #define DEVICE_PASSWORD "mDashDeviceToken" 22 | 23 | static void rpc_gpio_write(struct mg_rpc_req *r) { 24 | long pin = mg_json_get_long(r->frame, "$.params.pin", -1); 25 | long val = mg_json_get_long(r->frame, "$.params.val", -1); 26 | if (pin < 0 || val < 0) { 27 | mg_rpc_err(r, 500, "%Q", "pin and val required"); 28 | } else { 29 | pinMode(pin, OUTPUT); 30 | digitalWrite(pin, val); 31 | mg_rpc_ok(r, "true"); 32 | } 33 | } 34 | 35 | void setup() { 36 | Serial.begin(115200); 37 | WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD); 38 | mDashBegin(DEVICE_PASSWORD); 39 | mg_rpc_add(&g_rpcs, mg_str("GPIO.Write"), rpc_gpio_write, NULL); 40 | } 41 | 42 | void loop() { 43 | delay(3000); 44 | } 45 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=mDash 2 | version=1.2.16 3 | author=Cesanta Software Limited 4 | maintainer=Cesanta Software Limited 5 | sentence=Remote control and OTA for ESP32 via mdash.net IoT backend 6 | paragraph=Provides OTA, MQTT, device shadow, Filesystem management for ESP32 7 | category=Communication 8 | url=https://github.com/cesanta/mDash 9 | architectures=esp32 10 | includes=mDash.h 11 | -------------------------------------------------------------------------------- /posix/Makefile: -------------------------------------------------------------------------------- 1 | PROG ?= mDashDeviceSimulator 2 | CFLAGS ?= -W -Wall -Wextra -g -I../src -pthread 3 | LFLAGS ?= -L/usr/lib -L/usr/local/opt/mbedtls/lib -I/opt/homebrew/opt/mbedtls/include -I/usrc/local -I/usr/local/opt -L/opt/homebrew/opt/mbedtls/lib -lmbedtls -lmbedx509 -lmbedcrypto -lmbedtls -lmbedx509 -lmbedcrypto 4 | SOURCES ?= main.c ../src/mDash.c ../src/mongoose.c 5 | 6 | all: $(PROG) 7 | 8 | $(PROG): $(SOURCES) 9 | $(CC) $(SOURCES) $(CFLAGS) $(LFLAGS) $(EXTRA) -o $(PROG) 10 | 11 | run: $(PROG) 12 | $(RUN) ./$(PROG) $(ARGS) 13 | 14 | clean: 15 | rm -rf $(PROG) _CL* *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb 16 | -------------------------------------------------------------------------------- /posix/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Cesanta Software Limited 2 | // All rights reserved 3 | // 4 | // This is mdash.net device simulator on POSIX systems. 5 | 6 | #include "mDash.h" 7 | 8 | static int s_sig; 9 | static void sighandler(int sig) { 10 | s_sig = sig; 11 | } 12 | 13 | int main(int argc, char *argv[]) { 14 | const char *pass = NULL; // *url = NULL, *report_interval = "5"; 15 | int loglevel = -1; 16 | 17 | for (int i = 1; i < argc; i++) { 18 | if (strcmp(argv[i], "-pass") == 0) { 19 | pass = argv[++i]; 20 | } else if (strcmp(argv[i], "-v") == 0) { 21 | loglevel = atoi(argv[++i]); 22 | } else if (strcmp(argv[i], "-ap") == 0) { 23 | // wifi = NULL; // if WiFi is NULL, mDash lib starts HTTP server 24 | } else { 25 | MG_ERROR(("Invalid option: [%s]\n", argv[i])); 26 | MG_ERROR(("Usage: %s --pass DEVICE_PASSWORD", argv[0])); 27 | return 1; 28 | } 29 | } 30 | 31 | if (pass == NULL) { 32 | MG_ERROR(("%s", "Please specify --pass DEVICE_PASSWORD")); 33 | return 1; 34 | } 35 | 36 | signal(SIGINT, sighandler); 37 | signal(SIGTERM, sighandler); 38 | signal(SIGPIPE, SIG_IGN); 39 | 40 | mDashBegin(pass); 41 | if (loglevel > 0) mg_log_set(loglevel); 42 | while (s_sig == 0) mDashPoll(); 43 | MG_INFO(("Got signal %d, exiting...", s_sig)); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /src/mDash.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Cesanta Software Limited 2 | // All rights reserved 3 | // 4 | // This software is dual-licensed: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License version 2 as 6 | // published by the Free Software Foundation. For the terms of this 7 | // license, see http://www.gnu.org/licenses/ 8 | // 9 | // You are free to use this software under the terms of the GNU General 10 | // Public License, but WITHOUT ANY WARRANTY; without even the implied 11 | // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | // See the GNU General Public License for more details. 13 | // 14 | // Alternatively, you can license this software under a commercial 15 | // license, as set out in https://www.mongoose.ws/licensing/ 16 | // 17 | // SPDX-License-Identifier: GPL-2.0 or commercial 18 | 19 | #include "mDash.h" 20 | 21 | static const char *s_mDashAppName = "arduino"; 22 | static const char *s_mDashFramework = "?"; 23 | static const char *s_mDashPassword = NULL; 24 | static const char *s_mDashPublicKey = NULL; 25 | static const char *s_mDashBuildTime = NULL; 26 | static const char *s_mDashURL = "wss://mdash.net/api/v2/rpc"; 27 | static int s_mDashLogLevel = MG_LL_INFO; 28 | static int s_mDashState = MDASH_STATE_NETWORK_LOST; 29 | static struct mg_connection *s_conn; // Cloud connection 30 | static struct mg_connection *s_sntp = NULL; // SNTP connection 31 | static struct mg_connection *s_http = NULL; // HTTP server connection 32 | static struct mg_mgr s_mgr; // Mongoose event manager 33 | static time_t s_start_time = 0; // Starting timestamp 34 | static size_t s_ota_size = 0; // Firmware size 35 | static size_t s_ota_written = 0; // Written firmware size 36 | struct mg_rpc *g_rpcs; // Registered RPC handlers 37 | 38 | static const char *s_pem = 39 | "-----BEGIN CERTIFICATE-----\n" 40 | "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" 41 | "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" 42 | "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" 43 | "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" 44 | "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" 45 | "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" 46 | "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" 47 | "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" 48 | "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" 49 | "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" 50 | "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" 51 | "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" 52 | "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" 53 | "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" 54 | "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" 55 | "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" 56 | "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" 57 | "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" 58 | "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" 59 | "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" 60 | "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" 61 | "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" 62 | "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" 63 | "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" 64 | "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" 65 | "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" 66 | "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" 67 | "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" 68 | "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" 69 | "-----END CERTIFICATE-----\n"; 70 | 71 | #if defined(ESP_PLATFORM) //////////////////////////////////// ESP32 specific 72 | #include 73 | #include 74 | #include 75 | #include 76 | #define HTTP_URL "http://0.0.0.0:80" 77 | #define MDASH_ARCH "esp32" 78 | #define CPU_CORE 0 79 | #define FS_ROOT "/spiffs" 80 | #define MDASH_CONFIG_FILE_NAME FS_ROOT "/mdash.cfg" 81 | static const esp_partition_t *s_update_partition; 82 | static esp_ota_handle_t s_update_handle; 83 | 84 | static void enable_wdt(void) { 85 | esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(CPU_CORE)); 86 | } 87 | static void disable_wdt(void) { 88 | esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(CPU_CORE)); 89 | } 90 | static unsigned long mDashGetFreeRam(void) { 91 | return xPortGetFreeHeapSize(); 92 | } 93 | static void reboot_with_delay(int delay_ms) { 94 | void *h = xTimerCreate("reboot", pdMS_TO_TICKS(delay_ms), 0, NULL, 95 | (void (*)(void *)) esp_restart); 96 | xTimerStart(h, 0); 97 | } 98 | static SemaphoreHandle_t s_sem = NULL; 99 | static void MDashMutexInit(void) { 100 | s_sem = xSemaphoreCreateMutex(); 101 | } 102 | static void MDashMutexLock(void) { 103 | xSemaphoreTakeRecursive(s_sem, portMAX_DELAY); 104 | } 105 | static void MDashMutexUnlock(void) { 106 | xSemaphoreGiveRecursive(s_sem); 107 | } 108 | static bool mount_fs(const char *path) { 109 | disable_wdt(); 110 | esp_vfs_spiffs_conf_t conf = { 111 | .base_path = path, 112 | .max_files = 20, 113 | .format_if_mount_failed = true, 114 | }; 115 | int res = esp_vfs_spiffs_register(&conf); 116 | enable_wdt(); 117 | MG_INFO(("FS -> %s: %d", conf.base_path, res)); 118 | return res == ESP_OK; 119 | } 120 | static uint64_t get_chip_id(void) { 121 | uint64_t id = 0; 122 | // efuse_hal_get_mac((uint8_t *) &id); 123 | esp_read_mac((uint8_t *) &id, ESP_MAC_WIFI_STA); 124 | return id; 125 | } 126 | #else /////////////////////////////////////////////////////// POSIX specific 127 | #define HTTP_URL "http://0.0.0.0:8080" 128 | #define xTaskCreatePinnedToCore(fn, b, c, d, e, f, g) 129 | #define MDASH_ARCH "posix" 130 | #define FS_ROOT "/tmp/spiffs" 131 | #define MDASH_CONFIG_FILE_NAME FS_ROOT "/mdash.cfg" 132 | static unsigned long mDashGetFreeRam(void) { 133 | return 42; 134 | } 135 | static void reboot_with_delay(int delay_ms) { 136 | MG_INFO(("outta reboot in %d milliseconds", delay_ms)); 137 | } 138 | static void MDashMutexInit(void) { 139 | } 140 | static void MDashMutexLock(void) { 141 | } 142 | static void MDashMutexUnlock(void) { 143 | } 144 | static bool mount_fs(const char *path) { 145 | if (path != NULL) mkdir(path, 0644); 146 | return true; 147 | } 148 | static int esp_reset_reason(void) { 149 | return 0; 150 | } 151 | static uint64_t get_chip_id(void) { 152 | return 0; 153 | } 154 | #endif ///////////////////////////////////////////// END of plaform-specific 155 | 156 | static const char *get_reset_reason(void) { 157 | switch (esp_reset_reason()) { 158 | #if defined(ESP_PLATFORM) 159 | case ESP_RST_POWERON: 160 | return "power-on"; 161 | case ESP_RST_EXT: 162 | return "external pin"; 163 | case ESP_RST_SW: 164 | return "soft reset"; 165 | case ESP_RST_PANIC: 166 | return "crash"; 167 | case ESP_RST_INT_WDT: 168 | return "interrupt watchdog (software or hardware)"; 169 | case ESP_RST_TASK_WDT: 170 | return "task watchdog"; 171 | case ESP_RST_WDT: 172 | return "other watchdog"; 173 | case ESP_RST_DEEPSLEEP: 174 | return "deep sleep wakeup"; 175 | case ESP_RST_BROWNOUT: 176 | return "Brownout (software or hardware)"; 177 | case ESP_RST_SDIO: 178 | return "Reset over SDIO"; 179 | #endif 180 | default: 181 | return "unknown"; 182 | } 183 | } 184 | 185 | static void asctimetoiso(char *buf, const char *ts) { 186 | const char *month = "01"; 187 | if (ts == NULL || strlen(ts) < 20) ts = "Jan 1 1970-00:00:00"; 188 | if (ts[0] == 'F') { 189 | month = "02"; 190 | } else if (ts[0] == 'M') { 191 | month = ts[2] == 'y' ? "05" : "03"; 192 | } else if (ts[0] == 'A') { 193 | month = ts[1] == 'p' ? "04" : "08"; 194 | } else if (ts[0] == 'J') { 195 | month = ts[1] == 'a' ? "01" : ts[2] == 'n' ? "06" : "07"; 196 | } else if (ts[0] == 'S') { 197 | month = "09"; 198 | } else if (ts[0] == 'O') { 199 | month = "10"; 200 | } else if (ts[0] == 'N') { 201 | month = "11"; 202 | } else if (ts[0] == 'D') { 203 | month = "12"; 204 | } 205 | 206 | memcpy(buf, ts + 7, 4); 207 | memcpy(buf + 4, month, 2); 208 | buf[6] = ts[4] == ' ' ? '0' : ts[4]; 209 | buf[7] = ts[5]; 210 | buf[8] = '-'; 211 | memcpy(buf + 9, ts + 12, 2); 212 | memcpy(buf + 11, ts + 15, 2); 213 | memcpy(buf + 13, ts + 18, 2); 214 | buf[15] = '\0'; 215 | } 216 | 217 | static void rpc_get_info(struct mg_rpc_req *r) { 218 | unsigned long uptime = time(NULL) - s_start_time; 219 | uint64_t chipid = get_chip_id(); 220 | char buf[21]; 221 | asctimetoiso(buf, s_mDashBuildTime); 222 | mg_rpc_ok(r, 223 | "{%Q:\"%s-%s\",%Q:%Q,%Q:%Q,%Q:%Q,%Q:%lu,%Q:%lu,%Q:%Q,%Q:%H,%Q:%Q}", 224 | "fw_version", MDASH_VERSION, s_mDashFramework, "arch", MDASH_ARCH, 225 | "fw_id", buf, "app", s_mDashAppName, "uptime", uptime, "ram_free", 226 | mDashGetFreeRam(), "reboot_reason", get_reset_reason(), "id", 227 | sizeof(chipid), &chipid, "public_key", 228 | s_mDashPublicKey == NULL ? "" : s_mDashPublicKey); 229 | } 230 | 231 | static void rpc_reboot(struct mg_rpc_req *r) { 232 | mg_rpc_ok(r, "true"); 233 | reboot_with_delay(1000); 234 | } 235 | 236 | static void rpc_sys_set(struct mg_rpc_req *r) { 237 | char *name = mg_json_get_str(r->frame, "$.params.name"); 238 | char *value = mg_json_get_str(r->frame, "$.params.value"); 239 | if (name == NULL) { 240 | mg_rpc_err(r, 400, "%Q", "name and value expected"); 241 | } else { 242 | int result = mDashConfigSet(name, value); 243 | mg_rpc_ok(r, "{%Q:%d}", "result", result); 244 | } 245 | free(name); 246 | free(value); 247 | } 248 | 249 | static void rpc_fs_format(struct mg_rpc_req *r) { 250 | #if defined(ESP_PLATFORM) 251 | disable_wdt(); 252 | esp_err_t err = esp_spiffs_format(NULL); 253 | enable_wdt(); 254 | if (err) { 255 | mg_rpc_err(r, 500, "\"spiffs_format err %d\"", err); 256 | } else { 257 | mg_rpc_ok(r, "true"); 258 | } 259 | #else 260 | mg_rpc_ok(r, "true"); 261 | #endif 262 | } 263 | 264 | static void rpc_fs_mount(struct mg_rpc_req *r) { 265 | // char path[20] = "/"; 266 | char *path = mg_json_get_str(r->frame, "$.params.path"); 267 | #if defined(ESP_PLATFORM) 268 | if (esp_spiffs_mounted(NULL)) { 269 | mg_rpc_err(r, 500, "%Q", "already mounted"); 270 | } else if (mount_fs(path == NULL ? FS_ROOT : path)) { 271 | mg_rpc_ok(r, "true"); 272 | } else { 273 | mg_rpc_err(r, 500, "%Q", "mount failed"); 274 | } 275 | #else 276 | mg_rpc_ok(r, "true"); 277 | #endif 278 | free(path); 279 | } 280 | 281 | static void rpc_fs_put(struct mg_rpc_req *r) { 282 | bool append = false; 283 | char *name = mg_json_get_str(r->frame, "$.params.filename"); 284 | int len = 0, ofs = mg_json_get(r->frame, "$.params.data", &len); 285 | mg_json_get_bool(r->frame, "$.params.append", &append); 286 | if (len <= 0 || r->frame.ptr[ofs] != '"' || name == NULL) { 287 | mg_rpc_err(r, 500, "%Q", "data and filename required"); 288 | } else { 289 | char path[MG_PATH_MAX]; 290 | FILE *fp; 291 | mg_snprintf(path, sizeof(path), "%s/%s", FS_ROOT, name); 292 | if ((fp = fopen(path, append ? "ab" : "wb")) == NULL) { 293 | mg_rpc_err(r, 500, "\"fopen(%q): %d\"", path, errno); 294 | } else { 295 | char *data = (char *) &r->frame.ptr[ofs]; 296 | int n = mg_base64_decode(data + 1, len - 2, data); // Decode in-place 297 | if ((int) fwrite(data, 1, n, fp) != n) { 298 | mg_rpc_err(r, 500, "\"fwrite(%q) %d, errno %d\"", path, n, errno); 299 | } else { 300 | mg_rpc_ok(r, "{%Q:%Q,%Q:%d}", "filename", name, "written", n); 301 | } 302 | fclose(fp); 303 | } 304 | } 305 | free(name); 306 | } 307 | 308 | static void rpc_fs_get(struct mg_rpc_req *r) { 309 | long offset = mg_json_get_long(r->frame, "$.params.offset", 0); 310 | long len = mg_json_get_long(r->frame, "$.params.len", 512); 311 | char *chunk = NULL; 312 | char *name = mg_json_get_str(r->frame, "$.params.filename"); 313 | if (name == NULL) { 314 | mg_rpc_err(r, 500, "%Q", "filename required"); 315 | } else if ((chunk = (char *) malloc(len)) == NULL) { 316 | mg_rpc_err(r, 500, "%Q", "OOM"); 317 | } else { 318 | FILE *fp = NULL; 319 | char path[MG_PATH_MAX]; 320 | mg_snprintf(path, sizeof(path), "%s/%s", FS_ROOT, name); 321 | if ((fp = fopen(path, "rb")) == NULL) { 322 | mg_rpc_err(r, 500, "\"fopen(%q): %d\"", path, errno); 323 | } else { 324 | fseek(fp, offset, SEEK_SET); 325 | long n = fread(chunk, 1, len, fp); 326 | fseek(fp, 0, SEEK_END); 327 | long total = ftell(fp); 328 | mg_rpc_ok(r, "{%Q:%Q,%Q:%ld,%Q:%ld,%Q:%V}", "filename", name, "offset", 329 | offset, "left", total - (n + offset), "data", (int) n, chunk); 330 | fclose(fp); 331 | } 332 | } 333 | free(chunk); 334 | free(name); 335 | } 336 | 337 | static void rpc_fs_rename(struct mg_rpc_req *r) { 338 | char *src = mg_json_get_str(r->frame, "$.params.src"); 339 | char *dst = mg_json_get_str(r->frame, "$.params.dst"); 340 | if (src == NULL || dst == NULL) { 341 | mg_rpc_err(r, 500, "%Q", "src and dst missing"); 342 | } else { 343 | char a[MG_PATH_MAX], b[MG_PATH_MAX]; 344 | mg_snprintf(a, sizeof(a), "%s/%s", FS_ROOT, src); 345 | mg_snprintf(b, sizeof(b), "%s/%s", FS_ROOT, dst); 346 | remove(b); 347 | if (rename(a, b) != 0) { 348 | mg_rpc_err(r, 500, "\"rename(%s, %s) failed: %d\"", a, a, errno); 349 | } else { 350 | mg_rpc_ok(r, "true"); 351 | } 352 | } 353 | free(src); 354 | free(dst); 355 | } 356 | 357 | static void rpc_fs_remove(struct mg_rpc_req *r) { 358 | char *name = mg_json_get_str(r->frame, "$.params.filename"); 359 | if (name == NULL) { 360 | mg_rpc_err(r, 500, "%Q", "filename required"); 361 | } else { 362 | char path[MG_PATH_MAX]; 363 | mg_snprintf(path, sizeof(path), "%s/%s", FS_ROOT, name); 364 | if (remove(path) != 0) { 365 | mg_rpc_err(r, 500, "", "\"remove(%s) failed: %d\"", path, errno); 366 | } else { 367 | mg_rpc_ok(r, "true"); 368 | } 369 | } 370 | free(name); 371 | } 372 | 373 | static size_t fslister(mg_pfn_t pfn, void *pfn_data, va_list *ap) { 374 | DIR *dirp = va_arg(*ap, DIR *); 375 | struct dirent *dp; 376 | const char *comma = ""; 377 | size_t n = 0; 378 | while ((dp = readdir(dirp)) != NULL) { 379 | if (strcmp((const char *) dp->d_name, ".") == 0 || 380 | strcmp((const char *) dp->d_name, "..") == 0) { 381 | continue; 382 | } 383 | n += mg_rprintf(pfn, pfn_data, "%s%Q", comma, dp->d_name); 384 | comma = ","; 385 | } 386 | return n; 387 | } 388 | 389 | static void rpc_fs_list(struct mg_rpc_req *r) { 390 | DIR *dirp = opendir(FS_ROOT); 391 | if (dirp == NULL) { 392 | mg_rpc_err(r, 500, "%Q", "cannot open fs root"); 393 | } else { 394 | mg_rpc_ok(r, "[%M]", fslister, dirp); 395 | closedir(dirp); 396 | } 397 | } 398 | 399 | int mDashNotify(const char *name, const char *fmt, ...) { 400 | int res = 0; 401 | MDashMutexLock(); 402 | va_list ap; 403 | if (s_conn != NULL) { 404 | struct mg_iobuf io = {0, 0, 0, 512}; 405 | mg_rprintf(mg_pfn_iobuf, &io, "{%Q:%Q,%Q:", "method", name, "params"); 406 | va_start(ap, fmt); 407 | mg_vrprintf(mg_pfn_iobuf, &io, fmt, &ap); 408 | va_end(ap); 409 | mg_rprintf(mg_pfn_iobuf, &io, "}"); 410 | if (io.buf != NULL) { 411 | mg_ws_send(s_conn, (char *) io.buf, io.len, WEBSOCKET_OP_TEXT); 412 | } 413 | mg_iobuf_free(&io); 414 | res = 1; 415 | } 416 | MDashMutexUnlock(); 417 | return res; 418 | } 419 | 420 | static void rpc_ota_begin(struct mg_rpc_req *r) { 421 | if (s_ota_size) { 422 | mg_rpc_err(r, 500, "%Q", "OTA already in progress"); 423 | } else { 424 | double dv; 425 | if (mg_json_get_num(r->frame, "$.params.size", &dv)) s_ota_size = dv; 426 | #if defined(ESP_PLATFORM) 427 | disable_wdt(); 428 | s_update_partition = esp_ota_get_next_update_partition(NULL); 429 | esp_err_t err = 430 | esp_ota_begin(s_update_partition, OTA_SIZE_UNKNOWN, &s_update_handle); 431 | enable_wdt(); 432 | MG_INFO(("Starting OTA. partition=%p, err=%d", s_update_partition, err)); 433 | #else 434 | MG_INFO(("Starting OTA: %.*s", (int) r->frame.len, r->frame.ptr)); 435 | #endif 436 | mDashNotify("State.Set", "{%Q:{%Q:0}}", "ota", "percent"); 437 | mg_rpc_ok(r, "true"); 438 | } 439 | } 440 | 441 | static void rpc_ota_write(struct mg_rpc_req *r) { 442 | int len = 0, ofs = 0; 443 | if (s_ota_size == 0) { 444 | mg_rpc_err(r, 500, "%Q", "call OTA.Begin"); 445 | } else if ((ofs = mg_json_get(r->frame, "$.params.data", &len)) <= 0 || 446 | r->frame.ptr[ofs] != '"') { 447 | mg_rpc_err(r, 500, "%Q", "data required"); 448 | } else { 449 | // Decode in-place 450 | char *data = (char *) &r->frame.ptr[ofs]; 451 | int n = mg_base64_decode(data + 1, len - 2, data); // Decode in-place 452 | 453 | #if defined(ESP_PLATFORM) 454 | disable_wdt(); 455 | esp_err_t err = esp_ota_write(s_update_handle, data, n); 456 | enable_wdt(); 457 | if (err != ESP_OK) { 458 | MG_ERROR(("ota finished, err %d", err)); 459 | mg_rpc_err(r, 500, "\"esp_ota_write: %d\"", err); 460 | return; 461 | } 462 | #endif 463 | 464 | s_ota_written += n; 465 | MG_INFO(("OTA write: got %d bytes", n)); 466 | mDashNotify("State.Set", "{%Q:{%Q:%d}}", "ota", "percent", 467 | (int) (s_ota_written * 100 / s_ota_size)); 468 | mg_rpc_ok(r, "{%Q:%d}", "queued", n); 469 | } 470 | } 471 | 472 | static void rpc_ota_end(struct mg_rpc_req *r) { 473 | s_ota_size = s_ota_written = 0; 474 | mDashNotify("State.Set", "{%Q:{%Q:null}}", "ota", "percent"); 475 | MG_INFO(("OTA finished")); 476 | #if defined(ESP_PLATFORM) 477 | esp_err_t err = esp_ota_end(s_update_handle); 478 | if (err != ESP_OK) { 479 | MG_ERROR(("ota finished, err %d", err)); 480 | mg_rpc_err(r, 500, "\"esp_ota_end: %d\"", err); 481 | return; 482 | } 483 | esp_ota_set_boot_partition(s_update_partition); 484 | reboot_with_delay(500); 485 | #endif 486 | mg_rpc_ok(r, "true"); 487 | } 488 | 489 | static void init_rpcs(void) { 490 | mg_rpc_add(&g_rpcs, mg_str("RPC.List"), mg_rpc_list, NULL); 491 | mg_rpc_add(&g_rpcs, mg_str("Sys.GetInfo"), rpc_get_info, NULL); 492 | mg_rpc_add(&g_rpcs, mg_str("Sys.Reboot"), rpc_reboot, NULL); 493 | mg_rpc_add(&g_rpcs, mg_str("Sys.Set"), rpc_sys_set, NULL); 494 | mg_rpc_add(&g_rpcs, mg_str("OTA.Begin"), rpc_ota_begin, NULL); 495 | mg_rpc_add(&g_rpcs, mg_str("OTA.Write"), rpc_ota_write, NULL); 496 | mg_rpc_add(&g_rpcs, mg_str("OTA.End"), rpc_ota_end, NULL); 497 | mg_rpc_add(&g_rpcs, mg_str("FS.List"), rpc_fs_list, NULL); 498 | mg_rpc_add(&g_rpcs, mg_str("FS.Remove"), rpc_fs_remove, NULL); 499 | mg_rpc_add(&g_rpcs, mg_str("FS.Rename"), rpc_fs_rename, NULL); 500 | mg_rpc_add(&g_rpcs, mg_str("FS.Get"), rpc_fs_get, NULL); 501 | mg_rpc_add(&g_rpcs, mg_str("FS.Put"), rpc_fs_put, NULL); 502 | mg_rpc_add(&g_rpcs, mg_str("FS.Mount"), rpc_fs_mount, NULL); 503 | mg_rpc_add(&g_rpcs, mg_str("FS.Format"), rpc_fs_format, NULL); 504 | } 505 | 506 | int mDashConfigGet(const char *name, char *buf, int bufsize) { 507 | MDashMutexLock(); 508 | 509 | char line[256], var[sizeof(line)], val[sizeof(line)]; 510 | FILE *fp = fopen(MDASH_CONFIG_FILE_NAME, "r"); 511 | int result = 0; 512 | if (fp == NULL) { 513 | MG_DEBUG(("%s: cannot open config file: %d", name, errno)); 514 | result = -1; // Fail - cannot open file 515 | goto out; 516 | } 517 | while (fgets(line, sizeof(line), fp) != NULL) { 518 | if (sscanf(line, "%s %[^\r\n]", var, val) != 2) continue; 519 | if (var[0] == '#' || var[0] == '\0' || val[0] == '\0') continue; 520 | if (strcmp(name, var) != 0) continue; 521 | if ((int) strlen(val) > bufsize) { 522 | MG_ERROR(("%s: dest buffer is too small", name)); 523 | result = -2; // Fail - dest buffer too small 524 | goto out; 525 | } 526 | strcpy(buf, val); 527 | goto out; // Success 528 | } 529 | result = -3; // Fail - variable not found 530 | out: 531 | if (fp != NULL) fclose(fp); 532 | MDashMutexUnlock(); 533 | return result; 534 | } 535 | 536 | int mDashConfigSet(const char *name, const char *value) { 537 | MDashMutexLock(); 538 | char line[128]; 539 | FILE *fp = fopen(MDASH_CONFIG_FILE_NAME, "r"); 540 | FILE *fp2 = fopen(MDASH_CONFIG_FILE_NAME ".tmp", "w"); 541 | if (fp2 == NULL) return -1; // Fail - cannot open temp file 542 | if (fp != NULL) { 543 | int len = strlen(name), found = 0; 544 | while (fgets(line, sizeof(line), fp) != NULL) { 545 | if (memcmp(line, name, len) == 0 && line[len] == ' ') { 546 | if (!found && value && value[0]) fprintf(fp2, "%s %s\n", name, value); 547 | found = 1; 548 | } else { 549 | fprintf(fp2, "%s", line); 550 | } 551 | } 552 | if (!found && value && value[0]) fprintf(fp2, "%s %s\n", name, value); 553 | fclose(fp); 554 | } else { 555 | if (value && value[0]) fprintf(fp2, "%s %s\n", name, value); 556 | } 557 | fclose(fp2); 558 | remove(MDASH_CONFIG_FILE_NAME); 559 | rename(MDASH_CONFIG_FILE_NAME ".tmp", MDASH_CONFIG_FILE_NAME); 560 | MDashMutexUnlock(); 561 | return 0; 562 | } 563 | 564 | int mDashConfigReset(void) { 565 | MG_ERROR(("%s", "Resetting config")); 566 | mDashConfigSet("wifi.sta.ssid", NULL); 567 | mDashConfigSet("wifi.sta.pass", NULL); 568 | mDashConfigSet("device.public_key", NULL); 569 | return 0; 570 | } 571 | 572 | #define MDASH_DATA_FILE_NAME FS_ROOT "/mdash.unsent" 573 | #define MDASH_DATA_FILE_SIZE_LIMIT 32768 574 | 575 | static void pfn_file(char c, void *data) { 576 | fputc(c, (FILE *) data); 577 | } 578 | 579 | int mDashStore(const char *topic, const char *fmt, ...) { 580 | FILE *fp = fopen(MDASH_DATA_FILE_NAME, "a"); 581 | if (fp == NULL) return 0; 582 | 583 | fprintf(fp, "%s ", topic); 584 | va_list ap; 585 | va_start(ap, fmt); 586 | mg_vrprintf(pfn_file, fp, fmt, &ap); 587 | va_end(ap); 588 | fputc('\n', fp); 589 | long size = ftell(fp); 590 | fclose(fp); 591 | 592 | if (size > MDASH_DATA_FILE_SIZE_LIMIT) { 593 | const char *to = MDASH_DATA_FILE_NAME ".old"; 594 | remove(to); 595 | rename(MDASH_DATA_FILE_NAME, to); 596 | } 597 | 598 | mDashNotify("DB.NewData", "true"); // Tell mDash we have unsaved data 599 | return 1; 600 | } 601 | 602 | static char *gen_public_key(char *buf, size_t len) { 603 | uint8_t tmp[len * 3 / 4 - 3]; 604 | mg_random(tmp, sizeof(tmp)); 605 | mg_base64_encode(tmp, sizeof(tmp), buf); 606 | return buf; 607 | } 608 | 609 | static const char *fromcfg(const char *ptr, const char *var) { 610 | char buf[256]; 611 | if (mDashConfigGet(var, buf, sizeof(buf)) == 0) ptr = strdup(buf); 612 | MG_DEBUG(("%s -> [%s]", var, ptr == NULL ? "NULL" : ptr)); 613 | return ptr; 614 | } 615 | 616 | static bool s_time_set; 617 | static void sntpcb(struct mg_connection *c, int ev, void *evd, void *fnd) { 618 | if (ev == MG_EV_SNTP_TIME) { 619 | uint64_t ms = *(uint64_t *) evd; 620 | struct timeval tv = {.tv_sec = ms / 1000, .tv_usec = (ms % 1000) * 1000}; 621 | MG_INFO(("SNTP: setting time to %lu.%lu", tv.tv_sec, tv.tv_usec)); 622 | settimeofday(&tv, NULL); 623 | s_time_set = true; 624 | } else if (ev == MG_EV_CLOSE) { 625 | s_sntp = NULL; 626 | } 627 | (void) c, (void) fnd; 628 | } 629 | 630 | static void dev_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { 631 | if (ev == MG_EV_CONNECT) { 632 | if (mg_url_is_ssl(s_mDashURL)) { 633 | struct mg_str host = mg_url_host(s_mDashURL); 634 | struct mg_tls_opts opts = {.ca = s_pem, .srvname = host}; 635 | mg_tls_init(s_conn, &opts); 636 | } 637 | } 638 | if (ev == MG_EV_WS_OPEN) { 639 | MG_INFO(("connected to %s", s_mDashURL)); 640 | s_mDashState = MDASH_STATE_CLOUD_CONNECTED; 641 | // mDashTriggerEvent(s_mDashState, NULL); 642 | } else if (ev == MG_EV_WS_MSG) { 643 | struct mg_ws_message *wm = evd; 644 | struct mg_str s = wm->data; 645 | while (s.len > 0 && isspace((uint8_t) s.ptr[s.len - 1])) s.len--; 646 | MG_INFO(("RPC: %.*s", (int) s.len, s.ptr)); 647 | struct mg_iobuf io = {0, 0, 0, 512}; 648 | struct mg_rpc_req r = {.head = &g_rpcs, 649 | .rpc = NULL, 650 | .pfn = mg_pfn_iobuf, 651 | .pfn_data = &io, 652 | .req_data = NULL, 653 | .frame = s}; 654 | mg_rpc_process(&r); 655 | if (io.buf != NULL) { 656 | MG_INFO((" %s", io.buf)); 657 | mg_ws_send(c, (const char *) io.buf, io.len, WEBSOCKET_OP_TEXT); 658 | } else { 659 | MG_VERBOSE(("%s", "RPC unhandled")); 660 | } 661 | mg_iobuf_free(&io); 662 | } else if (ev == MG_EV_CLOSE) { 663 | s_conn = NULL; 664 | MG_INFO(("disconnected, ram %lu", mDashGetFreeRam())); 665 | if (s_mDashState == MDASH_STATE_CLOUD_CONNECTED) 666 | s_mDashState = MDASH_STATE_NETWORK_CONNECTED; 667 | // mDashTriggerEvent(s_mDashState, NULL); 668 | } 669 | (void) c, (void) fnd; 670 | } 671 | 672 | static int sv(struct mg_str json, const char *path, const char *name) { 673 | bool set = false; 674 | char *val = mg_json_get_str(json, path); 675 | if (val != NULL) { 676 | mDashConfigSet(name, val); 677 | free(val); 678 | set = true; 679 | } 680 | return set; 681 | } 682 | 683 | static void httpcb(struct mg_connection *c, int ev, void *evd, void *fnd) { 684 | const char *extra_headers = 685 | "Access-Control-Allow-Origin: *\r\n" 686 | "Content-Type: application/json\r\n"; 687 | if (ev == MG_EV_HTTP_MSG) { 688 | struct mg_http_message *hm = evd; 689 | if (mg_http_match_uri(hm, "/GetKey")) { 690 | if (s_mDashPublicKey != NULL) { 691 | mg_http_reply(c, 200, extra_headers, "{%Q:%Q}\n", "result", 692 | s_mDashPublicKey); 693 | } else { 694 | mg_http_reply(c, 200, extra_headers, "{%Q:%Q}\n", "error", 695 | "key not set"); 696 | } 697 | } else if (mg_http_match_uri(hm, "/setup") && hm->body.len > 0) { 698 | if (sv(hm->body, "$.ssid", "wifi.sta.ssid") && 699 | sv(hm->body, "$.pass", "wifi.sta.pass")) { 700 | mg_http_reply(c, 200, extra_headers, "{%Q:true}\n", "result"); 701 | reboot_with_delay(3000); 702 | } else { 703 | mg_http_reply(c, 200, extra_headers, "{%Q:%Q}\n", "error", 704 | "set ssid and pass"); 705 | } 706 | } else { 707 | mg_http_reply(c, 200, extra_headers, "{%Q:%Q}\n", "error", 708 | "use /setup endpoint"); 709 | } 710 | } 711 | (void) fnd; 712 | } 713 | 714 | static void timercb(void *userdata) { 715 | struct mg_mgr *mgr = userdata; 716 | MG_DEBUG(("ram: %lu, %d %p/%p/%p", mDashGetFreeRam(), s_mDashState, s_conn, 717 | s_http, s_sntp)); 718 | if (s_conn == NULL && s_mDashState == MDASH_STATE_NETWORK_CONNECTED && 719 | s_mDashPassword != NULL && s_time_set) { 720 | s_conn = mg_ws_connect(mgr, s_mDashURL, dev_cb, NULL, 721 | "Origin: %s\r\n" 722 | "Authorization: Bearer %s\r\n", 723 | s_mDashURL, s_mDashPassword); 724 | } 725 | if (s_sntp == NULL && (s_mDashState == MDASH_STATE_NETWORK_CONNECTED || 726 | s_mDashState == MDASH_STATE_CLOUD_CONNECTED)) { 727 | s_sntp = mg_sntp_connect(mgr, NULL, sntpcb, NULL); 728 | } 729 | // Trigger periodic STNP reconnect, and time resync, by closing s_sntp 730 | if (s_sntp && ((time(0) % 3600) == 0)) s_sntp->is_closing = 1; 731 | if (s_http == NULL && s_mDashState == MDASH_STATE_NETWORK_AP) { 732 | s_http = mg_http_listen(mgr, HTTP_URL, httpcb, NULL); 733 | } 734 | } 735 | 736 | void mDashPoll(void) { 737 | MDashMutexLock(); 738 | mg_mgr_poll(&s_mgr, 50); 739 | MDashMutexUnlock(); 740 | } 741 | 742 | void mDashTask(void *data) { 743 | for (;;) mDashPoll(); 744 | (void) data; 745 | } 746 | 747 | int mDashGetState(void) { 748 | return s_mDashState; 749 | } 750 | 751 | static void doinit(const char *id, const char *pass, const char *name, 752 | const char *ts, const char *framework) { 753 | mount_fs(FS_ROOT); // Do it first, to enable configuration API 754 | s_start_time = time(NULL); 755 | s_mDashPassword = fromcfg(pass, "device.pass"); 756 | s_mDashPublicKey = fromcfg(NULL, "device.public_key"); 757 | if (s_mDashPublicKey == NULL) { 758 | char buf[40]; 759 | s_mDashPublicKey = strdup(gen_public_key(buf, sizeof(buf))); 760 | mDashConfigSet("device.public_key", s_mDashPublicKey); 761 | } 762 | s_mDashBuildTime = ts; 763 | const char *p = strrchr(name, '/'); 764 | if (p == NULL) p = strrchr(name, '\\'); 765 | if (p != NULL) name = p + 1; 766 | s_mDashAppName = name; 767 | s_mDashFramework = framework; 768 | s_mDashURL = (char *) fromcfg(s_mDashURL, "mdash.url"); 769 | 770 | char ll[2] = "2"; // Default log level 771 | if (s_mDashLogLevel == 2 && 772 | mDashConfigGet("debug.level", ll, sizeof(ll)) == 0) { 773 | MG_INFO(("Setting log level (from config) to: %s", ll)); 774 | s_mDashLogLevel = atoi(ll); 775 | } 776 | mg_log_set(s_mDashLogLevel); 777 | init_rpcs(); 778 | mg_mgr_init(&s_mgr); 779 | mg_timer_add(&s_mgr, 1000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timercb, 780 | &s_mgr); 781 | MG_INFO(("Initialised, id %s", id)); 782 | } 783 | 784 | void mDashInit(const char *id, const char *pass, const char *name, 785 | const char *ts, const char *framework) { 786 | s_mDashState = MDASH_STATE_NETWORK_CONNECTED; 787 | MDashMutexInit(); 788 | MDashMutexLock(); 789 | doinit(id, pass, name, ts, framework); 790 | xTaskCreatePinnedToCore(&mDashTask, "mDashTask", 16384, 0, 5, 0, CPU_CORE); 791 | MDashMutexUnlock(); 792 | } 793 | -------------------------------------------------------------------------------- /src/mDash.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2022 Cesanta Software Limited 2 | // All rights reserved 3 | // 4 | // This software is dual-licensed: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License version 2 as 6 | // published by the Free Software Foundation. For the terms of this 7 | // license, see http://www.gnu.org/licenses/ 8 | // 9 | // You are free to use this software under the terms of the GNU General 10 | // Public License, but WITHOUT ANY WARRANTY; without even the implied 11 | // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | // See the GNU General Public License for more details. 13 | // 14 | // Alternatively, you can license this software under a commercial 15 | // license, as set out in https://www.mongoose.ws/licensing/ 16 | // 17 | // SPDX-License-Identifier: GPL-2.0 or commercial 18 | 19 | #pragma once 20 | 21 | #include "mongoose.h" 22 | 23 | #define MDASH_VERSION "1.2.16" 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | // Get current connection state 30 | int mDashGetState(void); 31 | #define MDASH_STATE_NETWORK_LOST 0 32 | #define MDASH_STATE_NETWORK_AP 1 33 | #define MDASH_STATE_NETWORK_CONNECTED 2 34 | #define MDASH_STATE_CLOUD_CONNECTED 3 35 | 36 | // Store data 37 | int mDashStore(const char *topic, const char *json_fmt, ...); 38 | 39 | // Send notification frame to mDash 40 | int mDashNotify(const char *name, const char *fmt, ...); 41 | #define mDashShadowUpdate(fmt, ...) \ 42 | mDashNotify("Dash.Shadow.Update", (fmt), __VA_ARGS__) 43 | 44 | int mDashConfigGet(const char *name, char *buf, int bufsize); 45 | int mDashConfigSet(const char *name, const char *value); 46 | int mDashConfigReset(void); 47 | void mDashPoll(void); 48 | 49 | // Registered RPC handlers. Use mg_rpc_add() to add new handlers 50 | extern struct mg_rpc *g_rpcs; 51 | 52 | void mDashInit(const char *device_id, const char *device_pass, 53 | const char *app_name, const char *build_time, 54 | const char *framework); 55 | #if ARDUINO 56 | #define __TOSTR(x) #x 57 | #define STR(x) __TOSTR(x) 58 | #ifndef ARDUINO_BOARD 59 | #define ARDUINO_BOARD "?" 60 | #endif 61 | #define MDASH_FRAMEWORK "a-" STR(ARDUINO) "-" ARDUINO_BOARD 62 | #else 63 | #define MDASH_FRAMEWORK "idf" 64 | #endif 65 | 66 | #ifndef MDASH_APP_NAME 67 | #define MDASH_APP_NAME __FILE__ 68 | #endif 69 | 70 | #define MDASH_BUILD_TIME __DATE__ "-" __TIME__ 71 | #define mDashBegin(b) \ 72 | mDashInit(NULL, (b), MDASH_APP_NAME, MDASH_BUILD_TIME, MDASH_FRAMEWORK) 73 | 74 | #ifdef __cplusplus 75 | } 76 | #endif 77 | -------------------------------------------------------------------------------- /src/mongoose.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2004-2013 Sergey Lyubka 2 | // Copyright (c) 2013-2022 Cesanta Software Limited 3 | // All rights reserved 4 | // 5 | // This software is dual-licensed: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License version 2 as 7 | // published by the Free Software Foundation. For the terms of this 8 | // license, see http://www.gnu.org/licenses/ 9 | // 10 | // You are free to use this software under the terms of the GNU General 11 | // Public License, but WITHOUT ANY WARRANTY; without even the implied 12 | // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | // See the GNU General Public License for more details. 14 | // 15 | // Alternatively, you can license this software under a commercial 16 | // license, as set out in https://www.mongoose.ws/licensing/ 17 | // 18 | // SPDX-License-Identifier: GPL-2.0 or commercial 19 | 20 | #ifndef MONGOOSE_H 21 | #define MONGOOSE_H 22 | 23 | #define MG_VERSION "7.7" 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | #define MG_ARCH_CUSTOM 0 31 | #define MG_ARCH_UNIX 1 32 | #define MG_ARCH_WIN32 2 33 | #define MG_ARCH_ESP32 3 34 | #define MG_ARCH_ESP8266 4 35 | #define MG_ARCH_FREERTOS_TCP 5 36 | #define MG_ARCH_FREERTOS_LWIP 6 37 | #define MG_ARCH_AZURERTOS 7 38 | #define MG_ARCH_RTX_LWIP 8 39 | #define MG_ARCH_ZEPHYR 9 40 | #define MG_ARCH_NEWLIB 10 41 | #define MG_ARCH_RTX 11 42 | #define MG_ARCH_TIRTOS 12 43 | #define MG_ARCH_RP2040 13 44 | 45 | #if !defined(MG_ARCH) 46 | #if defined(__unix__) || defined(__APPLE__) 47 | #define MG_ARCH MG_ARCH_UNIX 48 | #elif defined(_WIN32) 49 | #define MG_ARCH MG_ARCH_WIN32 50 | #elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR) 51 | #define MG_ARCH MG_ARCH_ESP8266 52 | #elif defined(ESP_PLATFORM) 53 | #define MG_ARCH MG_ARCH_ESP32 54 | #elif defined(FREERTOS_IP_H) 55 | #define MG_ARCH MG_ARCH_FREERTOS_TCP 56 | #elif defined(AZURE_RTOS_THREADX) 57 | #define MG_ARCH MG_ARCH_AZURERTOS 58 | #elif defined(__ZEPHYR__) 59 | #define MG_ARCH MG_ARCH_ZEPHYR 60 | #elif defined(PICO_TARGET_NAME) 61 | #define MG_ARCH MG_ARCH_RP2040 62 | #endif 63 | 64 | #if !defined(MG_ARCH) 65 | #error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..." 66 | #endif 67 | #endif // !defined(MG_ARCH) 68 | 69 | #if MG_ARCH == MG_ARCH_CUSTOM 70 | #include 71 | #endif 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | #if MG_ARCH == MG_ARCH_AZURERTOS 86 | 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | 93 | #include 94 | #include 95 | 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | #ifdef __REDLIB__ 102 | #define va_copy(d, s) __builtin_va_copy(d, s) 103 | #endif 104 | 105 | #define PATH_MAX FX_MAXIMUM_PATH 106 | #define MG_DIRSEP '\\' 107 | 108 | #define socklen_t int 109 | #define closesocket(x) soc_close(x) 110 | 111 | #undef FOPEN_MAX 112 | 113 | #endif 114 | 115 | 116 | #if MG_ARCH == MG_ARCH_ESP32 117 | 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | #include 124 | #include 125 | #include 126 | #include 127 | #include 128 | #include 129 | #include 130 | #include 131 | #include 132 | 133 | #include 134 | 135 | #define MG_PATH_MAX 128 136 | 137 | #endif 138 | 139 | 140 | #if MG_ARCH == MG_ARCH_ESP8266 141 | 142 | #include 143 | #include 144 | #include 145 | #include 146 | #include 147 | #include 148 | #include 149 | #include 150 | #include 151 | #include 152 | #include 153 | #include 154 | #include 155 | #include 156 | #include 157 | #include 158 | 159 | #include 160 | 161 | #define MG_PATH_MAX 128 162 | 163 | #endif 164 | 165 | 166 | #if MG_ARCH == MG_ARCH_FREERTOS_LWIP 167 | 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | #include 174 | 175 | #if defined(__GNUC__) 176 | #include 177 | #include 178 | #else 179 | struct timeval { 180 | time_t tv_sec; 181 | long tv_usec; 182 | }; 183 | #endif 184 | 185 | #include 186 | #include 187 | 188 | #include 189 | 190 | #if LWIP_SOCKET != 1 191 | // Sockets support disabled in LWIP by default 192 | #error Set LWIP_SOCKET variable to 1 (in lwipopts.h) 193 | #endif 194 | 195 | // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib 196 | static inline void *mg_calloc(int cnt, size_t size) { 197 | void *p = pvPortMalloc(cnt * size); 198 | if (p != NULL) memset(p, 0, size * cnt); 199 | return p; 200 | } 201 | #define calloc(a, b) mg_calloc((a), (b)) 202 | #define free(a) vPortFree(a) 203 | #define malloc(a) pvPortMalloc(a) 204 | 205 | #define mkdir(a, b) (-1) 206 | 207 | #ifndef MG_IO_SIZE 208 | #define MG_IO_SIZE 512 209 | #endif 210 | 211 | #ifndef MG_PATH_MAX 212 | #define MG_PATH_MAX 128 213 | #endif 214 | 215 | #endif // MG_ARCH == MG_ARCH_FREERTOS_LWIP 216 | 217 | 218 | #if MG_ARCH == MG_ARCH_FREERTOS_TCP 219 | 220 | #include 221 | #include 222 | #include 223 | #include 224 | #include 225 | #include 226 | #include 227 | #include 228 | #include 229 | #include 230 | #include 231 | 232 | #include 233 | #include 234 | #include 235 | #include 236 | 237 | // Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing 238 | // with FreeRTOS_ prefix, is beyond me 239 | #define IPPROTO_TCP FREERTOS_IPPROTO_TCP 240 | #define IPPROTO_UDP FREERTOS_IPPROTO_UDP 241 | #define AF_INET FREERTOS_AF_INET 242 | #define SOCK_STREAM FREERTOS_SOCK_STREAM 243 | #define SOCK_DGRAM FREERTOS_SOCK_DGRAM 244 | #define SO_BROADCAST 0 245 | #define SO_ERROR 0 246 | #define SOL_SOCKET 0 247 | #define SO_REUSEADDR 0 248 | #define sockaddr_in freertos_sockaddr 249 | #define sockaddr freertos_sockaddr 250 | #define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) 251 | #define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) 252 | #define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) 253 | #define listen(a, b) FreeRTOS_listen((a), (b)) 254 | #define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) 255 | #define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) 256 | #define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) 257 | #define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) 258 | #define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) 259 | #define recvfrom(a, b, c, d, e, f) \ 260 | FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) 261 | #define closesocket(x) FreeRTOS_closesocket(x) 262 | #define gethostbyname(x) FreeRTOS_gethostbyname(x) 263 | #define getsockname(a, b, c) (-1) 264 | #define getpeername(a, b, c) 0 265 | 266 | // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib 267 | static inline void *mg_calloc(int cnt, size_t size) { 268 | void *p = pvPortMalloc(cnt * size); 269 | if (p != NULL) memset(p, 0, size * cnt); 270 | return p; 271 | } 272 | #define calloc(a, b) mg_calloc((a), (b)) 273 | #define free(a) vPortFree(a) 274 | #define malloc(a) pvPortMalloc(a) 275 | #define mkdir(a, b) (-1) 276 | 277 | #if !defined(__GNUC__) 278 | // copied from GCC on ARM; for some reason useconds are signed 279 | struct timeval { 280 | time_t tv_sec; 281 | long tv_usec; 282 | }; 283 | #endif 284 | 285 | #ifndef EINPROGRESS 286 | #define EINPROGRESS pdFREERTOS_ERRNO_EINPROGRESS 287 | #endif 288 | #ifndef EWOULDBLOCK 289 | #define EWOULDBLOCK pdFREERTOS_ERRNO_EWOULDBLOCK 290 | #endif 291 | #ifndef EAGAIN 292 | #define EAGAIN pdFREERTOS_ERRNO_EAGAIN 293 | #endif 294 | #ifndef EINTR 295 | #define EINTR pdFREERTOS_ERRNO_EINTR 296 | #endif 297 | 298 | #endif // MG_ARCH == MG_ARCH_FREERTOS_TCP 299 | 300 | 301 | #if MG_ARCH == MG_ARCH_NEWLIB 302 | #define _POSIX_TIMERS 303 | 304 | #include 305 | #include 306 | #include 307 | #include 308 | #include 309 | #include 310 | #include 311 | #include 312 | #include 313 | #include 314 | #include 315 | #include 316 | 317 | #define MG_PATH_MAX 100 318 | #define MG_ENABLE_SOCKET 0 319 | #define MG_ENABLE_DIRLIST 0 320 | 321 | #endif 322 | 323 | 324 | #if MG_ARCH == MG_ARCH_RP2040 325 | #include 326 | #include 327 | #include 328 | #include 329 | #include 330 | #include 331 | #include 332 | #include 333 | 334 | #include 335 | int mkdir(const char *, mode_t); 336 | #endif 337 | 338 | 339 | #if MG_ARCH == MG_ARCH_RTX 340 | 341 | #include 342 | #include 343 | #include 344 | #include 345 | #include 346 | #include 347 | #include 348 | #include 349 | #include 350 | #include 351 | 352 | #include 353 | 354 | #define MG_ENABLE_CUSTOM_MILLIS 1 355 | typedef int socklen_t; 356 | #define closesocket(x) closesocket(x) 357 | #define mkdir(a, b) (-1) 358 | #define EWOULDBLOCK BSD_EWOULDBLOCK 359 | #define EAGAIN BSD_EWOULDBLOCK 360 | #define EINPROGRESS BSD_EWOULDBLOCK 361 | #define EINTR BSD_EWOULDBLOCK 362 | #define ECONNRESET BSD_ECONNRESET 363 | #define EPIPE BSD_ECONNRESET 364 | #define TCP_NODELAY SO_KEEPALIVE 365 | 366 | #endif 367 | 368 | 369 | #if MG_ARCH == MG_ARCH_RTX_LWIP 370 | 371 | #include 372 | #include 373 | #include 374 | #include 375 | #include 376 | #include 377 | 378 | #if defined(__GNUC__) 379 | #include 380 | #include 381 | #else 382 | struct timeval { 383 | time_t tv_sec; 384 | long tv_usec; 385 | }; 386 | #endif 387 | 388 | #include 389 | 390 | #if LWIP_SOCKET != 1 391 | // Sockets support disabled in LWIP by default 392 | #error Set LWIP_SOCKET variable to 1 (in lwipopts.h) 393 | #endif 394 | 395 | #define mkdir(a, b) (-1) 396 | 397 | #ifndef MG_IO_SIZE 398 | #define MG_IO_SIZE 512 399 | #endif 400 | 401 | #ifndef MG_PATH_MAX 402 | #define MG_PATH_MAX 128 403 | #endif 404 | 405 | 406 | #endif 407 | 408 | 409 | #if MG_ARCH == MG_ARCH_TIRTOS 410 | 411 | #include 412 | #include 413 | #include 414 | #include 415 | #include 416 | #include 417 | #include 418 | #include 419 | #include 420 | #include 421 | 422 | #include 423 | 424 | extern int SockStatus(SOCKET hSock, int request, int *results ); 425 | extern int SockSet(SOCKET hSock, int Type, int Prop, void *pbuf, int size); 426 | 427 | #endif 428 | 429 | 430 | #if MG_ARCH == MG_ARCH_UNIX 431 | 432 | #define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors 433 | 434 | #if defined(__APPLE__) 435 | #include 436 | #endif 437 | 438 | #if !defined(MG_ENABLE_EPOLL) && defined(__linux__) 439 | #define MG_ENABLE_EPOLL 1 440 | #elif !defined(MG_ENABLE_POLL) 441 | #define MG_ENABLE_POLL 1 442 | #endif 443 | 444 | #include 445 | #include 446 | #include 447 | #include 448 | #include 449 | #include 450 | #include 451 | #include 452 | #include 453 | #include 454 | #include 455 | #include 456 | #include 457 | #include 458 | #include 459 | #include 460 | #include 461 | #include 462 | 463 | #if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL 464 | #include 465 | #elif defined(MG_ENABLE_POLL) && MG_ENABLE_POLL 466 | #include 467 | #else 468 | #include 469 | #endif 470 | 471 | #include 472 | #include 473 | #include 474 | #include 475 | #include 476 | #include 477 | 478 | #ifndef MG_ENABLE_DIRLIST 479 | #define MG_ENABLE_DIRLIST 1 480 | #endif 481 | 482 | #ifndef MG_PATH_MAX 483 | #define MG_PATH_MAX FILENAME_MAX 484 | #endif 485 | 486 | #endif 487 | 488 | 489 | #if MG_ARCH == MG_ARCH_WIN32 490 | 491 | #ifndef WIN32_LEAN_AND_MEAN 492 | #define WIN32_LEAN_AND_MEAN 493 | #endif 494 | 495 | #ifndef _CRT_SECURE_NO_WARNINGS 496 | #define _CRT_SECURE_NO_WARNINGS 497 | #endif 498 | 499 | #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS 500 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 501 | #endif 502 | 503 | #include 504 | #include 505 | #include 506 | #include 507 | #include 508 | #include 509 | #include 510 | #include 511 | #include 512 | #include 513 | #include 514 | #include 515 | #include 516 | #include 517 | 518 | #if defined(_MSC_VER) && _MSC_VER < 1700 519 | #define __func__ "" 520 | typedef __int64 int64_t; 521 | typedef unsigned __int64 uint64_t; 522 | typedef unsigned char uint8_t; 523 | typedef char int8_t; 524 | typedef unsigned short uint16_t; 525 | typedef short int16_t; 526 | typedef unsigned int uint32_t; 527 | typedef int int32_t; 528 | typedef enum { false = 0, true = 1 } bool; 529 | #else 530 | #include 531 | #include 532 | #include 533 | #endif 534 | 535 | #include 536 | #include 537 | #include 538 | 539 | // Protect from calls like std::snprintf in app code 540 | // See https://github.com/cesanta/mongoose/issues/1047 541 | #ifndef __cplusplus 542 | #define snprintf _snprintf 543 | #define vsnprintf _vsnprintf 544 | #ifndef strdup // For MSVC with _DEBUG, see #1359 545 | #define strdup(x) _strdup(x) 546 | #endif 547 | #endif 548 | 549 | typedef int socklen_t; 550 | #define MG_DIRSEP '\\' 551 | 552 | #ifndef MG_PATH_MAX 553 | #define MG_PATH_MAX FILENAME_MAX 554 | #endif 555 | 556 | #ifndef EINPROGRESS 557 | #define EINPROGRESS WSAEINPROGRESS 558 | #endif 559 | #ifndef EWOULDBLOCK 560 | #define EWOULDBLOCK WSAEWOULDBLOCK 561 | #endif 562 | 563 | #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) 564 | #define sleep(x) Sleep(x) 565 | #define mkdir(a, b) _mkdir(a) 566 | 567 | #ifndef va_copy 568 | #ifdef __va_copy 569 | #define va_copy __va_copy 570 | #else 571 | #define va_copy(x, y) (x) = (y) 572 | #endif 573 | #endif 574 | #ifndef S_ISDIR 575 | #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) 576 | #endif 577 | 578 | #ifndef MG_ENABLE_DIRLIST 579 | #define MG_ENABLE_DIRLIST 1 580 | #endif 581 | 582 | #endif 583 | 584 | 585 | #if MG_ARCH == MG_ARCH_ZEPHYR 586 | 587 | #include 588 | 589 | #include 590 | #include 591 | #include 592 | #include 593 | #include 594 | #include 595 | #include 596 | #include 597 | #include 598 | #include 599 | #include 600 | #include 601 | 602 | #define MG_PUTCHAR(x) printk("%c", x) 603 | #define strerror(x) zsock_gai_strerror(x) 604 | #define FD_CLOEXEC 0 605 | #define F_SETFD 0 606 | #define MG_ENABLE_SSI 0 607 | 608 | int rand(void); 609 | int sscanf(const char *, const char *, ...); 610 | 611 | #endif 612 | 613 | 614 | #ifndef MG_ENABLE_LOG 615 | #define MG_ENABLE_LOG 1 616 | #endif 617 | 618 | #ifndef MG_ENABLE_MIP 619 | #define MG_ENABLE_MIP 0 620 | #endif 621 | 622 | #ifndef MG_ENABLE_POLL 623 | #define MG_ENABLE_POLL 0 624 | #endif 625 | 626 | #ifndef MG_ENABLE_EPOLL 627 | #define MG_ENABLE_EPOLL 0 628 | #endif 629 | 630 | #ifndef MG_ENABLE_FATFS 631 | #define MG_ENABLE_FATFS 0 632 | #endif 633 | 634 | #ifndef MG_ENABLE_SOCKET 635 | #define MG_ENABLE_SOCKET 1 636 | #endif 637 | 638 | #ifndef MG_ENABLE_MBEDTLS 639 | #define MG_ENABLE_MBEDTLS 0 640 | #endif 641 | 642 | #ifndef MG_ENABLE_OPENSSL 643 | #define MG_ENABLE_OPENSSL 0 644 | #endif 645 | 646 | #ifndef MG_ENABLE_CUSTOM_TLS 647 | #define MG_ENABLE_CUSTOM_TLS 0 648 | #endif 649 | 650 | #ifndef MG_ENABLE_SSI 651 | #define MG_ENABLE_SSI 0 652 | #endif 653 | 654 | #ifndef MG_ENABLE_IPV6 655 | #define MG_ENABLE_IPV6 0 656 | #endif 657 | 658 | #ifndef MG_ENABLE_MD5 659 | #define MG_ENABLE_MD5 0 660 | #endif 661 | 662 | // Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) 663 | #ifndef MG_ENABLE_WINSOCK 664 | #define MG_ENABLE_WINSOCK 1 665 | #endif 666 | 667 | #ifndef MG_ENABLE_DIRLIST 668 | #define MG_ENABLE_DIRLIST 0 669 | #endif 670 | 671 | #ifndef MG_ENABLE_CUSTOM_RANDOM 672 | #define MG_ENABLE_CUSTOM_RANDOM 0 673 | #endif 674 | 675 | #ifndef MG_ENABLE_CUSTOM_MILLIS 676 | #define MG_ENABLE_CUSTOM_MILLIS 0 677 | #endif 678 | 679 | #ifndef MG_ENABLE_PACKED_FS 680 | #define MG_ENABLE_PACKED_FS 0 681 | #endif 682 | 683 | // Granularity of the send/recv IO buffer growth 684 | #ifndef MG_IO_SIZE 685 | #define MG_IO_SIZE 2048 686 | #endif 687 | 688 | // Maximum size of the recv IO buffer 689 | #ifndef MG_MAX_RECV_SIZE 690 | #define MG_MAX_RECV_SIZE (3 * 1024 * 1024) 691 | #endif 692 | 693 | #ifndef MG_MAX_HTTP_HEADERS 694 | #define MG_MAX_HTTP_HEADERS 40 695 | #endif 696 | 697 | #ifndef MG_HTTP_INDEX 698 | #define MG_HTTP_INDEX "index.html" 699 | #endif 700 | 701 | #ifndef MG_PATH_MAX 702 | #ifdef PATH_MAX 703 | #define MG_PATH_MAX PATH_MAX 704 | #else 705 | #define MG_PATH_MAX 128 706 | #endif 707 | #endif 708 | 709 | #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE 710 | #define MG_SOCK_LISTEN_BACKLOG_SIZE 3 711 | #endif 712 | 713 | #ifndef MG_DIRSEP 714 | #define MG_DIRSEP '/' 715 | #endif 716 | 717 | #ifndef MG_ENABLE_FILE 718 | #if defined(FOPEN_MAX) 719 | #define MG_ENABLE_FILE 1 720 | #else 721 | #define MG_ENABLE_FILE 0 722 | #endif 723 | #endif 724 | 725 | #if MG_ENABLE_EPOLL 726 | #define MG_EPOLL_ADD(c) \ 727 | do { \ 728 | struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ 729 | epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \ 730 | } while (0) 731 | #define MG_EPOLL_MOD(c, wr) \ 732 | do { \ 733 | struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ 734 | if (wr) ev.events |= EPOLLOUT; \ 735 | epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \ 736 | } while (0) 737 | #else 738 | #define MG_EPOLL_ADD(c) 739 | #define MG_EPOLL_MOD(c, wr) 740 | #endif 741 | 742 | 743 | 744 | 745 | struct mg_str { 746 | const char *ptr; // Pointer to string data 747 | size_t len; // String len 748 | }; 749 | 750 | #define MG_NULL_STR \ 751 | { NULL, 0 } 752 | 753 | #define MG_C_STR(a) \ 754 | { (a), sizeof(a) - 1 } 755 | 756 | // Using macro to avoid shadowing C++ struct constructor, see #1298 757 | #define mg_str(s) mg_str_s(s) 758 | 759 | struct mg_str mg_str(const char *s); 760 | struct mg_str mg_str_n(const char *s, size_t n); 761 | int mg_lower(const char *s); 762 | int mg_ncasecmp(const char *s1, const char *s2, size_t len); 763 | int mg_casecmp(const char *s1, const char *s2); 764 | int mg_vcmp(const struct mg_str *s1, const char *s2); 765 | int mg_vcasecmp(const struct mg_str *str1, const char *str2); 766 | int mg_strcmp(const struct mg_str str1, const struct mg_str str2); 767 | struct mg_str mg_strstrip(struct mg_str s); 768 | struct mg_str mg_strdup(const struct mg_str s); 769 | const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); 770 | bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); 771 | bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); 772 | bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); 773 | bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char delim); 774 | char *mg_hex(const void *buf, size_t len, char *dst); 775 | void mg_unhex(const char *buf, size_t len, unsigned char *to); 776 | unsigned long mg_unhexn(const char *s, size_t len); 777 | int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip); 778 | int64_t mg_to64(struct mg_str str); 779 | uint64_t mg_tou64(struct mg_str str); 780 | char *mg_remove_double_dots(char *s); 781 | 782 | 783 | 784 | 785 | 786 | typedef void (*mg_pfn_t)(char, void *); // Custom putchar 787 | typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M printer 788 | void mg_pfn_iobuf(char ch, void *param); // iobuf printer 789 | 790 | size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex); 791 | double mg_atod(const char *buf, int len, int *numlen); 792 | size_t mg_dtoa(char *buf, size_t len, double d, int width); 793 | 794 | size_t mg_vrprintf(void (*)(char, void *), void *, const char *fmt, va_list *); 795 | size_t mg_rprintf(void (*fn)(char, void *), void *, const char *fmt, ...); 796 | size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap); 797 | size_t mg_snprintf(char *, size_t, const char *fmt, ...); 798 | size_t mg_asprintf(char **, size_t, const char *fmt, ...); 799 | size_t mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap); 800 | char *mg_mprintf(const char *fmt, ...); 801 | char *mg_vmprintf(const char *fmt, va_list ap); 802 | 803 | 804 | 805 | 806 | 807 | enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; 808 | void mg_log(const char *fmt, ...); 809 | bool mg_log_prefix(int ll, const char *file, int line, const char *fname); 810 | void mg_log_set(int log_level); 811 | void mg_hexdump(const void *buf, size_t len); 812 | void mg_log_set_fn(void (*logfunc)(unsigned char ch)); 813 | 814 | #if MG_ENABLE_LOG 815 | #define MG_LOG(level, args) \ 816 | do { \ 817 | if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \ 818 | } while (0) 819 | #else 820 | #define MG_LOG(level, args) \ 821 | do { \ 822 | if (0) mg_log args; \ 823 | } while (0) 824 | #endif 825 | 826 | #define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args) 827 | #define MG_INFO(args) MG_LOG(MG_LL_INFO, args) 828 | #define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args) 829 | #define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args) 830 | 831 | 832 | 833 | 834 | struct mg_timer { 835 | unsigned long id; // Timer ID 836 | uint64_t period_ms; // Timer period in milliseconds 837 | uint64_t prev_ms; // Timestamp of a previous poll 838 | uint64_t expire; // Expiration timestamp in milliseconds 839 | unsigned flags; // Possible flags values below 840 | #define MG_TIMER_ONCE 0 // Call function once 841 | #define MG_TIMER_REPEAT 1 // Call function periodically 842 | #define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set 843 | void (*fn)(void *); // Function to call 844 | void *arg; // Function argument 845 | struct mg_timer *next; // Linkage 846 | }; 847 | 848 | void mg_timer_init(struct mg_timer **head, struct mg_timer *timer, 849 | uint64_t milliseconds, unsigned flags, void (*fn)(void *), 850 | void *arg); 851 | void mg_timer_free(struct mg_timer **head, struct mg_timer *); 852 | void mg_timer_poll(struct mg_timer **head, uint64_t new_ms); 853 | 854 | 855 | 856 | 857 | 858 | enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; 859 | 860 | // Filesystem API functions 861 | // st() returns MG_FS_* flags and populates file size and modification time 862 | // ls() calls fn() for every directory entry, allowing to list a directory 863 | // 864 | // NOTE: UNIX-style shorthand names for the API functions are deliberately 865 | // chosen to avoid conflicts with some libraries that make macros for e.g. 866 | // stat(), write(), read() calls. 867 | struct mg_fs { 868 | int (*st)(const char *path, size_t *size, time_t *mtime); // stat file 869 | void (*ls)(const char *path, void (*fn)(const char *, void *), void *); 870 | void *(*op)(const char *path, int flags); // Open file 871 | void (*cl)(void *fd); // Close file 872 | size_t (*rd)(void *fd, void *buf, size_t len); // Read file 873 | size_t (*wr)(void *fd, const void *buf, size_t len); // Write file 874 | size_t (*sk)(void *fd, size_t offset); // Set file position 875 | bool (*mv)(const char *from, const char *to); // Rename file 876 | bool (*rm)(const char *path); // Delete file 877 | bool (*mkd)(const char *path); // Create directory 878 | }; 879 | 880 | extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek 881 | extern struct mg_fs mg_fs_packed; // Packed FS, see examples/device-dashboard 882 | extern struct mg_fs mg_fs_fat; // FAT FS 883 | 884 | // File descriptor 885 | struct mg_fd { 886 | void *fd; 887 | struct mg_fs *fs; 888 | }; 889 | 890 | struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags); 891 | void mg_fs_close(struct mg_fd *fd); 892 | char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size); 893 | bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t); 894 | bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); 895 | 896 | 897 | 898 | 899 | 900 | 901 | void mg_random(void *buf, size_t len); 902 | char *mg_random_str(char *buf, size_t len); 903 | uint16_t mg_ntohs(uint16_t net); 904 | uint32_t mg_ntohl(uint32_t net); 905 | uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); 906 | uint64_t mg_millis(void); 907 | 908 | #define mg_htons(x) mg_ntohs(x) 909 | #define mg_htonl(x) mg_ntohl(x) 910 | 911 | // Linked list management macros 912 | #define LIST_ADD_HEAD(type_, head_, elem_) \ 913 | do { \ 914 | (elem_)->next = (*head_); \ 915 | *(head_) = (elem_); \ 916 | } while (0) 917 | 918 | #define LIST_ADD_TAIL(type_, head_, elem_) \ 919 | do { \ 920 | type_ **h = head_; \ 921 | while (*h != NULL) h = &(*h)->next; \ 922 | *h = (elem_); \ 923 | } while (0) 924 | 925 | #define LIST_DELETE(type_, head_, elem_) \ 926 | do { \ 927 | type_ **h = head_; \ 928 | while (*h != (elem_)) h = &(*h)->next; \ 929 | *h = (elem_)->next; \ 930 | } while (0) 931 | 932 | 933 | 934 | unsigned short mg_url_port(const char *url); 935 | int mg_url_is_ssl(const char *url); 936 | struct mg_str mg_url_host(const char *url); 937 | struct mg_str mg_url_user(const char *url); 938 | struct mg_str mg_url_pass(const char *url); 939 | const char *mg_url_uri(const char *url); 940 | 941 | 942 | 943 | 944 | struct mg_iobuf { 945 | unsigned char *buf; // Pointer to stored data 946 | size_t size; // Total size available 947 | size_t len; // Current number of bytes 948 | size_t align; // Alignment during allocation 949 | }; 950 | 951 | int mg_iobuf_init(struct mg_iobuf *, size_t, size_t); 952 | int mg_iobuf_resize(struct mg_iobuf *, size_t); 953 | void mg_iobuf_free(struct mg_iobuf *); 954 | size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t); 955 | size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len); 956 | 957 | int mg_base64_update(unsigned char p, char *to, int len); 958 | int mg_base64_final(char *to, int len); 959 | int mg_base64_encode(const unsigned char *p, int n, char *to); 960 | int mg_base64_decode(const char *src, int n, char *dst); 961 | 962 | 963 | 964 | 965 | typedef struct { 966 | uint32_t buf[4]; 967 | uint32_t bits[2]; 968 | unsigned char in[64]; 969 | } mg_md5_ctx; 970 | 971 | void mg_md5_init(mg_md5_ctx *c); 972 | void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len); 973 | void mg_md5_final(mg_md5_ctx *c, unsigned char[16]); 974 | 975 | 976 | 977 | 978 | typedef struct { 979 | uint32_t state[5]; 980 | uint32_t count[2]; 981 | unsigned char buffer[64]; 982 | } mg_sha1_ctx; 983 | 984 | void mg_sha1_init(mg_sha1_ctx *); 985 | void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); 986 | void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); 987 | 988 | 989 | struct mg_connection; 990 | typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, 991 | void *ev_data, void *fn_data); 992 | void mg_call(struct mg_connection *c, int ev, void *ev_data); 993 | void mg_error(struct mg_connection *c, const char *fmt, ...); 994 | 995 | enum { 996 | MG_EV_ERROR, // Error char *error_message 997 | MG_EV_OPEN, // Connection created NULL 998 | MG_EV_POLL, // mg_mgr_poll iteration uint64_t *milliseconds 999 | MG_EV_RESOLVE, // Host name is resolved NULL 1000 | MG_EV_CONNECT, // Connection established NULL 1001 | MG_EV_ACCEPT, // Connection accepted NULL 1002 | MG_EV_READ, // Data received from socket struct mg_str * 1003 | MG_EV_WRITE, // Data written to socket long *bytes_written 1004 | MG_EV_CLOSE, // Connection closed NULL 1005 | MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * 1006 | MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct mg_http_message * 1007 | MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * 1008 | MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * 1009 | MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * 1010 | MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * 1011 | MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * 1012 | MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code 1013 | MG_EV_SNTP_TIME, // SNTP time received uint64_t *milliseconds 1014 | MG_EV_USER, // Starting ID for user events 1015 | }; 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | struct mg_dns { 1025 | const char *url; // DNS server URL 1026 | struct mg_connection *c; // DNS server connection 1027 | }; 1028 | 1029 | struct mg_addr { 1030 | uint16_t port; // TCP or UDP port in network byte order 1031 | uint32_t ip; // IP address in network byte order 1032 | uint8_t ip6[16]; // IPv6 address 1033 | bool is_ip6; // True when address is IPv6 address 1034 | }; 1035 | 1036 | struct mg_mgr { 1037 | struct mg_connection *conns; // List of active connections 1038 | struct mg_dns dns4; // DNS for IPv4 1039 | struct mg_dns dns6; // DNS for IPv6 1040 | int dnstimeout; // DNS resolve timeout in milliseconds 1041 | bool use_dns6; // Use DNS6 server by default, see #1532 1042 | unsigned long nextid; // Next connection ID 1043 | unsigned long timerid; // Next timer ID 1044 | void *userdata; // Arbitrary user data pointer 1045 | uint16_t mqtt_id; // MQTT IDs for pub/sub 1046 | void *active_dns_requests; // DNS requests in progress 1047 | struct mg_timer *timers; // Active timers 1048 | int epoll_fd; // Used when MG_EPOLL_ENABLE=1 1049 | void *priv; // Used by the MIP stack 1050 | size_t extraconnsize; // Used by the MIP stack 1051 | #if MG_ARCH == MG_ARCH_FREERTOS_TCP 1052 | SocketSet_t ss; // NOTE(lsm): referenced from socket struct 1053 | #endif 1054 | }; 1055 | 1056 | struct mg_connection { 1057 | struct mg_connection *next; // Linkage in struct mg_mgr :: connections 1058 | struct mg_mgr *mgr; // Our container 1059 | struct mg_addr loc; // Local address 1060 | struct mg_addr rem; // Remote address 1061 | void *fd; // Connected socket, or LWIP data 1062 | unsigned long id; // Auto-incrementing unique connection ID 1063 | struct mg_iobuf recv; // Incoming data 1064 | struct mg_iobuf send; // Outgoing data 1065 | mg_event_handler_t fn; // User-specified event handler function 1066 | void *fn_data; // User-specified function parameter 1067 | mg_event_handler_t pfn; // Protocol-specific handler function 1068 | void *pfn_data; // Protocol-specific function parameter 1069 | char label[50]; // Arbitrary label 1070 | void *tls; // TLS specific data 1071 | unsigned is_listening : 1; // Listening connection 1072 | unsigned is_client : 1; // Outbound (client) connection 1073 | unsigned is_accepted : 1; // Accepted (server) connection 1074 | unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress 1075 | unsigned is_connecting : 1; // Non-blocking connect is in progress 1076 | unsigned is_tls : 1; // TLS-enabled connection 1077 | unsigned is_tls_hs : 1; // TLS handshake is in progress 1078 | unsigned is_udp : 1; // UDP connection 1079 | unsigned is_websocket : 1; // WebSocket connection 1080 | unsigned is_mqtt5 : 1; // For MQTT connection, v5 indicator 1081 | unsigned is_hexdumping : 1; // Hexdump in/out traffic 1082 | unsigned is_draining : 1; // Send remaining data, then close and free 1083 | unsigned is_closing : 1; // Close and free the connection immediately 1084 | unsigned is_full : 1; // Stop reads, until cleared 1085 | unsigned is_resp : 1; // Response is still being generated 1086 | unsigned is_readable : 1; // Connection is ready to read 1087 | unsigned is_writable : 1; // Connection is ready to write 1088 | }; 1089 | 1090 | void mg_mgr_poll(struct mg_mgr *, int ms); 1091 | void mg_mgr_init(struct mg_mgr *); 1092 | void mg_mgr_free(struct mg_mgr *); 1093 | 1094 | struct mg_connection *mg_listen(struct mg_mgr *, const char *url, 1095 | mg_event_handler_t fn, void *fn_data); 1096 | struct mg_connection *mg_connect(struct mg_mgr *, const char *url, 1097 | mg_event_handler_t fn, void *fn_data); 1098 | struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, 1099 | mg_event_handler_t fn, void *fn_data); 1100 | void mg_connect_resolved(struct mg_connection *); 1101 | bool mg_send(struct mg_connection *, const void *, size_t); 1102 | size_t mg_printf(struct mg_connection *, const char *fmt, ...); 1103 | size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list ap); 1104 | char *mg_straddr(struct mg_addr *, char *, size_t); 1105 | bool mg_aton(struct mg_str str, struct mg_addr *addr); 1106 | char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len); 1107 | int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp); 1108 | 1109 | // These functions are used to integrate with custom network stacks 1110 | struct mg_connection *mg_alloc_conn(struct mg_mgr *); 1111 | void mg_close_conn(struct mg_connection *c); 1112 | bool mg_open_listener(struct mg_connection *c, const char *url); 1113 | struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, 1114 | unsigned flags, void (*fn)(void *), void *arg); 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | struct mg_http_header { 1124 | struct mg_str name; // Header name 1125 | struct mg_str value; // Header value 1126 | }; 1127 | 1128 | struct mg_http_message { 1129 | struct mg_str method, uri, query, proto; // Request/response line 1130 | struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers 1131 | struct mg_str body; // Body 1132 | struct mg_str head; // Request + headers 1133 | struct mg_str chunk; // Chunk for chunked encoding, or partial body 1134 | struct mg_str message; // Request + headers + body 1135 | }; 1136 | 1137 | // Parameter for mg_http_serve_dir() 1138 | struct mg_http_serve_opts { 1139 | const char *root_dir; // Web root directory, must be non-NULL 1140 | const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml 1141 | const char *extra_headers; // Extra HTTP headers to add in responses 1142 | const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. 1143 | const char *page404; // Path to the 404 page, or NULL by default 1144 | struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX 1145 | }; 1146 | 1147 | // Parameter for mg_http_next_multipart 1148 | struct mg_http_part { 1149 | struct mg_str name; // Form field name 1150 | struct mg_str filename; // Filename for file uploads 1151 | struct mg_str body; // Part contents 1152 | }; 1153 | 1154 | int mg_http_parse(const char *s, size_t len, struct mg_http_message *); 1155 | int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); 1156 | void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); 1157 | void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); 1158 | void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm); 1159 | struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, 1160 | mg_event_handler_t fn, void *fn_data); 1161 | struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, 1162 | mg_event_handler_t fn, void *fn_data); 1163 | void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, 1164 | const struct mg_http_serve_opts *); 1165 | void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, 1166 | const char *path, const struct mg_http_serve_opts *); 1167 | void mg_http_reply(struct mg_connection *, int status_code, const char *headers, 1168 | const char *body_fmt, ...); 1169 | struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); 1170 | struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); 1171 | int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); 1172 | int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); 1173 | size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); 1174 | void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); 1175 | bool mg_http_match_uri(const struct mg_http_message *, const char *glob); 1176 | long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, 1177 | struct mg_fs *fs, const char *path, size_t max_size); 1178 | void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); 1179 | struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); 1180 | size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); 1181 | int mg_http_status(const struct mg_http_message *hm); 1182 | 1183 | 1184 | void mg_http_serve_ssi(struct mg_connection *c, const char *root, 1185 | const char *fullpath); 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | struct mg_tls_opts { 1193 | const char *ca; // CA certificate file. For both listeners and clients 1194 | const char *crl; // Certificate Revocation List. For clients 1195 | const char *cert; // Certificate 1196 | const char *certkey; // Certificate key 1197 | const char *ciphers; // Cipher list 1198 | struct mg_str srvname; // If not empty, enables server name verification 1199 | struct mg_fs *fs; // FS API for reading certificate files 1200 | }; 1201 | 1202 | void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *); 1203 | void mg_tls_free(struct mg_connection *); 1204 | long mg_tls_send(struct mg_connection *, const void *buf, size_t len); 1205 | long mg_tls_recv(struct mg_connection *, void *buf, size_t len); 1206 | size_t mg_tls_pending(struct mg_connection *); 1207 | void mg_tls_handshake(struct mg_connection *); 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | #if MG_ENABLE_MBEDTLS 1216 | #include 1217 | #include 1218 | #include 1219 | 1220 | struct mg_tls { 1221 | char *cafile; // CA certificate path 1222 | mbedtls_x509_crt ca; // Parsed CA certificate 1223 | mbedtls_x509_crt cert; // Parsed certificate 1224 | mbedtls_ssl_context ssl; // SSL/TLS context 1225 | mbedtls_ssl_config conf; // SSL-TLS config 1226 | mbedtls_pk_context pk; // Private key context 1227 | }; 1228 | #endif 1229 | 1230 | 1231 | #if MG_ENABLE_OPENSSL 1232 | 1233 | #include 1234 | #include 1235 | 1236 | struct mg_tls { 1237 | SSL_CTX *ctx; 1238 | SSL *ssl; 1239 | }; 1240 | #endif 1241 | 1242 | 1243 | #define WEBSOCKET_OP_CONTINUE 0 1244 | #define WEBSOCKET_OP_TEXT 1 1245 | #define WEBSOCKET_OP_BINARY 2 1246 | #define WEBSOCKET_OP_CLOSE 8 1247 | #define WEBSOCKET_OP_PING 9 1248 | #define WEBSOCKET_OP_PONG 10 1249 | 1250 | 1251 | 1252 | struct mg_ws_message { 1253 | struct mg_str data; // Websocket message data 1254 | uint8_t flags; // Websocket message flags 1255 | }; 1256 | 1257 | struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url, 1258 | mg_event_handler_t fn, void *fn_data, 1259 | const char *fmt, ...); 1260 | void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *, 1261 | const char *fmt, ...); 1262 | size_t mg_ws_send(struct mg_connection *, const char *buf, size_t len, int op); 1263 | size_t mg_ws_wrap(struct mg_connection *, size_t len, int op); 1264 | size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...); 1265 | size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt, va_list); 1266 | 1267 | 1268 | 1269 | 1270 | struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, 1271 | mg_event_handler_t fn, void *fn_data); 1272 | void mg_sntp_request(struct mg_connection *c); 1273 | int64_t mg_sntp_parse(const unsigned char *buf, size_t len); 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | #define MQTT_CMD_CONNECT 1 1280 | #define MQTT_CMD_CONNACK 2 1281 | #define MQTT_CMD_PUBLISH 3 1282 | #define MQTT_CMD_PUBACK 4 1283 | #define MQTT_CMD_PUBREC 5 1284 | #define MQTT_CMD_PUBREL 6 1285 | #define MQTT_CMD_PUBCOMP 7 1286 | #define MQTT_CMD_SUBSCRIBE 8 1287 | #define MQTT_CMD_SUBACK 9 1288 | #define MQTT_CMD_UNSUBSCRIBE 10 1289 | #define MQTT_CMD_UNSUBACK 11 1290 | #define MQTT_CMD_PINGREQ 12 1291 | #define MQTT_CMD_PINGRESP 13 1292 | #define MQTT_CMD_DISCONNECT 14 1293 | #define MQTT_CMD_AUTH 15 1294 | 1295 | enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; 1296 | 1297 | struct mg_mqtt_opts { 1298 | struct mg_str user; // Username, can be empty 1299 | struct mg_str pass; // Password, can be empty 1300 | struct mg_str client_id; // Client ID 1301 | struct mg_str will_topic; // Will topic 1302 | struct mg_str will_message; // Will message 1303 | uint8_t will_qos; // Will message quality of service 1304 | uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4. 1305 | uint16_t keepalive; // Keep-alive timer in seconds 1306 | bool will_retain; // Retain last will 1307 | bool clean; // Use clean session, 0 or 1 1308 | }; 1309 | 1310 | struct mg_mqtt_message { 1311 | struct mg_str topic; // Parsed topic 1312 | struct mg_str data; // Parsed message 1313 | struct mg_str dgram; // Whole MQTT datagram, including headers 1314 | uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH 1315 | uint8_t cmd; // MQTT command, one of MQTT_CMD_* 1316 | uint8_t qos; // Quality of service 1317 | uint8_t ack; // Connack return code. 0 - success 1318 | }; 1319 | 1320 | struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, 1321 | const struct mg_mqtt_opts *opts, 1322 | mg_event_handler_t fn, void *fn_data); 1323 | struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, 1324 | mg_event_handler_t fn, void *fn_data); 1325 | void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts); 1326 | void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic, 1327 | struct mg_str data, int qos, bool retain); 1328 | void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos); 1329 | int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *); 1330 | void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, 1331 | uint32_t len); 1332 | size_t mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic, 1333 | uint8_t *qos, size_t pos); 1334 | size_t mg_mqtt_next_unsub(struct mg_mqtt_message *msg, struct mg_str *topic, 1335 | size_t pos); 1336 | void mg_mqtt_ping(struct mg_connection *); 1337 | void mg_mqtt_pong(struct mg_connection *); 1338 | void mg_mqtt_disconnect(struct mg_connection *); 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | // Mongoose sends DNS queries that contain only one question: 1345 | // either A (IPv4) or AAAA (IPv6) address lookup. 1346 | // Therefore, we expect zero or one answer. 1347 | // If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. 1348 | struct mg_dns_message { 1349 | uint16_t txnid; // Transaction ID 1350 | bool resolved; // Resolve successful, addr is set 1351 | struct mg_addr addr; // Resolved address 1352 | char name[256]; // Host name 1353 | }; 1354 | 1355 | struct mg_dns_header { 1356 | uint16_t txnid; // Transaction ID 1357 | uint16_t flags; 1358 | uint16_t num_questions; 1359 | uint16_t num_answers; 1360 | uint16_t num_authority_prs; 1361 | uint16_t num_other_prs; 1362 | }; 1363 | 1364 | // DNS resource record 1365 | struct mg_dns_rr { 1366 | uint16_t nlen; // Name or pointer length 1367 | uint16_t atype; // Address type 1368 | uint16_t aclass; // Address class 1369 | uint16_t alen; // Address length 1370 | }; 1371 | 1372 | void mg_resolve(struct mg_connection *, const char *url); 1373 | void mg_resolve_cancel(struct mg_connection *); 1374 | bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); 1375 | size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, 1376 | bool is_question, struct mg_dns_rr *); 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | #ifndef MG_JSON_MAX_DEPTH 1383 | #define MG_JSON_MAX_DEPTH 30 1384 | #endif 1385 | 1386 | // Error return values - negative. Successful returns are >= 0 1387 | enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; 1388 | int mg_json_get(struct mg_str json, const char *path, int *toklen); 1389 | 1390 | bool mg_json_get_num(struct mg_str json, const char *path, double *v); 1391 | bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); 1392 | long mg_json_get_long(struct mg_str json, const char *path, long dflt); 1393 | char *mg_json_get_str(struct mg_str json, const char *path); 1394 | char *mg_json_get_hex(struct mg_str json, const char *path, int *len); 1395 | char *mg_json_get_b64(struct mg_str json, const char *path, int *len); 1396 | 1397 | 1398 | 1399 | 1400 | // JSON-RPC request descriptor 1401 | struct mg_rpc_req { 1402 | struct mg_rpc **head; // RPC handlers list head 1403 | struct mg_rpc *rpc; // RPC handler being called 1404 | mg_pfn_t pfn; // Response printing function 1405 | void *pfn_data; // Response printing function data 1406 | void *req_data; // Arbitrary request data 1407 | struct mg_str frame; // Request, e.g. {"id":1,"method":"add","params":[1,2]} 1408 | }; 1409 | 1410 | // JSON-RPC method handler 1411 | struct mg_rpc { 1412 | struct mg_rpc *next; // Next in list 1413 | struct mg_str method; // Method pattern 1414 | void (*fn)(struct mg_rpc_req *); // Handler function 1415 | void *fn_data; // Handler function argument 1416 | }; 1417 | 1418 | void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern, 1419 | void (*handler)(struct mg_rpc_req *), void *handler_data); 1420 | void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *)); 1421 | void mg_rpc_process(struct mg_rpc_req *); 1422 | 1423 | // Helper functions to print result or error frame 1424 | void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...); 1425 | void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap); 1426 | void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...); 1427 | void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *); 1428 | void mg_rpc_list(struct mg_rpc_req *r); 1429 | 1430 | 1431 | 1432 | 1433 | 1434 | struct mip_driver { 1435 | void *data; // Driver-specific data 1436 | void (*init)(void *data); // Initialise driver 1437 | size_t (*tx)(const void *, size_t, void *data); // Transmit frame 1438 | size_t (*rx)(void *buf, size_t len, void *data); // Receive frame (polling) 1439 | bool (*status)(void *data); // Up/down status 1440 | // Set receive callback for interrupt-driven drivers 1441 | void (*rxcb)(void (*fn)(void *buf, size_t len, void *rxdata), void *rxdata); 1442 | }; 1443 | 1444 | struct mip_ipcfg { 1445 | uint8_t mac[6]; // MAC address. Must not be 0 1446 | uint32_t ip, mask, gw; // IP, netmask, GW. If IP is 0, DHCP is used 1447 | }; 1448 | 1449 | void mip_init(struct mg_mgr *, struct mip_ipcfg *, struct mip_driver *); 1450 | 1451 | extern struct mip_driver mip_driver_stm32; 1452 | 1453 | #ifdef __cplusplus 1454 | } 1455 | #endif 1456 | #endif // MONGOOSE_H 1457 | --------------------------------------------------------------------------------