From 1236423afd1eefb5ac55f676f4368b62b8b82f88 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Thu, 5 Sep 2024 23:56:36 +0200 Subject: [PATCH 01/29] CommandLineArgs parser : config file --- src/CommandLineArgs.h | 50 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 5 ++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/CommandLineArgs.h diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h new file mode 100644 index 00000000..496f008e --- /dev/null +++ b/src/CommandLineArgs.h @@ -0,0 +1,50 @@ +#ifndef CMD_ARGS_H_ +#define CMD_ARGS_H_ +#include +#include +#include +#include +#define assert_tuple_arg (assert(i < argc && "Expecting value")) + +namespace CommandLineArgs +{ + struct { + bool skipDialog; + const char* configFile; + const char* shaderFile; + } Args; + + void parse_args(int argc,const char *argv[]) { + Args.skipDialog = false; + Args.configFile = "config.json"; + Args.shaderFile = "shader.glsl"; + for(size_t i=0;i 8 ) return 0xFFFFFFFF; @@ -71,9 +72,11 @@ int main( int argc, const char * argv[] ) Misc::PlatformStartup(); const char * configFile = "config.json"; + + CommandLineArgs::parse_args(argc,argv); if ( argc > 1 ) { - configFile = argv[ 1 ]; + configFile = CommandLineArgs::Args.configFile; printf( "Loading config file '%s'...\n", configFile ); } else From 9b79b1ec5bcd6a15e08ac2a564494a94b58e975e Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Fri, 6 Sep 2024 21:44:36 +0200 Subject: [PATCH 02/29] All LC option --- src/CommandLineArgs.h | 45 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index 496f008e..3e332d3e 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -8,42 +8,81 @@ namespace CommandLineArgs { + enum NetworkMode { + SENDER, + GRABBER, + OFFLINE + }; struct { bool skipDialog; const char* configFile; const char* shaderFile; + const char* serverURL; + NetworkMode networkMode; } Args; void parse_args(int argc,const char *argv[]) { Args.skipDialog = false; Args.configFile = "config.json"; Args.shaderFile = "shader.glsl"; + Args.networkMode = OFFLINE; + Args.serverURL = "ws://drone.alkama.com:9000/roomname/username"; for(size_t i=0;i Date: Fri, 6 Sep 2024 21:44:53 +0200 Subject: [PATCH 03/29] Make Resizable --- src/platform_glfw/Renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_glfw/Renderer.cpp b/src/platform_glfw/Renderer.cpp index 15f8be2b..ee7840f5 100644 --- a/src/platform_glfw/Renderer.cpp +++ b/src/platform_glfw/Renderer.cpp @@ -254,7 +254,7 @@ bool Open( Renderer::Settings * settings ) #endif // TODO: change in case of resize support - glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE ); + glfwWindowHint( GLFW_RESIZABLE, GLFW_TRUE ); // Prevent fullscreen window minimize on focus loss glfwWindowHint( GLFW_AUTO_ICONIFY, GL_FALSE ); From 19c7e4ee9c7f62af29f9a649251ecaf9911ac90d Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 9 Sep 2024 00:28:36 +0200 Subject: [PATCH 04/29] Network Basic --- external/mongoose/LICENSE | 16 + external/mongoose/mongoose.c | 19052 +++++++++++++++++++++++++++++++++ external/mongoose/mongoose.h | 3185 ++++++ src/CommandLineArgs.h | 17 +- src/Network.h | 155 + src/main.cpp | 21 +- 6 files changed, 22435 insertions(+), 11 deletions(-) create mode 100644 external/mongoose/LICENSE create mode 100644 external/mongoose/mongoose.c create mode 100644 external/mongoose/mongoose.h create mode 100644 src/Network.h diff --git a/external/mongoose/LICENSE b/external/mongoose/LICENSE new file mode 100644 index 00000000..8fdd2a93 --- /dev/null +++ b/external/mongoose/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2004-2013 Sergey Lyubka +Copyright (c) 2013-2021 Cesanta Software Limited +All rights reserved + +This software is dual-licensed: you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. For the terms of this +license, see . + +You are free to use this software under the terms of the GNU General +Public License, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +Alternatively, you can license this software under a commercial +license, as set out in . diff --git a/external/mongoose/mongoose.c b/external/mongoose/mongoose.c new file mode 100644 index 00000000..b6d44732 --- /dev/null +++ b/external/mongoose/mongoose.c @@ -0,0 +1,19052 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2024 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.gnu.org/licenses/ +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in https://www.mongoose.ws/licensing/ +// +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#include "mongoose.h" + +#ifdef MG_ENABLE_LINES +#line 1 "src/base64.c" +#endif + + +static int mg_base64_encode_single(int c) { + if (c < 26) { + return c + 'A'; + } else if (c < 52) { + return c - 26 + 'a'; + } else if (c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int mg_base64_decode_single(int c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if (c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else if (c == '=') { + return 64; + } else { + return -1; + } +} + +size_t mg_base64_update(unsigned char ch, char *to, size_t n) { + unsigned long rem = (n & 3) % 3; + if (rem == 0) { + to[n] = (char) mg_base64_encode_single(ch >> 2); + to[++n] = (char) ((ch & 3) << 4); + } else if (rem == 1) { + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4)); + to[++n] = (char) ((ch & 15) << 2); + } else { + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6)); + to[++n] = (char) mg_base64_encode_single(ch & 63); + n++; + } + return n; +} + +size_t mg_base64_final(char *to, size_t n) { + size_t saved = n; + // printf("---[%.*s]\n", n, to); + if (n & 3) n = mg_base64_update(0, to, n); + if ((saved & 3) == 2) n--; + // printf(" %d[%.*s]\n", n, n, to); + while (n & 3) to[n++] = '='; + to[n] = '\0'; + return n; +} + +size_t mg_base64_encode(const unsigned char *p, size_t n, char *to, size_t dl) { + size_t i, len = 0; + if (dl > 0) to[0] = '\0'; + if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0; + for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len); + len = mg_base64_final(to, len); + return len; +} + +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t dl) { + const char *end = src == NULL ? NULL : src + n; // Cannot add to NULL + size_t len = 0; + if (dl < n / 4 * 3 + 1) goto fail; + while (src != NULL && src + 3 < end) { + int a = mg_base64_decode_single(src[0]), + b = mg_base64_decode_single(src[1]), + c = mg_base64_decode_single(src[2]), + d = mg_base64_decode_single(src[3]); + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) { + goto fail; + } + dst[len++] = (char) ((a << 2) | (b >> 4)); + if (src[2] != '=') { + dst[len++] = (char) ((b << 4) | (c >> 2)); + if (src[3] != '=') dst[len++] = (char) ((c << 6) | d); + } + src += 4; + } + dst[len] = '\0'; + return len; +fail: + if (dl > 0) dst[0] = '\0'; + return 0; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_ch32v307.c" +#endif + + + +#if MG_DEVICE == MG_DEVICE_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +#define FLASH_BASE 0x40022000 +#define FLASH_ACTLR (FLASH_BASE + 0) +#define FLASH_KEYR (FLASH_BASE + 4) +#define FLASH_OBKEYR (FLASH_BASE + 8) +#define FLASH_STATR (FLASH_BASE + 12) +#define FLASH_CTLR (FLASH_BASE + 16) +#define FLASH_ADDR (FLASH_BASE + 20) +#define FLASH_OBR (FLASH_BASE + 28) +#define FLASH_WPR (FLASH_BASE + 32) + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 480 * 1024; // First 320k is 0-wait +} +size_t mg_flash_sector_size(void) { + return 4096; +} +size_t mg_flash_write_align(void) { + return 4; +} +int mg_flash_bank(void) { + return 0; +} +void mg_device_reset(void) { + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() +} +static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} +static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; +} + +bool mg_flash_erase(void *addr) { + //MG_INFO(("%p", addr)); + flash_unlock(); + flash_wait(); + MG_REG(FLASH_ADDR) = (uint32_t) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; + flash_wait(); + return true; +} + +static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (mg_flash_sector_size() - 1)) == 0; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + //MG_INFO(("%p %p %lu", addr, buf, len)); + //mg_hexdump(buf, len); + flash_unlock(); + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; + uint16_t *dst = (uint16_t *) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG + //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); + while (src < end) { + if (is_page_boundary(dst)) mg_flash_erase(dst); + *dst++ = *src++; + flash_wait(); + } + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_dummy.c" +#endif + + +#if MG_DEVICE == MG_DEVICE_NONE +void *mg_flash_start(void) { + return NULL; +} +size_t mg_flash_size(void) { + return 0; +} +size_t mg_flash_sector_size(void) { + return 0; +} +size_t mg_flash_write_align(void) { + return 0; +} +int mg_flash_bank(void) { + return 0; +} +bool mg_flash_erase(void *location) { + (void) location; + return false; +} +bool mg_flash_swap_bank(void) { + return true; +} +bool mg_flash_write(void *addr, const void *buf, size_t len) { + (void) addr, (void) buf, (void) len; + return false; +} +void mg_device_reset(void) { +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_flash.c" +#endif + + +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \ + MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 +// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) +// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an +// object, we pad it at the end for alignment. +// +// Objects in the flash sector are stored sequentially: +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... +// +// In order to get to the next object, read its size, then align up. + +// Traverse the list of saved objects +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { + size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { + if (size) *size = (size_t) p32[0]; + if (key) *key = p32[1]; + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail + } + return aligned_size; +} + +// Return the last sector of Bank 2 +static char *flash_last_sector(void) { + size_t ss = mg_flash_sector_size(), size = mg_flash_size(); + char *base = (char *) mg_flash_start(), *last = base + size - ss; + if (mg_flash_bank() == 2) last -= size / 2; + return last; +} + +// Find a saved object with a given key +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; + bool ok = false; + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + uint32_t k, scanned = 0; + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); + // mg_hexdump(s + ofs, n); + if (k == key && sz == len) { + res = s + ofs + sizeof(uint32_t) * 2; + memcpy(buf, res, len); // Copy object + ok = true; // Keep scanning for the newer versions of it + } + ofs += n, scanned++; + } + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); + } + return ok; +} + +// For all saved objects in the sector, delete old versions of objects +static void mg_flash_sector_cleanup(char *sector) { + // Buffer all saved objects into an IO buffer (backed by RAM) + // erase sector, and re-save them. + struct mg_iobuf io = {0, 0, 0, 2048}; + size_t ss = mg_flash_sector_size(); + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; + uint32_t key; + // Traverse all objects + MG_DEBUG(("Cleaning up sector %p", sector)); + while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { + // Delete an old copy of this object in the cache + for (size_t o = 0; o < io.len; o += size2 + hs) { + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); + size2 = *(uint32_t *) (io.buf + o); + if (k == key) { + mg_iobuf_del(&io, o, size2 + hs); + break; + } + } + // And add the new copy + mg_iobuf_add(&io, io.len, sector + ofs, size + hs); + ofs += n; + } + // All objects are cached in RAM now + if (mg_flash_erase(sector)) { // Erase sector. If successful, + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects + size = *(uint32_t *) (io.buf + ofs); + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash + } + } + mg_iobuf_free(&io); +} + +// Save an object with a given key - append to the end of an object list +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { + char *base = (char *) mg_flash_start(), *s = (char *) sector; + size_t ss = mg_flash_sector_size(), ofs = 0, n; + bool ok = false; + if (s == NULL) s = flash_last_sector(); + if (s < base || s >= base + mg_flash_size()) { + MG_ERROR(("%p is outsize of flash", sector)); + } else if (((s - base) % ss) != 0) { + MG_ERROR(("%p is not a sector boundary", sector)); + } else { + char ab[mg_flash_write_align()]; // Aligned write block + uint32_t hdr[2] = {(uint32_t) len, key}; + size_t needed = sizeof(hdr) + len; + size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab)); + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + + // If there is not enough space left, cleanup sector and re-eval ofs + if (ofs + needed_aligned >= ss) { + mg_flash_sector_cleanup(s); + ofs = 0; + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; + } + + if (ofs + needed_aligned <= ss) { + // Enough space to save this object + if (sizeof(ab) < sizeof(hdr)) { + // Flash write granularity is 32 bit or less, write with no buffering + ok = mg_flash_write(s + ofs, hdr, sizeof(hdr)); + if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len); + } else { + // Flash granularity is sizeof(hdr) or more. We need to save in + // 3 chunks: initial block, bulk, rest. This is because we have + // two memory chunks to write: hdr and buf, on aligned boundaries. + n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write + if (n > len) n = len; // is + memset(ab, 0xff, sizeof(ab)); // initialized to all-one + memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size) + memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf + MG_INFO(("saving initial block of %lu", sizeof(ab))); + ok = mg_flash_write(s + ofs, ab, sizeof(ab)); + if (ok && len > n) { + size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab)); + if (n2 > 0) { + MG_INFO(("saving bulk, %lu", n2)); + ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2); + } + if (ok && len > n) { + size_t n3 = len - n - n2; + if (n3 > sizeof(ab)) n3 = sizeof(ab); + memset(ab, 0xff, sizeof(ab)); + memcpy(ab, (char *) buf + n + n2, n3); + MG_INFO(("saving rest, %lu", n3)); + ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab)); + } + } + } + MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned, + s + ofs, key, ok)); + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); + } else { + MG_ERROR(("Sector is full")); + } + } + return ok; +} +#else +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { + (void) sector, (void) key, (void) buf, (void) len; + return false; +} +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { + (void) sector, (void) key, (void) buf, (void) len; + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_imxrt.c" +#endif + + + +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 + +struct mg_flexspi_lut_seq { + uint8_t seqNum; + uint8_t seqId; + uint16_t reserved; +}; + +struct mg_flexspi_mem_config { + uint32_t tag; + uint32_t version; + uint32_t reserved0; + uint8_t readSampleClkSrc; + uint8_t csHoldTime; + uint8_t csSetupTime; + uint8_t columnAddressWidth; + uint8_t deviceModeCfgEnable; + uint8_t deviceModeType; + uint16_t waitTimeCfgCommands; + struct mg_flexspi_lut_seq deviceModeSeq; + uint32_t deviceModeArg; + uint8_t configCmdEnable; + uint8_t configModeType[3]; + struct mg_flexspi_lut_seq configCmdSeqs[3]; + uint32_t reserved1; + uint32_t configCmdArgs[3]; + uint32_t reserved2; + uint32_t controllerMiscOption; + uint8_t deviceType; + uint8_t sflashPadType; + uint8_t serialClkFreq; + uint8_t lutCustomSeqEnable; + uint32_t reserved3[2]; + uint32_t sflashA1Size; + uint32_t sflashA2Size; + uint32_t sflashB1Size; + uint32_t sflashB2Size; + uint32_t csPadSettingOverride; + uint32_t sclkPadSettingOverride; + uint32_t dataPadSettingOverride; + uint32_t dqsPadSettingOverride; + uint32_t timeoutInMs; + uint32_t commandInterval; + uint16_t dataValidTime[2]; + uint16_t busyOffset; + uint16_t busyBitPolarity; + uint32_t lookupTable[64]; + struct mg_flexspi_lut_seq lutCustomSeq[12]; + uint32_t reserved4[4]; +}; + +struct mg_flexspi_nor_config { + struct mg_flexspi_mem_config memConfig; + uint32_t pageSize; + uint32_t sectorSize; + uint8_t ipcmdSerialClkFreq; + uint8_t isUniformBlockSize; + uint8_t reserved0[2]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t reserve2[11]; +}; + +/* FLEXSPI memory config block related defintions */ +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 + +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \ + MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) + +#define MG_CMD_SDR 0x01 +#define MG_CMD_DDR 0x21 +#define MG_DUMMY_SDR 0x0C +#define MG_DUMMY_DDR 0x2C +#define MG_RADDR_SDR 0x02 +#define MG_RADDR_DDR 0x22 +#define MG_READ_SDR 0x09 +#define MG_READ_DDR 0x29 +#define MG_WRITE_SDR 0x08 +#define MG_WRITE_DDR 0x28 +#define MG_STOP 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_2PAD 1 +#define MG_FLEXSPI_4PAD 2 +#define MG_FLEXSPI_8PAD 3 + +#define MG_FLEXSPI_QSPI_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD, \ + 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD, \ + 0x04), \ + [4 * 1 + 0] = \ + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ + [4 * 3 + 0] = \ + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR, \ + MG_FLEXSPI_1PAD, 0x18), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR, \ + MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR, \ + MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 1] = \ + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 11 + 0] = \ + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + } + +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) + +#define FLEXSPI_NOR_INSTANCE 0 + +#if MG_DEVICE == MG_DEVICE_RT1020 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, + const uint32_t *src); + uint32_t reserved; + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, + uint32_t lengthInBytes); + uint32_t reserved2; + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, + uint32_t seqNumber); + int (*xfer)(uint32_t instance, char *xfer); + void (*clear_cache)(uint32_t instance); +}; +#elif MG_DEVICE == MG_DEVICE_RT1060 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, + const uint32_t *src); + int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, + uint32_t lengthInBytes); + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr, + uint32_t lengthInBytes); + void (*clear_cache)(uint32_t instance); + int (*xfer)(uint32_t instance, char *xfer); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, + uint32_t seqNumber); + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option); +}; +#endif + +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \ + (*(uint32_t*)0x0020001c + 16))) + +static bool s_flash_irq_disabled; + +MG_IRAM void *mg_flash_start(void) { + return (void *) 0x60000000; +} +MG_IRAM size_t mg_flash_size(void) { + return 8 * 1024 * 1024; +} +MG_IRAM size_t mg_flash_sector_size(void) { + return 4 * 1024; // 4k +} +MG_IRAM size_t mg_flash_write_align(void) { + return 256; +} +MG_IRAM int mg_flash_bank(void) { + return 0; +} + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +// Note: the get_config function below works both for RT1020 and 1060 +#if MG_DEVICE == MG_DEVICE_RT1020 +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { + struct mg_flexspi_nor_config default_config = { + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .controllerMiscOption = MG_BIT(4), + .deviceType = 1, // serial NOR + .sflashPadType = 4, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 8 * 1024 * 1024, + .lookupTable = MG_FLEXSPI_QSPI_LUT}, + .pageSize = 256, + .sectorSize = 4 * 1024, + .ipcmdSerialClkFreq = 1, + .blockSize = 64 * 1024, + .isUniformBlockSize = false}; + + *config = default_config; + return 0; +} +#else +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { + uint32_t options[] = {0xc0000000, 0x00}; + + MG_ARM_DISABLE_IRQ(); + uint32_t status = + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options); + if (!s_flash_irq_disabled) { + MG_ARM_ENABLE_IRQ(); + } + if (status) { + MG_ERROR(("Failed to extract flash configuration: status %u", status)); + } + return status; +} +#endif + +MG_IRAM bool mg_flash_erase(void *addr) { + struct mg_flexspi_nor_config config; + if (flexspi_nor_get_config(&config) != 0) { + return false; + } + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *)((char *) addr - (char *) mg_flash_start()); + + // Note: Interrupts must be disabled before any call to the ROM API on RT1020 + // and 1060 + MG_ARM_DISABLE_IRQ(); + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst, + mg_flash_sector_size()) == 0); + if (!s_flash_irq_disabled) { + MG_ARM_ENABLE_IRQ(); // Reenable them after the call + } + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM bool mg_flash_swap_bank(void) { + return true; +} + +static inline void spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +static inline void flash_wait(void) { + while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) + spin(1); +} + +MG_IRAM static void *flash_code_location(void) { + return (void *) ((char *) mg_flash_start() + 0x2000); +} + +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config; + if (flexspi_nor_get_config(&config) != 0) { + return false; + } + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + + if ((char *) addr < (char *) mg_flash_start()) { + MG_ERROR(("Invalid flash write address: %p", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + + // Note: If we overwrite the flash irq section of the image, we must also + // make sure interrupts are disabled and are not reenabled until we write + // this sector with another irq table. + if ((char *) addr == (char *) flash_code_location()) { + s_flash_irq_disabled = true; + MG_ARM_DISABLE_IRQ(); + } + + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) { + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start(); + if ((char *) buf >= (char *) mg_flash_start()) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t); + uint32_t tmp[tmp_buf_size]; + + for (size_t i = 0; i < tmp_buf_size; i++) { + flash_wait(); + tmp[i] = src[i]; + } + MG_ARM_DISABLE_IRQ(); + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, tmp); + } else { + MG_ARM_DISABLE_IRQ(); + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, src); + } + if (!s_flash_irq_disabled) { + MG_ARM_ENABLE_IRQ(); + } + src = (uint32_t *) ((char *) src + mg_flash_write_align()); + dst = (uint32_t *) ((char *) dst + mg_flash_write_align()); + if (status != 0) { + ok = false; + } + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM void mg_device_reset(void) { + MG_DEBUG(("Resetting device...")); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_stm32h5.c" +#endif + + + +#if MG_DEVICE == MG_DEVICE_STM32H5 + +#define FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (FLASH_BASE + 0xc) +#define FLASH_OPTCR (FLASH_BASE + 0x1c) +#define FLASH_NSSR (FLASH_BASE + 0x20) +#define FLASH_NSCR (FLASH_BASE + 0x28) +#define FLASH_NSCCR (FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) + +void *mg_flash_start(void) { + return (void *) 0x08000000; +} +size_t mg_flash_size(void) { + return 2 * 1024 * 1024; // 2Mb +} +size_t mg_flash_sector_size(void) { + return 8 * 1024; // 8k +} +size_t mg_flash_write_align(void) { + return 16; // 128 bit +} +int mg_flash_bank(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0Xcdef89ab; + MG_REG(FLASH_OPTKEYR) = 0x08192a3b; + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; + unlocked = true; + } +} + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +} + +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 0; + } +} + +static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +bool mg_flash_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < 128 && flash_bank_is_swapped()) || + (sector > 127 && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector > 127) sector -= 128; + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing + flash_wait(); + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); + // mg_hexdump(location, 32); + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + } + return ok; +} + +bool mg_flash_swap_bank(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(); + MG_ARM_DISABLE_IRQ(); + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; + } + MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), + MG_REG(FLASH_NSSR))); + MG_REG(FLASH_NSCR) = 0; // Clear flags + return ok; +} + +void mg_device_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/device_stm32h7.c" +#endif + + + +#if MG_DEVICE == MG_DEVICE_STM32H7 + +#define FLASH_BASE1 0x52002000 // Base address for bank1 +#define FLASH_BASE2 0x52002100 // Base address for bank2 +#define FLASH_KEYR 0x04 // See RM0433 4.9.2 +#define FLASH_OPTKEYR 0x08 +#define FLASH_OPTCR 0x18 +#define FLASH_SR 0x10 +#define FLASH_CR 0x0c +#define FLASH_CCR 0x14 +#define FLASH_OPTSR_CUR 0x1c +#define FLASH_OPTSR_PRG 0x20 +#define FLASH_SIZE_REG 0x1ff1e880 + +MG_IRAM void *mg_flash_start(void) { + return (void *) 0x08000000; +} +MG_IRAM size_t mg_flash_size(void) { + return MG_REG(FLASH_SIZE_REG) * 1024; +} +MG_IRAM size_t mg_flash_sector_size(void) { + return 128 * 1024; // 128k +} +MG_IRAM size_t mg_flash_write_align(void) { + return 32; // 256 bit +} +MG_IRAM int mg_flash_bank(void) { + if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; +} + +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; + if (mg_flash_bank() > 0) { + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; + } + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once + unlocked = true; + } +} + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; +} + +MG_IRAM static bool flash_is_err(uint32_t bank) { + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 +} + +MG_IRAM static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +} + +MG_IRAM static void flash_clear_err(uint32_t bank) { + flash_wait(bank); // Wait until ready + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors +} + +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +} + +// Figure out flash bank based on the address +MG_IRAM static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) mg_flash_start(); + if (mg_flash_bank() == 0) return FLASH_BASE1; + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +MG_IRAM bool mg_flash_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + uintptr_t diff = (char *) addr - (char *) mg_flash_start(); + uint32_t sector = diff / mg_flash_sector_size(); + uint32_t bank = flash_bank(addr); + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value + + flash_unlock(); + if (sector > 7) sector -= 8; + + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing + ok = !flash_is_err(bank); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR + } + return ok; +} + +MG_IRAM bool mg_flash_swap_bank(void) { + if (mg_flash_bank() == 0) return true; + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { + if ((len % mg_flash_write_align()) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); + return false; + } + uint32_t bank = flash_bank(addr); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + flash_unlock(); + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag + return ok; +} + +MG_IRAM void mg_device_reset(void) { + // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/dns.c" +#endif + + + + + + + + +struct dns_data { + struct dns_data *next; + struct mg_connection *c; + uint64_t expire; + uint16_t txnid; +}; + +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +static void mg_dns_free(struct dns_data **head, struct dns_data *d) { + LIST_DELETE(struct dns_data, head, d); + free(d); +} + +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + if (d->c == c) mg_dns_free(head, d); + } +} + +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, size_t j, + int depth) { + size_t i = 0; + if (tolen > 0 && depth == 0) to[0] = '\0'; + if (depth > 5) return 0; + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { + i++; + break; + } + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + // MG_INFO(("PTR %lx", (unsigned long) ptr)); + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); + j += n; + i += n + 1; + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk + // MG_INFO(("--> [%s]", to)); + } + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + return i; +} + +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, + char *dst, size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); + if (len < sizeof(struct mg_dns_header)) return 0; // Too small + if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) + return 0; + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (is_question) return (size_t) (rr->nlen + 4); + + s += 6; + if (s > e) return 0; + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (s + rr->alen > e) return 0; + return (size_t) (rr->nlen + rr->alen + 10); +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, num_answers, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); + + if (len < sizeof(*h)) return 0; // Too small, headers dont fit + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity + num_answers = mg_ntohs(h->num_answers); + if (num_answers > 10) { + MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers)); + num_answers = 10; // Sanity cap + } + dm->txnid = mg_ntohs(h->txnid); + + for (i = 0; i < mg_ntohs(h->num_questions); i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); + ofs += n; + } + for (i = 0; i < num_answers; i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, + // dm->name)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); + dm->resolved = true; + break; // Return success + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip, &buf[ofs - 16], 16); + dm->resolved = true; + break; // Return success + } + } + return true; +} + +static void dns_cb(struct mg_connection *c, int ev, void *ev_data) { + struct dns_data *d, *tmp; + struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests; + if (ev == MG_EV_POLL) { + uint64_t now = *(uint64_t *) ev_data; + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + // MG_DEBUG ("%lu %lu dns poll", d->expire, now)); + if (now > d->expire) mg_error(d->c, "DNS timeout"); + } + } else if (ev == MG_EV_READ) { + struct mg_dns_message dm; + int resolved = 0; + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { + MG_ERROR(("Unexpected DNS response:")); + mg_hexdump(c->recv.buf, c->recv.len); + } else { + // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); + if (dm.txnid != d->txnid) continue; + if (d->c->is_resolving) { + if (dm.resolved) { + dm.addr.port = d->c->rem.port; // Save port + d->c->rem = dm.addr; // Copy resolved address + MG_DEBUG( + ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem)); + mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && + c->mgr->use_dns6 == false) { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif + } else { + mg_error(d->c, "%s DNS lookup failed", dm.name); + } + } else { + MG_ERROR(("%lu already resolved", d->c->id)); + } + mg_dns_free(head, d); + resolved = 1; + } + } + if (!resolved) MG_ERROR(("stray DNS reply")); + c->recv.len = 0; + } else if (ev == MG_EV_CLOSE) { + for (d = *head; d != NULL; d = tmp) { + tmp = d->next; + mg_error(d->c, "DNS error"); + mg_dns_free(head, d); + } + } +} + +static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, + uint16_t txnid, bool ipv6) { + struct { + struct mg_dns_header header; + uint8_t data[256]; + } pkt; + size_t i, n; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.txnid = mg_htons(txnid); + pkt.header.flags = mg_htons(0x100); + pkt.header.num_questions = mg_htons(1); + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { + if (name->buf[i] == '.' || i >= name->len) { + pkt.data[n] = (uint8_t) (i - n); + memcpy(&pkt.data[n + 1], name->buf + n, i - n); + n = i + 1; + } + if (i >= name->len) break; + } + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query + n += 5; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; + return mg_send(c, &pkt, sizeof(pkt.header) + n); +} + +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; + d->next = (struct dns_data *) c->mgr->active_dns_requests; + c->mgr->active_dns_requests = d; + d->expire = mg_millis() + (uint64_t) ms; + d->c = c; + c->is_resolving = 1; + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, + name->buf, dnsc->url, d->txnid)); + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { + mg_error(dnsc->c, "DNS send"); + } + } +} + +void mg_resolve(struct mg_connection *c, const char *url) { + struct mg_str host = mg_url_host(url); + c->rem.port = mg_htons(mg_url_port(url)); + if (mg_aton(host, &c->rem)) { + // host is an IP address, do not fire name resolution + mg_connect_resolved(c); + } else { + // host is not an IP, send DNS resolution request + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/event.c" +#endif + + + + + + +void mg_call(struct mg_connection *c, int ev, void *ev_data) { +#if MG_ENABLE_PROFILE + const char *names[] = { + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE", + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ", + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK", + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD", + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"}; + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) { + MG_PROF_ADD(c, names[ev]); + } +#endif + // Fire protocol handler first, user handler second. See #2559 + if (c->pfn != NULL) c->pfn(c, ev, ev_data); + if (c->fn != NULL) c->fn(c, ev, ev_data); +} + +void mg_error(struct mg_connection *c, const char *fmt, ...) { + char buf[64]; + va_list ap; + va_start(ap, fmt); + mg_vsnprintf(buf, sizeof(buf), fmt, &ap); + va_end(ap); + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf)); + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fmt.c" +#endif + + + + +static bool is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static int addexp(char *buf, int e, int sign) { + int n = 0; + buf[n++] = 'e'; + buf[n++] = (char) sign; + if (e > 400) return 0; + if (e < 10) buf[n++] = '0'; + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); + buf[n++] = (char) (e + '0'); + return n; +} + +static int xisinf(double x) { + union { + double f; + uint64_t u; + } ieee754 = {x}; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && + ((unsigned) ieee754.u == 0); +} + +static int xisnan(double x) { + union { + double f; + uint64_t u; + } ieee754 = {x}; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + + ((unsigned) ieee754.u != 0) > + 0x7ff00000; +} + +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) { + char buf[40]; + int i, s = 0, n = 0, e = 0; + double t, mul, saved; + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); + if (d < 0.0) d = -d, buf[s++] = '-'; + + // Round + saved = d; + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; + d += t; + // Calculate exponent, and 'mul' for scientific representation + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; + // printf(" --> %g %d %g %g\n", saved, e, t, mul); + + if (e >= width && width > 1) { + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); + n += addexp(buf + s + n, e, '+'); + return mg_snprintf(dst, dstlen, "%.*s", n, buf); + } else if (e <= -width && width > 1) { + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); + n += addexp(buf + s + n, -e, '-'); + return mg_snprintf(dst, dstlen, "%.*s", n, buf); + } else { + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { + int ch = (int) (d / t); + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); + if (n == 0) buf[s++] = '0'; + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { + int ch = (int) (d / t); + buf[s + n++] = (char) (ch + '0'); + d -= ch * t; + t /= 10.0; + } + } + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot + n += s; + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; + buf[n] = '\0'; + return mg_snprintf(dst, dstlen, "%s", buf); +} + +static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { + const char *letters = "0123456789abcdef"; + uint64_t v = (uint64_t) val; + size_t s = 0, n, i; + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); + // This loop prints a number in reverse order. I guess this is because we + // write numbers from right to left: least significant digit comes last. + // Maybe because we use Arabic numbers, and Arabs write RTL? + if (is_hex) { + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; + } else { + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; + } + // Reverse a string + for (i = 0; i < n / 2; i++) { + char t = buf[s + i]; + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; + } + if (val == 0) buf[n++] = '0'; // Handle special case + return n + s; +} + +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, + size_t len) { + size_t i = 0; + while (i < len && buf[i] != '\0') out(buf[i++], ptr); + return i; +} + +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vxprintf(out, ptr, fmt, &ap); + va_end(ap); + return len; +} + +size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, + va_list *ap) { + size_t i = 0, n = 0; + while (fmt[i] != '\0') { + if (fmt[i] == '%') { + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; + char pad = ' ', minus = 0, c = fmt[++i]; + if (c == '#') x++, c = fmt[++i]; + if (c == '-') minus++, c = fmt[++i]; + if (c == '0') pad = '0', c = fmt[++i]; + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; + if (c == '.') { + c = fmt[++i]; + if (c == '*') { + pr = (size_t) va_arg(*ap, int); + c = fmt[++i]; + } else { + pr = 0; + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; + } + } + while (c == 'h') c = fmt[++i]; // Treat h and hh as int + if (c == 'l') { + is_long++, c = fmt[++i]; + if (c == 'l') is_long++, c = fmt[++i]; + } + if (c == 'p') x = 1, is_long = 1; + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || + c == 'g' || c == 'f') { + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); + char tmp[40]; + size_t xl = x ? 2 : 0; + if (c == 'g' || c == 'f') { + double v = va_arg(*ap, double); + if (pr == ~0U) pr = 6; + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g'); + } else if (is_long == 2) { + int64_t v = va_arg(*ap, int64_t); + k = mg_lld(tmp, v, s, h); + } else if (is_long == 1) { + long v = va_arg(*ap, long); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); + } else { + int v = va_arg(*ap, int); + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); + } + for (j = 0; j < xl && w > 0; j++) w--; + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, (char *) "0x", xl); + for (j = 0; pad == '0' && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, tmp, k); + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == 'm' || c == 'M') { + mg_pm_t f = va_arg(*ap, mg_pm_t); + if (c == 'm') out('"', param); + n += f(out, param, ap); + if (c == 'm') n += 2, out('"', param); + } else if (c == 'c') { + int ch = va_arg(*ap, int); + out((char) ch, param); + n++; + } else if (c == 's') { + char *p = va_arg(*ap, char *); + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); + for (j = 0; !minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + n += scpy(out, param, p, pr); + for (j = 0; minus && pr < w && j + pr < w; j++) + n += scpy(out, param, &pad, 1); + } else if (c == '%') { + out('%', param); + n++; + } else { + out('%', param); + out(c, param); + n += 2; + } + i++; + } else { + out(fmt[i], param), n++, i++; + } + } + return n; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs.c" +#endif + + + + +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + if (fd != NULL) { + fd->fd = fs->op(path, flags); + fd->fs = fs; + if (fd->fd == NULL) { + free(fd); + fd = NULL; + } + } + return fd; +} + +void mg_fs_close(struct mg_fd *fd) { + if (fd != NULL) { + fd->fs->cl(fd->fd); + free(fd); + } +} + +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { + struct mg_str result = {NULL, 0}; + void *fp; + fs->st(path, &result.len, NULL); + if ((fp = fs->op(path, MG_FS_READ)) != NULL) { + result.buf = (char *) calloc(1, result.len + 1); + if (result.buf != NULL && + fs->rd(fp, (void *) result.buf, result.len) != result.len) { + free((void *) result.buf); + result.buf = NULL; + } + fs->cl(fp); + } + if (result.buf == NULL) result.len = 0; + return result; +} + +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, + size_t len) { + bool result = false; + struct mg_fd *fd; + char tmp[MG_PATH_MAX]; + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { + result = fs->wr(fd->fd, buf, len) == len; + mg_fs_close(fd); + if (result) { + fs->rm(path); + fs->mv(tmp, path); + } else { + fs->rm(tmp); + } + } + return result; +} + +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { + va_list ap; + char *data; + bool result = false; + va_start(ap, fmt); + data = mg_vmprintf(fmt, &ap); + va_end(ap); + result = mg_file_write(fs, path, data, strlen(data)); + free(data); + return result; +} + +// This helper function allows to scan a filesystem in a sequential way, +// without using callback function: +// char buf[100] = ""; +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) { +// ... +static void mg_fs_ls_fn(const char *filename, void *param) { + struct mg_str *s = (struct mg_str *) param; + if (s->buf[0] == '\0') { + mg_snprintf((char *) s->buf, s->len, "%s", filename); + } else if (strcmp(s->buf, filename) == 0) { + ((char *) s->buf)[0] = '\0'; // Fetch next file + } +} + +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) { + struct mg_str s = {buf, len}; + fs->ls(path, mg_fs_ls_fn, &s); + return buf[0] != '\0'; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_fat.c" +#endif + + + +#if MG_ENABLE_FATFS +#include + +static int mg_days_from_epoch(int y, int m, int d) { + y -= m <= 2; + int era = y / 400; + int yoe = y - era * 400; + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; +} + +static time_t mg_timegm(const struct tm *t) { + int year = t->tm_year + 1900; + int month = t->tm_mon; // 0-11 + if (month > 11) { + year += month / 12; + month %= 12; + } else if (month < 0) { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + int x = mg_days_from_epoch(year, month + 1, t->tm_mday); + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; +} + +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_sec = (ftime << 1) & 0x3e; + tm.tm_min = ((ftime >> 5) & 0x3f); + tm.tm_hour = ((ftime >> 11) & 0x1f); + tm.tm_mday = (fdate & 0x1f); + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; + tm.tm_year = ((fdate >> 9) & 0x7f) + 80; + return mg_timegm(&tm); +} + +static int ff_stat(const char *path, size_t *size, time_t *mtime) { + FILINFO fi; + if (path[0] == '\0') { + if (size) *size = 0; + if (mtime) *mtime = 0; + return MG_FS_DIR; + } else if (f_stat(path, &fi) == 0) { + if (size) *size = (size_t) fi.fsize; + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); + } else { + return 0; + } +} + +static void ff_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + DIR d; + FILINFO fi; + if (f_opendir(&d, dir) == FR_OK) { + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; + fn(fi.fname, userdata); + } + f_closedir(&d); + } +} + +static void *ff_open(const char *path, int flags) { + FIL f; + unsigned char mode = FA_READ; + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + if (f_open(&f, path, mode) == 0) { + FIL *fp; + if ((fp = calloc(1, sizeof(*fp))) != NULL) { + memcpy(fp, &f, sizeof(*fp)); + return fp; + } + } + return NULL; +} + +static void ff_close(void *fp) { + if (fp != NULL) { + f_close((FIL *) fp); + free(fp); + } +} + +static size_t ff_read(void *fp, void *buf, size_t len) { + UINT n = 0, misalign = ((size_t) buf) & 3; + if (misalign) { + char aligned[4]; + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); + memcpy(buf, aligned, n); + } else { + f_read((FIL *) fp, buf, len, &n); + } + return n; +} + +static size_t ff_write(void *fp, const void *buf, size_t len) { + UINT n = 0; + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; +} + +static size_t ff_seek(void *fp, size_t offset) { + f_lseek((FIL *) fp, offset); + return offset; +} + +static bool ff_rename(const char *from, const char *to) { + return f_rename(from, to) == FR_OK; +} + +static bool ff_remove(const char *path) { + return f_unlink(path) == FR_OK; +} + +static bool ff_mkdir(const char *path) { + return f_mkdir(path) == FR_OK; +} + +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_packed.c" +#endif + + + + +struct packed_file { + const char *data; + size_t size; + size_t pos; +}; + +#if MG_ENABLE_PACKED_FS +#else +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { + *size = 0, *mtime = 0; + (void) path; + return NULL; +} +const char *mg_unlist(size_t no) { + (void) no; + return NULL; +} +#endif + +struct mg_str mg_unpacked(const char *path) { + size_t len = 0; + const char *buf = mg_unpack(path, &len, NULL); + return mg_str_n(buf, len); +} + +static int is_dir_prefix(const char *prefix, size_t n, const char *path) { + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); + return n < strlen(path) && strncmp(prefix, path, n) == 0 && + (n == 0 || path[n] == '/' || path[n - 1] == '/'); +} + +static int packed_stat(const char *path, size_t *size, time_t *mtime) { + const char *p; + size_t i, n = strlen(path); + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file + // Scan all files. If `path` is a dir prefix for any of them, it's a dir + for (i = 0; (p = mg_unlist(i)) != NULL; i++) { + if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + } + return 0; +} + +static void packed_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + char buf[MG_PATH_MAX], tmp[sizeof(buf)]; + const char *path, *begin, *end; + size_t i, n = strlen(dir); + tmp[0] = '\0'; // Previously listed entry + for (i = 0; (path = mg_unlist(i)) != NULL; i++) { + if (!is_dir_prefix(dir, n, path)) continue; + begin = &path[n + 1]; + end = strchr(begin, '/'); + if (end == NULL) end = begin + strlen(begin); + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); + buf[sizeof(buf) - 1] = '\0'; + // If this entry has been already listed, skip + // NOTE: we're assuming that file list is sorted alphabetically + if (strcmp(buf, tmp) == 0) continue; + fn(buf, userdata); // Not yet listed, call user function + strcpy(tmp, buf); // And save this entry as listed + } +} + +static void *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size, NULL); + struct packed_file *fp = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) { + fp->size = size; + fp->data = data; + } + return (void *) fp; +} + +static void packed_close(void *fp) { + if (fp != NULL) free(fp); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; +} + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + return fp->pos; +} + +static bool packed_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} + +static bool packed_remove(const char *path) { + (void) path; + return false; +} + +static bool packed_mkdir(const char *path) { + (void) path; + return false; +} + +struct mg_fs mg_fs_packed = { + packed_stat, packed_list, packed_open, packed_close, packed_read, + packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_posix.c" +#endif + + +#if MG_ENABLE_POSIX_FS + +#ifndef MG_STAT_STRUCT +#define MG_STAT_STRUCT stat +#endif + +#ifndef MG_STAT_FUNC +#define MG_STAT_FUNC stat +#endif + +static int p_stat(const char *path, size_t *size, time_t *mtime) { +#if !defined(S_ISDIR) + MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); + return 0; +#else +#if MG_ARCH == MG_ARCH_WIN32 + struct _stati64 st; + wchar_t tmp[MG_PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; + // If path is a symlink, windows reports 0 in st.st_size. + // Get a real file size by opening it and jumping to the end + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) { + FILE *fp = _wfopen(tmp, L"rb"); + if (fp != NULL) { + fseek(fp, 0, SEEK_END); + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+ + fclose(fp); + } + } +#else + struct MG_STAT_STRUCT st; + if (MG_STAT_FUNC(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +#endif +} + +#if MG_ARCH == MG_ARCH_WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +#if 0 +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} +#endif + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + +static void p_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { +#if MG_ENABLE_DIRLIST + struct dirent *dp; + DIR *dirp; + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); + } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif +} + +static void *p_open(const char *path, int flags) { +#if MG_ARCH == MG_ARCH_WIN32 + const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; + wchar_t b1[MG_PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + return (void *) _wfopen(b1, b2); +#else + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e for CLOEXEC + return (void *) fopen(path, mode); +#endif +} + +static void p_close(void *fp) { + fclose((FILE *) fp); +} + +static size_t p_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); +} + +static size_t p_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); +} + +static size_t p_seek(void *fp, size_t offset) { +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; +#else + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; +#endif + return (size_t) ftell((FILE *) fp); +} + +static bool p_rename(const char *from, const char *to) { + return rename(from, to) == 0; +} + +static bool p_remove(const char *path) { + return remove(path) == 0; +} + +static bool p_mkdir(const char *path) { + return mkdir(path, 0775) == 0; +} + +#else + +static int p_stat(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} +static void p_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} +static void *p_open(const char *path, int flags) { + (void) path, (void) flags; + return NULL; +} +static void p_close(void *fp) { + (void) fp; +} +static size_t p_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; +} +static bool p_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} +static bool p_remove(const char *path) { + (void) path; + return false; +} +static bool p_mkdir(const char *path) { + (void) path; + return false; +} +#endif + +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, + p_write, p_seek, p_rename, p_remove, p_mkdir}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/http.c" +#endif + + + + + + + + + + + + + +static int mg_ncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + if (len > 0) do { + int c = *s1++, d = *s2++; + if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; + if (d >= 'A' && d <= 'Z') d += 'a' - 'A'; + diff = c - d; + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + return diff; +} + +bool mg_to_size_t(struct mg_str str, size_t *val); +bool mg_to_size_t(struct mg_str str, size_t *val) { + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0; + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; + if (i < str.len && str.buf[i] == '-') return false; + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') { + size_t digit = (size_t) (str.buf[i] - '0'); + if (result > max2) return false; // Overflow + result *= 10; + if (result > max - digit) return false; // Overflow + result += digit; + i++, ndigits++; + } + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; + if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT + if (i != str.len) return false; // Ditto + *val = (size_t) result; + return true; +} + +// Chunk deletion marker is the MSB in the "processed" counter +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1)) + +// Multipart POST example: +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.buf; + size_t b = ofs, h1, h2, b1, b2, max = body.len; + + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); + } + h1 = h2 = h2 + 2; + } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && + memcmp(&s[b2 + 2], s, b - ofs) == 0)) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; +} + +void mg_http_bauth(struct mg_connection *c, const char *user, + const char *pass) { + struct mg_str u = mg_str(user), p = mg_str(pass); + size_t need = c->send.len + 36 + (u.len + p.len) * 2; + if (c->send.size < need) mg_iobuf_resize(&c->send, need); + if (c->send.size >= need) { + size_t i, n = 0; + char *buf = (char *) &c->send.buf[c->send.len]; + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send! + for (i = 0; i < u.len; i++) { + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n); + } + if (p.len > 0) { + n = mg_base64_update(':', buf + 21, n); + for (i = 0; i < p.len; i++) { + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n); + } + } + n = mg_base64_final(buf + 21, n); + c->send.len += 21 + (size_t) n + 2; + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); + } else { + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need)); + } +} + +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { + struct mg_str entry, k, v, result = mg_str_n(NULL, 0); + while (mg_span(buf, &entry, &buf, '&')) { + if (mg_span(entry, &k, &v, '=') && name.len == k.len && + mg_ncasecmp(name.buf, k.buf, k.len) == 0) { + result = v; + break; + } + } + return result; +} + +int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, + size_t dst_len) { + int len; + if (dst != NULL && dst_len > 0) { + dst[0] = '\0'; // If destination buffer is valid, always nul-terminate it + } + if (dst == NULL || dst_len == 0) { + len = -2; // Bad destination + } else if (buf->buf == NULL || name == NULL || buf->len == 0) { + len = -1; // Bad source + } else { + struct mg_str v = mg_http_var(*buf, mg_str(name)); + if (v.buf == NULL) { + len = -4; // Name does not exist + } else { + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1); + if (len < 0) len = -3; // Failed to decode + } + } + return len; +} + +static bool isx(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, + int is_form_url_encoded) { + size_t i, j; + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { + if (src[i] == '%') { + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) { + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t)); + i += 2; + } else { + return -1; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination + return i >= src_len && j < dst_len ? (int) j : -1; +} + +static bool isok(uint8_t c) { + return c == '\n' || c == '\r' || c == '\t' || c >= ' '; +} + +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { + size_t i; + for (i = 0; i < buf_len; i++) { + if (!isok(buf[i])) return -1; + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) + return (int) i + 1; + } + return 0; +} +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { + size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); + for (i = 0; i < max && h->headers[i].name.len > 0; i++) { + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v; + } + return NULL; +} + +// Is it a valid utf-8 continuation byte +static bool vcb(uint8_t c) { + return (c & 0xc0) == 0x80; +} + +// Get character length (valid utf-8). Used to parse method, URI, headers +static size_t clen(const char *s, const char *end) { + const unsigned char *u = (unsigned char *) s, c = *u; + long n = (long) (end - s); + if (c > ' ' && c < '~') return 1; // Usual ascii printed char + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8 + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3; + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3])) + return 4; + return 0; +} + +// Skip until the newline. Return advanced `s`, or NULL on error +static const char *skiptorn(const char *s, const char *end, struct mg_str *v) { + v->buf = (char *) s; + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r + if (s < end && s[0] == '\r') s++; // Skip \r + if (s >= end || *s++ != '\n') return NULL; // Skip \n + return s; +} + +static bool mg_http_parse_headers(const char *s, const char *end, + struct mg_http_header *h, size_t max_hdrs) { + size_t i, n; + for (i = 0; i < max_hdrs; i++) { + struct mg_str k = {NULL, 0}, v = {NULL, 0}; + if (s >= end) return false; + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break; + k.buf = (char *) s; + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n; + if (k.len == 0) return false; // Empty name + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 + if (*s++ != ':') return false; // Invalid, not followed by : + // if (clen(s, end) == 0) return false; // Invalid UTF-8 + while (s < end && (s[0] == ' ' || s[0] == '\t')) s++; // Skip spaces + if ((s = skiptorn(s, end, &v)) == NULL) return false; + while (v.len > 0 && (v.buf[v.len - 1] == ' ' || v.buf[v.len - 1] == '\t')) { + v.len--; // Trim spaces + } + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); + h[i].name = k, h[i].value = v; // Success. Assign values + } + return true; +} + +int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { + int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); + const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL + const struct mg_str *cl; + size_t n; + + memset(hm, 0, sizeof(*hm)); + if (req_len <= 0) return req_len; + + hm->message.buf = hm->head.buf = (char *) s; + hm->body.buf = (char *) end; + hm->head.len = (size_t) req_len; + hm->message.len = hm->body.len = (size_t) -1; // Set body length to infinite + + // Parse request line + hm->method.buf = (char *) s; + while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n; + while (s < end && s[0] == ' ') s++; // Skip spaces + hm->uri.buf = (char *) s; + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; + while (s < end && s[0] == ' ') s++; // Skip spaces + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; + + // If URI contains '?' character, setup query string + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) { + hm->query.buf = (char *) qs + 1; + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1)); + hm->uri.len = (size_t) (qs - hm->uri.buf); + } + + // Sanity check. Allow protocol/reason to be empty + // Do this check after hm->method.len and hm->uri.len are finalised + if (hm->method.len == 0 || hm->uri.len == 0) return -1; + + if (!mg_http_parse_headers(s, end, hm->headers, + sizeof(hm->headers) / sizeof(hm->headers[0]))) + return -1; // error when parsing + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1; + hm->message.len = (size_t) req_len + hm->body.len; + } + + // mg_http_parse() is used to parse both HTTP requests and HTTP + // responses. If HTTP response does not have Content-Length set, then + // body is read until socket is closed, i.e. body.len is infinite (~0). + // + // For HTTP requests though, according to + // http://tools.ietf.org/html/rfc7231#section-8.1.3, + // only POST and PUT methods have defined body semantics. + // Therefore, if Content-Length is not specified and methods are + // not one of PUT or POST, set body length to 0. + // + // So, if it is HTTP request, and Content-Length is not set, + // and method is not (PUT or POST) then reset body length to zero. + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0; + if (hm->body.len == (size_t) ~0 && !is_response && + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 && + mg_strcasecmp(hm->method, mg_str("POST")) != 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + + // The 204 (No content) responses also have 0 body length + if (hm->body.len == (size_t) ~0 && is_response && + mg_strcasecmp(hm->uri, mg_str("204")) == 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + if (hm->message.len < (size_t) req_len) return -1; // Overflow protection + + return req_len; +} + +static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, + va_list *ap) { + size_t len = c->send.len; + mg_send(c, " \r\n", 10); + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + if (c->send.len >= len + 10) { + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); + c->send.buf[len + 8] = '\r'; + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker + } + mg_send(c, "\r\n", 2); +} + +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_http_vprintf_chunk(c, fmt, &ap); + va_end(ap); +} + +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { + mg_printf(c, "%lx\r\n", (unsigned long) len); + mg_send(c, buf, len); + mg_send(c, "\r\n", 2); + if (len == 0) c->is_resp = 0; +} + +// clang-format off +static const char *mg_http_status_code_str(int status_code) { + switch (status_code) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 444: return "Connection Closed Without Response"; + case 451: return "Unavailable For Legal Reasons"; + case 499: return "Client Closed Request"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + case 599: return "Network Connect Timeout Error"; + default: return ""; + } +} +// clang-format on + +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { + va_list ap; + size_t len; + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code, + mg_http_status_code_str(code), headers == NULL ? "" : headers); + len = c->send.len; + va_start(ap, fmt); + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap); + va_end(ap); + if (c->send.len > 16) { + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu", + (unsigned long) (c->send.len - len)); + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space + } + c->is_resp = 0; +} + +static void http_cb(struct mg_connection *, int, void *); +static void restore_http_cb(struct mg_connection *c) { + mg_fs_close((struct mg_fd *) c->pfn_data); + c->pfn_data = NULL; + c->pfn = http_cb; + c->is_resp = 0; +} + +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); + return buf; +} + +static void static_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { + struct mg_fd *fd = (struct mg_fd *) c->pfn_data; + // Read to send IO buffer directly, avoid extra on-stack buffer + size_t n, max = MG_IO_SIZE, space; + size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / + sizeof(size_t) * sizeof(size_t)]; + if (c->send.size < max) mg_iobuf_resize(&c->send, max); + if (c->send.len >= c->send.size) return; // Rate limit + if ((space = c->send.size - c->send.len) > *cl) space = *cl; + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); + c->send.len += n; + *cl -= n; + if (n == 0) restore_http_cb(c); + } else if (ev == MG_EV_CLOSE) { + restore_http_cb(c); + } + (void) ev_data; +} + +// Known mime types. Keep it outside guess_content_type() function, since +// some environments don't like it defined there. +// clang-format off +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 } +static struct mg_str s_known_types[] = { + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), + MG_C_STR("gif"), MG_C_STR("image/gif"), + MG_C_STR("png"), MG_C_STR("image/png"), + MG_C_STR("jpg"), MG_C_STR("image/jpeg"), + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), + MG_C_STR("woff"), MG_C_STR("font/woff"), + MG_C_STR("ttf"), MG_C_STR("font/ttf"), + MG_C_STR("svg"), MG_C_STR("image/svg+xml"), + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), + MG_C_STR("csv"), MG_C_STR("text/csv"), + MG_C_STR("doc"), MG_C_STR("application/msword"), + MG_C_STR("exe"), MG_C_STR("application/octet-stream"), + MG_C_STR("gz"), MG_C_STR("application/gzip"), + MG_C_STR("ico"), MG_C_STR("image/x-icon"), + MG_C_STR("json"), MG_C_STR("application/json"), + MG_C_STR("mov"), MG_C_STR("video/quicktime"), + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), + MG_C_STR("mp4"), MG_C_STR("video/mp4"), + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), + MG_C_STR("pdf"), MG_C_STR("application/pdf"), + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), + MG_C_STR("wav"), MG_C_STR("audio/wav"), + MG_C_STR("webp"), MG_C_STR("image/webp"), + MG_C_STR("zip"), MG_C_STR("application/zip"), + MG_C_STR("3gp"), MG_C_STR("video/3gpp"), + {0, 0}, +}; +// clang-format on + +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + struct mg_str entry, k, v, s = mg_str(extra), asterisk = mg_str_n("*", 1); + size_t i = 0; + + // Shrink path to its extension only + while (i < path.len && path.buf[path.len - i - 1] != '.') i++; + path.buf += path.len - i; + path.len = i; + + // Process user-provided mime type overrides, if any + while (mg_span(s, &entry, &s, ',')) { + if (mg_span(entry, &k, &v, '=') && + (mg_strcmp(asterisk, k) == 0 || mg_strcmp(path, k) == 0)) + return v; + } + + // Process built-in mime types + for (i = 0; s_known_types[i].buf != NULL; i += 2) { + if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; + } + + return mg_str("text/plain; charset=utf-8"); +} + +static int getrange(struct mg_str *s, size_t *a, size_t *b) { + size_t i, numparsed = 0; + for (i = 0; i + 6 < s->len; i++) { + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6); + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue; + if (mg_span(v, &k, &v, '-')) { + if (mg_to_size_t(k, a)) numparsed++; + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++; + } else { + if (mg_to_size_t(v, a)) numparsed++; + } + break; + } + return (int) numparsed; +} + +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, + const char *path, + const struct mg_http_serve_opts *opts) { + char etag[64], tmp[MG_PATH_MAX]; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = NULL; + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); + bool gzip = false; + + if (path != NULL) { + // If a browser sends us "Accept-Encoding: gzip", try to open .gz first + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding"); + if (ae != NULL) { + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf); + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) { + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); + fd = mg_fs_open(fs, tmp, MG_FS_READ); + if (fd != NULL) gzip = true, path = tmp; + } + free(ae_); + } + // No luck opening .gz? Open what we've told to open + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); + } + + // Failed to open, and page404 is configured? Open it, then + if (fd == NULL && opts->page404 != NULL) { + fd = mg_fs_open(fs, opts->page404, MG_FS_READ); + path = opts->page404; + mime = guess_content_type(mg_str(path), opts->mime_types); + } + + if (fd == NULL || fs->st(path, &size, &mtime) == 0) { + mg_http_reply(c, 404, opts->extra_headers, "Not found\n"); + mg_fs_close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_strcasecmp(*inm, mg_str(etag)) == 0) { + mg_fs_close(fd); + mg_http_reply(c, 304, opts->extra_headers, ""); + } else { + int n, status = 200; + char range[100]; + size_t r1 = 0, r2 = 0, cl = size; + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + range[0] = '\0'; + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", + (int64_t) size); + } else { + status = 206; + cl = r2 - r1 + 1; + mg_snprintf(range, sizeof(range), + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1, + (uint64_t) (r1 + cl - 1), (uint64_t) size); + fs->sk(fd->fd, r1); + } + } + mg_printf(c, + "HTTP/1.1 %d %s\r\n" + "Content-Type: %.*s\r\n" + "Etag: %s\r\n" + "Content-Length: %llu\r\n" + "%s%s%s\r\n", + status, mg_http_status_code_str(status), (int) mime.len, mime.buf, + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", + range, opts->extra_headers ? opts->extra_headers : ""); + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) { + c->is_resp = 0; + mg_fs_close(fd); + } else { + // Track to-be-sent content length at the end of c->data, aligned + size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) / + sizeof(size_t) * sizeof(size_t)]; + c->pfn = static_cb; + c->pfn_data = fd; + *clp = cl; + } + } +} + +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + const struct mg_http_serve_opts *opts; + const char *dir; +}; + +#if MG_ENABLE_DIRLIST +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t t = 0; + char path[MG_PATH_MAX], sz[40], mod[40]; + int flags, n = 0; + + // MG_DEBUG(("[%s] [%s]", d->dir, name)); + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > + sizeof(path)) { + MG_ERROR(("%s truncated", name)); + } else if ((flags = fs->st(path, &size, &t)) == 0) { + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); + } else { + const char *slash = flags & MG_FS_DIR ? "/" : ""; + if (flags & MG_FS_DIR) { + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else { + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); + } +#if defined(MG_HTTP_DIRLIST_TIME_FMT) + { + char time_str[40]; + struct tm *time_info = localtime(&t); + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); + mg_snprintf(mod, sizeof(mod), "%s", time_str); + } +#else + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t); +#endif + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " %s%s" + "%s%s\n", + n, path, slash, name, slash, (unsigned long) t, mod, + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); + } +} + +static void listdir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *dir) { + const char *sort_js_code = + ""; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10], buf[MG_PATH_MAX]; + size_t off, n; + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0); + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; + + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "Index of %.*s%s%s" + "" + "

Index of %.*s

" + "" + "" + "" + "" + "\n", + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int) uri.len, + uri.buf); + mg_printf(c, "%s", + " " + "\n"); + + fs->ls(dir, printdirentry, &d); + mg_printf(c, + "" + "
Name" + "ModifiedSize

..[DIR]

Mongoose v.%s
\n", + MG_VERSION); + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 12, tmp, n); // Set content length + c->is_resp = 0; // Mark response end +} +#endif + +// Resolve requested file into `path` and return its fs->st() result +static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, struct mg_str url, struct mg_str dir, + char *path, size_t path_size) { + int flags, tmp; + // Append URI to the root_dir, and sanitize it + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf); + if (n + 2 >= path_size) { + mg_http_reply(c, 400, "", "Exceeded path size"); + return -1; + } + path[path_size - 1] = '\0'; + // Terminate root dir with slash + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; + if (url.len < hm->uri.len) { + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n, + path_size - n, 0); + } + path[path_size - 1] = '\0'; // Double-check + if (!mg_path_is_sane(mg_str_n(path, path_size))) { + mg_http_reply(c, 400, "", "Invalid path"); + return -1; + } + n = strlen(path); + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR + : fs->st(path, NULL, NULL); + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path, + flags)); + if (flags == 0) { + // Do nothing - let's caller decide + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && + hm->uri.buf[hm->uri.len - 1] != '/') { + mg_printf(c, + "HTTP/1.1 301 Moved\r\n" + "Location: %.*s/\r\n" + "Content-Length: 0\r\n" + "\r\n", + (int) hm->uri.len, hm->uri.buf); + c->is_resp = 0; + flags = -1; + } else if (flags & MG_FS_DIR) { + if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0) || + (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0))) { + flags = tmp; + } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > + 0 && + (tmp = fs->st(path, NULL, NULL)) != + 0)) { // check for gzipped index + flags = tmp; + path[n + 1 + strlen(MG_HTTP_INDEX)] = + '\0'; // Remove appended .gz in index file name + } else { + path[n] = '\0'; // Remove appended index file name + } + } + return flags; +} + +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *path, + size_t path_size) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u; + while (mg_span(s, &part, &s, ',')) { + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v; + if (hm->uri.len < k.len) continue; + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue; + u = k, p = v; + } + return uri_to_path2(c, hm, fs, u, p, path, path_size); +} + +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts) { + char path[MG_PATH_MAX]; + const char *sp = opts->ssi_pattern; + int flags = uri_to_path(c, hm, opts, path, sizeof(path)); + if (flags < 0) { + // Do nothing: the response has already been sent by uri_to_path() + } else if (flags & MG_FS_DIR) { +#if MG_ENABLE_DIRLIST + listdir(c, hm, opts, path); +#else + mg_http_reply(c, 403, "", "Forbidden\n"); +#endif + } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) { + mg_http_serve_ssi(c, opts->root_dir, path); + } else { + mg_http_serve_file(c, hm, path, opts); + } +} + +static bool mg_is_url_safe(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; +} + +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { + size_t i, n = 0; + for (i = 0; i < sl; i++) { + int c = *(unsigned char *) &s[i]; + if (n + 4 >= len) return 0; + if (mg_is_url_safe(c)) { + buf[n++] = s[i]; + } else { + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]); + n += 3; + } + } + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the destination + if (len > 0) buf[len - 1] = '\0'; // Always. + return n; +} + +void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, + char *pass, size_t passlen) { + struct mg_str *v = mg_http_get_header(hm, "Authorization"); + user[0] = pass[0] = '\0'; + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) { + char buf[256]; + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf)); + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0); + if (p != NULL) { + mg_snprintf(user, userlen, "%.*s", p - buf, buf); + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1); + } + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) { + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7); + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf); + } else { + mg_http_get_var(&hm->query, "access_token", pass, passlen); + } +} + +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"' + ? mg_str_n(s.buf + 1, s.len - 2) + : s; +} + +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) { + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) + p++; + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len, + // v.buf, (int) (p - b), b)); + return stripquotes(mg_str_n(b, (size_t) (p - b + q))); + } + } + return mg_str_n(NULL, 0); +} + +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir, size_t max_size) { + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX]; + long res = 0, offset; + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); + mg_http_get_var(&hm->query, "file", file, sizeof(file)); + offset = strtol(buf, NULL, 0); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file); + if (hm->body.len == 0) { + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write + } else if (file[0] == '\0') { + mg_http_reply(c, 400, "", "file required"); + res = -1; + } else if (mg_path_is_sane(mg_str(file)) == false) { + mg_http_reply(c, 400, "", "%s: invalid file", file); + res = -2; + } else if (offset < 0) { + mg_http_reply(c, 400, "", "offset required"); + res = -3; + } else if ((size_t) offset + hm->body.len > max_size) { + mg_http_reply(c, 400, "", "%s: over max size of %lu", path, + (unsigned long) max_size); + res = -4; + } else { + struct mg_fd *fd; + size_t current_size = 0; + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset)); + if (offset == 0) fs->rm(path); // If offset if 0, truncate file + fs->st(path, ¤t_size, NULL); + if (offset > 0 && current_size != (size_t) offset) { + mg_http_reply(c, 400, "", "%s: offset mismatch", path); + res = -5; + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + res = -6; + } else { + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len); + mg_fs_close(fd); + mg_http_reply(c, 200, "", "%ld", res); + } + } + return res; +} + +int mg_http_status(const struct mg_http_message *hm) { + return atoi(hm->uri.buf); +} + +static bool is_hex_digit(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +static int skip_chunk(const char *buf, int len, int *pl, int *dl) { + int i = 0, n = 0; + if (len < 3) return 0; + while (i < len && is_hex_digit(buf[i])) i++; + if (i == 0) return -1; // Error, no length specified + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // Error + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false) + return -1; // Decode chunk length, overflow + if (n < 0) return -1; // Error. TODO(): some checks now redundant + if (n > len - i - 4) return 0; // Chunk not yet fully buffered + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // Error + *pl = i + 2, *dl = n; + return i + 2 + n + 2; +} + +static void http_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ || ev == MG_EV_CLOSE || + (ev == MG_EV_POLL && c->is_accepted && !c->is_draining && + c->recv.len > 0)) { // see #2796 + struct mg_http_message hm; + size_t ofs = 0; // Parsing offset + while (c->is_resp == 0 && ofs < c->recv.len) { + const char *buf = (char *) c->recv.buf + ofs; + int n = mg_http_parse(buf, c->recv.len - ofs, &hm); + struct mg_str *te; // Transfer - encoding header + bool is_chunked = false; + if (n < 0) { + // We don't use mg_error() here, to avoid closing pipelined requests + // prematurely, see #2592 + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len)); + c->is_draining = 1; + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs); + c->recv.len = 0; + return; + } + if (n == 0) break; // Request is not buffered yet + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG + hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf); + } + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) { + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) { + is_chunked = true; + } else { + mg_error(c, "Invalid Transfer-Encoding"); // See #2460 + return; + } + } else if (mg_http_get_header(&hm, "Content-length") == NULL) { + // #2593: HTTP packets must contain either Transfer-Encoding or + // Content-length + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0; + bool require_content_len = false; + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 || + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) { + // POST and PUT should include an entity body. Therefore, they should + // contain a Content-length header. Other requests can also contain a + // body, but their content has no defined semantics (RFC 7231) + require_content_len = true; + ofs += (size_t) n; // this request has been processed + } else if (is_response) { + // HTTP spec 7.2 Entity body: All other responses must include a body + // or Content-Length header field defined with a value of 0. + int status = mg_http_status(&hm); + require_content_len = status >= 200 && status != 204 && status != 304; + } + if (require_content_len) { + mg_http_reply(c, 411, "", ""); + MG_ERROR(("%s", "Content length missing from request")); + } + } + + if (is_chunked) { + // For chunked data, strip off prefixes and suffixes from chunks + // and relocate them right after the headers, then report a message + char *s = (char *) c->recv.buf + ofs + n; + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n); + + // Find zero-length chunk (the end of the body) + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl; + if (cl == 0) break; // No zero-len chunk, buffer more data + if (cl < 0) { + mg_error(c, "Invalid chunk"); + break; + } + + // Zero chunk found. Second pass: strip + relocate + o = 0, hm.body.len = 0, hm.message.len = (size_t) n; + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) { + memmove(s + hm.body.len, s + o + pl, (size_t) dl); + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl; + if (dl == 0) break; + } + ofs += (size_t) (n + o); + } else { // Normal, non-chunked data + size_t len = c->recv.len - ofs - (size_t) n; + if (hm.body.len > len) break; // Buffer more data + ofs += (size_t) n + hm.body.len; + } + + if (c->is_accepted) c->is_resp = 1; // Start generating response + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp + if (c->is_accepted) { + struct mg_str *cc = mg_http_get_header(&hm, "Connection"); + if (cc != NULL && mg_strcasecmp(*cc, mg_str("close")) == 0) { + c->is_draining = 1; // honor "Connection: close" + break; + } + } + } + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data + } + (void) ev_data; +} + +static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/quit"), NULL)) { + mg_http_reply(c, 200, "", "ok\n"); + c->is_draining = 1; + c->data[0] = 'X'; + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) { + int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); + mg_log_set(level); + mg_http_reply(c, 200, "", "Debug level set to %d\n", level); + } else { + mg_http_reply(c, 200, "", "hi\n"); + } + } else if (ev == MG_EV_CLOSE) { + if (c->data[0] == 'X') *(bool *) c->fn_data = true; + } +} + +void mg_hello(const char *url) { + struct mg_mgr mgr; + bool done = false; + mg_mgr_init(&mgr); + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true; + while (done == false) mg_mgr_poll(&mgr, 100); + mg_mgr_free(&mgr); +} + +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/iobuf.c" +#endif + + + + + +static size_t roundup(size_t size, size_t align) { + return align == 0 ? size : (size + align - 1) / align * align; +} + +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + int ok = 1; + new_size = roundup(new_size, io->align); + if (new_size == 0) { + mg_bzero(io->buf, io->size); + free(io->buf); + io->buf = NULL; + io->len = io->size = 0; + } else if (new_size != io->size) { + // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the + // porting to some obscure platforms like FreeRTOS + void *p = calloc(1, new_size); + if (p != NULL) { + size_t len = new_size < io->len ? new_size : io->len; + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len); + mg_bzero(io->buf, io->size); + free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + } else { + ok = 0; + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); + } + } + return ok; +} + +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { + io->buf = NULL; + io->align = align; + io->size = io->len = 0; + return mg_iobuf_resize(io, size); +} + +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, + size_t len) { + size_t new_size = roundup(io->len + len, io->align); + mg_iobuf_resize(io, new_size); // Attempt to resize + if (new_size != io->size) len = 0; // Resize failure, append nothing + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); + if (buf != NULL) memmove(io->buf + ofs, buf, len); + if (ofs > io->len) io->len += ofs - io->len; + io->len += len; + return len; +} + +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { + if (ofs > io->len) ofs = io->len; + if (ofs + len > io->len) len = io->len - ofs; + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); + if (io->buf) mg_bzero(io->buf + io->len - len, len); + io->len -= len; + return len; +} + +void mg_iobuf_free(struct mg_iobuf *io) { + mg_iobuf_resize(io, 0); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/json.c" +#endif + + + + +static const char *escapeseq(int esc) { + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; +} + +static char json_esc(int c, int esc) { + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); + for (p = esc1; *p != '\0'; p++) { + if (*p == c) return esc2[p - esc1]; + } + return 0; +} + +static int mg_pass_string(const char *s, int len) { + int i; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { + i++; + } else if (s[i] == '\0') { + return MG_JSON_INVALID; + } else if (s[i] == '"') { + return i; + } + } + return MG_JSON_INVALID; +} + +static double mg_atod(const char *p, int len, int *numlen) { + double d = 0.0; + int i = 0, sign = 1; + + // Sign + if (i < len && *p == '-') { + sign = -1, i++; + } else if (i < len && *p == '+') { + i++; + } + + // Decimal + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { + d *= 10.0; + d += p[i] - '0'; + } + d *= sign; + + // Fractional + if (i < len && p[i] == '.') { + double frac = 0.0, base = 0.1; + i++; + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { + frac += base * (p[i] - '0'); + base /= 10.0; + } + d += frac * sign; + } + + // Exponential + if (i < len && (p[i] == 'e' || p[i] == 'E')) { + int j, exp = 0, minus = 0; + i++; + if (i < len && p[i] == '-') minus = 1, i++; + if (i < len && p[i] == '+') i++; + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308) + exp = exp * 10 + (p[i++] - '0'); + if (minus) exp = -exp; + for (j = 0; j < exp; j++) d *= 10.0; + for (j = 0; j < -exp; j++) d /= 10.0; + } + + if (numlen != NULL) *numlen = i; + return d; +} + +// Iterate over object or array elements +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val) { + if (ofs >= obj.len) { + ofs = 0; // Out of boundaries, stop scanning + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) { + ofs = 0; // Not an array or object, stop + } else { + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs); + if (ofs == 0) ofs++, sub.buf++, sub.len--; + if (*obj.buf == '[') { // Iterate over an array + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(NULL, 0); + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); + } + } else { // Iterate over an object + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(sub.buf + o, (size_t) n); + sub.buf += o + n, sub.len -= (size_t) (o + n); + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++; + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++; + n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing value, stop scanning + } else { + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); + } + } + } + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf)); + while (ofs && ofs < obj.len && + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' || + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) { + ofs++; + } + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++; + if (ofs > obj.len) ofs = 0; + } + return ofs; +} + +int mg_json_get(struct mg_str json, const char *path, int *toklen) { + const char *s = json.buf; + int len = (int) json.len; + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; + unsigned char nesting[MG_JSON_MAX_DEPTH]; + int i = 0; // Current offset in `s` + int j = 0; // Offset in `s` we're looking for (return value) + int depth = 0; // Current depth (nesting level) + int ed = 0; // Expected depth + int pos = 1; // Current position in `path` + int ci = -1, ei = -1; // Current and expected index in array + + if (toklen) *toklen = 0; + if (path[0] != '$') return MG_JSON_INVALID; + +#define MG_CHECKRET(x) \ + do { \ + if (depth == ed && path[pos] == '\0' && ci == ei) { \ + if (toklen) *toklen = i - j + 1; \ + return j; \ + } \ + } while (0) + +// In the ascii table, the distance between `[` and `]` is 2. +// Ditto for `{` and `}`. Hence +2 in the code below. +#define MG_EOO(x) \ + do { \ + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ + depth--; \ + MG_CHECKRET(x); \ + } while (0) + + for (i = 0; i < len; i++) { + unsigned char c = ((unsigned char *) s)[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; + switch (expecting) { + case S_VALUE: + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei); + if (depth == ed) j = i; + if (c == '{') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '.' && ci == ei) { + // If we start the object, reset array indices + ed++, pos++, ci = ei = -1; + } + nesting[depth++] = c; + expecting = S_KEY; + break; + } else if (c == '[') { + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; + if (depth == ed && path[pos] == '[' && ei == ci) { + ed++, pos++, ci = 0; + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { + ei *= 10; + ei += path[pos] - '0'; + } + if (path[pos] != 0) pos++; + } + nesting[depth++] = c; + break; + } else if (c == ']' && depth > 0) { // Empty array + MG_EOO(']'); + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { + i += 3; + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { + i += 3; + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { + i += 4; + } else if (c == '-' || ((c >= '0' && c <= '9'))) { + int numlen = 0; + mg_atod(&s[i], len - i, &numlen); + i += numlen - 1; + } else if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + i += n + 1; + } else { + return MG_JSON_INVALID; + } + MG_CHECKRET('V'); + if (depth == ed && ei >= 0) ci++; + expecting = S_COMMA_OR_EOO; + break; + + case S_KEY: + if (c == '"') { + int n = mg_pass_string(&s[i + 1], len - i - 1); + if (n < 0) return n; + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND; + if (depth < ed) return MG_JSON_NOT_FOUND; + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND; + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, + // &s[i + 1], n, depth, ed, ci, ei); + // NOTE(cpq): in the check sequence below is important. + // strncmp() must go first: it fails fast if the remaining length + // of the path is smaller than `n`. + if (depth == ed && path[pos - 1] == '.' && + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && + (path[pos + n] == '\0' || path[pos + n] == '.' || + path[pos + n] == '[')) { + pos += n; + } + i += n + 1; + expecting = S_COLON; + } else if (c == '}') { // Empty object + MG_EOO('}'); + expecting = S_COMMA_OR_EOO; + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COLON: + if (c == ':') { + expecting = S_VALUE; + } else { + return MG_JSON_INVALID; + } + break; + + case S_COMMA_OR_EOO: + if (depth <= 0) { + return MG_JSON_INVALID; + } else if (c == ',') { + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; + } else if (c == ']' || c == '}') { + if (depth == ed && c == '}' && path[pos - 1] == '.') + return MG_JSON_NOT_FOUND; + if (depth == ed && c == ']' && path[pos - 1] == ',') + return MG_JSON_NOT_FOUND; + MG_EOO('O'); + if (depth == ed && ei >= 0) ci++; + } else { + return MG_JSON_INVALID; + } + break; + } + } + return MG_JSON_NOT_FOUND; +} + +struct mg_str mg_json_get_tok(struct mg_str json, const char *path) { + int len = 0, ofs = mg_json_get(json, path, &len); + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs, + (size_t) (len < 0 ? 0 : len)); +} + +bool mg_json_get_num(struct mg_str json, const char *path, double *v) { + int n, toklen, found = 0; + if ((n = mg_json_get(json, path, &toklen)) >= 0 && + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) { + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL); + found = 1; + } + return found; +} + +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { + int found = 0, off = mg_json_get(json, path, NULL); + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) { + if (v != NULL) *v = json.buf[off] == 't'; + found = 1; + } + return found; +} + +bool mg_json_unescape(struct mg_str s, char *to, size_t n) { + size_t i, j; + for (i = 0, j = 0; i < s.len && j < n; i++, j++) { + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') { + // \uXXXX escape. We process simple one-byte chars \u00xx within ASCII + // range. More complex chars would require dragging in a UTF8 library, + // which is too much for us + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j], + sizeof(uint8_t)) == false) + return false; + i += 5; + } else if (s.buf[i] == '\\' && i + 1 < s.len) { + char c = json_esc(s.buf[i + 1], 0); + if (c == 0) return false; + to[j] = c; + i++; + } else { + to[j] = s.buf[i]; + } + } + if (j >= n) return false; + if (n > 0) to[j] = '\0'; + return true; +} + +char *mg_json_get_str(struct mg_str json, const char *path) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && len > 1 && json.buf[off] == '"') { + if ((result = (char *) calloc(1, (size_t) len)) != NULL && + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), + result, (size_t) len)) { + free(result); + result = NULL; + } + } + return result; +} + +char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && json.buf[off] == '"' && len > 1 && + (result = (char *) calloc(1, (size_t) len)) != NULL) { + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result, + (size_t) len); + if (slen != NULL) *slen = (int) k; + } + return result; +} + +char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { + char *result = NULL; + int len = 0, off = mg_json_get(json, path, &len); + if (off >= 0 && json.buf[off] == '"' && len > 1 && + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { + int i; + for (i = 0; i < len - 2; i += 2) { + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1], + sizeof(uint8_t)); + } + result[len / 2 - 1] = '\0'; + if (slen != NULL) *slen = len / 2 - 1; + } + return result; +} + +long mg_json_get_long(struct mg_str json, const char *path, long dflt) { + double dv; + long result = dflt; + if (mg_json_get_num(json, path, &dv)) result = (long) dv; + return result; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + + + +int mg_log_level = MG_LL_INFO; +static mg_pfn_t s_log_func = mg_pfn_stdout; +static void *s_log_func_param = NULL; + +void mg_log_set_fn(mg_pfn_t fn, void *param) { + s_log_func = fn; + s_log_func_param = param; +} + +static void logc(unsigned char c) { + s_log_func((char) c, s_log_func_param); +} + +static void logs(const char *buf, size_t len) { + size_t i; + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); +} + +#if MG_ENABLE_CUSTOM_LOG +// Let user define their own mg_log_prefix() and mg_log() +#else +void mg_log_prefix(int level, const char *file, int line, const char *fname) { + const char *p = strrchr(file, '/'); + char buf[41]; + size_t n; + if (p == NULL) p = strrchr(file, '\\'); + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level, + p == NULL ? file : p + 1, line, fname); + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; + while (n < sizeof(buf)) buf[n++] = ' '; + logs(buf, n - 1); +} + +void mg_log(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap); + va_end(ap); + logs("\r\n", 2); +} +#endif + +static unsigned char nibble(unsigned c) { + return (unsigned char) (c < 10 ? c + '0' : c + 'W'); +} + +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~') +void mg_hexdump(const void *buf, size_t len) { + const unsigned char *p = (const unsigned char *) buf; + unsigned char ascii[16], alen = 0; + size_t i; + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + // Print buffered ascii chars + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; + // Print hex address, then \t + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); + } + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 + logc(' '); // Space after hex number + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf + } + while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; + logs(" ", 2), logs((char *) ascii, 16), logc('\n'); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/md5.c" +#endif + + + +// This code implements the MD5 message-digest algorithm. +// The algorithm is due to Ron Rivest. This code was +// written by Colin Plumb in 1993, no copyright is claimed. +// This code is in the public domain; do with it what you wish. +// +// Equivalent code is available from RSA Data Security, Inc. +// This code has been tested against that, and is equivalent, +// except that you don't need to include two pages of legalese +// with every copy. +// +// To compute the message digest of a chunk of bytes, declare an +// MD5Context structure, pass it to MD5Init, call MD5Update as +// needed on buffers full of bytes, and then call MD5Final, which +// will fill a supplied 16-byte array with the digest. + +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5 + +static void mg_byte_reverse(unsigned char *buf, unsigned longs) { + if (MG_BIG_ENDIAN) { + do { + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); + } else { + (void) buf, (void) longs; // Little endian. Do nothing + } +} + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void mg_md5_init(mg_md5_ctx *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { + uint32_t t; + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; + ctx->bits[1] += (uint32_t) len >> 29; + + t = (t >> 3) & 0x3f; + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + memcpy(ctx->in, buf, len); +} + +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { + unsigned count; + unsigned char *p; + uint32_t *a; + + count = (ctx->bits[0] >> 3) & 0x3F; + + p = ctx->in + count; + *p++ = 0x80; + count = 64 - 1 - count; + if (count < 8) { + memset(p, 0, count); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + memset(ctx->in, 0, 56); + } else { + memset(p, 0, count - 8); + } + mg_byte_reverse(ctx->in, 14); + + a = (uint32_t *) ctx->in; + a[14] = ctx->bits[0]; + a[15] = ctx->bits[1]; + + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + mg_byte_reverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(*ctx)); +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/mqtt.c" +#endif + + + + + + + + +#define MQTT_CLEAN_SESSION 0x02 +#define MQTT_HAS_WILL 0x04 +#define MQTT_WILL_RETAIN 0x20 +#define MQTT_HAS_PASSWORD 0x40 +#define MQTT_HAS_USER_NAME 0x80 + +struct mg_mqtt_pmap { + uint8_t id; + uint8_t type; +}; + +static const struct mg_mqtt_pmap s_prop_map[] = { + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT}, + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA}, + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING}, + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT}, + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR}, + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT}, + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE}, + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}}; + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; + buf[0] = (uint8_t) ((cmd << 4) | flags); + do { + *vlen = len % 0x80; + len /= 0x80; + if (len > 0) *vlen |= 0x80; + vlen++; + } while (len > 0 && vlen < &buf[sizeof(buf)]); + mg_send(c, buf, (size_t) (vlen - buf)); +} + +static void mg_send_u16(struct mg_connection *c, uint16_t value) { + mg_send(c, &value, sizeof(value)); +} + +static void mg_send_u32(struct mg_connection *c, uint32_t value) { + mg_send(c, &value, sizeof(value)); +} + +static uint8_t varint_size(size_t length) { + uint8_t bytes_needed = 0; + do { + bytes_needed++; + length /= 0x80; + } while (length > 0); + return bytes_needed; +} + +static size_t encode_varint(uint8_t *buf, size_t value) { + size_t len = 0; + + do { + uint8_t b = (uint8_t) (value % 128); + value /= 128; + if (value > 0) b |= 0x80; + buf[len++] = b; + } while (value > 0); + + return len; +} + +static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) { + size_t multiplier = 1, offset; + *value = 0; + + for (offset = 0; offset < 4 && offset < len; offset++) { + uint8_t encoded_byte = buf[offset]; + *value += (encoded_byte & 0x7f) * multiplier; + multiplier *= 128; + + if ((encoded_byte & 0x80) == 0) return offset + 1; + } + + return 0; +} + +static int mqtt_prop_type_by_id(uint8_t prop_id) { + size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]); + for (i = 0; i < num_properties; ++i) { + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type; + } + return -1; // Property ID not found +} + +// Returns the size of the properties section, without the +// size of the content's length +static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) { + size_t i, size = 0; + for (i = 0; i < count; i++) { + size++; // identifier + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + size += (uint32_t) (props[i].val.len + props[i].key.len + + 2 * sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_STRING: + size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_BINARY_DATA: + size += (uint32_t) (props[i].val.len + sizeof(uint16_t)); + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + size += varint_size((uint32_t) props[i].iv); + break; + case MQTT_PROP_TYPE_INT: + size += (uint32_t) sizeof(uint32_t); + break; + case MQTT_PROP_TYPE_SHORT: + size += (uint32_t) sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_BYTE: + size += (uint32_t) sizeof(uint8_t); + break; + default: + return size; // cannot parse further down + } + } + + return size; +} + +// returns the entire size of the properties section, including the +// size of the variable length of the content +static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) { + size_t size = get_properties_length(props, count); + size += varint_size(size); + return size; +} + +static void mg_send_mqtt_properties(struct mg_connection *c, + struct mg_mqtt_prop *props, size_t nprops) { + size_t total_size = get_properties_length(props, nprops); + uint8_t buf_v[4] = {0, 0, 0, 0}; + uint8_t buf[4] = {0, 0, 0, 0}; + size_t i, len = encode_varint(buf, total_size); + + mg_send(c, buf, (size_t) len); + for (i = 0; i < nprops; i++) { + mg_send(c, &props[i].id, sizeof(props[i].id)); + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); + mg_send(c, props[i].key.buf, props[i].key.len); + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); + break; + case MQTT_PROP_TYPE_BYTE: + mg_send(c, &props[i].iv, sizeof(uint8_t)); + break; + case MQTT_PROP_TYPE_SHORT: + mg_send_u16(c, mg_htons((uint16_t) props[i].iv)); + break; + case MQTT_PROP_TYPE_INT: + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv)); + break; + case MQTT_PROP_TYPE_STRING: + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); + break; + case MQTT_PROP_TYPE_BINARY_DATA: + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); + mg_send(c, props[i].val.buf, props[i].val.len); + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = encode_varint(buf_v, props[i].iv); + mg_send(c, buf_v, (size_t) len); + break; + } + } +} + +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, + size_t ofs) { + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs; + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len; + size_t new_pos = ofs, len; + prop->id = i[0]; + + if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size) + return 0; + i++, new_pos++; + + switch (mqtt_prop_type_by_id(prop->id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->key.buf = (char *) i + 2; + i += 2 + prop->key.len; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; + break; + case MQTT_PROP_TYPE_BYTE: + prop->iv = (uint8_t) i[0]; + new_pos++; + break; + case MQTT_PROP_TYPE_SHORT: + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + new_pos += sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_INT: + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) | + ((uint32_t) i[2] << 8) | i[3]; + new_pos += sizeof(uint32_t); + break; + case MQTT_PROP_TYPE_STRING: + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv); + new_pos = (!len) ? 0 : new_pos + len; + break; + default: + new_pos = 0; + } + + return new_pos; +} + +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + char client_id[21]; + struct mg_str cid = opts->client_id; + size_t total_len = 7 + 1 + 2 + 2; + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0}; + + if (cid.len == 0) { + mg_random_str(client_id, sizeof(client_id) - 1); + client_id[sizeof(client_id) - 1] = '\0'; + cid = mg_str(client_id); + } + + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1) + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags + if (opts->user.len > 0) { + total_len += 2 + (uint32_t) opts->user.len; + hdr[7] |= MQTT_HAS_USER_NAME; + } + if (opts->pass.len > 0) { + total_len += 2 + (uint32_t) opts->pass.len; + hdr[7] |= MQTT_HAS_PASSWORD; + } + if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; + hdr[7] |= MQTT_HAS_WILL; + } + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION; + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN; + total_len += (uint32_t) cid.len; + if (c->is_mqtt5) { + total_len += get_props_size(opts->props, opts->num_props); + if (hdr[7] & MQTT_HAS_WILL) + total_len += get_props_size(opts->will_props, opts->num_will_props); + } + + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len); + mg_send(c, hdr, sizeof(hdr)); + // keepalive == 0 means "do not disconnect us!" + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); + + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + mg_send_u16(c, mg_htons((uint16_t) cid.len)); + mg_send(c, cid.buf, cid.len); + + if (hdr[7] & MQTT_HAS_WILL) { + if (c->is_mqtt5) + mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); + + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); + mg_send(c, opts->message.buf, opts->message.len); + } + if (opts->user.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); + mg_send(c, opts->user.buf, opts->user.len); + } + if (opts->pass.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); + mg_send(c, opts->pass.buf, opts->pass.len); + } +} + +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + uint16_t id = opts->retransmit_id; + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); + size_t len = 2 + opts->topic.len + opts->message.len; + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, + (char *) opts->topic.buf, (int) opts->message.len, + (char *) opts->message.buf)); + if (opts->qos > 0) len += 2; + if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); + + if (opts->qos > 0 && id != 0) flags |= 1 << 3; + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + if (opts->qos > 0) { // need to send 'id' field + if (id == 0) { // generate new one if not resending + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + id = c->mgr->mqtt_id; + } + mg_send_u16(c, mg_htons(id)); + } + + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len); + return id; +} + +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + uint8_t qos_ = opts->qos & 3; + size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; + size_t len = 2 + opts->topic.len + 2 + 1 + plen; + + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len); + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); + mg_send(c, opts->topic.buf, opts->topic.len); + mg_send(c, &qos_, sizeof(qos_)); +} + +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, + struct mg_mqtt_message *m) { + uint8_t lc = 0, *p, *end; + uint32_t n = 0, len_len = 0; + + memset(m, 0, sizeof(*m)); + m->dgram.buf = (char *) buf; + if (len < 2) return MQTT_INCOMPLETE; + m->cmd = (uint8_t) (buf[0] >> 4); + m->qos = (buf[0] >> 1) & 3; + + n = len_len = 0; + p = (uint8_t *) buf + 1; + while ((size_t) (p - buf) < len) { + lc = *((uint8_t *) p++); + n += (uint32_t) ((lc & 0x7f) << 7 * len_len); + len_len++; + if (!(lc & 0x80)) break; + if (len_len >= 4) return MQTT_MALFORMED; + } + end = p + n; + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE; + m->dgram.len = (size_t) (end - buf); + + switch (m->cmd) { + case MQTT_CMD_CONNACK: + if (end - p < 2) return MQTT_MALFORMED; + m->ack = p[1]; + break; + case MQTT_CMD_PUBACK: + case MQTT_CMD_PUBREC: + case MQTT_CMD_PUBREL: + case MQTT_CMD_PUBCOMP: + case MQTT_CMD_SUBSCRIBE: + case MQTT_CMD_SUBACK: + case MQTT_CMD_UNSUBSCRIBE: + case MQTT_CMD_UNSUBACK: + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + break; + case MQTT_CMD_PUBLISH: { + if (p + 2 > end) return MQTT_MALFORMED; + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + m->topic.buf = (char *) p + 2; + p += 2 + m->topic.len; + if (p > end) return MQTT_MALFORMED; + if (m->qos > 0) { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + } + if (p > end) return MQTT_MALFORMED; + if (version == 5 && p + 2 < end) { + len_len = + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size); + if (!len_len) return MQTT_MALFORMED; + m->props_start = (size_t) (p + len_len - buf); + p += len_len + m->props_size; + } + if (p > end) return MQTT_MALFORMED; + m->data.buf = (char *) p; + m->data.len = (size_t) (end - p); + break; + } + default: + break; + } + return MQTT_OK; +} + +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + for (;;) { + uint8_t version = c->is_mqtt5 ? 5 : 4; + struct mg_mqtt_message mm; + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm); + if (rc == MQTT_MALFORMED) { + MG_ERROR(("%lu MQTT malformed message", c->id)); + c->is_closing = 1; + break; + } else if (rc == MQTT_OK) { + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd, + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf)); + switch (mm.cmd) { + case MQTT_CMD_CONNACK: + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); + if (mm.ack == 0) { + MG_DEBUG(("%lu Connected", c->id)); + } else { + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); + c->is_closing = 1; + } + break; + case MQTT_CMD_PUBLISH: { + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/ + if (mm.qos > 0) { + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); + if (c->is_mqtt5) remaining_len += 2; // 3.4.2 + + mg_mqtt_send_header( + c, + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK), + 0, remaining_len); + mg_send(c, &id, sizeof(id)); + + if (c->is_mqtt5) { + uint16_t zero = 0; + mg_send(c, &zero, sizeof(zero)); + } + } + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff + break; + } + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1 + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len); + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2 + break; + } + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc + uint16_t id = mg_ntohs(mm.id); + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1 + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len); + mg_send(c, &id, sizeof(id)); + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; +} + +void mg_mqtt_ping(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); +} + +void mg_mqtt_pong(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); +} + +void mg_mqtt_disconnect(struct mg_connection *c, + const struct mg_mqtt_opts *opts) { + size_t len = 0; + if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props); + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len); + + if (c->is_mqtt5) { + uint8_t zero = 0; + mg_send(c, &zero, sizeof(zero)); // reason code + mg_send_mqtt_properties(c, opts->props, opts->num_props); + } +} + +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, + const struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, opts == NULL ? &empty : opts); + c->pfn = mqtt_cb; + } + return c; +} + +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net.c" +#endif + + + + + + + + + +size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) { + size_t old = c->send.len; + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + return c->send.len - old; +} + +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vprintf(c, fmt, &ap); + va_end(ap); + return len; +} + +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + uint32_t localhost = mg_htonl(0x7f000001); + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false; + memcpy(addr->ip, &localhost, sizeof(uint32_t)); + addr->is_ip6 = false; + return true; +} + +static bool mg_atone(struct mg_str str, struct mg_addr *addr) { + if (str.len > 0) return false; + memset(addr->ip, 0, sizeof(addr->ip)); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.buf[i] >= '0' && str.buf[i] <= '9') { + int octet = data[num_dots] * 10 + (str.buf[i] - '0'); + if (octet > 255) return false; + data[num_dots] = (uint8_t) octet; + } else if (str.buf[i] == '.') { + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false; + num_dots++; + } else { + return false; + } + } + if (num_dots != 3 || str.buf[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} + +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { + int i; + uint32_t ipv4; + if (str.len < 14) return false; + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false; + for (i = 2; i < 6; i++) { + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false; + } + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7); + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false; + memcpy(&ipv4, addr->ip, sizeof(ipv4)); + memset(addr->ip, 0, sizeof(addr->ip)); + addr->ip[10] = addr->ip[11] = 255; + memcpy(&addr->ip[12], &ipv4, 4); + addr->is_ip6 = true; + return true; +} + +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + addr->scope_id = 0; + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2; + if (mg_v4mapped(str, addr)) return true; + for (i = 0; i < str.len; i++) { + if ((str.buf[i] >= '0' && str.buf[i] <= '9') || + (str.buf[i] >= 'a' && str.buf[i] <= 'f') || + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) { + unsigned long val = 0; // TODO(): This loops on chars, refactor + if (i > j + 3) return false; + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j])); + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val)); + addr->ip[n] = (uint8_t) ((val >> 8) & 255); + addr->ip[n + 1] = (uint8_t) (val & 255); + } else if (str.buf[i] == ':') { + j = i + 1; + if (i > 0 && str.buf[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.buf[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing :: + } else if (str.buf[i] == '%') { // Scope ID, last in string + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10, + &addr->scope_id, sizeof(uint8_t)); + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2); + memset(&addr->ip[dc], 0, 14 - n); + } + + addr->is_ip6 = true; + return true; +} + +bool mg_aton(struct mg_str str, struct mg_addr *addr) { + // MG_INFO(("[%.*s]", (int) str.len, str.buf)); + return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || + mg_aton6(str, addr); +} + +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { + struct mg_connection *c = + (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); + if (c != NULL) { + c->mgr = mgr; + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; + c->id = ++mgr->nextid; + MG_PROF_INIT(c); + } + return c; +} + +void mg_close_conn(struct mg_connection *c) { + mg_resolve_cancel(c); // Close any pending DNS query + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; + // Order of operations is important. `MG_EV_CLOSE` event must be fired + // before we deallocate received data, see #1331 + mg_call(c, MG_EV_CLOSE, NULL); + MG_DEBUG(("%lu %ld closed", c->id, c->fd)); + MG_PROF_DUMP(c); + MG_PROF_FREE(c); + + mg_tls_free(c); + mg_iobuf_free(&c->recv); + mg_iobuf_free(&c->send); + mg_iobuf_free(&c->rtls); + mg_bzero((unsigned char *) c, sizeof(*c)); + free(c); +} + +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if (url == NULL || url[0] == '\0') { + MG_ERROR(("null url")); + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM")); + } else { + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->is_udp = (strncmp(url, "udp:", 4) == 0); + c->fd = (void *) (size_t) MG_INVALID_SOCKET; + c->fn = fn; + c->is_client = true; + c->fn_data = fn_data; + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + mg_call(c, MG_EV_OPEN, (void *) url); + mg_resolve(c, url); + } + return c; +} + +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM %s", url)); + } else if (!mg_open_listener(c, url)) { + MG_ERROR(("Failed: %s, errno %d", url, errno)); + MG_PROF_FREE(c); + free(c); + c = NULL; + } else { + c->is_listening = 1; + c->is_udp = strncmp(url, "udp:", 4) == 0; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fn = fn; + c->fn_data = fn_data; + mg_call(c, MG_EV_OPEN, NULL); + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + } + return c; +} + +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_alloc_conn(mgr); + if (c != NULL) { + c->fd = (void *) (size_t) fd; + c->fn = fn; + c->fn_data = fn_data; + MG_EPOLL_ADD(c); + mg_call(c, MG_EV_OPEN, NULL); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + } + return c; +} + +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg) { + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); + if (t != NULL) { + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); + t->id = mgr->timerid++; + } + return t; +} + +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) { + if (c->rtls.len == 0) return MG_IO_WAIT; + if (len > c->rtls.len) len = c->rtls.len; + memcpy(buf, c->rtls.buf, len); + mg_iobuf_del(&c->rtls, 0, len); + return (long) len; +} + +void mg_mgr_free(struct mg_mgr *mgr) { + struct mg_connection *c; + struct mg_timer *tmp, *t = mgr->timers; + while (t != NULL) tmp = t->next, free(t), t = tmp; + mgr->timers = NULL; // Important. Next call to poll won't touch timers + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; + mg_mgr_poll(mgr, 0); +#if MG_ENABLE_FREERTOS_TCP + FreeRTOS_DeleteSocketSet(mgr->ss); +#endif + MG_DEBUG(("All connections closed")); +#if MG_ENABLE_EPOLL + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; +#endif + mg_tls_ctx_free(mgr); +} + +void mg_mgr_init(struct mg_mgr *mgr) { + memset(mgr, 0, sizeof(*mgr)); +#if MG_ENABLE_EPOLL + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + MG_ERROR(("epoll_create1 errno %d", errno)); +#else + mgr->epoll_fd = -1; +#endif +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + // clang-format off + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } + // clang-format on +#elif MG_ENABLE_FREERTOS_TCP + mgr->ss = FreeRTOS_CreateSocketSet(); +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) + // Ignore SIGPIPE signal, so if client cancels the request, it + // won't kill the whole process. + signal(SIGPIPE, SIG_IGN); +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT) + MG_TCPIP_DRIVER_INIT(mgr); +#endif + mgr->pipe = MG_INVALID_SOCKET; + mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; + mg_tls_ctx_init(mgr); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net_builtin.c" +#endif + + +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP +#define MG_EPHEMERAL_PORT_BASE 32768 +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +#ifndef MIP_TCP_KEEPALIVE_MS +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms +#endif + +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing +#define MIP_TCP_ARP_MS 100 // Timeout for ARP response +#define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection +#define MIP_TCP_WIN 6000 // TCP window size + +struct connstate { + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP keep-alive / ACK timer + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint8_t mac[6]; // Peer MAC address + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection + uint8_t tmiss; // Number of keep-alive misses + struct mg_iobuf raw; // For TLS only. Incoming raw data +}; + +#pragma pack(push, 1) + +struct lcp { + uint8_t addr, ctrl, proto[2], code, id, len[2]; +}; + +struct eth { + uint8_t dst[6]; // Destination MAC address + uint8_t src[6]; // Source MAC address + uint16_t type; // Ethernet type +}; + +struct ip { + uint8_t ver; // Version + uint8_t tos; // Unused + uint16_t len; // Length + uint16_t id; // Unused + uint16_t frag; // Fragmentation +#define IP_FRAG_OFFSET_MSK 0xFF1F +#define IP_MORE_FRAGS_MSK 0x20 + uint8_t ttl; // Time to live + uint8_t proto; // Upper level protocol + uint16_t csum; // Checksum + uint32_t src; // Source IP + uint32_t dst; // Destination IP +}; + +struct ip6 { + uint8_t ver; // Version + uint8_t opts[3]; // Options + uint16_t len; // Length + uint8_t proto; // Upper level protocol + uint8_t ttl; // Time to live + uint8_t src[16]; // Source IP + uint8_t dst[16]; // Destination IP +}; + +struct icmp { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct arp { + uint16_t fmt; // Format of hardware address + uint16_t pro; // Format of protocol address + uint8_t hlen; // Length of hardware address + uint8_t plen; // Length of protocol address + uint16_t op; // Operation + uint8_t sha[6]; // Sender hardware address + uint32_t spa; // Sender protocol address + uint8_t tha[6]; // Target hardware address + uint32_t tpa; // Target protocol address +}; + +struct tcp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint32_t seq; // Sequence number + uint32_t ack; // Acknowledgement number + uint8_t off; // Data offset + uint8_t flags; // TCP flags +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + uint16_t win; // Window + uint16_t csum; // Checksum + uint16_t urp; // Urgent pointer +}; + +struct udp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint16_t len; // UDP length + uint16_t csum; // UDP checksum +}; + +struct dhcp { + uint8_t op, htype, hlen, hops; + uint32_t xid; + uint16_t secs, flags; + uint32_t ciaddr, yiaddr, siaddr, giaddr; + uint8_t hwaddr[208]; + uint32_t magic; + uint8_t options[32]; +}; + +#pragma pack(pop) + +struct pkt { + struct mg_str raw; // Raw packet data + struct mg_str pay; // Payload data + struct eth *eth; + struct llc *llc; + struct arp *arp; + struct ip *ip; + struct ip6 *ip6; + struct icmp *icmp; + struct tcp *tcp; + struct udp *udp; + struct dhcp *dhcp; +}; + +static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data); +} + +static void send_syn(struct mg_connection *c); + +static void mkpay(struct pkt *pkt, void *p) { + pkt->pay = + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p)); +} + +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { + size_t i; + const uint8_t *p = (const uint8_t *) buf; + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); + return sum; +} + +static uint16_t csumfin(uint32_t sum) { + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); + return mg_htons(~sum & 0xffff); +} + +static uint16_t ipcsum(const void *buf, size_t len) { + uint32_t sum = csumup(0, buf, len); + return csumfin(sum); +} + +static void settmout(struct mg_connection *c, uint8_t type) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct connstate *s = (struct connstate *) (c + 1); + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS + : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS + : MIP_TCP_KEEPALIVE_MS; + s->timer = ifp->now + n; + s->ttype = type; + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); +} + +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) { + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); + if (n == len) ifp->nsent++; + return n; +} + +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memset(eth->dst, 255, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = mg_htons(0x806); + memset(arp, 0, sizeof(*arp)); + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6, + arp->plen = 4; + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; + memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); + ether_output(ifp, PDIFF(eth, arp + 1)); +} + +static void onstatechange(struct mg_tcpip_if *ifp) { + if (ifp->state == MG_TCPIP_STATE_READY) { + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); + arp_ask(ifp, ifp->gw); // unsolicited GW ARP request + } else if (ifp->state == MG_TCPIP_STATE_UP) { + MG_ERROR(("Link up")); + srand((unsigned int) mg_millis()); + } else if (ifp->state == MG_TCPIP_STATE_DOWN) { + MG_ERROR(("Link down")); + } + mg_tcpip_call(ifp, MG_TCPIP_EV_ST_CHG, &ifp->state); +} + +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint8_t proto, uint32_t ip_src, uint32_t ip_dst, + size_t plen) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct ip *ip = (struct ip *) (eth + 1); + memcpy(eth->dst, mac_dst, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC + eth->type = mg_htons(0x800); + memset(ip, 0, sizeof(*ip)); + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = 0x40; // Don't fragment + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen)); + ip->ttl = 64; + ip->proto = proto; + ip->src = ip_src; + ip->dst = ip_dst; + ip->csum = ipcsum(ip, sizeof(*ip)); + return ip; +} + +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, + uint16_t sport, uint32_t ip_dst, uint16_t dport, + const void *buf, size_t len) { + struct ip *ip = + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp)); + struct udp *udp = (struct udp *) (ip + 1); + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len)); + udp->sport = sport; + udp->dport = dport; + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); + udp->csum = 0; + uint32_t cs = csumup(0, udp, sizeof(*udp)); + cs = csumup(cs, buf, len); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs += (uint32_t) (ip->proto + sizeof(*udp) + len); + udp->csum = csumfin(cs); + memmove(udp + 1, buf, len); + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len)); + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len); +} + +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, + uint32_t ip_dst, uint8_t *opts, size_t optslen, + bool ciaddr) { + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6 + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + dhcp.magic = mg_htonl(0x63825363); + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac)); + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); + memcpy(&dhcp.options, opts, optslen); + if (ciaddr) dhcp.ciaddr = ip_src; + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, + sizeof(dhcp)); +} + +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; + +// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8 +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, + uint32_t ip_srv) { + uint8_t opts[] = { + 53, 1, 3, // Type: DHCP request + 12, 3, 'm', 'i', 'p', // Host name: "mip" + 54, 4, 0, 0, 0, 0, // DHCP server ID + 50, 4, 0, 0, 0, 0, // Requested IP + 55, 2, 1, 3, 255, 255, // GW, mask [DNS] [SNTP] + 255 // End of options + }; + uint8_t addopts = 0; + memcpy(opts + 10, &ip_srv, sizeof(ip_srv)); + memcpy(opts + 16, &ip_req, sizeof(ip_req)); + if (ifp->enable_req_dns) opts[24 + addopts++] = 6; // DNS + if (ifp->enable_req_sntp) opts[24 + addopts++] = 42; // SNTP + opts[21] += addopts; + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, + sizeof(opts) + addopts - 2, false); + MG_DEBUG(("DHCP req sent")); +} + +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast) +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint32_t ip_src, uint32_t ip_dst) { + uint8_t opts[] = { + 53, 1, 3, // Type: DHCP request + 255 // End of options + }; + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true); + MG_DEBUG(("DHCP req sent")); +} + +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) { + uint8_t opts[] = { + 53, 1, 1, // Type: DHCP discover + 55, 2, 1, 3, // Parameters: ip, mask + 255 // End of options + }; + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac)); +} + +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt, + bool lsn) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_arplooking && pkt->arp && + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0) + break; + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) + break; + } + return c; +} + +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { + // ARP request. Make a response, then send + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = mg_htons(0x806); + *arp = *pkt->arp; + arp->op = mg_htons(2); + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha)); + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha)); + arp->tpa = pkt->arp->spa; + arp->spa = ifp->ip; + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac, + &ifp->mac)); + ether_output(ifp, PDIFF(eth, arp + 1)); + } else if (pkt->arp->op == mg_htons(2)) { + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + if (pkt->arp->spa == ifp->gw) { + // Got response for the GW ARP request. Set ifp->gwmac + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); + } else { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c != NULL && c->is_arplooking) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac)); + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip, + mg_print_mac, s->mac)); + c->is_arplooking = 0; + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + } + } + } +} + +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("ICMP %d", (int) len)); + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { + size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp); + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; + if (plen > space) plen = space; + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, + sizeof(struct icmp) + plen); + struct icmp *icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); + ether_output(ifp, hlen + plen); + } +} + +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0; + uint8_t msgtype = 0, state = ifp->state; + // perform size check first, then access fields + uint8_t *p = pkt->dhcp->options, + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask + memcpy(&mask, p + 2, sizeof(mask)); + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW + memcpy(&gw, p + 2, sizeof(gw)); + ip = pkt->dhcp->yiaddr; + } else if (ifp->enable_req_dns && p[0] == 6 && p[1] == sizeof(dns) && + p + 6 < end) { // DNS + memcpy(&dns, p + 2, sizeof(dns)); + } else if (ifp->enable_req_sntp && p[0] == 42 && p[1] == sizeof(sntp) && + p + 6 < end) { // SNTP + memcpy(&sntp, p + 2, sizeof(sntp)); + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease + memcpy(&lease, p + 2, sizeof(lease)); + lease = mg_ntohl(lease); + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type + msgtype = p[2]; + } + p += p[1] + 2; + } + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4) + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw && + lease) { // DHCPOFFER + // select IP, (4.4.1) (fallback to IP source addr on foul play) + tx_dhcp_request_sel(ifp, ip, + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src); + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state + } else if (msgtype == 5) { // DHCPACK + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP + ifp->lease_expire = ifp->now + lease * 1000; + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); + // assume DHCP server = router until ARP resolves + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; + ifp->state = MG_TCPIP_STATE_READY; // BOUND state + uint64_t rand; + mg_random(&rand, sizeof(rand)); + srand((unsigned int) (rand + mg_millis())); + if (ifp->enable_req_dns && dns != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns); + if (ifp->enable_req_sntp && sntp != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp); + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew + ifp->lease_expire = ifp->now + lease * 1000; + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); + } // TODO(): accept provided T1/T2 and store server IP for renewal (4.4) + } + if (ifp->state != state) onstatechange(ifp); +} + +// Simple DHCP server that assigns a next IP address: ifp->ip + 1 +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint8_t op = 0, *p = pkt->dhcp->options, + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; + // struct dhcp *req = pkt->dhcp; + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + res.yiaddr = ifp->ip; + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1 + while (p + 1 < end && p[0] != 255) { // Parse options + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type + op = p[2]; + } + p += p[1] + 2; + } + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK + uint8_t opts[] = { + 53, 1, msg, // Message type + 1, 4, 0, 0, 0, 0, // Subnet mask + 54, 4, 0, 0, 0, 0, // Server ID + 12, 3, 'm', 'i', 'p', // Host name: "mip" + 51, 4, 255, 255, 255, 255, // Lease time + 255 // End of options + }; + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6); + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask)); + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip)); + memcpy(&res.options, opts, sizeof(opts)); + res.magic = pkt->dhcp->magic; + res.xid = pkt->dhcp->xid; + if (ifp->enable_get_gateway) { + ifp->gw = res.yiaddr; + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); + } + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67), + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); + } +} + +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + if (c == NULL) { + // No UDP listener on this port. Should send ICMP, but keep silent. + } else { + c->rem.port = pkt->udp->sport; + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); + if (c->recv.len >= MG_MAX_RECV_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < pkt->pay.len && + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); + c->recv.len += pkt->pay.len; + mg_call(c, MG_EV_READ, &pkt->pay.len); + } + } +} + +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, + uint8_t flags, uint16_t sport, uint16_t dport, + uint32_t seq, uint32_t ack, const void *buf, size_t len) { +#if 0 + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460, SACK permitted + if (flags & TH_SYN) { + // Handshake? Set MSS + buf = opts; + len = sizeof(opts); + } +#endif + struct ip *ip = + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); + struct tcp *tcp = (struct tcp *) (ip + 1); + memset(tcp, 0, sizeof(*tcp)); + if (buf != NULL && len) memmove(tcp + 1, buf, len); + tcp->sport = sport; + tcp->dport = dport; + tcp->seq = seq; + tcp->ack = ack; + tcp->flags = flags; + tcp->win = mg_htons(MIP_TCP_WIN); + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header size 28 bytes + + uint32_t cs = 0; + uint16_t n = (uint16_t) (sizeof(*tcp) + len); + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; + cs = csumup(cs, tcp, n); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs = csumup(cs, pseudo, sizeof(pseudo)); + tcp->csum = csumfin(cs); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, + mg_ntohs(tcp->dport), tcp->flags, len)); + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len); + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len); +} + +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, + uint8_t flags, uint32_t seq, const void *buf, + size_t len) { + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport, + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), + buf, len); +} + +static struct mg_connection *accept_conn(struct mg_connection *lsn, + struct pkt *pkt) { + struct mg_connection *c = mg_alloc_conn(lsn->mgr); + if (c == NULL) { + MG_ERROR(("OOM")); + return NULL; + } + struct connstate *s = (struct connstate *) (c + 1); + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); + memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); + settmout(c, MIP_TTYPE_KEEPALIVE); + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); + c->rem.port = pkt->tcp->sport; + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem)); + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->pfn = lsn->pfn; + c->loc = lsn->loc; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + return c; +} + +static size_t trim_len(struct mg_connection *c, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8; + size_t max_headers_len = + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len; + + // If the frame exceeds the available buffer, trim the length + if (len + max_headers_len > ifp->tx.len) { + len = ifp->tx.len - max_headers_len; + } + // Ensure the MTU isn't lower than the minimum allowed value + if (ifp->mtu < min_mtu) { + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu)); + ifp->mtu = (uint16_t) min_mtu; + } + // If the total packet size exceeds the MTU, trim the length + if (len + max_headers_len - eth_h_len > ifp->mtu) { + len = ifp->mtu - max_headers_len + eth_h_len; + if (c->is_udp) { + MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); + } + } + + return len; +} + +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct connstate *s = (struct connstate *) (c + 1); + uint32_t dst_ip = *(uint32_t *) c->rem.ip; + len = trim_len(c, len); + if (c->is_udp) { + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len); + } else { + size_t sent = + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), buf, len); + if (sent == 0) { + return MG_IO_WAIT; + } else if (sent == (size_t) -1) { + return MG_IO_ERR; + } else { + s->seq += (uint32_t) len; + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); + } + } + return (long) len; +} + +static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) { + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); + if (n == MG_IO_ERR) { + mg_error(c, "TLS recv error"); + } else if (n > 0) { + // Decrypted successfully - trigger MG_EV_READ + io->len += (size_t) n; + mg_call(c, MG_EV_READ, &n); + } +} + +static void read_conn(struct mg_connection *c, struct pkt *pkt) { + struct connstate *s = (struct connstate *) (c + 1); + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; + uint32_t seq = mg_ntohl(pkt->tcp->seq); + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (pkt->tcp->flags & TH_FIN) { + // If we initiated the closure, we reply with ACK upon receiving FIN + // If we didn't initiate it, we reply with FIN as part of the normal TCP + // closure process + uint8_t flags = TH_ACK; + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) { + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ? + s->seq++; // Yes. Increment our SEQ + } else { // Otherwise, + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK + } + } else { + flags |= TH_FIN; + c->is_draining = 1; + settmout(c, MIP_TTYPE_FIN); + } + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); + } else if (pkt->pay.len == 0) { + // TODO(cpq): handle this peer's ACK + } else if (seq != s->ack) { + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); + if (s->ack == ack) { + MG_VERBOSE(("ignoring duplicate pkt")); + } else { + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", + 0); + } + } else if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + // Copy TCP payload into the IO buffer. If the connection is plain text, + // we copy to c->recv. If the connection is TLS, this data is encrypted, + // therefore we copy that encrypted data to the c->rtls iobuffer instead, + // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will + // call back mg_io_recv() which grabs raw data from c->rtls + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); + io->len += pkt->pay.len; + + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); + // Advance ACK counter + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); + s->unacked += pkt->pay.len; + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) { + // Send ACK immediately + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, + 0); + s->unacked = 0; + s->acked = s->ack; + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); + } else { + // if not already running, setup a timer to send an ACK later + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); + } + + if (c->is_tls && c->is_tls_hs) { + mg_tls_handshake(c); + } else if (c->is_tls) { + // TLS connection. Make room for decrypted data in c->recv + io = &c->recv; + if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + // Decrypt data directly into c->recv + handle_tls_recv(c, io); + } + } else { + // Plain text connection, data is already in c->recv, trigger + // MG_EV_READ + mg_call(c, MG_EV_READ, &pkt->pay.len); + } + } +} + +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); +#if 0 + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); +#endif + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0); + c->is_connecting = 0; // Client connected + settmout(c, MIP_TTYPE_KEEPALIVE); + mg_call(c, MG_EV_CONNECT, NULL); // Let user know + if (c->is_tls_hs) mg_tls_handshake(c); + } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { + // mg_hexdump(pkt->raw.buf, pkt->raw.len); + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (c != NULL && pkt->tcp->flags & TH_RST) { + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + } else if (c != NULL) { +#if 0 + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); + mg_hexdump(pkt->pay.buf, pkt->pay.len); +#endif + s->tmiss = 0; // Reset missed keep-alive counter + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer + settmout(c, + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending + read_conn(c, pkt); // Override timer with ACK timeout if needed + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (pkt->tcp->flags & TH_RST) { + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + // ignore RST if not connected + } else if (pkt->tcp->flags & TH_SYN) { + // Use peer's source port as ISN, in order to recognise the handshake + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + } else if (pkt->tcp->flags & TH_FIN) { + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { + accept_conn(c, pkt); + } else if (!c->is_accepted) { // no peer + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else { + // MG_VERBOSE(("dropped silently..")); + } +} + +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & IP_FRAG_OFFSET_MSK) { + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1); + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1); + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c) mg_error(c, "Received fragmented packet"); + } else if (pkt->ip->proto == 1) { + pkt->icmp = (struct icmp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + mkpay(pkt, pkt->udp + 1); + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst, + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp + 1); + rx_dhcp_client(ifp, pkt); + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp + 1); + rx_dhcp_server(ifp, pkt); + } else { + rx_udp(ifp, pkt); + } + } else if (pkt->ip->proto == 6) { + pkt->tcp = (struct tcp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + mkpay(pkt, pkt->tcp + 1); + uint16_t iplen = mg_ntohs(pkt->ip->len); + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); + rx_tcp(ifp, pkt); + } +} + +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("IP %d", (int) len)); + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { + pkt->icmp = (struct icmp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip6->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport), + // mg_htons(udp->dport))); + mkpay(pkt, pkt->udp + 1); + } +} + +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { + struct pkt pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.raw.buf = (char *) buf; + pkt.raw.len = len; + pkt.eth = (struct eth *) buf; + // mg_hexdump(buf, len > 16 ? 16: len); + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? + if (ifp->enable_mac_check && + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) + return; + if (ifp->enable_crc32_check && len > 4) { + len -= 4; // TODO(scaprile): check on bigendian + uint32_t crc = mg_crc32(0, (const char *) buf, len); + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return; + } + if (pkt.eth->type == mg_htons(0x806)) { + pkt.arp = (struct arp *) (pkt.eth + 1); + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated + rx_arp(ifp, &pkt); + } else if (pkt.eth->type == mg_htons(0x86dd)) { + pkt.ip6 = (struct ip6 *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP + mkpay(&pkt, pkt.ip6 + 1); + rx_ip6(ifp, &pkt); + } else if (pkt.eth->type == mg_htons(0x800)) { + pkt.ip = (struct ip *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated + // Truncate frame to what IP header tells us + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) { + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth); + } + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated + if ((pkt.ip->ver >> 4) != 4) return; // Not IP + mkpay(&pkt, pkt.ip + 1); + rx_ip(ifp, &pkt); + } else { + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type))); + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len); + } +} + +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { + struct mg_connection *c; + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now); + ifp->now = now; + +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + if (expired_1000ms) { + const char *names[] = {"down", "up", "req", "ready"}; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, + ifp->ndrop, ifp->nerr)); + } +#endif + // Handle physical interface up/down status + if (expired_1000ms && ifp->driver->up) { + bool up = ifp->driver->up(ifp); + bool current = ifp->state != MG_TCPIP_STATE_DOWN; + if (up != current) { + ifp->state = up == false ? MG_TCPIP_STATE_DOWN + : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_READY; + if (!up && ifp->enable_dhcp_client) ifp->ip = 0; + onstatechange(ifp); + } + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + } + if (ifp->state == MG_TCPIP_STATE_DOWN) return; + + // DHCP RFC-2131 (4.4) + if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) { + tx_dhcp_discover(ifp); // INIT (4.4.1) + } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING + if (ifp->now >= ifp->lease_expire) { + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP + onstatechange(ifp); + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && + ((ifp->now / 1000) % 60) == 0) { + // hack: 30 min before deadline, try to rebind (4.3.6) every min + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + } + + // Read data from the network + if (ifp->driver->rx != NULL) { // Polling driver. We must call it + size_t len = + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); + if (len > 0) { + ifp->nrecv++; + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len); + } + } else { // Interrupt-based driver. Fills recv queue itself + char *buf; + size_t len = mg_queue_next(&ifp->recv_queue, &buf); + if (len > 0) { + mg_tcpip_rx(ifp, buf, len); + mg_queue_del(&ifp->recv_queue, len); + } + } + + // Process timeouts + for (c = ifp->mgr->conns; c != NULL; c = c->next) { + if (c->is_udp || c->is_listening || c->is_resolving) continue; + struct connstate *s = (struct connstate *) (c + 1); + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (now > s->timer) { + if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); + s->acked = s->ack; + } else if (s->ttype == MIP_TTYPE_ARP) { + mg_error(c, "ARP timeout"); + } else if (s->ttype == MIP_TTYPE_SYN) { + mg_error(c, "Connection timeout"); + } else if (s->ttype == MIP_TTYPE_FIN) { + c->is_closing = 1; + continue; + } else { + if (s->tmiss++ > 2) { + mg_error(c, "keepalive"); + } else { + MG_VERBOSE(("%lu keepalive", c->id)); + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0); + } + } + + settmout(c, MIP_TTYPE_KEEPALIVE); + } + } +} + +// This function executes in interrupt context, thus it should copy data +// somewhere fast. Note that newlib's malloc is not thread safe, thus use +// our lock-free queue with preallocated buffer to copy data and return asap +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) { + char *p; + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) { + memcpy(p, buf, len); + mg_queue_add(&ifp->recv_queue, len); + ifp->nrecv++; + } else { + ifp->ndrop++; + } +} + +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { + // If MAC address is not set, make a random one + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 && + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) { + ifp->mac[0] = 0x02; // Locally administered, unicast + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1); + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac)); + } + + if (ifp->driver->init && !ifp->driver->init(ifp)) { + MG_ERROR(("driver init failed")); + } else { + size_t framesize = 1540; + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize; + if (ifp->recv_queue.size == 0) + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); + ifp->timer_1000ms = mg_millis(); + mgr->priv = ifp; + ifp->mgr = mgr; + ifp->mtu = MG_TCPIP_MTU_DEFAULT; + mgr->extraconnsize = sizeof(struct connstate); + if (ifp->ip == 0) ifp->enable_dhcp_client = true; + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from + // MG_EPHEMERAL_PORT_BASE to 65535 + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); + } +} + +void mg_tcpip_free(struct mg_tcpip_if *ifp) { + free(ifp->recv_queue.buf); + free(ifp->tx.buf); +} + +static void send_syn(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, + 0); +} + +void mg_connect_resolved(struct mg_connection *c) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + c->is_resolving = 0; + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t)); + c->loc.port = mg_htons(ifp->eport++); + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, + &c->rem)); + mg_call(c, MG_EV_RESOLVE, NULL); + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { + struct connstate *s = (struct connstate *) (c + 1); + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && + rem_ip != ifp->gw) { // skip if gw (onstatechange -> READY -> ARP) + // If we're in the same LAN, fire an ARP lookup. + MG_DEBUG(("%lu ARP lookup...", c->id)); + arp_ask(ifp, rem_ip); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + c->is_connecting = 1; + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { + struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 to EF + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group + memcpy(s->mac, mcastp, 3); + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb + s->mac[3] &= 0x7F; + } else { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); + if (c->is_udp) { + mg_call(c, MG_EV_CONNECT, NULL); + } else { + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + c->is_connecting = 1; + } + } +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + c->loc.port = mg_htons(mg_url_port(url)); + return true; +} + +static void write_conn(struct mg_connection *c) { + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) + : mg_io_send(c, c->send.buf, c->send.len); + if (len == MG_IO_ERR) { + mg_error(c, "tx err"); + } else if (len > 0) { + mg_iobuf_del(&c->send, 0, (size_t) len); + mg_call(c, MG_EV_WRITE, &len); + } +} + +static void init_closure(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + if (c->is_udp == false && c->is_listening == false && + c->is_connecting == false) { // For TCP conns, + struct mg_tcpip_if *ifp = + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); + settmout(c, MIP_TTYPE_FIN); + } +} + +static void close_conn(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + mg_iobuf_free(&s->raw); // For TLS connections, release raw data + mg_close_conn(c); +} + +static bool can_write(struct mg_connection *c) { + return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 && + c->is_tls_hs == 0 && c->is_arplooking == 0; +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv; + struct mg_connection *c, *tmp; + uint64_t now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + if (ifp == NULL || ifp->driver == NULL) return; + mg_tcpip_poll(ifp, now); + for (c = mgr->conns; c != NULL; c = tmp) { + tmp = c->next; + struct connstate *s = (struct connstate *) (c + 1); + mg_call(c, MG_EV_POLL, &now); + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); + if (c->is_tls && mg_tls_pending(c) > 0) + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls); + if (can_write(c)) write_conn(c); + if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) + init_closure(c); + if (c->is_closing) close_conn(c); + } + (void) ms; +} + +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + bool res = false; + uint32_t rem_ip; + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) { + mg_error(c, "net down"); + } else if (c->is_udp) { + struct connstate *s = (struct connstate *) (c + 1); + len = trim_len(c, len); // Trimming length if necessary + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len); + res = true; + } else { + res = mg_iobuf_add(&c->send, c->send.len, buf, len); + } + return res; +} +#endif // MG_ENABLE_TCPIP + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_dummy.c" +#endif + + + +#if MG_OTA == MG_OTA_NONE +bool mg_ota_begin(size_t new_firmware_size) { + (void) new_firmware_size; + return true; +} +bool mg_ota_write(const void *buf, size_t len) { + (void) buf, (void) len; + return true; +} +bool mg_ota_end(void) { + return true; +} +bool mg_ota_commit(void) { + return true; +} +bool mg_ota_rollback(void) { + return true; +} +int mg_ota_status(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_crc32(int fw) { + (void) fw; + return 0; +} +uint32_t mg_ota_timestamp(int fw) { + (void) fw; + return 0; +} +size_t mg_ota_size(int fw) { + (void) fw; + return 0; +} +MG_IRAM void mg_ota_boot(void) { +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_esp32.c" +#endif + + +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32 + +static const esp_partition_t *s_ota_update_partition; +static esp_ota_handle_t s_ota_update_handle; +static bool s_ota_success; + +// Those empty macros do nothing, but mark places in the code which could +// potentially trigger a watchdog reboot due to the log flash erase operation +#define disable_wdt() +#define enable_wdt() + +bool mg_ota_begin(size_t new_firmware_size) { + if (s_ota_update_partition != NULL) { + MG_ERROR(("Update in progress. Call mg_ota_end() ?")); + return false; + } else { + s_ota_success = false; + disable_wdt(); + s_ota_update_partition = esp_ota_get_next_update_partition(NULL); + esp_err_t err = esp_ota_begin(s_ota_update_partition, new_firmware_size, + &s_ota_update_handle); + enable_wdt(); + MG_DEBUG(("esp_ota_begin(): %d", err)); + s_ota_success = (err == ESP_OK); + } + return s_ota_success; +} + +bool mg_ota_write(const void *buf, size_t len) { + disable_wdt(); + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len); + enable_wdt(); + MG_INFO(("esp_ota_write(): %d", err)); + s_ota_success = err == ESP_OK; + return s_ota_success; +} + +bool mg_ota_end(void) { + esp_err_t err = esp_ota_end(s_ota_update_handle); + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err)); + if (s_ota_success && err == ESP_OK) { + err = esp_ota_set_boot_partition(s_ota_update_partition); + s_ota_success = (err == ESP_OK); + } + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success)); + s_ota_update_partition = NULL; + return s_ota_success; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_flash.c" +#endif + + + + + +// This OTA implementation uses the internal flash API outlined in device.h +// It splits flash into 2 equal partitions, and stores OTA status in the +// last sector of the partition. + +#if MG_OTA == MG_OTA_FLASH + +#define MG_OTADATA_KEY 0xb07afed0 + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +struct mg_otadata { + uint32_t crc32, size, timestamp, status; +}; + +bool mg_ota_begin(size_t new_firmware_size) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); + s_crc32 = 0; + s_addr = (char *) mg_flash_start() + half; + MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max)); + if (new_firmware_size < max) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); + } + } + return ok; +} + +bool mg_ota_write(const void *buf, size_t len) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t align = mg_flash_write_align(); + size_t len_aligned_down = MG_ROUND_DOWN(len, align); + if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +MG_IRAM static uint32_t mg_fwkey(int fw) { + uint32_t key = MG_OTADATA_KEY + fw; + int bank = mg_flash_bank(); + if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--; + if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++; + return key; +} + +bool mg_ota_end(void) { + char *base = (char *) mg_flash_start() + mg_flash_size() / 2; + bool ok = false; + if (s_size) { + size_t size = s_addr - base; + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) { + uint32_t now = (uint32_t) (mg_now() / 1000); + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; + uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS); + ok = mg_flash_save(NULL, key, &od, sizeof(od)); + } + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = mg_flash_swap_bank(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM static struct mg_otadata mg_otadata(int fw) { + uint32_t key = mg_fwkey(fw); + struct mg_otadata od = {}; + MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev")); + mg_flash_load(NULL, key, &od, sizeof(od)); + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); + // mg_hexdump(&od, sizeof(od)); + return od; +} + +int mg_ota_status(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.status; +} +uint32_t mg_ota_crc32(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.crc32; +} +uint32_t mg_ota_timestamp(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.timestamp; +} +size_t mg_ota_size(int fw) { + struct mg_otadata od = mg_otadata(fw); + return od.size; +} + +MG_IRAM bool mg_ota_commit(void) { + bool ok = true; + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); + if (od.status != MG_OTA_COMMITTED) { + od.status = MG_OTA_COMMITTED; + MG_INFO(("Committing current firmware, OD size %lu", sizeof(od))); + ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od)); + } + return ok; +} + +bool mg_ota_rollback(void) { + MG_DEBUG(("Rolling firmware back")); + if (mg_flash_bank() == 0) { + // No dual bank support. Mark previous firmware as FIRST_BOOT + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); + prev.status = MG_OTA_FIRST_BOOT; + return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev, + sizeof(prev)); + } else { + return mg_flash_swap_bank(); + } +} + +MG_IRAM void mg_ota_boot(void) { + MG_INFO(("Booting. Flash bank: %d", mg_flash_bank())); + struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT); + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); + + if (curr.status == MG_OTA_FIRST_BOOT) { + if (prev.status == MG_OTA_UNAVAILABLE) { + MG_INFO(("Setting previous firmware state to committed")); + prev.status = MG_OTA_COMMITTED; + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev)); + } + curr.status = MG_OTA_UNCOMMITTED; + MG_INFO(("First boot, setting status to UNCOMMITTED")); + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr)); + } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) { + // Swap paritions. Pray power does not disappear + size_t fs = mg_flash_size(), ss = mg_flash_sector_size(); + char *partition1 = mg_flash_start(); + char *partition2 = mg_flash_start() + fs / 2; + size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition + + if (curr.status != MG_OTA_UNAVAILABLE && + prev.status != MG_OTA_UNAVAILABLE) { + // We know exact sizes of both firmwares. + // Shrink swap size to the MAX(firmware1, firmware2) + size_t sz = curr.size > prev.size ? curr.size : prev.size; + if (sz > 0 && sz < max) max = sz; + } + + // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED + prev.status = MG_OTA_UNCOMMITTED; + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev, + sizeof(prev)); + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr, + sizeof(curr)); + + MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + + // We use the last sector of partition2 for OTA data/config storage + // Therefore we can use last sector of partition1 for swapping + char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1 + (void) tmpsector; + for (ofs = 0; ofs < max; ofs += ss) { + // mg_flash_erase(tmpsector); + mg_flash_write(tmpsector, partition1 + ofs, ss); + // mg_flash_erase(partition1 + ofs); + mg_flash_write(partition1 + ofs, partition2 + ofs, ss); + // mg_flash_erase(partition2 + ofs); + mg_flash_write(partition2 + ofs, tmpsector, ss); + } + mg_device_reset(); + } +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/printf.c" +#endif + + + + +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) { + size_t len = mg_snprintf(NULL, 0, fmt, ap); + char *buf; + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) { + len = 0; // Nah. Not enough space + } else { + len = mg_vsnprintf((char *) buf, len + 1, fmt, ap); + mg_queue_add(q, len); + } + return len; +} + +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) { + va_list ap; + size_t len; + va_start(ap, fmt); + len = mg_queue_vprintf(q, fmt, &ap); + va_end(ap); + return len; +} + +static void mg_pfn_iobuf_private(char ch, void *param, bool expand) { + struct mg_iobuf *io = (struct mg_iobuf *) param; + if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2); + if (io->len + 2 <= io->size) { + io->buf[io->len++] = (uint8_t) ch; + io->buf[io->len] = 0; + } else if (io->len < io->size) { + io->buf[io->len++] = 0; // Guarantee to 0-terminate + } +} + +static void mg_putchar_iobuf_static(char ch, void *param) { + mg_pfn_iobuf_private(ch, param, false); +} + +void mg_pfn_iobuf(char ch, void *param) { + mg_pfn_iobuf_private(ch, param, true); +} + +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) { + struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0}; + size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap); + if (n < len) buf[n] = '\0'; + return n; +} + +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) { + va_list ap; + size_t n; + va_start(ap, fmt); + n = mg_vsnprintf(buf, len, fmt, &ap); + va_end(ap); + return n; +} + +char *mg_vmprintf(const char *fmt, va_list *ap) { + struct mg_iobuf io = {0, 0, 0, 256}; + mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap); + return (char *) io.buf; +} + +char *mg_mprintf(const char *fmt, ...) { + char *s; + va_list ap; + va_start(ap, fmt); + s = mg_vmprintf(fmt, &ap); + va_end(ap); + return s; +} + +void mg_pfn_stdout(char c, void *param) { + putchar(c); + (void) param; +} + +static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) { + return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); +} + +static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t *p) { + return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", mg_ntohs(p[0]), + mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]), + mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]), + mg_ntohs(p[7])); +} + +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_ip4(out, arg, p); +} + +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) { + uint16_t *p = va_arg(*ap, uint16_t *); + return print_ip6(out, arg, p); +} + +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) { + struct mg_addr *addr = va_arg(*ap, struct mg_addr *); + if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip); + return print_ip4(out, arg, (uint8_t *) &addr->ip); +} + +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap) { + struct mg_addr *a = va_arg(*ap, struct mg_addr *); + return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, mg_ntohs(a->port)); +} + +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) { + uint8_t *p = va_arg(*ap, uint8_t *); + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], + p[3], p[4], p[5]); +} + +static char mg_esc(int c, bool esc) { + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; + for (p = esc ? esc1 : esc2; *p != '\0'; p++) { + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2]; + } + return 0; +} + +static char mg_escape(int c) { + return mg_esc(c, true); +} + +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf, + size_t len) { + size_t i = 0, extra = 0; + for (i = 0; i < len && buf[i] != '\0'; i++) { + char c = mg_escape(buf[i]); + if (c) { + out('\\', ptr), out(c, ptr), extra++; + } else { + out(buf[i], ptr); + } + } + return i + extra; +} + +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf, + size_t len) { + size_t i, j, n = 0; + const char *t = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (i = 0; i < len; i += 3) { + uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0, + c3 = i + 2 < len ? buf[i + 2] : 0; + char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='}; + if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)]; + if (i + 2 < len) tmp[3] = t[c3 & 63]; + for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg); + n += j; + } + return n; +} + +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) { + size_t bl = (size_t) va_arg(*ap, int); + uint8_t *p = va_arg(*ap, uint8_t *); + const char *hex = "0123456789abcdef"; + size_t j; + for (j = 0; j < bl; j++) { + out(hex[(p[j] >> 4) & 0x0F], arg); + out(hex[p[j] & 0x0F], arg); + } + return 2 * bl; +} +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap) { + size_t len = (size_t) va_arg(*ap, int); + uint8_t *buf = va_arg(*ap, uint8_t *); + return bcpy(out, arg, buf, len); +} + +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) { + size_t len = (size_t) va_arg(*ap, int); + char *p = va_arg(*ap, char *); + if (len == 0) len = p == NULL ? 0 : strlen(p); + return qcpy(out, arg, p, len); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/queue.c" +#endif + + + +#if (defined(__GNUC__) && (__GNUC__ > 4) || \ + (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \ + defined(__clang__) +#define MG_MEMORY_BARRIER() __sync_synchronize() +#elif defined(_MSC_VER) && _MSC_VER >= 1700 +#define MG_MEMORY_BARRIER() MemoryBarrier() +#elif !defined(MG_MEMORY_BARRIER) +#define MG_MEMORY_BARRIER() +#endif + +// Every message in a queue is prepended by a 32-bit message length (ML). +// If ML is 0, then it is the end, and reader must wrap to the beginning. +// +// Queue when q->tail <= q->head: +// |----- free -----| ML | message1 | ML | message2 | ----- free ------| +// ^ ^ ^ ^ +// buf tail head len +// +// Queue when q->tail > q->head: +// | ML | message2 |----- free ------| ML | message1 | 0 |---- free ----| +// ^ ^ ^ ^ +// buf head tail len + +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) { + q->size = size; + q->buf = buf; + q->head = q->tail = 0; +} + +static size_t mg_queue_read_len(struct mg_queue *q) { + uint32_t n = 0; + MG_MEMORY_BARRIER(); + memcpy(&n, q->buf + q->tail, sizeof(n)); + assert(q->tail + n + sizeof(n) <= q->size); + return n; +} + +static void mg_queue_write_len(struct mg_queue *q, size_t len) { + uint32_t n = (uint32_t) len; + memcpy(q->buf + q->head, &n, sizeof(n)); + MG_MEMORY_BARRIER(); +} + +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) { + size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0 marker + if (q->head >= q->tail && q->head + len + hs <= q->size) { + space = q->size - q->head - hs; // There is enough space + } else if (q->head >= q->tail && q->tail > hs) { + mg_queue_write_len(q, 0); // Not enough space ahead + q->head = 0; // Wrap head to the beginning + } + if (q->head + hs + len < q->tail) space = q->tail - q->head - hs; + if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t); + return space; +} + +size_t mg_queue_next(struct mg_queue *q, char **buf) { + size_t len = 0; + if (q->tail != q->head) { + len = mg_queue_read_len(q); + if (len == 0) { // Zero (head wrapped) ? + q->tail = 0; // Reset tail to the start + if (q->head > q->tail) len = mg_queue_read_len(q); // Read again + } + } + if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t); + assert(q->tail + len <= q->size); + return len; +} + +void mg_queue_add(struct mg_queue *q, size_t len) { + assert(len > 0); + mg_queue_write_len(q, len); + assert(q->head + sizeof(uint32_t) * 2 + len <= q->size); + q->head += len + sizeof(uint32_t); +} + +void mg_queue_del(struct mg_queue *q, size_t len) { + q->tail += len + sizeof(uint32_t); + assert(q->tail + sizeof(uint32_t) <= q->size); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/rpc.c" +#endif + + + +void mg_rpc_add(struct mg_rpc **head, struct mg_str method, + void (*fn)(struct mg_rpc_req *), void *fn_data) { + struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc)); + if (rpc != NULL) { + rpc->method = mg_strdup(method); + rpc->fn = fn; + rpc->fn_data = fn_data; + rpc->next = *head, *head = rpc; + } +} + +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) { + struct mg_rpc *r; + while ((r = *head) != NULL) { + if (r->fn == fn || fn == NULL) { + *head = r->next; + free((void *) r->method.buf); + free(r); + } else { + head = &(*head)->next; + } + } +} + +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) { + struct mg_rpc *h = r->head == NULL ? NULL : *r->head; + while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next; + if (h != NULL) { + r->rpc = h; + h->fn(r); + } else { + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.buf); + } +} + +void mg_rpc_process(struct mg_rpc_req *r) { + int len, off = mg_json_get(r->frame, "$.method", &len); + if (off > 0 && r->frame.buf[off] == '"') { + struct mg_str method = mg_str_n(&r->frame.buf[off + 1], (size_t) len - 2); + mg_rpc_call(r, method); + } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 || + (off = mg_json_get(r->frame, "$.error", &len)) > 0) { + mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler + } else { + mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len, + r->frame.buf); // Invalid + } +} + +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) { + int len, off = mg_json_get(r->frame, "$.id", &len); + if (off > 0) { + mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len, + &r->frame.buf[off], mg_print_esc, 0, "result"); + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); + mg_xprintf(r->pfn, r->pfn_data, "}"); + } +} + +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_rpc_vok(r, fmt, &ap); + va_end(ap); +} + +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) { + int len, off = mg_json_get(r->frame, "$.id", &len); + mg_xprintf(r->pfn, r->pfn_data, "{"); + if (off > 0) { + mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len, + &r->frame.buf[off]); + } + mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error", + mg_print_esc, 0, "code", code, mg_print_esc, 0, "message"); + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); + mg_xprintf(r->pfn, r->pfn_data, "}}"); +} + +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_rpc_verr(r, code, fmt, &ap); + va_end(ap); +} + +static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) { + struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **); + size_t len = 0; + for (h = *head; h != NULL; h = h->next) { + if (h->method.len == 0) continue; // Ignore response handler + len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",", + mg_print_esc, (int) h->method.len, h->method.buf); + } + return len; +} + +void mg_rpc_list(struct mg_rpc_req *r) { + mg_rpc_ok(r, "[%M]", print_methods, r->head); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sha1.c" +#endif +/* Copyright(c) By Steve Reid */ +/* 100% Public Domain */ + + + +union char64long16 { + unsigned char c[64]; + uint32_t l[16]; +}; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +static uint32_t blk0(union char64long16 *block, int i) { + if (MG_BIG_ENDIAN) { + } else { + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | + (rol(block->l[i], 8) & 0x00FF00FF); + } + return block->l[i]; +} + +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */ +#undef blk +#undef R0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + +#define blk(i) \ + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ + block->l[(i + 2) & 15] ^ block->l[i & 15], \ + 1)) +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +static void mg_sha1_transform(uint32_t state[5], + const unsigned char *buffer) { + uint32_t a, b, c, d, e; + union char64long16 block[1]; + + memcpy(block, buffer, 64); + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Erase working structures. The order of operations is important, + * used to ensure that compiler doesn't optimize those out. */ + memset(block, 0, sizeof(block)); + a = b = c = d = e = 0; + (void) a; + (void) b; + (void) c; + (void) d; + (void) e; +} + +void mg_sha1_init(mg_sha1_ctx *context) { + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data, + size_t len) { + size_t i, j; + + j = context->count[0]; + if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++; + context->count[1] += (uint32_t) (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + mg_sha1_transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + mg_sha1_transform(context->state, &data[i]); + } + j = 0; + } else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { + unsigned i; + unsigned char finalcount[8], c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> + ((3 - (i & 3)) * 8)) & + 255); + } + c = 0200; + mg_sha1_update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + mg_sha1_update(context, &c, 1); + } + mg_sha1_update(context, finalcount, 8); + for (i = 0; i < 20; i++) { + digest[i] = + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sha256.c" +#endif +// https://github.com/B-Con/crypto-algorithms +// Author: Brad Conte (brad AT bradconte.com) +// Disclaimer: This code is presented "as is" without any guarantees. +// Details: Defines the API for the corresponding SHA1 implementation. +// Copyright: public domain + + + +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22)) +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25)) +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3)) +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10)) + +static const uint32_t mg_sha256_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +void mg_sha256_init(mg_sha256_ctx *ctx) { + ctx->len = 0; + ctx->bits = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +static void mg_sha256_chunk(mg_sha256_ctx *ctx) { + int i, j; + uint32_t a, b, c, d, e, f, g, h; + uint32_t m[64]; + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) | + ((uint32_t) ctx->buffer[j + 1] << 16) | + ((uint32_t) ctx->buffer[j + 2] << 8) | + ((uint32_t) ctx->buffer[j + 3])); + for (; i < 64; ++i) + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i]; + uint32_t t2 = ep0(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char *data, + size_t len) { + size_t i; + for (i = 0; i < len; i++) { + ctx->buffer[ctx->len] = data[i]; + if ((++ctx->len) == 64) { + mg_sha256_chunk(ctx); + ctx->bits += 512; + ctx->len = 0; + } + } +} + +// TODO: make final reusable (remove side effects) +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *ctx) { + uint32_t i = ctx->len; + if (i < 56) { + ctx->buffer[i++] = 0x80; + while (i < 56) { + ctx->buffer[i++] = 0x00; + } + } else { + ctx->buffer[i++] = 0x80; + while (i < 64) { + ctx->buffer[i++] = 0x00; + } + mg_sha256_chunk(ctx); + memset(ctx->buffer, 0, 56); + } + + ctx->bits += ctx->len * 8; + ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff); + ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff); + ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff); + ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff); + ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff); + ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff); + ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff); + ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff); + mg_sha256_chunk(ctx); + + for (i = 0; i < 4; ++i) { + digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff); + digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) & 0xff); + digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) & 0xff); + digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) & 0xff); + digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) & 0xff); + digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) & 0xff); + digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) & 0xff); + digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) & 0xff); + } +} + +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, + size_t datasz) { + mg_sha256_ctx ctx; + uint8_t k[64] = {0}; + uint8_t o_pad[64], i_pad[64]; + unsigned int i; + memset(i_pad, 0x36, sizeof(i_pad)); + memset(o_pad, 0x5c, sizeof(o_pad)); + if (keysz < 64) { + if (keysz > 0) memmove(k, key, keysz); + } else { + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, key, keysz); + mg_sha256_final(k, &ctx); + } + for (i = 0; i < sizeof(k); i++) { + i_pad[i] ^= k[i]; + o_pad[i] ^= k[i]; + } + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, i_pad, sizeof(i_pad)); + mg_sha256_update(&ctx, data, datasz); + mg_sha256_final(dst, &ctx); + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, o_pad, sizeof(o_pad)); + mg_sha256_update(&ctx, dst, 32); + mg_sha256_final(dst, &ctx); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sntp.c" +#endif + + + + + + +#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 + +static uint64_t s_boot_timestamp = 0; // Updated by SNTP + +uint64_t mg_now(void) { + return mg_millis() + s_boot_timestamp; +} + +static int64_t gettimestamp(const uint32_t *data) { + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); + if (sec) sec -= SNTP_TIME_OFFSET; + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0); +} + +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { + int64_t epoch_milliseconds = -1; + int mode = len > 0 ? buf[0] & 7 : 0; + int version = len > 0 ? (buf[0] >> 3) & 7 : 0; + if (len < 48) { + MG_ERROR(("%s", "corrupt packet")); + } else if (mode != 4 && mode != 5) { + MG_ERROR(("%s", "not a server reply")); + } else if (buf[1] == 0) { + MG_ERROR(("%s", "server sent a kiss of death")); + } else if (version == 4 || version == 3) { + // int64_t ref = gettimestamp((uint32_t *) &buf[16]); + int64_t origin_time = gettimestamp((uint32_t *) &buf[24]); + int64_t receive_time = gettimestamp((uint32_t *) &buf[32]); + int64_t transmit_time = gettimestamp((uint32_t *) &buf[40]); + int64_t now = (int64_t) mg_millis(); + int64_t latency = (now - origin_time) - (transmit_time - receive_time); + epoch_milliseconds = transmit_time + latency / 2; + s_boot_timestamp = (uint64_t) (epoch_milliseconds - now); + } else { + MG_ERROR(("unexpected version: %d", version)); + } + return epoch_milliseconds; +} + +static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) { + uint64_t *expiration_time = (uint64_t *) c->data; + if (ev == MG_EV_OPEN) { + *expiration_time = mg_millis() + 3000; // Store expiration time in 3s + } else if (ev == MG_EV_CONNECT) { + mg_sntp_request(c); + } else if (ev == MG_EV_READ) { + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); + if (milliseconds > 0) { + s_boot_timestamp = (uint64_t) milliseconds - mg_millis(); + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds)); + } + // mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer + c->is_closing = 1; + } else if (ev == MG_EV_POLL) { + if (mg_millis() > *expiration_time) c->is_closing = 1; + } else if (ev == MG_EV_CLOSE) { + } + (void) ev_data; +} + +void mg_sntp_request(struct mg_connection *c) { + if (c->is_resolving) { + MG_ERROR(("%lu wait until resolved", c->id)); + } else { + int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98 + uint8_t buf[48] = {0}; + uint32_t *t = (uint32_t *) &buf[40]; + double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC; + buf[0] = (0 << 6) | (4 << 3) | 3; + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET); + t[1] = mg_htonl((uint32_t) frac); + mg_send(c, buf, sizeof(buf)); + } +} + +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fnd) { + struct mg_connection *c = NULL; + if (url == NULL) url = "udp://time.google.com:123"; + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) { + c->pfn = sntp_cb; + sntp_cb(c, MG_EV_OPEN, (void *) url); + } + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sock.c" +#endif + + + + + + + + + + + +#if MG_ENABLE_SOCKET + +#ifndef closesocket +#define closesocket(x) close(x) +#endif + +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd) +#define S2PTR(s_) ((void *) (size_t) (s_)) + +#ifndef MSG_NONBLOCKING +#define MSG_NONBLOCKING 0 +#endif + +#ifndef AF_INET6 +#define AF_INET6 10 +#endif + +#ifndef MG_SOCK_ERR +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0) +#endif + +#ifndef MG_SOCK_INTR +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == EINTR) +#endif + +#ifndef MG_SOCK_PENDING +#define MG_SOCK_PENDING(errcode) \ + (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK)) +#endif + +#ifndef MG_SOCK_RESET +#define MG_SOCK_RESET(errcode) \ + (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET)) +#endif + +union usa { + struct sockaddr sa; + struct sockaddr_in sin; +#if MG_ENABLE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +static socklen_t tousa(struct mg_addr *a, union usa *usa) { + socklen_t len = sizeof(usa->sin); + memset(usa, 0, sizeof(*usa)); + usa->sin.sin_family = AF_INET; + usa->sin.sin_port = a->port; + memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t)); +#if MG_ENABLE_IPV6 + if (a->is_ip6) { + usa->sin.sin_family = AF_INET6; + usa->sin6.sin6_port = a->port; + usa->sin6.sin6_scope_id = a->scope_id; + memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip)); + len = sizeof(usa->sin6); + } +#endif + return len; +} + +static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) { + a->is_ip6 = is_ip6; + a->port = usa->sin.sin_port; + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t)); +#if MG_ENABLE_IPV6 + if (is_ip6) { + memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip)); + a->port = usa->sin6.sin6_port; + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id; + } +#endif +} + +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) { + union usa usa; + socklen_t n = sizeof(usa); + if (getsockname(fd, &usa.sa, &n) == 0) { + tomgaddr(&usa, addr, n != sizeof(usa.sin)); + } +} + +static void iolog(struct mg_connection *c, char *buf, long n, bool r) { + if (n == MG_IO_WAIT) { + // Do nothing + } else if (n <= 0) { + c->is_closing = 1; // Termination. Don't call mg_error(): #1529 + } else if (n > 0) { + if (c->is_hexdumping) { + MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc, + r ? "<-" : "->", mg_print_ip_port, &c->rem, n)); + mg_hexdump(buf, (size_t) n); + } + if (r) { + c->recv.len += (size_t) n; + mg_call(c, MG_EV_READ, &n); + } else { + mg_iobuf_del(&c->send, 0, (size_t) n); + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); + if (c->send.len == 0) { + MG_EPOLL_MOD(c, 0); + } + mg_call(c, MG_EV_WRITE, &n); + } + } +} + +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { + long n; + if (c->is_udp) { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen); + if (n > 0) setlocaddr(FD(c), &c->loc); + } else { + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); + } + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; + if (MG_SOCK_RESET(n)) return MG_IO_RESET; + if (n <= 0) return MG_IO_ERR; + return n; +} + +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + if (c->is_udp) { + long n = mg_io_send(c, buf, len); + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len, + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n))); + iolog(c, (char *) buf, n, false); + return n > 0; + } else { + return mg_iobuf_add(&c->send, c->send.len, buf, len); + } +} + +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) { +#if defined(MG_CUSTOM_NONBLOCK) + MG_CUSTOM_NONBLOCK(fd); +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + unsigned long on = 1; + ioctlsocket(fd, FIONBIO, &on); +#elif MG_ENABLE_RL + unsigned long on = 1; + ioctlsocket(fd, FIONBIO, &on); +#elif MG_ENABLE_FREERTOS_TCP + const BaseType_t off = 0; + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0; + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0; +#elif MG_ENABLE_LWIP + lwip_fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_AZURERTOS + fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_TIRTOS + int val = 0; + setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val)); + // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT + int sz = sizeof(val); + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz); + val /= 2; // set send low-water mark at half send buffer size + setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)); +#else + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec +#endif +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; + bool success = false; + c->loc.port = mg_htons(mg_url_port(url)); + if (!mg_aton(mg_url_host(url), &c->loc)) { + MG_ERROR(("invalid listening URL: %s", url)); + } else { + union usa usa; + socklen_t slen = tousa(&c->loc, &usa); + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET; + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + (void) on; + + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) { + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1))); +#if defined(SO_EXCLUSIVEADDRUSE) + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (char *) &on, sizeof(on))) != 0) { + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc))); +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE) + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, + sizeof(on))) != 0) { + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On + // Windows, SO_REUSEADDR allows to bind a socket to a port without error + // even if the port is already open by another program. This is not the + // behavior SO_REUSEADDR was designed for, and leads to hard-to-track + // failure scenarios. + // + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but + // won't work! (setsockopt will return EINVAL) + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc))); +#endif +#if MG_IPV6_V6ONLY + // Bind only to the V6 address, not V4 address on this port + } else if (c->loc.is_ip6 && + (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on, + sizeof(on))) != 0) { + // See #2089. Allow to bind v4 and v6 sockets on the same port + MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc))); +#endif + } else if ((rc = bind(fd, &usa.sa, slen)) != 0) { + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc))); + } else if ((type == SOCK_STREAM && + (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) { + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit + // In case port was set to 0, get the real port number + MG_ERROR(("listen: %d", MG_SOCK_ERR(rc))); + } else { + setlocaddr(fd, &c->loc); + mg_set_non_blocking_mode(fd); + c->fd = S2PTR(fd); + MG_EPOLL_ADD(c); + success = true; + } + } + if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd); + return success; +} + +static long recv_raw(struct mg_connection *c, void *buf, size_t len) { + long n = 0; + if (c->is_udp) { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); + } else { + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); + } + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; + if (MG_SOCK_RESET(n)) return MG_IO_RESET; + if (n <= 0) return MG_IO_ERR; + return n; +} + +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) { + bool res = false; + if (io->len >= MG_MAX_RECV_SIZE) { + mg_error(c, "MG_MAX_RECV_SIZE"); + } else if (io->size <= io->len && + !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) { + mg_error(c, "OOM"); + } else { + res = true; + } + return res; +} + +// NOTE(lsm): do only one iteration of reads, cause some systems +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data +static void read_conn(struct mg_connection *c) { + if (ioalloc(c, &c->recv)) { + char *buf = (char *) &c->recv.buf[c->recv.len]; + size_t len = c->recv.size - c->recv.len; + long n = -1; + if (c->is_tls) { + // Do not read to the raw TLS buffer if it already has enough. + // This is to prevent overflowing c->rtls if our reads are slow + if (c->rtls.len < 16 * 1024 + 40) { // TLS record, header, MAC, padding + if (!ioalloc(c, &c->rtls)) return; + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len], + c->rtls.size - c->rtls.len); + if (n == MG_IO_ERR) { + if (c->rtls.len == 0 || c->is_io_err) { + // Close only when we have fully drained both rtls and TLS buffers + c->is_closing = 1; // or there's nothing we can do about it. + } else { // TLS buffer is capped to max record size, mark and + c->is_io_err = 1; // give TLS a chance to process that. + } + } else { + if (n > 0) c->rtls.len += (size_t) n; + if (c->is_tls_hs) mg_tls_handshake(c); + } + } + n = c->is_tls_hs ? (long) MG_IO_WAIT + : c->is_closing ? -1 + : mg_tls_recv(c, buf, len); + } else { + n = recv_raw(c, buf, len); + } + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len, + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n))); + iolog(c, buf, n, true); + } +} + +static void write_conn(struct mg_connection *c) { + char *buf = (char *) c->send.buf; + size_t len = c->send.len; + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len); + MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, + (long) c->send.len, (long) c->send.size, (long) c->recv.len, + (long) c->recv.size, n, MG_SOCK_ERR(n))); + iolog(c, buf, n, false); +} + +static void close_conn(struct mg_connection *c) { + if (FD(c) != MG_INVALID_SOCKET) { +#if MG_ENABLE_EPOLL + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL); +#endif + closesocket(FD(c)); +#if MG_ENABLE_FREERTOS_TCP + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); +#endif + } + mg_close_conn(c); +} + +static void connect_conn(struct mg_connection *c) { + union usa usa; + socklen_t n = sizeof(usa); + // Use getpeername() to test whether we have connected + if (getpeername(FD(c), &usa.sa, &n) == 0) { + c->is_connecting = 0; + setlocaddr(FD(c), &c->loc); + mg_call(c, MG_EV_CONNECT, NULL); + MG_EPOLL_MOD(c, 0); + if (c->is_tls_hs) mg_tls_handshake(c); + } else { + mg_error(c, "socket error"); + } +} + +static void setsockopts(struct mg_connection *c) { +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \ + MG_ARCH == MG_ARCH_TIRTOS + (void) c; +#else + int on = 1; +#if !defined(SOL_TCP) +#define SOL_TCP IPPROTO_TCP +#endif + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0) + (void) 0; + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) != + 0) + (void) 0; +#endif +} + +void mg_connect_resolved(struct mg_connection *c) { + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has resolved IP + c->fd = S2PTR(socket(af, type, proto)); // Create outbound socket + c->is_resolving = 0; // Clear resolving flag + if (FD(c) == MG_INVALID_SOCKET) { + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1)); + } else if (c->is_udp) { + MG_EPOLL_ADD(c); +#if MG_ARCH == MG_ARCH_TIRTOS + union usa usa; // TI-RTOS NDK requires binding to receive on UDP sockets + socklen_t slen = tousa(&c->loc, &usa); + if ((rc = bind(c->fd, &usa.sa, slen)) != 0) + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc))); +#endif + setlocaddr(FD(c), &c->loc); + mg_call(c, MG_EV_RESOLVE, NULL); + mg_call(c, MG_EV_CONNECT, NULL); + } else { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + MG_EPOLL_ADD(c); + mg_call(c, MG_EV_RESOLVE, NULL); + rc = connect(FD(c), &usa.sa, slen); // Attempt to connect + if (rc == 0) { // Success + setlocaddr(FD(c), &c->loc); + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake + MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem)); + c->is_connecting = 1; + } else { + mg_error(c, "connect: %d", MG_SOCK_ERR(rc)); + } + } +} + +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa, + socklen_t *len) { + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; + do { + memset(usa, 0, sizeof(*usa)); + fd = accept(sock, &usa->sa, len); + } while (MG_SOCK_INTR(fd)); + return fd; +} + +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { + struct mg_connection *c = NULL; + union usa usa; + socklen_t sa_len = sizeof(usa); + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len); + if (fd == MG_INVALID_SOCKET) { +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS) + // AzureRTOS, in non-block socket mode can mark listening socket readable + // even it is not. See comment for 'select' func implementation in + // nx_bsd.c That's not an error, just should try later + if (errno != EAGAIN) +#endif + MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1))); +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \ + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL + } else if ((long) fd >= FD_SETSIZE) { + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE)); + closesocket(fd); +#endif + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("%lu OOM", lsn->id)); + closesocket(fd); + } else { + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin)); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fd = S2PTR(fd); + MG_EPOLL_ADD(c); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->loc = lsn->loc; + c->pfn = lsn->pfn; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port, + &c->rem, mg_print_ip_port, &c->loc)); + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + } +} + +static bool can_read(const struct mg_connection *c) { + return c->is_full == false; +} + +static bool can_write(const struct mg_connection *c) { + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0); +} + +static bool skip_iotest(const struct mg_connection *c) { + return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) || + (can_read(c) == false && can_write(c) == false); +} + +static void mg_iotest(struct mg_mgr *mgr, int ms) { +#if MG_ENABLE_FREERTOS_TCP + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (skip_iotest(c)) continue; + if (can_read(c)) + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); + if (c->is_closing) ms = 1; + } + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms)); + for (c = mgr->conns; c != NULL; c = c->next) { + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss); + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0; + c->is_writable = bits & eSELECT_WRITE ? 1U : 0; + if (c->fd != MG_INVALID_SOCKET) + FreeRTOS_FD_CLR(c->fd, mgr->ss, + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE); + } +#elif MG_ENABLE_EPOLL + size_t max = 1; + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1; + if (can_write(c)) MG_EPOLL_MOD(c, 1); + if (c->is_closing) ms = 1; + max++; + } + struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0])); + int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms); + for (int i = 0; i < n; i++) { + struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr; + if (evs[i].events & EPOLLERR) { + mg_error(c, "socket error"); + } else if (c->is_readable == 0) { + bool rd = evs[i].events & (EPOLLIN | EPOLLHUP); + bool wr = evs[i].events & EPOLLOUT; + c->is_readable = can_read(c) && rd ? 1U : 0; + c->is_writable = can_write(c) && wr ? 1U : 0; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; + } + } + (void) skip_iotest; +#elif MG_ENABLE_POLL + nfds_t n = 0; + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++; + struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0])); + memset(fds, 0, n * sizeof(fds[0])); + n = 0; + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (c->is_closing) ms = 1; + if (skip_iotest(c)) { + // Socket not valid, ignore + } else { + // Don't wait if TLS is ready + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1; + fds[n].fd = FD(c); + if (can_read(c)) fds[n].events |= POLLIN; + if (can_write(c)) fds[n].events |= POLLOUT; + n++; + } + } + + // MG_INFO(("poll n=%d ms=%d", (int) n, ms)); + if (poll(fds, n, ms) < 0) { +#if MG_ARCH == MG_ARCH_WIN32 + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets +#endif + memset(fds, 0, n * sizeof(fds[0])); + } + n = 0; + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { + if (skip_iotest(c)) { + // Socket not valid, ignore + } else { + if (fds[n].revents & POLLERR) { + mg_error(c, "socket error"); + } else { + c->is_readable = + (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0); + c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0); + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; + } + n++; + } + } +#else + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, *tvp; + struct mg_connection *c; + fd_set rset, wset, eset; + MG_SOCKET_TYPE maxfd = 0; + int rc; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + tvp = ms < 0 ? NULL : &tv; + for (c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = c->is_writable = 0; + if (skip_iotest(c)) continue; + FD_SET(FD(c), &eset); + if (can_read(c)) FD_SET(FD(c), &rset); + if (can_write(c)) FD_SET(FD(c), &wset); + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero; + if (FD(c) > maxfd) maxfd = FD(c); + if (c->is_closing) tvp = &tv_zero; + } + + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) { +#if MG_ARCH == MG_ARCH_WIN32 + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets +#else + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc))); +#endif + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + } + + for (c = mgr->conns; c != NULL; c = c->next) { + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) { + mg_error(c, "socket error"); + } else { + c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset); + c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset); + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; + } + } +#endif +} + +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) { + socklen_t n = sizeof(usa[0].sin); + bool success = false; + + sp[0] = sp[1] = MG_INVALID_SOCKET; + (void) memset(&usa[0], 0, sizeof(usa[0])); + usa[0].sin.sin_family = AF_INET; + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1 + usa[1] = usa[0]; + + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != MG_INVALID_SOCKET && + (sp[1] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != MG_INVALID_SOCKET && + bind(sp[0], &usa[0].sa, n) == 0 && // + bind(sp[1], &usa[1].sa, n) == 0 && // + getsockname(sp[0], &usa[0].sa, &n) == 0 && // + getsockname(sp[1], &usa[1].sa, &n) == 0 && // + connect(sp[0], &usa[1].sa, n) == 0 && // + connect(sp[1], &usa[0].sa, n) == 0) { // + success = true; + } + if (!success) { + if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]); + if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]); + sp[0] = sp[1] = MG_INVALID_SOCKET; + } + return success; +} + +// mg_wakeup() event handler +static void wufn(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + unsigned long *id = (unsigned long *) c->recv.buf; + // MG_INFO(("Got data")); + // mg_hexdump(c->recv.buf, c->recv.len); + if (c->recv.len >= sizeof(*id)) { + struct mg_connection *t; + for (t = c->mgr->conns; t != NULL; t = t->next) { + if (t->id == *id) { + struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id), + c->recv.len - sizeof(*id)); + mg_call(t, MG_EV_WAKEUP, &data); + } + } + } + c->recv.len = 0; // Consume received data + } else if (ev == MG_EV_CLOSE) { + closesocket(c->mgr->pipe); // When we're closing, close the other + c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too + } + (void) ev_data; +} + +bool mg_wakeup_init(struct mg_mgr *mgr) { + bool ok = false; + if (mgr->pipe == MG_INVALID_SOCKET) { + union usa usa[2]; + MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET}; + struct mg_connection *c = NULL; + if (!mg_socketpair(sp, usa)) { + MG_ERROR(("Cannot create socket pair")); + } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) == NULL) { + closesocket(sp[0]); + closesocket(sp[1]); + sp[0] = sp[1] = MG_INVALID_SOCKET; + } else { + tomgaddr(&usa[0], &c->rem, false); + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0])); + mgr->pipe = sp[0]; + ok = true; + } + } + return ok; +} + +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, + size_t len) { + if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) { + char *extended_buf = (char *) alloca(len + sizeof(conn_id)); + memcpy(extended_buf, &conn_id, sizeof(conn_id)); + memcpy(extended_buf + sizeof(conn_id), buf, len); + send(mgr->pipe, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING); + return true; + } + return false; +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + uint64_t now; + + mg_iotest(mgr, ms); + now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + + for (c = mgr->conns; c != NULL; c = tmp) { + bool is_resp = c->is_resp; + tmp = c->next; + mg_call(c, MG_EV_POLL, &now); + if (is_resp && !c->is_resp) { + long n = 0; + mg_call(c, MG_EV_READ, &n); + } + MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id, + c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-', + c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c', + c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r', + c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len)); + if (c->is_resolving || c->is_closing) { + // Do nothing + } else if (c->is_listening && c->is_udp == 0) { + if (c->is_readable) accept_conn(mgr, c); + } else if (c->is_connecting) { + if (c->is_readable || c->is_writable) connect_conn(c); + //} else if (c->is_tls_hs) { + // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c); + } else { + if (c->is_readable) read_conn(c); + if (c->is_writable) write_conn(c); + } + + if (c->is_draining && c->send.len == 0) c->is_closing = 1; + if (c->is_closing) close_conn(c); + } +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ssi.c" +#endif + + + + +#ifndef MG_MAX_SSI_DEPTH +#define MG_MAX_SSI_DEPTH 5 +#endif + +#ifndef MG_SSI_BUFSIZ +#define MG_SSI_BUFSIZ 1024 +#endif + +#if MG_ENABLE_SSI +static char *mg_ssi(const char *path, const char *root, int depth) { + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE}; + FILE *fp = fopen(path, "rb"); + if (fp != NULL) { + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)]; + int ch, intag = 0; + size_t len = 0; + buf[0] = arg[0] = '\0'; + while ((ch = fgetc(fp)) != EOF) { + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { + buf[len++] = (char) (ch & 0xff); + buf[len] = '\0'; + if (sscanf(buf, " %#x %#x", s_txdesc[s_txno][1], tsr)); + if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= MG_BIT(31); + } + + GMAC_REGS->GMAC_RSR = rsr; + GMAC_REGS->GMAC_TSR = tsr; +} + +struct mg_tcpip_driver mg_tcpip_driver_same54 = { + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL, + mg_tcpip_driver_same54_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/stm32f.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ + MG_ENABLE_DRIVER_STM32F +struct stm32f_eth { + volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR, + MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR, + MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR, + MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, + RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR, + RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR, + RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR, + PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564], + DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER, + DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR, + DMACHRBAR; +}; +#undef ETH +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000) + +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + ETH->MACMIIAR &= (7 << 2); + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); + ETH->MACMIIAR |= MG_BIT(0); + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0; + return ETH->MACMIIDR & 0xffff; +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH->MACMIIDR = val; + ETH->MACMIIAR &= (7 << 2); + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); + ETH->MACMIIAR |= MG_BIT(0); + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0; +} + +static uint32_t get_hclk(void) { + struct rcc { + volatile uint32_t CR, PLLCFGR, CFGR; + } *rcc = (struct rcc *) 0x40023800; + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */; + + if (rcc->CFGR & (1 << 2)) { + clk = hse; + } else if (rcc->CFGR & (1 << 3)) { + uint32_t vco, m, n, p; + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0; + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6; + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2; + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi; + vco = (uint32_t) ((uint64_t) clk * n / m); + clk = vco / p; + } else { + clk = hsi; + } + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4; + if (hpre < 8) return clk; + + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) + return ((uint32_t) clk) >> ahbptab[hpre - 8]; +} + +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, +// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived +// from the HSI (internal RC), and it can go above specs, the datasheets +// specify a range of frequencies and activate one of a series of dividers to +// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on +// HCLK with a +5% drift. If the user uses a different clock from our +// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers + uint32_t hclk = get_hclk(); // Guess system HCLK + int result = -1; // Invalid CR value + if (hclk < 25000000) { + MG_ERROR(("HCLK too low")); + } else { + for (int i = 0; i < 6; i++) { + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("HCLK too high")); + } + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); + return result; +} + +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_stm32f_data *d = + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + s_ifp = ifp; + + // Init RX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = MG_BIT(31); // Own + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer + s_rxdesc[i][3] = + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain + } + + // Init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + s_txdesc[i][3] = + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain + } + + ETH->DMABMR |= MG_BIT(0); // Software reset + while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until done + + // Set MDC clock divider. If user told us the value, use it. Otherwise, guess + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; + ETH->MACMIIAR = ((uint32_t) cr & 7) << 2; + + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use + // hardware checksum. Therefore, descriptor size is 4, not 8 + // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | + // MG_BIT(25); + ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT + ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause + // ETH->MACFFR = MG_BIT(31); // Receive all + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC); + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE + ETH->MACCR = + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast + ETH->DMAOMR = + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF + + // MAC address filtering + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | + ((uint32_t) ifp->mac[2] << 16) | + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; + return true; +} + +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { + ifp->nerr++; + MG_ERROR(("No free descriptors")); + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len + s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); // Chain,FS,LS + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + MG_DSB(); // ensure descriptors have been written + ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS + ETH->DMATPDR = 0; // and resume + return len; +} + +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_stm32f_data *d = + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + // tmp = reg with flags set to the most likely situation: 100M full-duplex + // if(link is slow or half) set flags otherwise + // reg = tmp + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex + ETH->MACCR = maccr; // IRQ handler does not fiddle with this register + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, + maccr & MG_BIT(11) ? "full" : "half")); + } + return up; +} + +#ifdef __riscv +__attribute__((interrupt())) // For RISCV CH32V307, which share the same MAC +#endif +void ETH_IRQHandler(void); +void ETH_IRQHandler(void) { + if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop + ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == + (MG_BIT(8) | MG_BIT(9))) && + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1)); + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0], + // ETH->DMASR); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + } + s_rxdesc[s_rxno][0] = MG_BIT(31); + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + // Cleanup flags + ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary + | MG_BIT(7); // Clear possible RBUS while processing + ETH->DMARPDR = 0; // and resume RX +} + +struct mg_tcpip_driver mg_tcpip_driver_stm32f = { + mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL, + mg_tcpip_driver_stm32f_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/stm32h.c" +#endif + + +#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN) +// STM32H: vendor modded single-queue Synopsys v4.2 +// MCXNx4x: dual-queue Synopsys v5.2 +// RT1170 ENET_QOS: quad-queue Synopsys v5.1 +struct synopsys_enet_qos { + volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R, + RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR, + RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR, + MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], MACLCSR, + MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10, + MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR, + RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR, + MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR, + MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR, + RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR, + RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR, + MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R, + RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R, + RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R, + MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR, + MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR, + RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28, + MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR, + RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, MACPPSIR, + MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, MACLMIR, + RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], MTLTQOMR, + MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, MTLRQDR, + RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], DMACCR, + DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR, + DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER, + DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR, + RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2], + DMACMFCR; +}; +#undef ETH +#if MG_ENABLE_DRIVER_STM32H +#define ETH \ + ((struct synopsys_enet_qos *) (uintptr_t) (0x40000000UL + 0x00020000UL + \ + 0x8000UL)) +#elif MG_ENABLE_DRIVER_MCXN +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x40100000UL) +#endif + +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + ETH->MACMDIOAR &= (0xF << 8); + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2; + ETH->MACMDIOAR |= MG_BIT(0); + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; + return (uint16_t) ETH->MACMDIODR; +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH->MACMDIODR = val; + ETH->MACMDIOAR &= (0xF << 8); + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2; + ETH->MACMDIOAR |= MG_BIT(0); + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; +} + +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_stm32h_data *d = + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; + s_ifp = ifp; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf; + + // Init RX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer + s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V + } + + // Init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + } + + ETH->DMAMR |= MG_BIT(0); // Software reset + for (int i = 0; i < 4; i++) + (void) 0; // wait at least 4 clocks before reading + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done + + // Set MDC clock divider. Get user value, else, assume max freq + int cr = (d == NULL || d->mdc_cr < 0) ? 7 : d->mdc_cr; + ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8; + + // NOTE(scaprile): We do not use timing facilities so the DMA engine does not + // re-write buffer address + ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset value) + ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this actually needed + ETH->MACIER = 0; // Do not enable additional irq sources (reset value) + ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause + // ETH->MACPFR = MG_BIT(31); // Receive all + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, phy_addr, phy_conf); + ETH->DMACRDLAR = + (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address + ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length + ETH->DMACRDTPR = + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - + 1]; // last valid descriptor address + ETH->DMACTDLAR = + (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address + ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length + ETH->DMACTDTPR = + (uint32_t) (uintptr_t) s_txdesc; // first available descriptor address + ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table) (reset value) +#if !MG_ENABLE_DRIVER_STM32H + MG_SET_BITS(ETH->DMACTCR, 0x3F << 16, MG_BIT(16)); + MG_SET_BITS(ETH->DMACRCR, 0x3F << 16, MG_BIT(16)); +#endif + ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE + ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) | + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved +#if MG_ENABLE_DRIVER_STM32H + ETH->MTLTQOMR |= MG_BIT(1); // TSF + ETH->MTLRQOMR |= MG_BIT(5); // RSF +#else + ETH->MTLTQOMR |= (7 << 16) | MG_BIT(3) | MG_BIT(1); // 2KB Q0, TSF + ETH->MTLRQOMR |= (7 << 20) | MG_BIT(5); // 2KB Q, RSF + MG_SET_BITS(ETH->RESERVED6[3], 3, 2); // Enable RxQ0 (MAC_RXQ_CTRL0) +#endif + ETH->DMACTCR |= MG_BIT(0); // ST + ETH->DMACRCR |= MG_BIT(0); // SR + + // MAC address filtering + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | + ((uint32_t) ifp->mac[2] << 16) | + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; + return true; +} + +static uint32_t s_txno; +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) { + ifp->nerr++; + MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno, + s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR)); + for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", s_txdesc[i][3])); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + s_txdesc[s_txno][2] = (uint32_t) len; // Set data len + s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD + s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA take over + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS + ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; // and resume + return len; + (void) ifp; +} + +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_stm32h_data *d = + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + // tmp = reg with flags set to the most likely situation: 100M full-duplex + // if(link is slow or half) set flags otherwise + // reg = tmp + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M, Full-duplex + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex + ETH->MACCR = maccr; // IRQ handler does not fiddle with this register + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, + maccr & MG_BIT(13) ? "full" : "half")); + } + return up; +} + +static uint32_t s_rxno; +#if MG_ENABLE_DRIVER_MCXN +void ETHERNET_IRQHandler(void); +void ETHERNET_IRQHandler(void) { +#else +void ETH_IRQHandler(void); +void ETH_IRQHandler(void) { +#endif + if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop + ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done + if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) == + (MG_BIT(28) | MG_BIT(29))) && + !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored frames + uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1); + // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3], + // ETH->DMACSR)); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + } + s_rxdesc[s_rxno][3] = + MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + ETH->DMACSR = + MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing + ETH->DMACRDTPR = + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX +} + +struct mg_tcpip_driver mg_tcpip_driver_stm32h = { + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL, + mg_tcpip_driver_stm32h_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/tm4c.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C +struct tm4c_emac { + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL, + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS, + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H, + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H, + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL, + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4, + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4], + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR, + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239], + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC, + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD, + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL, + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294], + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR, + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT, + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA, + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS, + EMACEPHYIM, EMACEPHYIMSC; +}; +#undef EMAC +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000) + +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { + EPHY_ADDR = 0, + EPHYBMCR = 0, + EPHYBMSR = 1, + EPHYSTS = 16 +}; // PHY constants + +static inline void tm4cspin(volatile uint32_t count) { + while (count--) (void) 0; +} + +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) { + EMAC->EMACMIIADDR &= (0xf << 2); + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); + EMAC->EMACMIIADDR |= MG_BIT(0); + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); + return EMAC->EMACMIIDATA; +} + +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { + EMAC->EMACMIIDATA = val; + EMAC->EMACMIIADDR &= (0xf << 2); + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); + EMAC->EMACMIIADDR |= MG_BIT(0); + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); +} + +static uint32_t get_sysclk(void) { + struct sysctl { + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0, + PLLFREQ1; + } *sysctl = (struct sysctl *) 0x400FE000; + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */; + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL + uint32_t fin, vco, mdiv, n, q, psysdiv; + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24; + if (pllsrc == 0) { + clk = piosc; + } else if (pllsrc == 3) { + clk = mosc; + } else { + MG_ERROR(("Unsupported clock source")); + } + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8; + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0; + fin = clk / ((q + 1) * (n + 1)); + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >> + 0; // mint + (mfrac / 1024); MFRAC not supported + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0; + vco = (uint32_t) ((uint64_t) fin * mdiv); + return vco / (psysdiv + 1); + } + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20; + if (oscsrc == 0) { + clk = piosc; + } else if (oscsrc == 3) { + clk = mosc; + } else { + MG_ERROR(("Unsupported clock source")); + } + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16; + return clk / (osysdiv + 1); +} + +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be +// derived from the PIOSC (internal RC), and it can go above specs, the +// datasheets specify a range of frequencies and activate one of a series of +// dividers to keep the MDC clock safely below 2.5MHz. We guess a divider +// setting based on SYSCLK with a +5% drift. If the user uses a different clock +// from our defaults, needs to set the macros on top Valid for TM4C129x (20.7) +// (4.5% worst case drift) +// The PHY receives the main oscillator (MOSC) (20.3.1) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK + int result = -1; // Invalid CR value + if (sysclk < 25000000) { + MG_ERROR(("SYSCLK too low")); + } else { + for (int i = 0; i < 4; i++) { + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("SYSCLK too high")); + } + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result)); + return result; +} + +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_tm4c_data *d = + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data; + s_ifp = ifp; + + // Init RX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = MG_BIT(31); // Own + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer + s_rxdesc[i][3] = + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain + // MG_DEBUG(("%d %p", i, s_rxdesc[i])); + } + + // Init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + s_txdesc[i][3] = + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain + } + + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); // Wait until done + + // Set MDC clock divider. If user told us the value, use it. Otherwise, guess + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; + EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2; + + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use + // hardware checksum. Therefore, descriptor size is 4, not 8 + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | MG_BIT(25); + EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause + // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all + // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset internal PHY (EPHY) + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set autonegotiation + EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors + EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX descriptors + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE + EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast + EMAC->EMACDMAOPMODE = + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF + EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; + EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) | + ((uint32_t) ifp->mac[2] << 16) | + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; + // NOTE(scaprile) There are 3 additional slots for filtering, disabled by + // default. This also applies to the STM32 driver (at least for F7) + return true; +} + +static uint32_t s_txno; +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // fail + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { + ifp->nerr++; + MG_ERROR(("No descriptors available")); + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) + // EMAC->EMACDMARIS); + len = 0; // fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len + s_txdesc[s_txno][0] = + MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); // Chain,FS,LS,IC + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior TU/UNF + EMAC->EMACTXPOLLD = 0; // and resume + return len; + (void) ifp; +} + +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) { + uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR); + bool up = (bmsr & MG_BIT(2)) ? 1 : 0; + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS); + // tmp = reg with flags set to the most likely situation: 100M full-duplex + // if(link is slow or half) set flags otherwise + // reg = tmp + uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex + EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with this register + MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10, + emaccfg & MG_BIT(11) ? "full" : "half")); + } + return up; +} + +void EMAC0_IRQHandler(void); +static uint32_t s_rxno; +void EMAC0_IRQHandler(void) { + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | MG_BIT(9))) && + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1)); + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0], + // EMAC->EMACDMARIS); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + } + s_rxdesc[s_rxno][0] = MG_BIT(31); + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while processing + EMAC->EMACRXPOLLD = 0; // and resume RX +} + +struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init, + mg_tcpip_driver_tm4c_tx, NULL, + mg_tcpip_driver_tm4c_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/w5500.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500 + +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 }; + +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, + bool wr, void *buf, size_t len) { + size_t i; + uint8_t *p = (uint8_t *) buf; + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255), + (uint8_t) ((block << 3) | (wr ? 4 : 0))}; + s->begin(s->spi); + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]); + for (i = 0; i < len; i++) { + uint8_t r = s->txn(s->spi, p[i]); + if (!wr) p[i] = r; + } + s->end(s->spi); +} + +// clang-format off +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); } +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); } +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); } +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); } +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; } +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } +// clang-format on + +static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until it is stable + // printf("RSR: %d\n", (int) n); + if (n > 0) { + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length + if (n <= len + 2 && n > 1) { + r = (uint16_t) (n - 2); + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r); + } + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance read pointer + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r); + } + return r; +} + +static size_t w5500_tx(const void *buf, size_t buflen, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t i, ptr, n = 0, len = (uint16_t) buflen; + while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space + ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer + w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data + w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance write pointer + w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND + for (i = 0; i < 40; i++) { + uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR + if (ir == 0) continue; + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr); + w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it! + if (ir & 8) len = 0; // Timeout. Report error + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout + } + return len; +} + +static bool w5500_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + s->end(s->spi); + w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80 + w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset + w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set + // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC + w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size + w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size + w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW + w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN + return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW +} + +static bool w5500_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e); + return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up) +} + +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, + w5500_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/xmc.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC + +struct ETH_GLOBAL_TypeDef { + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH, + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION, + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2], + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW, + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW, + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL, + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK, + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2, + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK, + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30], + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT, + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS, + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE, + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS, + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS, + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND, + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS, + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE, + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER, + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS, + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR, + CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS, + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE; +}; + +#undef ETH0 +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL) + +#define ETH_PKT_SIZE 1536 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | + ((uint32_t)addr << 11) | + ((uint32_t)reg << 6) | 1; + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; + return (uint16_t)(ETH0->GMII_DATA & 0xffff); +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH0->GMII_DATA = val; + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | + ((uint32_t)addr << 11) | + ((uint32_t)reg << 6) | 3; + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; +} + +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data *d) { + if (d->mdc_cr == -1) { + // assume ETH clock is 60MHz by default + // then according to 13.2.8.1, we need to set value 3 + return 3; + } + + return d->mdc_cr; +} + +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc_data *d = + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data; + s_ifp = ifp; + + // reset MAC + ETH0->BUS_MODE |= 1; + while (ETH0->BUS_MODE & 1) (void) 0; + + // set clock rate + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2; + + // init phy + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + + // configure MAC: DO, DM, FES, TC + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14) | MG_BIT(24); + + // set the MAC address + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]); + ETH0->MAC_ADDRESS0_LOW = + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]); + + // Configure the receive filter + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC + // Disable flow control + ETH0->FLOW_CONTROL = 0; + // Enable store and forward mode + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF + + // Configure DMA bus mode (AAL, USP, RPBL, PBL) + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8); + + // init RX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE; + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0]; + } else { + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0]; + } + } + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0]; + + // init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20); + s_txdesc[i][2] = (uint32_t) s_txbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0]; + } else { + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0]; + } + } + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_txdesc[0][0]; + + // Clear interrupts + ETH0->STATUS = 0xFFFFFFFF; + + // Disable MAC interrupts + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM + + //Enable interrupts (NIE, RIE, TIE) + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0); + + // Enable MAC transmission and reception (TE, RE) + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2); + // Enable DMA transmission and reception (ST, SR) + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1); + return true; +} + +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { + ifp->nerr++; + MG_ERROR(("No free descriptors")); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); + s_txdesc[s_txno][1] = len; + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH) + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) | MG_BIT(20); + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control to DMA + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + + // Resume processing + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable + ETH0->TRANSMIT_POLL_DEMAND = 0; + return len; +} + +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc_data *d = + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 100, + full_duplex ? "full" : "half")); + } + return up; +} + +void ETH0_IRQHandler(void); +void ETH0_IRQHandler(void) { + uint32_t irq_status = ETH0->STATUS; + + // check if a frame was received + if (irq_status & MG_BIT(6)) { + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) { + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16; + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to DMA + // Resume processing + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI + ETH0->RECEIVE_POLL_DEMAND = 0; + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + ETH0->STATUS = MG_BIT(6); + } + + // clear Successful transmission interrupt + if (irq_status & 1) { + ETH0->STATUS = 1; + } + + // clear normal interrupt + if (irq_status & MG_BIT(16)) { + ETH0->STATUS = MG_BIT(16); + } +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc = { + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL, + mg_tcpip_driver_xmc_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/xmc7.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 + +struct ETH_Type { + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL, + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG, + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS, + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT, PAUSE_TIME, + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU, JUMBO_MAX_LENGTH, + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL, + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM, HASH_TOP, + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP, + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP, + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER, + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM, + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST, + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC, + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC, + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP, + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED, + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256, + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS, + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS, + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM, + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED, + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65, FRAMES_RXED_128, + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024, FRAMES_RXED_1519, + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS, + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS, RX_RESOURCE_ERRORS, + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS, + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC, TSU_TIMER_MSB_SEC, + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC, TSU_TIMER_SEC, + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC, + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC, + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL, + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP, + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS, RESERVED6[8], + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3, RESERVED7, + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1, + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4, DESIGNCFG_DEBUG5, + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8, DESIGNCFG_DEBUG9, + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP, + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS, + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS, RESERVED11, + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11], + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR, + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15, + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3, RESERVED16[3], + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A, CBS_IDLESLOPE_Q_B, + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL, UPPER_RX_Q_BASE_ADDR, + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3, + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5; +}; + +#define ETH0 ((struct ETH_Type *) 0x40490000) + +#define ETH_PKT_SIZE 1536 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 2 // Descriptor size (words) + +// TODO(): handle these in a portable compiler-independent CMSIS-friendly way +#define MG_8BYTE_ALIGNED __attribute__((aligned((8U)))) + +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_8BYTE_ALIGNED; +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_8BYTE_ALIGNED; +static uint8_t s_txno MG_8BYTE_ALIGNED; // Current TX descriptor +static uint8_t s_rxno MG_8BYTE_ALIGNED; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + // WRITE1, READ OPERATION, PHY, REG, WRITE10 + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf) << 24) | + ((reg & 0x1f) << 18) | MG_BIT(17); + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0; + return ETH0->PHY_MANAGEMENT & 0xffff; +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf) << 24) | + ((reg & 0x1f) << 18) | MG_BIT(17) | val; + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0; +} + +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) { + // see ETH0 -> NETWORK_CONFIG register + (void) d; + return 3; +} + +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc7_data *d = + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data; + s_ifp = ifp; + + // enable controller, set RGMII mode + ETH0->CTL = MG_BIT(31) | (4 << 8) | 2; + + uint32_t cr = get_clock_rate(d); + // set NSP change, ignore RX FCS, data bus width, clock rate + // frame length 1536, full duplex, speed + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) | + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) | MG_BIT(1) | + MG_BIT(0); + + // config DMA settings: Force TX burst, Discard on Error, set RX buffer size + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH + ETH0->DMA_CONFIG = + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8) | 4; + + // initialize descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor + } + + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor + if (i == ETH_DESC_CNT - 1) { + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor + } + } + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc; + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc; + + // disable other queues + ETH0->TRANSMIT_Q2_PTR = 1; + ETH0->TRANSMIT_Q1_PTR = 1; + ETH0->RECEIVE_Q2_PTR = 1; + ETH0->RECEIVE_Q1_PTR = 1; + + // enable interrupts (RX complete) + ETH0->INT_ENABLE = MG_BIT(1); + + // set MAC address + ETH0->SPEC_ADD1_BOTTOM = + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 | ifp->mac[0]; + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4]; + + // enable MDIO, TX, RX + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2); + + // start transmission + ETH0->NETWORK_CONTROL |= MG_BIT(9); + + // init phy + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + + (void) d; + return true; +} + +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) { + ifp->nerr++; + MG_ERROR(("No free descriptors")); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30) : 0) | + MG_BIT(15) | len; // Last buffer and length + + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + + MG_DSB(); + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS; + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission + + return len; +} + +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc7_data *d = + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + // tmp = reg with flags set to the most likely situation: 100M full-duplex + // if(link is slow or half) set flags otherwise + // reg = tmp + uint32_t netconf = ETH0->NETWORK_CONFIG; + MG_SET_BITS(netconf, MG_BIT(10), + MG_BIT(1) | MG_BIT(0)); // 100M, Full-duplex + uint32_t ctl = ETH0->CTL; + MG_SET_BITS(ctl, 0xFF00, 4 << 8); // /5 for 25M clock + if (speed == MG_PHY_SPEED_1000M) { + netconf |= MG_BIT(10); // 1000M + MG_SET_BITS(ctl, 0xFF00, 0); // /1 for 125M clock TODO() IS THIS NEEDED ? + } else if (speed == MG_PHY_SPEED_10M) { + netconf &= ~MG_BIT(0); // 10M + MG_SET_BITS(ctl, 0xFF00, 49); // /50 for 2.5M clock + } + if (full_duplex == false) netconf &= ~MG_BIT(1); // Half-duplex + ETH0->NETWORK_CONFIG = netconf; // IRQ handler does not fiddle with these + ETH0->CTL = ctl; + MG_DEBUG(("Link is %uM %s-duplex", + speed == MG_PHY_SPEED_10M + ? 10 + : (speed == MG_PHY_SPEED_100M ? 100 : 1000), + full_duplex ? "full" : "half")); + } + return up; +} + +void ETH_IRQHandler(void) { + uint32_t irq_status = ETH0->INT_STATUS; + if (irq_status & MG_BIT(1)) { + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) { + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control to DMA + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + } + + ETH0->INT_STATUS = irq_status; +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init, + mg_tcpip_driver_xmc7_tx, NULL, + mg_tcpip_driver_xmc7_up}; +#endif diff --git a/external/mongoose/mongoose.h b/external/mongoose/mongoose.h new file mode 100644 index 00000000..2ff7b347 --- /dev/null +++ b/external/mongoose/mongoose.h @@ -0,0 +1,3185 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2024 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.gnu.org/licenses/ +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in https://www.mongoose.ws/licensing/ +// +// SPDX-License-Identifier: GPL-2.0-only or commercial + +#ifndef MONGOOSE_H +#define MONGOOSE_H + +#define MG_VERSION "7.15" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h +#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ... +#define MG_ARCH_WIN32 2 // Windows +#define MG_ARCH_ESP32 3 // ESP32 +#define MG_ARCH_ESP8266 4 // ESP8266 +#define MG_ARCH_FREERTOS 5 // FreeRTOS +#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS +#define MG_ARCH_NEWLIB 8 // Bare metal ARM +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS +#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040 +#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard +#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS) +#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS + +#if !defined(MG_ARCH) +#if defined(__unix__) || defined(__APPLE__) +#define MG_ARCH MG_ARCH_UNIX +#elif defined(_WIN32) +#define MG_ARCH MG_ARCH_WIN32 +#endif +#endif // !defined(MG_ARCH) + +#if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM) +#include "mongoose_config.h" // keep this include +#endif + +#if !defined(MG_ARCH) +#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in your compiler" +#endif + +// http://esr.ibiblio.org/?p=5095 +#define MG_BIG_ENDIAN (*(uint16_t *) "\0\xff" < 0x100) + + + + + + + + + + + + + + + +#if MG_ARCH == MG_ARCH_AZURERTOS + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define PATH_MAX FX_MAXIMUM_PATH +#define MG_DIRSEP '\\' + +#define socklen_t int +#define closesocket(x) soc_close(x) + +#undef FOPEN_MAX + +#endif + + +#if MG_ARCH == MG_ARCH_ESP32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // Use angle brackets to avoid +#include // amalgamation ditching them + +#define MG_PATH_MAX 128 + +#endif + + +#if MG_ARCH == MG_ARCH_ESP8266 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MG_PATH_MAX 128 + +#endif + + +#if MG_ARCH == MG_ARCH_FREERTOS + +#include +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include // rand(), strtol(), atoi() +#include +#if defined(__ARMCC_VERSION) +#define mode_t size_t +#include +#include +#elif defined(__CCRH__) +#else +#include +#endif + +#include +#include + +#define calloc(a, b) mg_calloc(a, b) +#define free(a) vPortFree(a) +#define malloc(a) pvPortMalloc(a) +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) + +// Re-route calloc/free to the FreeRTOS's functions, don't use stdlib +static inline void *mg_calloc(size_t cnt, size_t size) { + void *p = pvPortMalloc(cnt * size); + if (p != NULL) memset(p, 0, size * cnt); + return p; +} + +#define mkdir(a, b) mg_mkdir(a, b) +static inline int mg_mkdir(const char *path, mode_t mode) { + (void) path, (void) mode; + return -1; +} + +#endif // MG_ARCH == MG_ARCH_FREERTOS + + +#if MG_ARCH == MG_ARCH_NEWLIB +#define _POSIX_TIMERS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PATH_MAX 100 +#define MG_ENABLE_SOCKET 0 +#define MG_ENABLE_DIRLIST 0 + +#endif + + +#if MG_ARCH == MG_ARCH_RP2040 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +int mkdir(const char *, mode_t); +#endif + + +#if MG_ARCH == MG_ARCH_RTTHREAD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 1460 +#endif + +#endif // MG_ARCH == MG_ARCH_RTTHREAD + + +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \ + MG_ARCH == MG_ARCH_CMSIS_RTOS2 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1 +#include "cmsis_os.h" // keep this include +// https://developer.arm.com/documentation/ka003821/latest +extern uint32_t rt_time_get(void); +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2 +#include "cmsis_os2.h" // keep this include +#endif + +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) + +#if defined(__ARMCC_VERSION) +#define mode_t size_t +#define mkdir(a, b) mg_mkdir(a, b) +static inline int mg_mkdir(const char *path, mode_t mode) { + (void) path, (void) mode; + return -1; +} +#endif + +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) && \ + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) +#define MG_ENABLE_RL 1 +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 +#endif +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_TIRTOS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#endif + + +#if MG_ARCH == MG_ARCH_UNIX + +#define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors + +#if defined(__APPLE__) +#include +#endif + +#if !defined(MG_ENABLE_EPOLL) && defined(__linux__) +#define MG_ENABLE_EPOLL 1 +#elif !defined(MG_ENABLE_POLL) +#define MG_ENABLE_POLL 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL +#include +#elif defined(MG_ENABLE_POLL) && MG_ENABLE_POLL +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX FILENAME_MAX +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define __func__ "" +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef unsigned char uint8_t; +typedef char int8_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef enum { false = 0, true = 1 } bool; +#else +#include +#include +#include +#endif + +#include +#include +#include + +// For mg_random() +#if defined(_MSC_VER) && _MSC_VER < 1700 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x400 // Let vc98 pick up wincrypt.h +#endif +#include +#pragma comment(lib, "advapi32.lib") +#else +#include +#if defined(_MSC_VER) +#pragma comment(lib, "bcrypt.lib") +#endif +#endif + +// Protect from calls like std::snprintf in app code +// See https://github.com/cesanta/mongoose/issues/1047 +#ifndef __cplusplus +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#ifndef strdup // For MSVC with _DEBUG, see #1359 +#define strdup(x) _strdup(x) +#endif +#endif + +#define MG_INVALID_SOCKET INVALID_SOCKET +#define MG_SOCKET_TYPE SOCKET +typedef unsigned long nfds_t; +#if defined(_MSC_VER) +#pragma comment(lib, "ws2_32.lib") +#ifndef alloca +#define alloca(a) _alloca(a) +#endif +#endif +#define poll(a, b, c) WSAPoll((a), (b), (c)) +#define closesocket(x) closesocket(x) + +typedef int socklen_t; +#define MG_DIRSEP '\\' + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX FILENAME_MAX +#endif + +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) +#endif + +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0) + +#define MG_SOCK_PENDING(errcode) \ + (((errcode) < 0) && \ + (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \ + WSAGetLastError() == WSAEWOULDBLOCK)) + +#define MG_SOCK_RESET(errcode) \ + (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET)) + +#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) +#define sleep(x) Sleep((x) * 1000) +#define mkdir(a, b) _mkdir(a) +#define timegm(x) _mkgmtime(x) + +#ifndef S_ISDIR +#define S_ISDIR(x) (((x) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#ifndef SIGPIPE +#define SIGPIPE 0 +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + +#endif + + +#if MG_ARCH == MG_ARCH_ZEPHYR + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PUTCHAR(x) printk("%c", x) +#ifndef strdup +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) +#endif +#define strerror(x) zsock_gai_strerror(x) + +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 0 +#endif + +#ifndef F_SETFD +#define F_SETFD 0 +#endif + +#define MG_ENABLE_SSI 0 + +int rand(void); +int sscanf(const char *, const char *, ...); + +#endif + + +#if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP + +#include +#include + +#include +#include + +#define MG_SOCKET_TYPE Socket_t +#define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET + +// Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing +// with FreeRTOS_ prefix, is beyond me +#define IPPROTO_TCP FREERTOS_IPPROTO_TCP +#define IPPROTO_UDP FREERTOS_IPPROTO_UDP +#define AF_INET FREERTOS_AF_INET +#define SOCK_STREAM FREERTOS_SOCK_STREAM +#define SOCK_DGRAM FREERTOS_SOCK_DGRAM +#define SO_BROADCAST 0 +#define SO_ERROR 0 +#define SOL_SOCKET 0 +#define SO_REUSEADDR 0 + +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) + +#define MG_SOCK_PENDING(errcode) \ + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \ + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \ + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \ + (errcode) == -pdFREERTOS_ERRNO_EAGAIN) + +#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN) + +// actually only if optional timeout is enabled +#define MG_SOCK_INTR(fd) (fd == NULL) + +#define sockaddr_in freertos_sockaddr +#define sockaddr freertos_sockaddr +#if ipFR_TCP_VERSION_MAJOR >= 4 +#define sin_addr sin_address.ulIP_IPv4 +#endif +#define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) +#define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) +#define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) +#define listen(a, b) FreeRTOS_listen((a), (b)) +#define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) +#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) +#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) +#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) +#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) +#define recvfrom(a, b, c, d, e, f) \ + FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) +#define closesocket(x) FreeRTOS_closesocket(x) +#define gethostbyname(x) FreeRTOS_gethostbyname(x) +#define getsockname(a, b, c) mg_getsockname((a), (b), (c)) +#define getpeername(a, b, c) mg_getpeername((a), (b), (c)) + +static inline int mg_getsockname(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { + (void) fd, (void) buf, (void) len; + return -1; +} + +static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) { + (void) fd, (void) buf, (void) len; + return 0; +} +#endif + + +#if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP + +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) +#include +#endif + +struct timeval; + +#include + +#if !LWIP_TIMEVAL_PRIVATE +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both +#include +#else +struct timeval { + time_t tv_sec; + long tv_usec; +}; +#endif +#endif + +#if LWIP_SOCKET != 1 +// Sockets support disabled in LWIP by default +#error Set LWIP_SOCKET variable to 1 (in lwipopts.h) +#endif +#endif + + +#if defined(MG_ENABLE_RL) && MG_ENABLE_RL +#include + +#define closesocket(x) closesocket(x) + +#define TCP_NODELAY SO_KEEPALIVE + +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0) + +#define MG_SOCK_PENDING(errcode) \ + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \ + (errcode) == BSD_EINPROGRESS) + +#define MG_SOCK_RESET(errcode) \ + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET) + +// In blocking mode, which is enabled by default, accept() waits for a +// connection request. In non blocking mode, you must call accept() +// again if the error code BSD_EWOULDBLOCK is returned. +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK) + +#define socklen_t int +#endif + + +#ifndef MG_ENABLE_LOG +#define MG_ENABLE_LOG 1 +#endif + +#ifndef MG_ENABLE_CUSTOM_LOG +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG +#endif + +#ifndef MG_ENABLE_TCPIP +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack +#endif + +#ifndef MG_ENABLE_LWIP +#define MG_ENABLE_LWIP 0 // lWIP network stack +#endif + +#ifndef MG_ENABLE_FREERTOS_TCP +#define MG_ENABLE_FREERTOS_TCP 0 // Amazon FreeRTOS-TCP network stack +#endif + +#ifndef MG_ENABLE_RL +#define MG_ENABLE_RL 0 // ARM MDK network stack +#endif + +#ifndef MG_ENABLE_SOCKET +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP +#endif + +#ifndef MG_ENABLE_POLL +#define MG_ENABLE_POLL 0 +#endif + +#ifndef MG_ENABLE_EPOLL +#define MG_ENABLE_EPOLL 0 +#endif + +#ifndef MG_ENABLE_FATFS +#define MG_ENABLE_FATFS 0 +#endif + +#ifndef MG_ENABLE_SSI +#define MG_ENABLE_SSI 0 +#endif + +#ifndef MG_ENABLE_IPV6 +#define MG_ENABLE_IPV6 0 +#endif + +#ifndef MG_IPV6_V6ONLY +#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4 address +#endif + +#ifndef MG_ENABLE_MD5 +#define MG_ENABLE_MD5 1 +#endif + +// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) +#ifndef MG_ENABLE_WINSOCK +#define MG_ENABLE_WINSOCK 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_RANDOM +#define MG_ENABLE_CUSTOM_RANDOM 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_MILLIS +#define MG_ENABLE_CUSTOM_MILLIS 0 +#endif + +#ifndef MG_ENABLE_PACKED_FS +#define MG_ENABLE_PACKED_FS 0 +#endif + +#ifndef MG_ENABLE_ASSERT +#define MG_ENABLE_ASSERT 0 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 256 // Granularity of the send/recv IO buffer growth +#endif + +#ifndef MG_MAX_RECV_SIZE +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv IO buffer size +#endif + +#ifndef MG_DATA_SIZE +#define MG_DATA_SIZE 32 // struct mg_connection :: data size +#endif + +#ifndef MG_MAX_HTTP_HEADERS +#define MG_MAX_HTTP_HEADERS 30 +#endif + +#ifndef MG_HTTP_INDEX +#define MG_HTTP_INDEX "index.html" +#endif + +#ifndef MG_PATH_MAX +#ifdef PATH_MAX +#define MG_PATH_MAX PATH_MAX +#else +#define MG_PATH_MAX 128 +#endif +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128 +#endif + +#ifndef MG_DIRSEP +#define MG_DIRSEP '/' +#endif + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 0 +#endif + +#ifndef MG_INVALID_SOCKET +#define MG_INVALID_SOCKET (-1) +#endif + +#ifndef MG_SOCKET_TYPE +#define MG_SOCKET_TYPE int +#endif + +#ifndef MG_SOCKET_ERRNO +#define MG_SOCKET_ERRNO errno +#endif + +#if MG_ENABLE_EPOLL +#define MG_EPOLL_ADD(c) \ + do { \ + struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \ + } while (0) +#define MG_EPOLL_MOD(c, wr) \ + do { \ + struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}}; \ + if (wr) ev.events |= EPOLLOUT; \ + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \ + } while (0) +#else +#define MG_EPOLL_ADD(c) +#define MG_EPOLL_MOD(c, wr) +#endif + +#ifndef MG_ENABLE_PROFILE +#define MG_ENABLE_PROFILE 0 +#endif + +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also initialize +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for +#endif // Mongoose built-in network stack + +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif + +#ifndef MG_TCPIP_MASK +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif + +#ifndef MG_TCPIP_GW +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif + +#ifndef MG_SET_MAC_ADDRESS +#define MG_SET_MAC_ADDRESS(mac) +#endif + +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 +#endif + + + + +// Describes an arbitrary chunk of memory +struct mg_str { + char *buf; // String data + size_t len; // String length +}; + +// Using macro to avoid shadowing C++ struct constructor, see #1298 +#define mg_str(s) mg_str_s(s) + +struct mg_str mg_str(const char *s); +struct mg_str mg_str_n(const char *s, size_t n); +int mg_casecmp(const char *s1, const char *s2); +int mg_strcmp(const struct mg_str str1, const struct mg_str str2); +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2); +struct mg_str mg_strdup(const struct mg_str s); +bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char delim); + +bool mg_str_to_num(struct mg_str, int base, void *val, size_t val_len); + + + + +// Single producer, single consumer non-blocking queue + +struct mg_queue { + char *buf; + size_t size; + volatile size_t tail; + volatile size_t head; +}; + +void mg_queue_init(struct mg_queue *, char *, size_t); // Init queue +size_t mg_queue_book(struct mg_queue *, char **buf, size_t); // Reserve space +void mg_queue_add(struct mg_queue *, size_t); // Add new message +size_t mg_queue_next(struct mg_queue *, char **); // Get oldest message +void mg_queue_del(struct mg_queue *, size_t); // Delete oldest message + + + + +typedef void (*mg_pfn_t)(char, void *); // Output function +typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M printer + +size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *); +size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...); + + + + + + +// Convenience wrappers around mg_xprintf +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap); +size_t mg_snprintf(char *, size_t, const char *fmt, ...); +char *mg_vmprintf(const char *fmt, va_list *ap); +char *mg_mprintf(const char *fmt, ...); +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *); +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); + +// %M print helper functions +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap); +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap); + +// Various output functions +void mg_pfn_iobuf(char ch, void *param); // param: struct mg_iobuf * +void mg_pfn_stdout(char c, void *param); // param: ignored + +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", MG_ESC("hi")) +#define MG_ESC(str) mg_print_esc, 0, (str) + + + + + + +enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE }; +extern int mg_log_level; // Current log level, one of MG_LL_* + +void mg_log(const char *fmt, ...); +void mg_log_prefix(int ll, const char *file, int line, const char *fname); +// bool mg_log2(int ll, const char *file, int line, const char *fmt, ...); +void mg_hexdump(const void *buf, size_t len); +void mg_log_set_fn(mg_pfn_t fn, void *param); + +#define mg_log_set(level_) mg_log_level = (level_) + +#if MG_ENABLE_LOG +#define MG_LOG(level, args) \ + do { \ + if ((level) <= mg_log_level) { \ + mg_log_prefix((level), __FILE__, __LINE__, __func__); \ + mg_log args; \ + } \ + } while (0) +#else +#define MG_LOG(level, args) \ + do { \ + if (0) mg_log args; \ + } while (0) +#endif + +#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args) +#define MG_INFO(args) MG_LOG(MG_LL_INFO, args) +#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args) +#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args) + + + + +struct mg_timer { + unsigned long id; // Timer ID + uint64_t period_ms; // Timer period in milliseconds + uint64_t expire; // Expiration timestamp in milliseconds + unsigned flags; // Possible flags values below +#define MG_TIMER_ONCE 0 // Call function once +#define MG_TIMER_REPEAT 1 // Call function periodically +#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set + void (*fn)(void *); // Function to call + void *arg; // Function argument + struct mg_timer *next; // Linkage +}; + +void mg_timer_init(struct mg_timer **head, struct mg_timer *timer, + uint64_t milliseconds, unsigned flags, void (*fn)(void *), + void *arg); +void mg_timer_free(struct mg_timer **head, struct mg_timer *); +void mg_timer_poll(struct mg_timer **head, uint64_t new_ms); +bool mg_timer_expired(uint64_t *expiration, uint64_t period, uint64_t now); + + + + + +enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; + +// Filesystem API functions +// st() returns MG_FS_* flags and populates file size and modification time +// ls() calls fn() for every directory entry, allowing to list a directory +// +// NOTE: UNIX-style shorthand names for the API functions are deliberately +// chosen to avoid conflicts with some libraries that make macros for e.g. +// stat(), write(), read() calls. +struct mg_fs { + int (*st)(const char *path, size_t *size, time_t *mtime); // stat file + void (*ls)(const char *path, void (*fn)(const char *, void *), + void *); // List directory entries: call fn(file_name, fn_data) + // for each directory entry + void *(*op)(const char *path, int flags); // Open file + void (*cl)(void *fd); // Close file + size_t (*rd)(void *fd, void *buf, size_t len); // Read file + size_t (*wr)(void *fd, const void *buf, size_t len); // Write file + size_t (*sk)(void *fd, size_t offset); // Set file position + bool (*mv)(const char *from, const char *to); // Rename file + bool (*rm)(const char *path); // Delete file + bool (*mkd)(const char *path); // Create directory +}; + +extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek +extern struct mg_fs mg_fs_packed; // Packed FS, see examples/device-dashboard +extern struct mg_fs mg_fs_fat; // FAT FS + +// File descriptor +struct mg_fd { + void *fd; + struct mg_fs *fs; +}; + +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags); +void mg_fs_close(struct mg_fd *fd); +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len); +struct mg_str mg_file_read(struct mg_fs *fs, const char *path); +bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t); +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); + +// Packed API +const char *mg_unpack(const char *path, size_t *size, time_t *mtime); +const char *mg_unlist(size_t no); // Get no'th packed filename +struct mg_str mg_unpacked(const char *path); // Packed file as mg_str + + + + + + + +#if MG_ENABLE_ASSERT +#include +#elif !defined(assert) +#define assert(x) +#endif + +void mg_bzero(volatile unsigned char *buf, size_t len); +bool mg_random(void *buf, size_t len); +char *mg_random_str(char *buf, size_t len); +uint16_t mg_ntohs(uint16_t net); +uint32_t mg_ntohl(uint32_t net); +uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); +uint64_t mg_millis(void); // Return milliseconds since boot +bool mg_path_is_sane(const struct mg_str path); + +#define mg_htons(x) mg_ntohs(x) +#define mg_htonl(x) mg_ntohl(x) + +#define MG_U32(a, b, c, d) \ + (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \ + ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255)) + +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d)) + +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip)) +#define MG_U8P(ADDR) ((uint8_t *) (ADDR)) +#define MG_IPADDR_PARTS(ADDR) \ + MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3] + +#define MG_REG(x) ((volatile uint32_t *) (x))[0] +#define MG_BIT(x) (((uint32_t) 1U) << (x)) +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) + +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a))) +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) + +#if defined(__GNUC__) +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory") +#elif defined(__CCRH__) +#define MG_RH850_DISABLE_IRQ() __DI() +#define MG_RH850_ENABLE_IRQ() __EI() +#else +#define MG_ARM_DISABLE_IRQ() +#define MG_ARM_ENABLE_IRQ() +#endif + +#if defined(__CC_ARM) +#define MG_DSB() __dsb(0xf) +#elif defined(__ARMCC_VERSION) +#define MG_DSB() __builtin_arm_dsb(0xf) +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__) +#define MG_DSB() asm("DSB 0xf") +#elif defined(__ICCARM__) +#define MG_DSB() __iar_builtin_DSB() +#else +#define MG_DSB() +#endif + +struct mg_addr; +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip); + +// Linked list management macros +#define LIST_ADD_HEAD(type_, head_, elem_) \ + do { \ + (elem_)->next = (*head_); \ + *(head_) = (elem_); \ + } while (0) + +#define LIST_ADD_TAIL(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != NULL) h = &(*h)->next; \ + *h = (elem_); \ + } while (0) + +#define LIST_DELETE(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != (elem_)) h = &(*h)->next; \ + *h = (elem_)->next; \ + } while (0) + + + +unsigned short mg_url_port(const char *url); +int mg_url_is_ssl(const char *url); +struct mg_str mg_url_host(const char *url); +struct mg_str mg_url_user(const char *url); +struct mg_str mg_url_pass(const char *url); +const char *mg_url_uri(const char *url); + + + + +struct mg_iobuf { + unsigned char *buf; // Pointer to stored data + size_t size; // Total size available + size_t len; // Current number of bytes + size_t align; // Alignment during allocation +}; + +int mg_iobuf_init(struct mg_iobuf *, size_t, size_t); +int mg_iobuf_resize(struct mg_iobuf *, size_t); +void mg_iobuf_free(struct mg_iobuf *); +size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t); +size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len); + + +size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len); +size_t mg_base64_final(char *buf, size_t len); +size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t); +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t); + + + + +typedef struct { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} mg_md5_ctx; + +void mg_md5_init(mg_md5_ctx *c); +void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len); +void mg_md5_final(mg_md5_ctx *c, unsigned char[16]); + + + + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} mg_sha1_ctx; + +void mg_sha1_init(mg_sha1_ctx *); +void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); +// https://github.com/B-Con/crypto-algorithms +// Author: Brad Conte (brad AT bradconte.com) +// Disclaimer: This code is presented "as is" without any guarantees. +// Details: Defines the API for the corresponding SHA1 implementation. +// Copyright: public domain + + + + + +typedef struct { + uint32_t state[8]; + uint64_t bits; + uint32_t len; + unsigned char buffer[64]; +} mg_sha256_ctx; + +void mg_sha256_init(mg_sha256_ctx *); +void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len); +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *); +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, + size_t datasz); +#ifndef TLS_X15519_H +#define TLS_X15519_H + + + +#define X25519_BYTES 32 +extern const uint8_t X25519_BASE_POINT[X25519_BYTES]; + +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES], + const uint8_t x1[X25519_BYTES], int clamp); + + +#endif /* TLS_X15519_H */ +/****************************************************************************** + * + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL + * + * This is a simple and straightforward implementation of AES-GCM authenticated + * encryption. The focus of this work was correctness & accuracy. It is written + * in straight 'C' without any particular focus upon optimization or speed. It + * should be endian (memory byte order) neutral since the few places that care + * are handled explicitly. + * + * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. + * + * It is intended for general purpose use, but was written in support of GRC's + * reference implementation of the SQRL (Secure Quick Reliable Login) client. + * + * See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf + * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \ + * gcm/gcm-revised-spec.pdf + * + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. + * + *******************************************************************************/ +#ifndef TLS_AES128_H +#define TLS_AES128_H + +typedef unsigned char uchar; // add some convienent shorter types +typedef unsigned int uint; + +/****************************************************************************** + * AES_CONTEXT : cipher context / holds inter-call data + ******************************************************************************/ +typedef struct { + int mode; // 1 for Encryption, 0 for Decryption + int rounds; // keysize-based rounds count + uint32_t *rk; // pointer to current round key + uint32_t buf[68]; // key expansion buffer +} aes_context; + + +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure + +/****************************************************************************** + * GCM_CONTEXT : MUST be called once before ANY use of this library + ******************************************************************************/ +int mg_gcm_initialize(void); + +// +// aes-gcm.h +// MKo +// +// Created by Markus Kosmal on 20/11/14. +// +// +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned char *input, + size_t input_length, const unsigned char *key, + const size_t key_len, const unsigned char *iv, + const size_t iv_len, unsigned char *aead, + size_t aead_len, unsigned char *tag, + const size_t tag_len); + +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input, + size_t input_length, const unsigned char *key, + const size_t key_len, const unsigned char *iv, + const size_t iv_len); + +#endif /* TLS_AES128_H */ + +// End of aes128 PD + + + +#define MG_UECC_SUPPORTS_secp256r1 1 +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ + +#ifndef _UECC_H_ +#define _UECC_H_ + +/* Platform selection options. +If MG_UECC_PLATFORM is not defined, the code will try to guess it based on +compiler macros. Possible values for MG_UECC_PLATFORM are defined below: */ +#define mg_uecc_arch_other 0 +#define mg_uecc_x86 1 +#define mg_uecc_x86_64 2 +#define mg_uecc_arm 3 +#define mg_uecc_arm_thumb 4 +#define mg_uecc_arm_thumb2 5 +#define mg_uecc_arm64 6 +#define mg_uecc_avr 7 + +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate for your platform +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly defined then it will +be automatically set based on your platform. */ + +/* Optimization level; trade speed for code size. + Larger values produce code that is faster but larger. + Currently supported values are 0 - 4; 0 is unusably slow for most + applications. Optimization level 4 currently only has an effect ARM platforms + where more than one curve is enabled. */ +#ifndef MG_UECC_OPTIMIZATION_LEVEL +#define MG_UECC_OPTIMIZATION_LEVEL 2 +#endif + +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a +specific function to be used for (scalar) squaring instead of the generic +multiplication function. This can make things faster somewhat faster, but +increases the code size. */ +#ifndef MG_UECC_SQUARE_FUNC +#define MG_UECC_SQUARE_FUNC 0 +#endif + +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will +switch to native little-endian format for *all* arrays passed in and out of the +public API. This includes public and private keys, shared secrets, signatures +and message hashes. Using this switch reduces the amount of call stack memory +used by uECC, since less intermediate translations are required. Note that this +will *only* work on native little-endian processors and it will treat the +uint8_t arrays passed into the public API as word arrays, therefore requiring +the provided byte arrays to be word aligned on architectures that do not support +unaligned accesses. IMPORTANT: Keys and signatures generated with +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same +endianness. */ +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0 +#endif + +/* Curve support selection. Set to 0 to remove that curve. */ +#ifndef MG_UECC_SUPPORTS_secp160r1 +#define MG_UECC_SUPPORTS_secp160r1 0 +#endif +#ifndef MG_UECC_SUPPORTS_secp192r1 +#define MG_UECC_SUPPORTS_secp192r1 0 +#endif +#ifndef MG_UECC_SUPPORTS_secp224r1 +#define MG_UECC_SUPPORTS_secp224r1 0 +#endif +#ifndef MG_UECC_SUPPORTS_secp256r1 +#define MG_UECC_SUPPORTS_secp256r1 1 +#endif +#ifndef MG_UECC_SUPPORTS_secp256k1 +#define MG_UECC_SUPPORTS_secp256k1 0 +#endif + +/* Specifies whether compressed point format is supported. + Set to 0 to disable point compression/decompression functions. */ +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1 +#endif + +struct MG_UECC_Curve_t; +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve; + +#ifdef __cplusplus +extern "C" { +#endif + +#if MG_UECC_SUPPORTS_secp160r1 +MG_UECC_Curve mg_uecc_secp160r1(void); +#endif +#if MG_UECC_SUPPORTS_secp192r1 +MG_UECC_Curve mg_uecc_secp192r1(void); +#endif +#if MG_UECC_SUPPORTS_secp224r1 +MG_UECC_Curve mg_uecc_secp224r1(void); +#endif +#if MG_UECC_SUPPORTS_secp256r1 +MG_UECC_Curve mg_uecc_secp256r1(void); +#endif +#if MG_UECC_SUPPORTS_secp256k1 +MG_UECC_Curve mg_uecc_secp256k1(void); +#endif + +/* MG_UECC_RNG_Function type +The RNG function should fill 'size' random bytes into 'dest'. It should return 1 +if 'dest' was filled with random data, or 0 if the random data could not be +generated. The filled-in values should be either truly random, or from a +cryptographically-secure PRNG. + +A correctly functioning RNG function must be set (using mg_uecc_set_rng()) +before calling mg_uecc_make_key() or mg_uecc_sign(). + +Setting a correctly functioning RNG function improves the resistance to +side-channel attacks for mg_uecc_shared_secret() and +mg_uecc_sign_deterministic(). + +A correct RNG function is set by default when building for Windows, Linux, or OS +X. If you are building on another POSIX-compliant system that supports +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined +RNG. For embedded platforms there is no predefined RNG function; you must +provide your own. +*/ +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size); + +/* mg_uecc_set_rng() function. +Set the function that will be used to generate random bytes. The RNG function +should return 1 if the random data was generated, or 0 if the random data could +not be generated. + +On platforms where there is no predefined RNG function (eg embedded platforms), +this must be called before mg_uecc_make_key() or mg_uecc_sign() are used. + +Inputs: + rng_function - The function that will be used to generate random bytes. +*/ +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function); + +/* mg_uecc_get_rng() function. + +Returns the function that will be used to generate random bytes. +*/ +MG_UECC_RNG_Function mg_uecc_get_rng(void); + +/* mg_uecc_curve_private_key_size() function. + +Returns the size of a private key for the curve in bytes. +*/ +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve); + +/* mg_uecc_curve_public_key_size() function. + +Returns the size of a public key for the curve in bytes. +*/ +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve); + +/* mg_uecc_make_key() function. +Create a public/private key pair. + +Outputs: + public_key - Will be filled in with the public key. Must be at least 2 * +the curve size (in bytes) long. For example, if the curve is secp256r1, +public_key must be 64 bytes long. private_key - Will be filled in with the +private key. Must be as long as the curve order; this is typically the same as +the curve size, except for secp160r1. For example, if the curve is secp256r1, +private_key must be 32 bytes long. + + For secp160r1, private_key must be 21 bytes long! Note that +the first byte will almost always be 0 (there is about a 1 in 2^80 chance of it +being non-zero). + +Returns 1 if the key pair was generated successfully, 0 if an error occurred. +*/ +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key, + MG_UECC_Curve curve); + +/* mg_uecc_shared_secret() function. +Compute a shared secret given your secret key and someone else's public key. If +the public key is not from a trusted source and has not been previously +verified, you should verify it first using mg_uecc_valid_public_key(). Note: It +is recommended that you hash the result of mg_uecc_shared_secret() before using +it for symmetric encryption or HMAC. + +Inputs: + public_key - The public key of the remote party. + private_key - Your private key. + +Outputs: + secret - Will be filled in with the shared secret value. Must be the same +size as the curve size; for example, if the curve is secp256r1, secret must be +32 bytes long. + +Returns 1 if the shared secret was generated successfully, 0 if an error +occurred. +*/ +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key, + uint8_t *secret, MG_UECC_Curve curve); + +#if MG_UECC_SUPPORT_COMPRESSED_POINT +/* mg_uecc_compress() function. +Compress a public key. + +Inputs: + public_key - The public key to compress. + +Outputs: + compressed - Will be filled in with the compressed public key. Must be at +least (curve size + 1) bytes long; for example, if the curve is secp256r1, + compressed must be 33 bytes long. +*/ +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed, + MG_UECC_Curve curve); + +/* mg_uecc_decompress() function. +Decompress a compressed public key. + +Inputs: + compressed - The compressed public key. + +Outputs: + public_key - Will be filled in with the decompressed public key. +*/ +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key, + MG_UECC_Curve curve); +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */ + +/* mg_uecc_valid_public_key() function. +Check to see if a public key is valid. + +Note that you are not required to check for a valid public key before using any +other uECC functions. However, you may wish to avoid spending CPU time computing +a shared secret or verifying a signature using an invalid public key. + +Inputs: + public_key - The public key to check. + +Returns 1 if the public key is valid, 0 if it is invalid. +*/ +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve); + +/* mg_uecc_compute_public_key() function. +Compute the corresponding public key for a private key. + +Inputs: + private_key - The private key to compute the public key for + +Outputs: + public_key - Will be filled in with the corresponding public key + +Returns 1 if the key was computed successfully, 0 if an error occurred. +*/ +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key, + MG_UECC_Curve curve); + +/* mg_uecc_sign() function. +Generate an ECDSA signature for a given hash value. + +Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and +pass it in to this function along with your private key. + +Inputs: + private_key - Your private key. + message_hash - The hash of the message to sign. + hash_size - The size of message_hash in bytes. + +Outputs: + signature - Will be filled in with the signature value. Must be at least 2 * +curve size long. For example, if the curve is secp256r1, signature must be 64 +bytes long. + +Returns 1 if the signature generated successfully, 0 if an error occurred. +*/ +int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash, + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve); + +/* MG_UECC_HashContext structure. +This is used to pass in an arbitrary hash function to +mg_uecc_sign_deterministic(). The structure will be used for multiple hash +computations; each time a new hash is computed, init_hash() will be called, +followed by one or more calls to update_hash(), and finally a call to +finish_hash() to produce the resulting hash. + +The intention is that you will create a structure that includes +MG_UECC_HashContext followed by any hash-specific data. For example: + +typedef struct SHA256_HashContext { + MG_UECC_HashContext uECC; + SHA256_CTX ctx; +} SHA256_HashContext; + +void init_SHA256(MG_UECC_HashContext *base) { + SHA256_HashContext *context = (SHA256_HashContext *)base; + SHA256_Init(&context->ctx); +} + +void update_SHA256(MG_UECC_HashContext *base, + const uint8_t *message, + unsigned message_size) { + SHA256_HashContext *context = (SHA256_HashContext *)base; + SHA256_Update(&context->ctx, message, message_size); +} + +void finish_SHA256(MG_UECC_HashContext *base, uint8_t *hash_result) { + SHA256_HashContext *context = (SHA256_HashContext *)base; + SHA256_Final(hash_result, &context->ctx); +} + +... when signing ... +{ + uint8_t tmp[32 + 32 + 64]; + SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64, +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash, &ctx.uECC, signature); +} +*/ +typedef struct MG_UECC_HashContext { + void (*init_hash)(const struct MG_UECC_HashContext *context); + void (*update_hash)(const struct MG_UECC_HashContext *context, + const uint8_t *message, unsigned message_size); + void (*finish_hash)(const struct MG_UECC_HashContext *context, + uint8_t *hash_result); + unsigned + block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ + unsigned + result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ + uint8_t *tmp; /* Must point to a buffer of at least (2 * result_size + + block_size) bytes. */ +} MG_UECC_HashContext; + +/* mg_uecc_sign_deterministic() function. +Generate an ECDSA signature for a given hash value, using a deterministic +algorithm (see RFC 6979). You do not need to set the RNG using mg_uecc_set_rng() +before calling this function; however, if the RNG is defined it will improve +resistance to side-channel attacks. + +Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and +pass it to this function along with your private key and a hash context. Note +that the message_hash does not need to be computed with the same hash function +used by hash_context. + +Inputs: + private_key - Your private key. + message_hash - The hash of the message to sign. + hash_size - The size of message_hash in bytes. + hash_context - A hash context to use. + +Outputs: + signature - Will be filled in with the signature value. + +Returns 1 if the signature generated successfully, 0 if an error occurred. +*/ +int mg_uecc_sign_deterministic(const uint8_t *private_key, + const uint8_t *message_hash, unsigned hash_size, + const MG_UECC_HashContext *hash_context, + uint8_t *signature, MG_UECC_Curve curve); + +/* mg_uecc_verify() function. +Verify an ECDSA signature. + +Usage: Compute the hash of the signed data using the same hash as the signer and +pass it to this function along with the signer's public key and the signature +values (r and s). + +Inputs: + public_key - The signer's public key. + message_hash - The hash of the signed data. + hash_size - The size of message_hash in bytes. + signature - The signature value. + +Returns 1 if the signature is valid, 0 if it is invalid. +*/ +int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash, + unsigned hash_size, const uint8_t *signature, + MG_UECC_Curve curve); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif /* _UECC_H_ */ + +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ + +#ifndef _UECC_TYPES_H_ +#define _UECC_TYPES_H_ + +#ifndef MG_UECC_PLATFORM +#if defined(__AVR__) && __AVR__ +#define MG_UECC_PLATFORM mg_uecc_avr +#elif defined(__thumb2__) || \ + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2 +#elif defined(__thumb__) +#define MG_UECC_PLATFORM mg_uecc_arm_thumb +#elif defined(__arm__) || defined(_M_ARM) +#define MG_UECC_PLATFORM mg_uecc_arm +#elif defined(__aarch64__) +#define MG_UECC_PLATFORM mg_uecc_arm64 +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \ + defined(__I86__) +#define MG_UECC_PLATFORM mg_uecc_x86 +#elif defined(__amd64__) || defined(_M_X64) +#define MG_UECC_PLATFORM mg_uecc_x86_64 +#else +#define MG_UECC_PLATFORM mg_uecc_arch_other +#endif +#endif + +#ifndef MG_UECC_ARM_USE_UMAAL +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6) +#define MG_UECC_ARM_USE_UMAAL 1 +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \ + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__) +#define MG_UECC_ARM_USE_UMAAL 1 +#else +#define MG_UECC_ARM_USE_UMAAL 0 +#endif +#endif + +#ifndef MG_UECC_WORD_SIZE +#if MG_UECC_PLATFORM == mg_uecc_avr +#define MG_UECC_WORD_SIZE 1 +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64) +#define MG_UECC_WORD_SIZE 8 +#else +#define MG_UECC_WORD_SIZE 4 +#endif +#endif + +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \ + (MG_UECC_WORD_SIZE != 8) +#error "Unsupported value for MG_UECC_WORD_SIZE" +#endif + +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1)) +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 1 +#endif + +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \ + (MG_UECC_WORD_SIZE != 4)) +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 4 +#endif + +typedef int8_t wordcount_t; +typedef int16_t bitcount_t; +typedef int8_t cmpresult_t; + +#if (MG_UECC_WORD_SIZE == 1) + +typedef uint8_t mg_uecc_word_t; +typedef uint16_t mg_uecc_dword_t; + +#define HIGH_BIT_SET 0x80 +#define MG_UECC_WORD_BITS 8 +#define MG_UECC_WORD_BITS_SHIFT 3 +#define MG_UECC_WORD_BITS_MASK 0x07 + +#elif (MG_UECC_WORD_SIZE == 4) + +typedef uint32_t mg_uecc_word_t; +typedef uint64_t mg_uecc_dword_t; + +#define HIGH_BIT_SET 0x80000000 +#define MG_UECC_WORD_BITS 32 +#define MG_UECC_WORD_BITS_SHIFT 5 +#define MG_UECC_WORD_BITS_MASK 0x01F + +#elif (MG_UECC_WORD_SIZE == 8) + +typedef uint64_t mg_uecc_word_t; + +#define HIGH_BIT_SET 0x8000000000000000U +#define MG_UECC_WORD_BITS 64 +#define MG_UECC_WORD_BITS_SHIFT 6 +#define MG_UECC_WORD_BITS_MASK 0x03F + +#endif /* MG_UECC_WORD_SIZE */ + +#endif /* _UECC_TYPES_H_ */ + +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ + +#ifndef _UECC_VLI_H_ +#define _UECC_VLI_H_ + +// +// + +/* Functions for raw large-integer manipulation. These are only available + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */ +#ifndef MG_UECC_ENABLE_VLI_API +#define MG_UECC_ENABLE_VLI_API 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if MG_UECC_ENABLE_VLI_API + +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words); + +/* Constant-time comparison to zero - secure way to compare long integers */ +/* Returns 1 if vli == 0, 0 otherwise. */ +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli, + wordcount_t num_words); + +/* Returns nonzero if bit 'bit' of vli is set. */ +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, bitcount_t bit); + +/* Counts the number of bits required to represent vli. */ +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli, + const wordcount_t max_words); + +/* Sets dest = src. */ +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t *src, + wordcount_t num_words); + +/* Constant-time comparison function - secure way to compare long integers */ +/* Returns one if left == right, zero otherwise */ +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); + +/* Constant-time comparison function - secure way to compare long integers */ +/* Returns sign of left - right, in constant time. */ +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, wordcount_t num_words); + +/* Computes vli = vli >> 1. */ +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t num_words); + +/* Computes result = left + right, returning carry. Can modify in place. */ +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); + +/* Computes result = left - right, returning borrow. Can modify in place. */ +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); + +/* Computes result = left * right. Result must be 2 * num_words long. */ +void mg_uecc_vli_mult(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, wordcount_t num_words); + +/* Computes result = left^2. Result must be 2 * num_words long. */ +void mg_uecc_vli_square(mg_uecc_word_t *result, const mg_uecc_word_t *left, + wordcount_t num_words); + +/* Computes result = (left + right) % mod. + Assumes that left < mod and right < mod, and that result does not overlap + mod. */ +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); + +/* Computes result = (left - right) % mod. + Assumes that left < mod and right < mod, and that result does not overlap + mod. */ +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); + +/* Computes result = product % mod, where product is 2N words long. + Currently only designed to work for mod == curve->p or curve_n. */ +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t *product, + const mg_uecc_word_t *mod, wordcount_t num_words); + +/* Calculates result = product (mod curve->p), where product is up to + 2 * curve->num_words long. */ +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product, + MG_UECC_Curve curve); + +/* Computes result = (left * right) % mod. + Currently only designed to work for mod == curve->p or curve_n. */ +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); + +/* Computes result = (left * right) % curve->p. */ +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, MG_UECC_Curve curve); + +/* Computes result = left^2 % mod. + Currently only designed to work for mod == curve->p or curve_n. */ +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *mod, wordcount_t num_words); + +/* Computes result = left^2 % curve->p. */ +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + MG_UECC_Curve curve); + +/* Computes result = (1 / input) % mod.*/ +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const mg_uecc_word_t *input, + const mg_uecc_word_t *mod, wordcount_t num_words); + +#if MG_UECC_SUPPORT_COMPRESSED_POINT +/* Calculates a = sqrt(a) (mod curve->p) */ +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve); +#endif + +/* Converts an integer in uECC native format to big-endian bytes. */ +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes, + const mg_uecc_word_t *native); +/* Converts big-endian bytes to an integer in uECC native format. */ +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const uint8_t *bytes, + int num_bytes); + +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve); + +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve); + +int mg_uecc_valid_point(const mg_uecc_word_t *point, MG_UECC_Curve curve); + +/* Multiplies a point by a scalar. Points are represented by the X coordinate + followed by the Y coordinate in the same array, both coordinates are + curve->num_words long. Note that scalar must be curve->num_n_words long (NOT + curve->num_words). */ +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point, + const mg_uecc_word_t *scalar, MG_UECC_Curve curve); + +/* Generates a random integer in the range 0 < random < top. + Both random and top have num_words words. */ +int mg_uecc_generate_random_int(mg_uecc_word_t *random, + const mg_uecc_word_t *top, + wordcount_t num_words); + +#endif /* MG_UECC_ENABLE_VLI_API */ + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif /* _UECC_VLI_H_ */ + +// End of uecc BSD-2 +// portable8439 v1.0.1 +// Source: https://github.com/DavyLandman/portable8439 +// Licensed under CC0-1.0 +// Contains poly1305-donna e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781 (Public +// Domain) + + + + +#ifndef __PORTABLE_8439_H +#define __PORTABLE_8439_H +#if defined(__cplusplus) +extern "C" { +#endif + +// provide your own decl specificier like -DPORTABLE_8439_DECL=ICACHE_RAM_ATTR +#ifndef PORTABLE_8439_DECL +#define PORTABLE_8439_DECL +#endif + +/* + This library implements RFC 8439 a.k.a. ChaCha20-Poly1305 AEAD + + You can use this library to avoid attackers mutating or reusing your + encrypted messages. This does assume you never reuse a nonce+key pair and, + if possible, carefully pick your associated data. +*/ + +/* Make sure we are either nested in C++ or running in a C99+ compiler +#if !defined(__cplusplus) && !defined(_MSC_VER) && \ + (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#error "C99 or newer required" +#endif */ + +// #if CHAR_BIT > 8 +// # error "Systems without native octals not suppoted" +// #endif + +#if defined(_MSC_VER) || defined(__cplusplus) +// add restrict support is possible +#if (defined(_MSC_VER) && _MSC_VER >= 1900) || defined(__clang__) || \ + defined(__GNUC__) +#define restrict __restrict +#else +#define restrict +#endif +#endif + +#define RFC_8439_TAG_SIZE (16) +#define RFC_8439_KEY_SIZE (32) +#define RFC_8439_NONCE_SIZE (12) + +/* + Encrypt/Seal plain text bytes into a cipher text that can only be + decrypted by knowing the key, nonce and associated data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated + for the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer + + output: + - cipher_text: encrypted plain_text with a tag appended. Make sure to + allocate at least plain_text_size + RFC_8439_TAG_SIZE + + returns: + - size of bytes written to cipher_text, can be -1 if overlapping + pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_encrypt( + uint8_t *restrict cipher_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], const uint8_t *restrict ad, + size_t ad_size, const uint8_t *restrict plain_text, size_t plain_text_size); + +/* + Decrypt/unseal cipher text given the right key, nonce, and additional data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated for + the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - cipher_text: encrypted message. + + output: + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer, leave at least enough room for + cipher_text_size - RFC_8439_TAG_SIZE + + returns: + - size of bytes written to plain_text, -1 signals either: + - incorrect key/nonce/ad + - corrupted cipher_text + - overlapping pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt( + uint8_t *restrict plain_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], + const uint8_t *restrict cipher_text, size_t cipher_text_size); +#if defined(__cplusplus) +} +#endif +#endif + + +struct mg_connection; +typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, + void *ev_data); +void mg_call(struct mg_connection *c, int ev, void *ev_data); +void mg_error(struct mg_connection *c, const char *fmt, ...); + +enum { + MG_EV_ERROR, // Error char *error_message + MG_EV_OPEN, // Connection created NULL + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis + MG_EV_RESOLVE, // Host name is resolved NULL + MG_EV_CONNECT, // Connection established NULL + MG_EV_ACCEPT, // Connection accepted NULL + MG_EV_TLS_HS, // TLS handshake succeeded NULL + MG_EV_READ, // Data received from socket long *bytes_read + MG_EV_WRITE, // Data written to socket long *bytes_written + MG_EV_CLOSE, // Connection closed NULL + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message * + MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message * + MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data + MG_EV_USER // Starting ID for user events +}; + + + + + + + + + +struct mg_dns { + const char *url; // DNS server URL + struct mg_connection *c; // DNS server connection +}; + +struct mg_addr { + uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte order + uint16_t port; // TCP or UDP port in network byte order + uint8_t scope_id; // IPv6 scope ID + bool is_ip6; // True when address is IPv6 address +}; + +struct mg_mgr { + struct mg_connection *conns; // List of active connections + struct mg_dns dns4; // DNS for IPv4 + struct mg_dns dns6; // DNS for IPv6 + int dnstimeout; // DNS resolve timeout in milliseconds + bool use_dns6; // Use DNS6 server by default, see #1532 + unsigned long nextid; // Next connection ID + unsigned long timerid; // Next timer ID + void *userdata; // Arbitrary user data pointer + void *tls_ctx; // TLS context shared by all TLS sessions + uint16_t mqtt_id; // MQTT IDs for pub/sub + void *active_dns_requests; // DNS requests in progress + struct mg_timer *timers; // Active timers + int epoll_fd; // Used when MG_EPOLL_ENABLE=1 + void *priv; // Used by the MIP stack + size_t extraconnsize; // Used by the MIP stack + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup() +#if MG_ENABLE_FREERTOS_TCP + SocketSet_t ss; // NOTE(lsm): referenced from socket struct +#endif +}; + +struct mg_connection { + struct mg_connection *next; // Linkage in struct mg_mgr :: connections + struct mg_mgr *mgr; // Our container + struct mg_addr loc; // Local address + struct mg_addr rem; // Remote address + void *fd; // Connected socket, or LWIP data + unsigned long id; // Auto-incrementing unique connection ID + struct mg_iobuf recv; // Incoming data + struct mg_iobuf send; // Outgoing data + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE + struct mg_iobuf rtls; // TLS only. Incoming encrypted data + mg_event_handler_t fn; // User-specified event handler function + void *fn_data; // User-specified function parameter + mg_event_handler_t pfn; // Protocol-specific handler function + void *pfn_data; // Protocol-specific function parameter + char data[MG_DATA_SIZE]; // Arbitrary connection data + void *tls; // TLS specific data + unsigned is_listening : 1; // Listening connection + unsigned is_client : 1; // Outbound (client) connection + unsigned is_accepted : 1; // Accepted (server) connection + unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress + unsigned is_arplooking : 1; // Non-blocking ARP resolution is in progress + unsigned is_connecting : 1; // Non-blocking connect is in progress + unsigned is_tls : 1; // TLS-enabled connection + unsigned is_tls_hs : 1; // TLS handshake is in progress + unsigned is_udp : 1; // UDP connection + unsigned is_websocket : 1; // WebSocket connection + unsigned is_mqtt5 : 1; // For MQTT connection, v5 indicator + unsigned is_hexdumping : 1; // Hexdump in/out traffic + unsigned is_draining : 1; // Send remaining data, then close and free + unsigned is_closing : 1; // Close and free the connection immediately + unsigned is_full : 1; // Stop reads, until cleared + unsigned is_resp : 1; // Response is still being generated + unsigned is_readable : 1; // Connection is ready to read + unsigned is_writable : 1; // Connection is ready to write + unsigned is_io_err : 1; // Remember IO_ERR condition for later use +}; + +void mg_mgr_poll(struct mg_mgr *, int ms); +void mg_mgr_init(struct mg_mgr *); +void mg_mgr_free(struct mg_mgr *); + +struct mg_connection *mg_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data); +void mg_connect_resolved(struct mg_connection *); +bool mg_send(struct mg_connection *, const void *, size_t); +size_t mg_printf(struct mg_connection *, const char *fmt, ...); +size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap); +bool mg_aton(struct mg_str str, struct mg_addr *addr); + +// These functions are used to integrate with custom network stacks +struct mg_connection *mg_alloc_conn(struct mg_mgr *); +void mg_close_conn(struct mg_connection *c); +bool mg_open_listener(struct mg_connection *c, const char *url); + +// Utility functions +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len); +bool mg_wakeup_init(struct mg_mgr *); +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg); + + + + + + + + +struct mg_http_header { + struct mg_str name; // Header name + struct mg_str value; // Header value +}; + +struct mg_http_message { + struct mg_str method, uri, query, proto; // Request/response line + struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers + struct mg_str body; // Body + struct mg_str head; // Request + headers + struct mg_str message; // Request + headers + body +}; + +// Parameter for mg_http_serve_dir() +struct mg_http_serve_opts { + const char *root_dir; // Web root directory, must be non-NULL + const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml + const char *extra_headers; // Extra HTTP headers to add in responses + const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. + const char *page404; // Path to the 404 page, or NULL by default + struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX +}; + +// Parameter for mg_http_next_multipart +struct mg_http_part { + struct mg_str name; // Form field name + struct mg_str filename; // Filename for file uploads + struct mg_str body; // Part contents +}; + +int mg_http_parse(const char *s, size_t len, struct mg_http_message *); +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); +void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); +void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm); +struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, + const struct mg_http_serve_opts *); +void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, + const char *path, const struct mg_http_serve_opts *); +void mg_http_reply(struct mg_connection *, int status_code, const char *headers, + const char *body_fmt, ...); +struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); +int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); +int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); +size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); +void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir, size_t max_size); +void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); +int mg_http_status(const struct mg_http_message *hm); +void mg_hello(const char *url); + + +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath); + + +#define MG_TLS_NONE 0 // No TLS support +#define MG_TLS_MBED 1 // mbedTLS +#define MG_TLS_OPENSSL 2 // OpenSSL +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL) +#define MG_TLS_BUILTIN 3 // Built-in +#define MG_TLS_CUSTOM 4 // Custom implementation + +#ifndef MG_TLS +#define MG_TLS MG_TLS_NONE +#endif + + + + + +struct mg_tls_opts { + struct mg_str ca; // PEM or DER + struct mg_str cert; // PEM or DER + struct mg_str key; // PEM or DER + struct mg_str name; // If not empty, enable host name verification + int skip_verification; // Skip certificate and host name verification +}; + +void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts); +void mg_tls_free(struct mg_connection *); +long mg_tls_send(struct mg_connection *, const void *buf, size_t len); +long mg_tls_recv(struct mg_connection *, void *buf, size_t len); +size_t mg_tls_pending(struct mg_connection *); +void mg_tls_handshake(struct mg_connection *); + +// Private +void mg_tls_ctx_init(struct mg_mgr *); +void mg_tls_ctx_free(struct mg_mgr *); + +// Low-level IO primives used by TLS layer +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 }; +long mg_io_send(struct mg_connection *c, const void *buf, size_t len); +long mg_io_recv(struct mg_connection *c, void *buf, size_t len); + + + + + + + +#if MG_TLS == MG_TLS_MBED +#include +#include +#include +#include + +struct mg_tls_ctx { + int dummy; +#ifdef MBEDTLS_SSL_SESSION_TICKETS + mbedtls_ssl_ticket_context tickets; +#endif +}; + +struct mg_tls { + mbedtls_x509_crt ca; // Parsed CA certificate + mbedtls_x509_crt cert; // Parsed certificate + mbedtls_pk_context pk; // Private key context + mbedtls_ssl_context ssl; // SSL/TLS context + mbedtls_ssl_config conf; // SSL-TLS config +#ifdef MBEDTLS_SSL_SESSION_TICKETS + mbedtls_ssl_ticket_context ticket; // Session tickets context +#endif +}; +#endif + + +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL + +#include +#include + +struct mg_tls { + BIO_METHOD *bm; + SSL_CTX *ctx; + SSL *ssl; +}; +#endif + + +#define WEBSOCKET_OP_CONTINUE 0 +#define WEBSOCKET_OP_TEXT 1 +#define WEBSOCKET_OP_BINARY 2 +#define WEBSOCKET_OP_CLOSE 8 +#define WEBSOCKET_OP_PING 9 +#define WEBSOCKET_OP_PONG 10 + + + +struct mg_ws_message { + struct mg_str data; // Websocket message data + uint8_t flags; // Websocket message flags +}; + +struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data, + const char *fmt, ...); +void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *, + const char *fmt, ...); +size_t mg_ws_send(struct mg_connection *, const void *buf, size_t len, int op); +size_t mg_ws_wrap(struct mg_connection *, size_t len, int op); +size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...); +size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt, + va_list *); + + + + +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data); +void mg_sntp_request(struct mg_connection *c); +int64_t mg_sntp_parse(const unsigned char *buf, size_t len); + +uint64_t mg_now(void); // Return milliseconds since Epoch + + + + + +#define MQTT_CMD_CONNECT 1 +#define MQTT_CMD_CONNACK 2 +#define MQTT_CMD_PUBLISH 3 +#define MQTT_CMD_PUBACK 4 +#define MQTT_CMD_PUBREC 5 +#define MQTT_CMD_PUBREL 6 +#define MQTT_CMD_PUBCOMP 7 +#define MQTT_CMD_SUBSCRIBE 8 +#define MQTT_CMD_SUBACK 9 +#define MQTT_CMD_UNSUBSCRIBE 10 +#define MQTT_CMD_UNSUBACK 11 +#define MQTT_CMD_PINGREQ 12 +#define MQTT_CMD_PINGRESP 13 +#define MQTT_CMD_DISCONNECT 14 +#define MQTT_CMD_AUTH 15 + +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01 +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02 +#define MQTT_PROP_CONTENT_TYPE 0x03 +#define MQTT_PROP_RESPONSE_TOPIC 0x08 +#define MQTT_PROP_CORRELATION_DATA 0x09 +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11 +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12 +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13 +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15 +#define MQTT_PROP_AUTHENTICATION_DATA 0x16 +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17 +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18 +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19 +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A +#define MQTT_PROP_SERVER_REFERENCE 0x1C +#define MQTT_PROP_REASON_STRING 0x1F +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21 +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22 +#define MQTT_PROP_TOPIC_ALIAS 0x23 +#define MQTT_PROP_MAXIMUM_QOS 0x24 +#define MQTT_PROP_RETAIN_AVAILABLE 0x25 +#define MQTT_PROP_USER_PROPERTY 0x26 +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27 +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28 +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29 +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A + +enum { + MQTT_PROP_TYPE_BYTE, + MQTT_PROP_TYPE_STRING, + MQTT_PROP_TYPE_STRING_PAIR, + MQTT_PROP_TYPE_BINARY_DATA, + MQTT_PROP_TYPE_VARIABLE_INT, + MQTT_PROP_TYPE_INT, + MQTT_PROP_TYPE_SHORT +}; + +enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; + +struct mg_mqtt_prop { + uint8_t id; // Enumerated at MQTT5 Reference + uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types + struct mg_str key; // Non-NULL only for user property type + struct mg_str val; // Non-NULL only for UTF-8 types and user properties +}; + +struct mg_mqtt_opts { + struct mg_str user; // Username, can be empty + struct mg_str pass; // Password, can be empty + struct mg_str client_id; // Client ID + struct mg_str topic; // message/subscription topic + struct mg_str message; // message content + uint8_t qos; // message quality of service + uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4 + uint16_t keepalive; // Keep-alive timer in seconds + uint16_t retransmit_id; // For PUBLISH, init to 0 + bool retain; // Retain flag + bool clean; // Clean session flag + struct mg_mqtt_prop *props; // MQTT5 props array + size_t num_props; // number of props + struct mg_mqtt_prop *will_props; // Valid only for CONNECT packet (MQTT5) + size_t num_will_props; // Number of will props +}; + +struct mg_mqtt_message { + struct mg_str topic; // Parsed topic for PUBLISH + struct mg_str data; // Parsed message for PUBLISH + struct mg_str dgram; // Whole MQTT packet, including headers + uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH + uint8_t cmd; // MQTT command, one of MQTT_CMD_* + uint8_t qos; // Quality of service + uint8_t ack; // CONNACK return code, 0 = success + size_t props_start; // Offset to the start of the properties (MQTT5) + size_t props_size; // Length of the properties +}; + +struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, + const struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data); +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts); +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts); +void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts); +int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *); +void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, + uint32_t len); +void mg_mqtt_ping(struct mg_connection *); +void mg_mqtt_pong(struct mg_connection *); +void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *); +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *, + size_t ofs); + + + + + +// Mongoose sends DNS queries that contain only one question: +// either A (IPv4) or AAAA (IPv6) address lookup. +// Therefore, we expect zero or one answer. +// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. +struct mg_dns_message { + uint16_t txnid; // Transaction ID + bool resolved; // Resolve successful, addr is set + struct mg_addr addr; // Resolved address + char name[256]; // Host name +}; + +struct mg_dns_header { + uint16_t txnid; // Transaction ID + uint16_t flags; + uint16_t num_questions; + uint16_t num_answers; + uint16_t num_authority_prs; + uint16_t num_other_prs; +}; + +// DNS resource record +struct mg_dns_rr { + uint16_t nlen; // Name or pointer length + uint16_t atype; // Address type + uint16_t aclass; // Address class + uint16_t alen; // Address length +}; + +void mg_resolve(struct mg_connection *, const char *url); +void mg_resolve_cancel(struct mg_connection *); +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *); + + + + + +#ifndef MG_JSON_MAX_DEPTH +#define MG_JSON_MAX_DEPTH 30 +#endif + +// Error return values - negative. Successful returns are >= 0 +enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 }; +int mg_json_get(struct mg_str json, const char *path, int *toklen); + +struct mg_str mg_json_get_tok(struct mg_str json, const char *path); +bool mg_json_get_num(struct mg_str json, const char *path, double *v); +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v); +long mg_json_get_long(struct mg_str json, const char *path, long dflt); +char *mg_json_get_str(struct mg_str json, const char *path); +char *mg_json_get_hex(struct mg_str json, const char *path, int *len); +char *mg_json_get_b64(struct mg_str json, const char *path, int *len); + +bool mg_json_unescape(struct mg_str str, char *buf, size_t len); +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val); + + + + +// JSON-RPC request descriptor +struct mg_rpc_req { + struct mg_rpc **head; // RPC handlers list head + struct mg_rpc *rpc; // RPC handler being called + mg_pfn_t pfn; // Response printing function + void *pfn_data; // Response printing function data + void *req_data; // Arbitrary request data + struct mg_str frame; // Request, e.g. {"id":1,"method":"add","params":[1,2]} +}; + +// JSON-RPC method handler +struct mg_rpc { + struct mg_rpc *next; // Next in list + struct mg_str method; // Method pattern + void (*fn)(struct mg_rpc_req *); // Handler function + void *fn_data; // Handler function argument +}; + +void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern, + void (*handler)(struct mg_rpc_req *), void *handler_data); +void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *)); +void mg_rpc_process(struct mg_rpc_req *); + +// Helper functions to print result or error frame +void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...); +void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap); +void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...); +void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *); +void mg_rpc_list(struct mg_rpc_req *r); +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved + + + + + +#define MG_OTA_NONE 0 // No OTA support +#define MG_OTA_FLASH 1 // OTA via an internal flash +#define MG_OTA_ESP32 2 // ESP32 OTA implementation +#define MG_OTA_CUSTOM 100 // Custom implementation + +#ifndef MG_OTA +#define MG_OTA MG_OTA_NONE +#endif + +#if defined(__GNUC__) && !defined(__APPLE__) +#define MG_IRAM __attribute__((section(".iram"))) +#else +#define MG_IRAM +#endif + +// Firmware update API +bool mg_ota_begin(size_t new_firmware_size); // Start writing +bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k +bool mg_ota_end(void); // Stop writing + +enum { + MG_OTA_UNAVAILABLE = 0, // No OTA information is present + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback + MG_OTA_COMMITTED = 3 // The firmware is good +}; +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; + +int mg_ota_status(int firmware); // Return firmware status MG_OTA_* +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch +size_t mg_ota_size(int firmware); // Firmware size + +bool mg_ota_commit(void); // Commit current firmware +bool mg_ota_rollback(void); // Rollback to the previous firmware +MG_IRAM void mg_ota_boot(void); // Bootloader function +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved + + + + + +#define MG_DEVICE_NONE 0 // Dummy system + +#define MG_DEVICE_STM32H5 1 // STM32 H5 +#define MG_DEVICE_STM32H7 2 // STM32 H7 +#define MG_DEVICE_CH32V307 100 // WCH CH32V307 +#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_DEVICE_RT1020 300 // IMXRT1020 +#define MG_DEVICE_RT1060 301 // IMXRT1060 +#define MG_DEVICE_CUSTOM 1000 // Custom implementation + +#ifndef MG_DEVICE +#define MG_DEVICE MG_DEVICE_NONE +#endif + +// Flash information +void *mg_flash_start(void); // Return flash start address +size_t mg_flash_size(void); // Return flash size +size_t mg_flash_sector_size(void); // Return flash sector size +size_t mg_flash_write_align(void); // Return flash write align, minimum 4 +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 + +// Write, erase, swap bank +bool mg_flash_write(void *addr, const void *buf, size_t len); +bool mg_flash_erase(void *sector); +bool mg_flash_swap_bank(void); + +// Convenience functions to store data on a flash sector with wear levelling +// If `sector` is NULL, then the last sector of flash is used +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); + +void mg_device_reset(void); // Reboot device immediately + + + + + + +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP +struct mg_tcpip_if; // Mongoose TCP/IP network interface +#define MG_TCPIP_IFACE(mgr_) ((struct mg_tcpip_if *) (mgr_)->priv) + +struct mg_tcpip_driver { + bool (*init)(struct mg_tcpip_if *); // Init driver + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit frame + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Receive frame + bool (*up)(struct mg_tcpip_if *); // Up/down status +}; + +typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, + void *ev_data); + +enum { + MG_TCPIP_EV_ST_CHG, // state change uint8_t * (&ifp->state) + MG_TCPIP_EV_DHCP_DNS, // DHCP DNS assignment uint32_t *ipaddr + MG_TCPIP_EV_DHCP_SNTP, // DHCP SNTP assignment uint32_t *ipaddr + MG_TCPIP_EV_USER // Starting ID for user events +}; + +// Network interface +struct mg_tcpip_if { + uint8_t mac[6]; // MAC address. Must be set to a valid MAC + uint32_t ip, mask, gw; // IP address, mask, default gateway + struct mg_str tx; // Output (TX) buffer + bool enable_dhcp_client; // Enable DCHP client + bool enable_dhcp_server; // Enable DCHP server + bool enable_get_gateway; // DCHP server sets client as gateway + bool enable_req_dns; // DCHP client requests DNS server + bool enable_req_sntp; // DCHP client requests SNTP server + bool enable_crc32_check; // Do a CRC check on RX frames and strip it + bool enable_mac_check; // Do a MAC check on RX frames + struct mg_tcpip_driver *driver; // Low level driver + void *driver_data; // Driver-specific data + mg_tcpip_event_handler_t fn; // User-specified event handler function + struct mg_mgr *mgr; // Mongoose event manager + struct mg_queue recv_queue; // Receive queue + uint16_t mtu; // Interface MTU +#define MG_TCPIP_MTU_DEFAULT 1500 + + // Internal state, user can use it but should not change it + uint8_t gwmac[6]; // Router's MAC + uint64_t now; // Current time + uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state + uint64_t lease_expire; // Lease expiration time, in ms + uint16_t eport; // Next ephemeral port + volatile uint32_t ndrop; // Number of received, but dropped frames + volatile uint32_t nrecv; // Number of received frames + volatile uint32_t nsent; // Number of transmitted frames + volatile uint32_t nerr; // Number of driver errors + uint8_t state; // Current state +#define MG_TCPIP_STATE_DOWN 0 // Interface is down +#define MG_TCPIP_STATE_UP 1 // Interface is up +#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP +#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned +}; + +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *); +void mg_tcpip_free(struct mg_tcpip_if *); +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp); + +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f; +extern struct mg_tcpip_driver mg_tcpip_driver_w5500; +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c; +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h; +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt; +extern struct mg_tcpip_driver mg_tcpip_driver_same54; +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis; +extern struct mg_tcpip_driver mg_tcpip_driver_ra; +extern struct mg_tcpip_driver mg_tcpip_driver_xmc; +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; + +// Drivers that require SPI, can use this SPI abstraction +struct mg_tcpip_spi { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply +}; +#endif + + + +// Macros to record timestamped events that happens with a connection. +// They are saved into a c->prof IO buffer, each event is a name and a 32-bit +// timestamp in milliseconds since connection init time. +// +// Test (run in two separate terminals): +// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1 +// curl localhost:8000 +// Output: +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile: +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE +// +// Usage: +// Enable profiling by setting MG_ENABLE_PROFILE=1 +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure + +#if MG_ENABLE_PROFILE +struct mg_profitem { + const char *name; // Event name + uint32_t timestamp; // Milliseconds since connection creation (MG_EV_OPEN) +}; + +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items wih to realloc + +// Adding a profile item to the c->prof. Must be as fast as possible. +// Reallocation of the c->prof iobuf is not desirable here, that's why we +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY. +// This macro just inits and copies 8 bytes, and calls mg_millis(), +// which should be fast enough. +#define MG_PROF_ADD(c, name_) \ + do { \ + struct mg_iobuf *io = &c->prof; \ + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \ + struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \ + mg_iobuf_add(io, io->len, &item, sizeof(item)); \ + } while (0) + +// Initialising profile for a new connection. Not time sensitive +#define MG_PROF_INIT(c) \ + do { \ + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \ + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \ + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \ + } while (0) + +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof) + +// Dumping the profile. Not time sensitive +#define MG_PROF_DUMP(c) \ + do { \ + struct mg_iobuf *io = &c->prof; \ + struct mg_profitem *p = (struct mg_profitem *) io->buf; \ + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \ + MG_INFO(("%lu profile:", c->id)); \ + while (p < e) { \ + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \ + p++; \ + } \ + } while (0) + +#else +#define MG_PROF_INIT(c) +#define MG_PROF_FREE(c) +#define MG_PROF_ADD(c, name) +#define MG_PROF_DUMP(c) +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS + +#include "Driver_ETH_MAC.h" // keep this include +#include "Driver_ETH_PHY.h" // keep this include + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT + +struct mg_tcpip_driver_imxrt_data { + // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk), + // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz + // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1)) + // ipg_clk mdc_cr VALUE + // -------------------------- + // -1 <-- TODO() tell driver to guess the value + // 25 MHz 4 + // 33 MHz 6 + // 40 MHz 7 + // 50 MHz 9 + // 66 MHz 13 + int mdc_cr; // Valid values: -1 to 63 + + uint8_t phy_addr; // PHY address +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 2 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 24 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_imxrt_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_imxrt; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + + + +struct mg_phy { + uint16_t (*read_reg)(uint8_t addr, uint8_t reg); + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value); +}; + +// PHY configuration settings, bitmask +enum { + // Set if PHY LEDs are connected to ground + MG_PHY_LEDS_ACTIVE_HIGH = (1 << 0), + // Set when PHY clocks MAC. Otherwise, MAC clocks PHY + MG_PHY_CLOCKS_MAC = (1 << 1), +}; + +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M }; + +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config); +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex, + uint8_t *speed); + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA + +struct mg_tcpip_driver_ra_data { + // MDC clock "divider". MDC clock is software generated, + uint32_t clock; // core clock frequency in Hz + uint16_t irqno; // IRQn, R_ICU->IELSR[irqno] + uint8_t phy_addr; // PHY address +}; + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54 + +struct mg_tcpip_driver_same54_data { + int mdc_cr; +}; + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 5 +#endif + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ + MG_ENABLE_DRIVER_STM32F + +struct mg_tcpip_driver_stm32f_data { + // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz + // HCLK range DIVIDER mdc_cr VALUE + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz HCLK/42 0 + // 100-150 MHz HCLK/62 1 + // 20-35 MHz HCLK/16 2 + // 35-60 MHz HCLK/26 3 + // 150-216 MHz HCLK/102 4 <-- value for Nucleo-F* on max speed + // 216-310 MHz HCLK/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + + uint8_t phy_addr; // PHY address +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_stm32f_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_stm32f; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP +#if !defined(MG_ENABLE_DRIVER_STM32H) +#define MG_ENABLE_DRIVER_STM32H 0 +#endif +#if !defined(MG_ENABLE_DRIVER_MCXN) +#define MG_ENABLE_DRIVER_MCXN 0 +#endif +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN + +struct mg_tcpip_driver_stm32h_data { + // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz + // HCLK range DIVIDER mdc_cr VALUE + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz HCLK/42 0 + // 100-150 MHz HCLK/62 1 + // 20-35 MHz HCLK/16 2 + // 35-60 MHz HCLK/26 3 + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI + // 300-500 MHz HCLK/204 6 + // 500-800 MHz HCLK/324 7 + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + + uint8_t phy_addr; // PHY address + uint8_t phy_conf; // PHY config +}; + +#ifndef MG_TCPIP_PHY_CONF +#define MG_TCPIP_PHY_CONF MG_PHY_CLOCKS_MAC +#endif + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_stm32h_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + driver_data_.phy_conf = MG_TCPIP_PHY_CONF; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_stm32h; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C + +struct mg_tcpip_driver_tm4c_data { + // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz + // SYSCLK range DIVIDER mdc_cr VALUE + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz SYSCLK/42 0 + // 100-150 MHz SYSCLK/62 1 <-- value for EK-TM4C129* on max speed + // 20-35 MHz SYSCLK/16 2 + // 35-60 MHz SYSCLK/26 3 + // 0x4-0xF Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3 +}; + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tm4c_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_tm4c; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tm4c, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC + +struct mg_tcpip_driver_xmc_data { + // 13.2.8.1 Station Management Functions + // MDC clock divider (). MDC clock is derived from ETH MAC clock + // It must not exceed 2.5MHz + // ETH Clock range DIVIDER mdc_cr VALUE + // -------------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz ETH Clock/42 0 + // 100-150 MHz ETH Clock/62 1 + // 20-35 MHz ETH Clock/16 2 + // 35-60 MHz ETH Clock/26 3 + // 150-250 MHz ETH Clock/102 4 + // 250-300 MHz ETH Clock/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_xmc_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_xmc; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 + +struct mg_tcpip_driver_xmc7_data { + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_xmc7_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_xmc7; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#ifdef __cplusplus +} +#endif +#endif // MONGOOSE_H diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index 3e332d3e..31914894 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -4,28 +4,25 @@ #include #include #include +#include #define assert_tuple_arg (assert(i < argc && "Expecting value")) namespace CommandLineArgs { - enum NetworkMode { - SENDER, - GRABBER, - OFFLINE - }; + struct { bool skipDialog; const char* configFile; const char* shaderFile; const char* serverURL; - NetworkMode networkMode; + Network::NetworkMode networkMode; } Args; void parse_args(int argc,const char *argv[]) { Args.skipDialog = false; Args.configFile = "config.json"; Args.shaderFile = "shader.glsl"; - Args.networkMode = OFFLINE; + Args.networkMode = Network::NetworkMode::OFFLINE; Args.serverURL = "ws://drone.alkama.com:9000/roomname/username"; for(size_t i=0;i +#include "mongoose.h" +#include +namespace Network { + struct mg_mgr mgr; + struct mg_connection* c; + bool done = false; + std::thread* tNetwork; + static const char* s_url = "ws://drone.alkama.com:9000/roomname/handle"; + + bool NewShader = false; + struct { + std::string Code; + int CaretPosition; + int AnchorPosition; + int FirstVisibleLine; + bool NeedRecompile; + } ShaderMessage; + + + enum NetworkMode { + SENDER, + GRABBER, + OFFLINE + }; + bool HasNewShader() { + if (NewShader) { + NewShader = false; + return true; + } + return false; + + } + void RecieveShader(size_t size, char* data) { + // TODO: very very bad, we should: + // - use json + // - verify size + // - non-ascii symbols ? + // - asynchronous update ? + //data[size - 1] = '\0'; + std::string TextJson(data); + jsonxx::Object NewShader; + jsonxx::Object Data; + bool ErrorFound = false; + + if (NewShader.parse(TextJson)) { + if (NewShader.has("Data")) { + Data = NewShader.get("Data"); + if (!Data.has("Code")) ErrorFound = true; + if (!Data.has("Caret")) ErrorFound = true; + if (!Data.has("Anchor")) ErrorFound = true; + if (!Data.has("FirstVisibleLine")) ErrorFound = true; + if (!Data.has("Compile")) ErrorFound = true; + } + else { + ErrorFound = true; + } + } + else { + ErrorFound = true; + } + if (ErrorFound) { + fprintf(stderr, "Invalid json formatting\n"); + return; + } + if (Data.has("ShaderTime")) { + + float t = Data.get("ShaderTime"); + ShaderMessage.Code = Data.get < jsonxx::String>("Code"); + ShaderMessage.AnchorPosition = Data.get("Anchor"); + ShaderMessage.CaretPosition = Data.get("Caret"); + ShaderMessage.NeedRecompile = Data.get("Compile"); + Network::NewShader = true; + + } + + } + static void fn(struct mg_connection* c, int ev, void* ev_data) { + if (ev == MG_EV_OPEN) { + c->is_hexdumping = 0; + } + else if (ev == MG_EV_ERROR) { + // On error, log error message + MG_ERROR(("%p %s", c->fd, (char*)ev_data)); + } + else if (ev == MG_EV_WS_OPEN) { + fprintf(stdout, "[Network]: Connected\n"); + // When websocket handshake is successful, send message + // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); + } + else if (ev == MG_EV_WS_MSG) { + // When we get echo response, print it + + struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; + RecieveShader((int)wm->data.len, wm->data.buf); + // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); + } + + /*/if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) { + *(bool*)c->fn_data = true; // Signal that we're done + }*/ + } + + void Create(){ + fprintf(stdout,"[Network]: Try to connect to %s\n", s_url); + + mg_mgr_init(&mgr); + c = mg_ws_connect(&mgr, s_url, fn, &done, NULL); + if (c == NULL) { + fprintf(stderr, "Invalid address\n"); + return; + } + while (true) { + mg_mgr_poll(&mgr, 1000); + } + mg_mgr_free(&mgr); + } + + void Init() { + std::thread network(Create); + tNetwork = &network; + tNetwork->detach(); + + } + bool ReloadShader() { + if (ShaderMessage.NeedRecompile) { + ShaderMessage.NeedRecompile = false; + return true; + } + return false; + } + void UpdateShader(ShaderEditor* mShaderEditor) { + if (Network::HasNewShader()) { + + int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); + int PreviousTopDocLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, PreviousTopLine, 0); + int PreviousTopLineTotal = PreviousTopDocLine; + + mShaderEditor->SetText(ShaderMessage.Code.c_str()); + mShaderEditor->WndProc(SCI_SETCURRENTPOS, ShaderMessage.CaretPosition, 0); + mShaderEditor->WndProc(SCI_SETANCHOR, ShaderMessage.AnchorPosition, 0); + mShaderEditor->WndProc(SCI_SETFIRSTVISIBLELINE, PreviousTopLineTotal, 0); + + //if (bGrabberFollowCaret) { + //mShaderEditor.WndProc(SCI_SETFIRSTVISIBLELINE, NewMessage.FirstVisibleLine, 0); + mShaderEditor->WndProc(SCI_SCROLLCARET, 0, 0); + //} + + + } + } +} +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a84d597e..2c6b3b60 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,8 @@ #include "SetupDialog.h" #include "CommandLineArgs.h" +#include "Network.h" + unsigned int ParseColor( const std::string & color ) { if ( color.size() < 6 || color.size() > 8 ) return 0xFFFFFFFF; @@ -408,10 +410,12 @@ int main( int argc, const char * argv[] ) Timer::Start(); float fNextTick = 0.1f; float fLastTimeMS = Timer::GetTime(); + Network::Init(); while ( !Renderer::WantsToQuit() ) { bool newShader = false; float time = Timer::GetTime() / 1000.0; // seconds + Renderer::StartFrame(); for ( int i = 0; i < Renderer::mouseEventBufferCount; i++ ) @@ -445,6 +449,9 @@ int main( int argc, const char * argv[] ) } Renderer::mouseEventBufferCount = 0; + // TODO: Netwokr Update here + Network::UpdateShader(&mShaderEditor); + for ( int i = 0; i < Renderer::keyEventBufferCount; i++ ) { #define FKEY(x) ((x)+281) @@ -514,7 +521,19 @@ int main( int argc, const char * argv[] ) } } Renderer::keyEventBufferCount = 0; - + if (Network::ReloadShader()) { + mShaderEditor.GetText(szShader, 65535); + if (Renderer::ReloadShader(szShader, (int)strlen(szShader), szError, 4096)) + { + // Shader compilation successful; we set a flag to save if the frame render was successful + // (If there is a driver crash, don't save.) + newShader = true; + } + else + { + mDebugOutput.SetText(szError); + } + } Renderer::SetShaderConstant( "fGlobalTime", time ); Renderer::SetShaderConstant( "v2Resolution", settings.sRenderer.nWidth, settings.sRenderer.nHeight ); From f38af46779c22b82e073a4beff23830c27463cbc Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 15 Sep 2024 22:09:43 +0200 Subject: [PATCH 05/29] Minimalistic sender --- data/windows/SetupDialog.aps | Bin 0 -> 197956 bytes data/windows/SetupDialog.rc | 114 +++++++++++++++--------- data/windows/resource.h | 11 ++- src/CommandLineArgs.h | 18 ++-- src/Network.h | 98 +++++++++++++++++--- src/main.cpp | 7 +- src/platform_w32_common/SetupDialog.cpp | 62 ++++++++++++- 7 files changed, 238 insertions(+), 72 deletions(-) create mode 100644 data/windows/SetupDialog.aps diff --git a/data/windows/SetupDialog.aps b/data/windows/SetupDialog.aps new file mode 100644 index 0000000000000000000000000000000000000000..a331ba8a8c846037f60d8907d64b7a7414ce3a53 GIT binary patch literal 197956 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%0Al$xZIG}nTxDP`b4Gy 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 literal 0 HcmV?d00001 diff --git a/data/windows/SetupDialog.rc b/data/windows/SetupDialog.rc index 5b1f99f1..d2a32e30 100644 --- a/data/windows/SetupDialog.rc +++ b/data/windows/SetupDialog.rc @@ -12,25 +12,71 @@ ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// Hungarian (Hungary) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HUN) +LANGUAGE LANG_HUNGARIAN, SUBLANG_DEFAULT +#pragma code_page(1250) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APPLICATION ICON "icon.ico" + +#endif // Hungarian (Hungary) resources +///////////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////////// // Neutral resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -#pragma code_page(1250) +#pragma code_page(1252) ///////////////////////////////////////////////////////////////////////////// // // Dialog // -IDD_SETUP DIALOGEX 0, 0, 186, 104 +IDD_SETUP DIALOGEX 0, 0, 187, 225 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "ümlaüt design" +CAPTION "�mla�t design" FONT 8, "Tahoma", 0, 0, 0x0 BEGIN - DEFPUSHBUTTON "Run",IDOK,60,84,116,14 - PUSHBUTTON "Cancel",IDCANCEL,6,84,50,14 + DEFPUSHBUTTON "Run",IDOK,62,196,116,14 + PUSHBUTTON "Cancel",IDCANCEL,8,196,50,14 RTEXT "Resolution:",IDC_STATIC,7,22,65,10 COMBOBOX IDC_RESOLUTION,83,20,91,125,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP RTEXT "Fullscreen:",IDC_STATIC,7,36,65,10 @@ -40,6 +86,16 @@ BEGIN CONTROL "",IDC_VSYNC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,83,50,9,10 RTEXT "Audio source:",IDC_STATIC,6,64,65,10 COMBOBOX IDC_AUDIOSOURCE,82,62,91,125,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + RTEXT "Network:",IDC_STATIC,6,79,65,10 + CONTROL "",IDC_NETWORK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,83,79,16,10 + RTEXT "Server:",IDC_STATIC,46,110,25,8 + RTEXT "Roomname:",IDC_STATIC,32,123,39,8 + RTEXT "Nickname:",IDC_STATIC,37,138,34,8 + EDITTEXT IDC_NETWORK_SERVER,83,108,90,14,ES_AUTOHSCROLL | WS_DISABLED + EDITTEXT IDC_NETWORK_ROOMNAME,83,123,90,14,ES_AUTOHSCROLL | WS_DISABLED + EDITTEXT IDC_NETWORK_NICKNAME,83,138,90,14,ES_AUTOHSCROLL | WS_DISABLED + COMBOBOX IDC_NETWORK_MODE,83,89,91,125,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + RTEXT "Mode:",IDC_STATIC,50,93,21,8 END @@ -53,57 +109,27 @@ GUIDELINES DESIGNINFO BEGIN IDD_SETUP, DIALOG BEGIN - BOTTOMMARGIN, 89 + RIGHTMARGIN, 186 + VERTGUIDE, 71 + VERTGUIDE, 83 + BOTTOMMARGIN, 210 + HORZGUIDE, 20 END END #endif // APSTUDIO_INVOKED -#endif // Neutral resources -///////////////////////////////////////////////////////////////////////////// - - -///////////////////////////////////////////////////////////////////////////// -// Hungarian (Hungary) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HUN) -LANGUAGE LANG_HUNGARIAN, SUBLANG_DEFAULT -#pragma code_page(1250) -#ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // -// TEXTINCLUDE +// AFX_DIALOG_LAYOUT // -1 TEXTINCLUDE +IDD_SETUP AFX_DIALOG_LAYOUT BEGIN - "resource.h\0" + 0 END -2 TEXTINCLUDE -BEGIN - "#include ""afxres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APPLICATION ICON "icon.ico" -#endif // Hungarian (Hungary) resources +#endif // Neutral resources ///////////////////////////////////////////////////////////////////////////// diff --git a/data/windows/resource.h b/data/windows/resource.h index 639a50c3..8e1654b9 100644 --- a/data/windows/resource.h +++ b/data/windows/resource.h @@ -1160,11 +1160,13 @@ #define IDC_COMBO2 1002 #define IDC_MULTISAMPLE 1002 #define IDC_RESOLUTION2 1002 +#define IDC_NETWORK_MODE 1002 #define IDC_CHECK1 1003 #define IDC_FULLSCREEN 1003 #define IDC_CHECK2 1004 #define IDC_ONTOP 1004 #define IDC_FULLSCREEN2 1004 +#define IDC_NETWORK 1004 #define IDC_CHECK3 1005 #define IDC_VSYNC 1005 #define IDC_COMBO3 1006 @@ -1174,6 +1176,10 @@ #define IDC_AUDIOSOURCE 1006 #define IDC_EDIT1 1007 #define IDC_RANDOMSEED 1007 +#define IDC_NETWORK_SERVER 1007 +#define IDC_NETWORK_ROOMNAME 1008 +#define IDC_NETWORK_NICKNAME 1009 +#define IDC_NETWORK_GROUP 1010 #define CF_GDIOBJLAST 0x03FF #define _WIN32_WINNT_NT4 0x0400 #define _WIN32_IE_IE40 0x0400 @@ -1520,15 +1526,14 @@ #define PWR_FAIL -1 #define UNICODE_NOCHAR 0xFFFF #define HTTRANSPARENT -1 -#define IDC_STATIC -1 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_RESOURCE_VALUE 104 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_CONTROL_VALUE 1011 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index 31914894..cd27aa29 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -14,16 +14,16 @@ namespace CommandLineArgs bool skipDialog; const char* configFile; const char* shaderFile; - const char* serverURL; - Network::NetworkMode networkMode; + } Args; void parse_args(int argc,const char *argv[]) { Args.skipDialog = false; Args.configFile = "config.json"; Args.shaderFile = "shader.glsl"; - Args.networkMode = Network::NetworkMode::OFFLINE; - Args.serverURL = "ws://drone.alkama.com:9000/roomname/username"; + Network::NetworkConfig.Mode = Network::NetworkMode::OFFLINE; + Network::NetworkConfig.Url = "ws://drone.alkama.com:9000/roomname/username"; + for(size_t i=0;i namespace Network { + enum NetworkMode { + OFFLINE, + SENDER, + GRABBER + }; + struct mg_mgr mgr; struct mg_connection* c; bool done = false; std::thread* tNetwork; - static const char* s_url = "ws://drone.alkama.com:9000/roomname/handle"; - bool NewShader = false; + char szShader[65535]; + bool connected = false; + struct { + char* Url; + NetworkMode Mode; + float updateInterval = 0.3; + } NetworkConfig; + struct { std::string Code; int CaretPosition; @@ -19,12 +31,18 @@ namespace Network { bool NeedRecompile; } ShaderMessage; + void PrintConfig() { + std::cout << "******************* Network Config ********************" << std::endl; + std::cout << Network::NetworkConfig.Url << std::endl; + if (NetworkConfig.Mode == NetworkMode::OFFLINE) { + std::cout << "OFFLINE" << std::endl; + } else if (NetworkConfig.Mode == NetworkMode::SENDER) { + std::cout << "SENDER" << std::endl; + } else if (NetworkConfig.Mode == NetworkMode::GRABBER) { + std::cout << "GRABBER" << std::endl; + } - enum NetworkMode { - SENDER, - GRABBER, - OFFLINE - }; + } bool HasNewShader() { if (NewShader) { NewShader = false; @@ -87,6 +105,7 @@ namespace Network { } else if (ev == MG_EV_WS_OPEN) { fprintf(stdout, "[Network]: Connected\n"); + connected = true; // When websocket handshake is successful, send message // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); } @@ -104,10 +123,10 @@ namespace Network { } void Create(){ - fprintf(stdout,"[Network]: Try to connect to %s\n", s_url); + fprintf(stdout,"[Network]: Try to connect to %s\n", NetworkConfig.Url); mg_mgr_init(&mgr); - c = mg_ws_connect(&mgr, s_url, fn, &done, NULL); + c = mg_ws_connect(&mgr, NetworkConfig.Url, fn, &done, NULL); if (c == NULL) { fprintf(stderr, "Invalid address\n"); return; @@ -132,8 +151,9 @@ namespace Network { return false; } void UpdateShader(ShaderEditor* mShaderEditor) { - if (Network::HasNewShader()) { - + + if (NetworkConfig.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode + int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); int PreviousTopDocLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, PreviousTopLine, 0); int PreviousTopLineTotal = PreviousTopDocLine; @@ -149,7 +169,63 @@ namespace Network { //} + } else if(NetworkConfig.Mode == Network::SENDER) { + mShaderEditor->GetText(szShader, 65535); + ShaderMessage.Code = std::string(szShader); + ShaderMessage.NeedRecompile = true; + ShaderMessage.CaretPosition = mShaderEditor->WndProc(SCI_GETCURRENTPOS, 0, 0); + ShaderMessage.AnchorPosition = mShaderEditor->WndProc(SCI_GETANCHOR, 0, 0); + int TopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); + ShaderMessage.FirstVisibleLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, TopLine, 0); + jsonxx::Object Data; + Data << "Code" << std::string(ShaderMessage.Code); + Data << "Compile" << ShaderMessage.NeedRecompile; + Data << "Caret" << ShaderMessage.CaretPosition; + Data << "Anchor" << ShaderMessage.AnchorPosition; + Data << "FirstVisibleLine" << ShaderMessage.FirstVisibleLine; + Data << "RoomName" << "RoomName"; + Data << "NickName" << "NickName"; + Data << "ShaderTime" << 1; + + jsonxx::Object Message = jsonxx::Object("Data", Data); + std::string TextJson = Message.json(); + if(connected){ + mg_ws_send(c, TextJson.c_str(), TextJson.length() , WEBSOCKET_OP_TEXT); + } + } + } + + void ParseSettings(jsonxx::Object *options) { + + if(options->has("network")) { + jsonxx::Object network = options->get("network"); + if(network.has("serverURL")){ + NetworkConfig.Url = strdup(network.get("serverURL").c_str()); + } + if (network.get("enabled")) { + + if (network.has("networkMode")) { + const char * mode = network.get("networkMode").c_str(); + if (strcmp(mode, "sender") == 0) { + NetworkConfig.Mode = SENDER; + } + else if (strcmp(mode, "grabber") == 0) { + NetworkConfig.Mode = GRABBER; + } + else { + NetworkConfig.Mode = GRABBER; + printf("Can't find 'networkMode', set to 'GRABBER'\n"); + } + } else { + printf("Can't find 'networkMode', set to 'OFFLINE'\n"); + } + } + + + } else { + NetworkConfig.Mode = OFFLINE; } + } } #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 2c6b3b60..70a54951 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,7 +103,7 @@ int main( int argc, const char * argv[] ) options.parse( szConfig ); } - + Network::ParseSettings(&options); FFT::Create(); bool skipSetupDialog = false; @@ -410,7 +410,10 @@ int main( int argc, const char * argv[] ) Timer::Start(); float fNextTick = 0.1f; float fLastTimeMS = Timer::GetTime(); + Network::Init(); + + Network::PrintConfig(); while ( !Renderer::WantsToQuit() ) { bool newShader = false; @@ -449,7 +452,7 @@ int main( int argc, const char * argv[] ) } Renderer::mouseEventBufferCount = 0; - // TODO: Netwokr Update here + // TODO: Network Update here Network::UpdateShader(&mShaderEditor); for ( int i = 0; i < Renderer::keyEventBufferCount; i++ ) diff --git a/src/platform_w32_common/SetupDialog.cpp b/src/platform_w32_common/SetupDialog.cpp index f5e6ed8b..017086ca 100644 --- a/src/platform_w32_common/SetupDialog.cpp +++ b/src/platform_w32_common/SetupDialog.cpp @@ -5,11 +5,15 @@ #include #include "../Renderer.h" #include "../FFT.h" + #include "../SetupDialog.h" + #include "resource.h" #include #include +#include + namespace SetupDialog { @@ -86,6 +90,10 @@ class CSetupDialog } bool DialogProcedure( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + + + + switch ( uMsg ) { case WM_INITDIALOG: @@ -123,6 +131,7 @@ class CSetupDialog _sntprintf( s, 50, _T( "%d * %d" ), gaResolutions[ i ].nWidth, gaResolutions[ i ].nHeight ); SendDlgItemMessage( hWnd, IDC_RESOLUTION, CB_ADDSTRING, 0, (LPARAM) s ); + if ( gaResolutions[ i ].nWidth == setup->sRenderer.nWidth && gaResolutions[ i ].nHeight == setup->sRenderer.nHeight ) { SendDlgItemMessage( hWnd, IDC_RESOLUTION, CB_SETCURSEL, i, 0 ); @@ -133,7 +142,13 @@ class CSetupDialog SendDlgItemMessage( hWnd, IDC_RESOLUTION, CB_SETCURSEL, i, 0 ); } } - + + TCHAR s[50]; + _sntprintf(s, 50, _T("SENDER")); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); + _sntprintf(s, 50, _T("GRABBER")); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_SETCURSEL, 0, 0); if ( setup->sRenderer.windowMode == Renderer::WINDOWMODE_FULLSCREEN ) { SendDlgItemMessage( hWnd, IDC_FULLSCREEN, BM_SETCHECK, 1, 1 ); @@ -143,6 +158,15 @@ class CSetupDialog SendDlgItemMessage( hWnd, IDC_VSYNC, BM_SETCHECK, 1, 1 ); } + std::string ServerName; + std::string RoomName; + std::string NickName; + // Network_Break_URL(network->ServerURL, ServerName, RoomName, NickName); + + SetDlgItemText(hWnd, IDC_NETWORK_SERVER, "ws://drone."); + SetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, "roomname"); + SetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, "handle"); + FFT::EnumerateDevices( FFTDeviceEnum, this ); return true; @@ -150,6 +174,7 @@ class CSetupDialog case WM_COMMAND: { + switch ( LOWORD( wParam ) ) { case IDOK: @@ -161,8 +186,41 @@ class CSetupDialog setup->sFFT.bUseRecordingDevice = gaAudioDevices[ SendDlgItemMessage( hWnd, IDC_AUDIOSOURCE, CB_GETCURSEL, 0, 0 ) ].bIsCapture; setup->sFFT.pDeviceID = gaAudioDevices[ SendDlgItemMessage( hWnd, IDC_AUDIOSOURCE, CB_GETCURSEL, 0, 0 ) ].pDeviceID; + + int ServerLen = SendDlgItemMessage(hWnd, IDC_NETWORK_SERVER, WM_GETTEXTLENGTH, 0, 0); + char ServerName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_SERVER, ServerName, min(ServerLen + 1, 511)); + + int RoomLen = SendDlgItemMessage(hWnd, IDC_NETWORK_ROOMNAME, WM_GETTEXTLENGTH, 0, 0); + char RoomName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, RoomName, min(RoomLen + 1, 511)); + + int NickLen = SendDlgItemMessage(hWnd, IDC_NETWORK_NICKNAME, WM_GETTEXTLENGTH, 0, 0); + char NickName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, NickName, min(NickLen + 1, 511)); EndDialog( hWnd, TRUE ); } break; + case IDC_NETWORK: // Combo Box Click + { + + if (SendDlgItemMessage(hWnd, IDC_NETWORK, BM_GETCHECK, 0, 0)) { + // Activate + + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_MODE), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_NICKNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_ROOMNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_SERVER), TRUE); +; } + else { + // Desactivate + + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_MODE), FALSE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_NICKNAME), FALSE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_ROOMNAME), FALSE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_SERVER), FALSE); + } + + } break; case IDCANCEL: { EndDialog( hWnd, FALSE ); @@ -188,7 +246,7 @@ INT_PTR CALLBACK DlgFunc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) return pGlobal->DialogProcedure( hWnd, uMsg, wParam, lParam ); } -bool Open( SetupDialog::SETTINGS * settings ) +bool Open( SetupDialog::SETTINGS * settings) { CSetupDialog dlg; dlg.setup = settings; From 2566725c107f86186eb78a7e4ade07af7c184b52 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Fri, 20 Sep 2024 00:24:25 +0200 Subject: [PATCH 06/29] SetupDialogWorking --- CMakeLists.txt | 19 +- data/windows/SetupDialog.aps | Bin 197956 -> 197956 bytes src/CommandLineArgs.h | 12 +- src/Network.h | 252 ++++-------------------- src/SetupDialog.h | 1 + src/ShaderEditor.h | 1 + src/main.cpp | 14 +- src/platform_common/Network.cpp | 232 ++++++++++++++++++++++ src/platform_w32_common/SetupDialog.cpp | 51 +++-- 9 files changed, 331 insertions(+), 251 deletions(-) create mode 100644 src/platform_common/Network.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d40d4aee..b472f324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,6 +248,23 @@ if (WIN32 AND BONZOMATIC_NDI) set(BZC_PROJECT_LIBS ${BZC_PROJECT_LIBS} "${NDI_SDK_DIR}/Lib/x86/Processing.NDI.Lib.x86.lib") endif () endif () +############################################################################## +# MONGOOSE NETWORK +set(BZC_PROJECT_INCLUDES ${BZC_PROJECT_INCLUDES} ${CMAKE_SOURCE_DIR}/external/mongoose) +set(MONGOOSE_SRCS + ${CMAKE_SOURCE_DIR}/external/mongoose/mongoose.c +) +add_library(bzc_mongoose STATIC ${MONGOOSE_SRCS}) + +target_include_directories(bzc_mongoose PUBLIC ${CMAKE_SOURCE_DIR}/external/mongoose) +if (MSVC) + target_compile_options(bzc_mongoose PUBLIC "$<$:/MT>") +endif () +set(BZC_PROJECT_LIBS ${BZC_PROJECT_LIBS} bzc_mongoose) + + +set(BZC_NETWORK_SRCS ${CMAKE_SOURCE_DIR}/src/platform_common/Network.cpp) +source_group("Bonzomatic\\Network" FILES ${BZC_NETWORK_SRCS}) ############################################################################## # SCINTILLA @@ -419,7 +436,7 @@ else () endif () source_group("Bonzomatic\\Capture" FILES ${BZC_CAPTURE_SRCS}) -set(BZC_PROJECT_SRCS ${BZC_PROJECT_SRCS} ${BZC_PLATFORM_SRCS} ${BZC_RESOURCES_DATA} ${BZC_CAPTURE_SRCS}) +set(BZC_PROJECT_SRCS ${BZC_PROJECT_SRCS} ${BZC_PLATFORM_SRCS} ${BZC_RESOURCES_DATA} ${BZC_CAPTURE_SRCS} ${BZC_NETWORK_SRCS}) set(BZC_PROJECT_INCLUDES ${CMAKE_SOURCE_DIR}/src ${BZC_PROJECT_INCLUDES}) diff --git a/data/windows/SetupDialog.aps b/data/windows/SetupDialog.aps index a331ba8a8c846037f60d8907d64b7a7414ce3a53..a81bfb6e28f1daa62d7c740ec969c6261831ea51 100644 GIT binary patch delta 82 zcmV-Y0ImPTiVVbx41k0Iv;yk=0!31n?EL~J0biFB{sLnHFnE`E{sLA4Ffo_V{sQU( oLPM9l{{o%@Lq(Nb12O_ZQk9ScG6DcFmBa(80zyT%Z~+6<17V^bqW}N^ delta 82 zcmX@o#dD;Kr=f*$3)8E=OblUii=_D5$07)MGV^u7@4my0{}gu9V7q% diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index cd27aa29..a478b196 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -4,7 +4,7 @@ #include #include #include -#include + #define assert_tuple_arg (assert(i < argc && "Expecting value")) namespace CommandLineArgs @@ -21,8 +21,8 @@ namespace CommandLineArgs Args.skipDialog = false; Args.configFile = "config.json"; Args.shaderFile = "shader.glsl"; - Network::NetworkConfig.Mode = Network::NetworkMode::OFFLINE; - Network::NetworkConfig.Url = "ws://drone.alkama.com:9000/roomname/username"; + //Network::config.Mode = Network::NetworkMode::OFFLINE; + //Network::config.Url = "ws://drone.alkama.com:9000/roomname/username"; for(size_t i=0;i #include "mongoose.h" #include +#include +#include "ShaderEditor.h" namespace Network { enum NetworkMode { - OFFLINE, - SENDER, - GRABBER - }; + OFFLINE, + SENDER, + GRABBER + }; + + struct NetworkConfig { + char* Url; + NetworkMode Mode; + float updateInterval = 0.3; + }; + struct ShaderMessage { + std::string Code; + int CaretPosition; + int AnchorPosition; + int FirstVisibleLine; + bool NeedRecompile; + float shaderTime = 0.0; + }; - struct mg_mgr mgr; - struct mg_connection* c; - bool done = false; - std::thread* tNetwork; - bool NewShader = false; - char szShader[65535]; - bool connected = false; - struct { - char* Url; - NetworkMode Mode; - float updateInterval = 0.3; - } NetworkConfig; - struct { - std::string Code; - int CaretPosition; - int AnchorPosition; - int FirstVisibleLine; - bool NeedRecompile; - } ShaderMessage; - - void PrintConfig() { - std::cout << "******************* Network Config ********************" << std::endl; - std::cout << Network::NetworkConfig.Url << std::endl; - if (NetworkConfig.Mode == NetworkMode::OFFLINE) { - std::cout << "OFFLINE" << std::endl; - } else if (NetworkConfig.Mode == NetworkMode::SENDER) { - std::cout << "SENDER" << std::endl; - } else if (NetworkConfig.Mode == NetworkMode::GRABBER) { - std::cout << "GRABBER" << std::endl; - } - - } - bool HasNewShader() { - if (NewShader) { - NewShader = false; - return true; - } - return false; - - } - void RecieveShader(size_t size, char* data) { - // TODO: very very bad, we should: - // - use json - // - verify size - // - non-ascii symbols ? - // - asynchronous update ? - //data[size - 1] = '\0'; - std::string TextJson(data); - jsonxx::Object NewShader; - jsonxx::Object Data; - bool ErrorFound = false; - - if (NewShader.parse(TextJson)) { - if (NewShader.has("Data")) { - Data = NewShader.get("Data"); - if (!Data.has("Code")) ErrorFound = true; - if (!Data.has("Caret")) ErrorFound = true; - if (!Data.has("Anchor")) ErrorFound = true; - if (!Data.has("FirstVisibleLine")) ErrorFound = true; - if (!Data.has("Compile")) ErrorFound = true; - } - else { - ErrorFound = true; - } - } - else { - ErrorFound = true; - } - if (ErrorFound) { - fprintf(stderr, "Invalid json formatting\n"); - return; - } - if (Data.has("ShaderTime")) { - - float t = Data.get("ShaderTime"); - ShaderMessage.Code = Data.get < jsonxx::String>("Code"); - ShaderMessage.AnchorPosition = Data.get("Anchor"); - ShaderMessage.CaretPosition = Data.get("Caret"); - ShaderMessage.NeedRecompile = Data.get("Compile"); - Network::NewShader = true; - } - - } - static void fn(struct mg_connection* c, int ev, void* ev_data) { - if (ev == MG_EV_OPEN) { - c->is_hexdumping = 0; - } - else if (ev == MG_EV_ERROR) { - // On error, log error message - MG_ERROR(("%p %s", c->fd, (char*)ev_data)); - } - else if (ev == MG_EV_WS_OPEN) { - fprintf(stdout, "[Network]: Connected\n"); - connected = true; - // When websocket handshake is successful, send message - // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); - } - else if (ev == MG_EV_WS_MSG) { - // When we get echo response, print it - - struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; - RecieveShader((int)wm->data.len, wm->data.buf); - // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); - } - - /*/if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) { - *(bool*)c->fn_data = true; // Signal that we're done - }*/ - } - void Create(){ - fprintf(stdout,"[Network]: Try to connect to %s\n", NetworkConfig.Url); - - mg_mgr_init(&mgr); - c = mg_ws_connect(&mgr, NetworkConfig.Url, fn, &done, NULL); - if (c == NULL) { - fprintf(stderr, "Invalid address\n"); - return; - } - while (true) { - mg_mgr_poll(&mgr, 1000); - } - mg_mgr_free(&mgr); - } - - void Init() { - std::thread network(Create); - tNetwork = &network; - tNetwork->detach(); - - } - bool ReloadShader() { - if (ShaderMessage.NeedRecompile) { - ShaderMessage.NeedRecompile = false; - return true; - } - return false; - } - void UpdateShader(ShaderEditor* mShaderEditor) { - - if (NetworkConfig.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode - - int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); - int PreviousTopDocLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, PreviousTopLine, 0); - int PreviousTopLineTotal = PreviousTopDocLine; - - mShaderEditor->SetText(ShaderMessage.Code.c_str()); - mShaderEditor->WndProc(SCI_SETCURRENTPOS, ShaderMessage.CaretPosition, 0); - mShaderEditor->WndProc(SCI_SETANCHOR, ShaderMessage.AnchorPosition, 0); - mShaderEditor->WndProc(SCI_SETFIRSTVISIBLELINE, PreviousTopLineTotal, 0); - - //if (bGrabberFollowCaret) { - //mShaderEditor.WndProc(SCI_SETFIRSTVISIBLELINE, NewMessage.FirstVisibleLine, 0); - mShaderEditor->WndProc(SCI_SCROLLCARET, 0, 0); - //} - - - } else if(NetworkConfig.Mode == Network::SENDER) { - mShaderEditor->GetText(szShader, 65535); - ShaderMessage.Code = std::string(szShader); - ShaderMessage.NeedRecompile = true; - ShaderMessage.CaretPosition = mShaderEditor->WndProc(SCI_GETCURRENTPOS, 0, 0); - ShaderMessage.AnchorPosition = mShaderEditor->WndProc(SCI_GETANCHOR, 0, 0); - int TopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); - ShaderMessage.FirstVisibleLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, TopLine, 0); - jsonxx::Object Data; - Data << "Code" << std::string(ShaderMessage.Code); - Data << "Compile" << ShaderMessage.NeedRecompile; - Data << "Caret" << ShaderMessage.CaretPosition; - Data << "Anchor" << ShaderMessage.AnchorPosition; - Data << "FirstVisibleLine" << ShaderMessage.FirstVisibleLine; - Data << "RoomName" << "RoomName"; - Data << "NickName" << "NickName"; - Data << "ShaderTime" << 1; - - jsonxx::Object Message = jsonxx::Object("Data", Data); - std::string TextJson = Message.json(); - if(connected){ - mg_ws_send(c, TextJson.c_str(), TextJson.length() , WEBSOCKET_OP_TEXT); - } - } - } - - void ParseSettings(jsonxx::Object *options) { - - if(options->has("network")) { - jsonxx::Object network = options->get("network"); - if(network.has("serverURL")){ - NetworkConfig.Url = strdup(network.get("serverURL").c_str()); - } - if (network.get("enabled")) { - - if (network.has("networkMode")) { - const char * mode = network.get("networkMode").c_str(); - if (strcmp(mode, "sender") == 0) { - NetworkConfig.Mode = SENDER; - } - else if (strcmp(mode, "grabber") == 0) { - NetworkConfig.Mode = GRABBER; - } - else { - NetworkConfig.Mode = GRABBER; - printf("Can't find 'networkMode', set to 'GRABBER'\n"); - } - } else { - printf("Can't find 'networkMode', set to 'OFFLINE'\n"); - } - } - - - } else { - NetworkConfig.Mode = OFFLINE; - } + void PrintConfig(); + bool HasNewShader(); + bool ReloadShader(); + void RecieveShader(size_t size, char* data); + static void fn(struct mg_connection* c, int ev, void* ev_data); + + void Create(); + void Init(); - } + void ParseSettings(jsonxx::Object* options); + void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime); + char* GetUrl(); + void SetUrl(char*); } -#endif \ No newline at end of file +#endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/SetupDialog.h b/src/SetupDialog.h index 185a8a09..666a2983 100644 --- a/src/SetupDialog.h +++ b/src/SetupDialog.h @@ -6,6 +6,7 @@ typedef struct { Renderer::Settings sRenderer; FFT::Settings sFFT; + } SETTINGS; bool Open( SETTINGS * pSettings ); diff --git a/src/ShaderEditor.h b/src/ShaderEditor.h index dd84d6d0..3d4fd2cf 100644 --- a/src/ShaderEditor.h +++ b/src/ShaderEditor.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include diff --git a/src/main.cpp b/src/main.cpp index 70a54951..f65524f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,9 +19,9 @@ #include "jsonxx.h" #include "Capture.h" #include "SetupDialog.h" - +#include #include "CommandLineArgs.h" -#include "Network.h" + unsigned int ParseColor( const std::string & color ) { @@ -412,8 +412,8 @@ int main( int argc, const char * argv[] ) float fLastTimeMS = Timer::GetTime(); Network::Init(); - Network::PrintConfig(); + while ( !Renderer::WantsToQuit() ) { bool newShader = false; @@ -452,9 +452,6 @@ int main( int argc, const char * argv[] ) } Renderer::mouseEventBufferCount = 0; - // TODO: Network Update here - Network::UpdateShader(&mShaderEditor); - for ( int i = 0; i < Renderer::keyEventBufferCount; i++ ) { #define FKEY(x) ((x)+281) @@ -475,6 +472,7 @@ int main( int argc, const char * argv[] ) } else if ( Renderer::keyEventBuffer[ i ].scanCode == FKEY( 5 ) || ( Renderer::keyEventBuffer[ i ].ctrl && Renderer::keyEventBuffer[ i ].scanCode == 'r' ) ) // F5 { + //Network::shaderMessage.NeedRecompile= true; mShaderEditor.GetText( szShader, 65535 ); if ( Renderer::ReloadShader( szShader, (int) strlen( szShader ), szError, 4096 ) ) { @@ -523,6 +521,10 @@ int main( int argc, const char * argv[] ) } } + // Network Update here + + Network::UpdateShader(&mShaderEditor, time); + Renderer::keyEventBufferCount = 0; if (Network::ReloadShader()) { mShaderEditor.GetText(szShader, 65535); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp new file mode 100644 index 00000000..c48813b5 --- /dev/null +++ b/src/platform_common/Network.cpp @@ -0,0 +1,232 @@ +#include "Network.h" + +namespace Network { + + Network::NetworkConfig config; + Network::ShaderMessage shaderMessage; + + struct mg_mgr mgr; + struct mg_connection* c; + bool done = false; + std::thread* tNetwork; + bool IsNewShader = false; + char szShader[65535]; + bool connected = false; + + + char* GetUrl() { + return config.Url; + } + void SetUrl( char* url) { + config.Url =url; + } + void PrintConfig() { + std::cout << "******************* Network Config ********************" << std::endl; + std::cout << config.Url << std::endl; + if (config.Mode == NetworkMode::OFFLINE) { + std::cout << "OFFLINE" << std::endl; + } + else if (config.Mode == NetworkMode::SENDER) { + std::cout << "SENDER" << std::endl; + } + else if (config.Mode == NetworkMode::GRABBER) { + std::cout << "GRABBER" << std::endl; + } + + } + bool HasNewShader() { + if (IsNewShader) { + IsNewShader = false; + return true; + } + return false; + + } + void RecieveShader(size_t size, char* data) { + // TODO: very very bad, we should: + // - use json + // - verify size + // - non-ascii symbols ? + // - asynchronous update ? + //data[size - 1] = '\0'; + std::string TextJson(data); + jsonxx::Object NewShader; + jsonxx::Object Data; + bool ErrorFound = false; + + if (NewShader.parse(TextJson)) { + if (NewShader.has("Data")) { + Data = NewShader.get("Data"); + if (!Data.has("Code")) ErrorFound = true; + if (!Data.has("Caret")) ErrorFound = true; + if (!Data.has("Anchor")) ErrorFound = true; + if (!Data.has("FirstVisibleLine")) ErrorFound = true; + if (!Data.has("Compile")) ErrorFound = true; + } + else { + ErrorFound = true; + } + } + else { + ErrorFound = true; + } + if (ErrorFound) { + fprintf(stderr, "Invalid json formatting\n"); + return; + } + if (Data.has("ShaderTime")) { + + float t = Data.get("ShaderTime"); + shaderMessage.Code = Data.get < jsonxx::String>("Code"); + shaderMessage.AnchorPosition = Data.get("Anchor"); + shaderMessage.CaretPosition = Data.get("Caret"); + shaderMessage.NeedRecompile = Data.get("Compile"); + IsNewShader = true; + + } + + } + static void fn(struct mg_connection* c, int ev, void* ev_data) { + if (ev == MG_EV_OPEN) { + c->is_hexdumping = 0; + } + else if (ev == MG_EV_ERROR) { + // On error, log error message + MG_ERROR(("%p %s", c->fd, (char*)ev_data)); + } + else if (ev == MG_EV_WS_OPEN) { + fprintf(stdout, "[Network]: Connected\n"); + connected = true; + // When websocket handshake is successful, send message + // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); + } + else if (ev == MG_EV_WS_MSG && config.Mode == GRABBER) { + // When we get echo response, print it + + struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; + + RecieveShader((int)wm->data.len, wm->data.buf); + + // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); + } + + /*/if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) { + *(bool*)c->fn_data = true; // Signal that we're done + }*/ + } + + void Create() { + fprintf(stdout, "[Network]: Try to connect to %s\n", config.Url); + mg_mgr_init(&mgr); + c = mg_ws_connect(&mgr, config.Url, fn, &done, NULL); + if (c == NULL) { + fprintf(stderr, "Invalid address\n"); + return; + } + while (true) { + mg_mgr_poll(&mgr, 100); + } + mg_mgr_free(&mgr); + } + + void Init() { + std::thread network(Create); + tNetwork = &network; + tNetwork->detach(); + } + bool ReloadShader() { + if (config.Mode == GRABBER && shaderMessage.NeedRecompile) { + shaderMessage.NeedRecompile = false; + return true; + } + return false; + } + + void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime) { + if (Network::config.Mode != Network::NetworkMode::OFFLINE) { + if (config.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode + + int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); + int PreviousTopDocLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, PreviousTopLine, 0); + int PreviousTopLineTotal = PreviousTopDocLine; + + mShaderEditor->SetText(shaderMessage.Code.c_str()); + mShaderEditor->WndProc(SCI_SETCURRENTPOS, shaderMessage.CaretPosition, 0); + mShaderEditor->WndProc(SCI_SETANCHOR, shaderMessage.AnchorPosition, 0); + mShaderEditor->WndProc(SCI_SETFIRSTVISIBLELINE, PreviousTopLineTotal, 0); + + //if (bGrabberFollowCaret) { + //mShaderEditor.WndProc(SCI_SETFIRSTVISIBLELINE, NewMessage.FirstVisibleLine, 0); + mShaderEditor->WndProc(SCI_SCROLLCARET, 0, 0); + //} + + + } + else if (config.Mode == Network::SENDER && shaderTime - shaderMessage.shaderTime > 0.1) { + //std::cout << shaderTime<<"-"<has("network")) { + jsonxx::Object network = options->get("network"); + if (network.has("serverURL")) { + config.Url = strdup(network.get("serverURL").c_str()); + } + if (network.get("enabled")) { + + if (network.has("networkMode")) { + const char* mode = network.get("networkMode").c_str(); + if (strcmp(mode, "sender") == 0) { + config.Mode = SENDER; + } + else if (strcmp(mode, "grabber") == 0) { + config.Mode = GRABBER; + } + else { + config.Mode = GRABBER; + printf("Can't find 'networkMode', set to 'GRABBER'\n"); + } + } + else { + printf("Can't find 'networkMode', set to 'OFFLINE'\n"); + } + } + + + } + else { + config.Mode = OFFLINE; + } + + } +} \ No newline at end of file diff --git a/src/platform_w32_common/SetupDialog.cpp b/src/platform_w32_common/SetupDialog.cpp index 017086ca..f8984d09 100644 --- a/src/platform_w32_common/SetupDialog.cpp +++ b/src/platform_w32_common/SetupDialog.cpp @@ -1,3 +1,4 @@ +#include "../Network.h" #include #ifdef __MINGW32__ #include @@ -158,15 +159,19 @@ class CSetupDialog SendDlgItemMessage( hWnd, IDC_VSYNC, BM_SETCHECK, 1, 1 ); } - std::string ServerName; - std::string RoomName; - std::string NickName; - // Network_Break_URL(network->ServerURL, ServerName, RoomName, NickName); - - SetDlgItemText(hWnd, IDC_NETWORK_SERVER, "ws://drone."); - SetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, "roomname"); - SetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, "handle"); - + + { // Parsing url, could everthing be inside Network.h ? + std::string FullUrl((const char*)Network::GetUrl()); + std::size_t PathPartPtr = FullUrl.find('/',6); + std::size_t HandlePtr = FullUrl.find('/',PathPartPtr+1); + std::string HostPort = FullUrl.substr(0,PathPartPtr); + std::string RoomName = FullUrl.substr(PathPartPtr+1,HandlePtr-PathPartPtr-1); + std::string NickName = FullUrl.substr(HandlePtr+1,FullUrl.size()-HandlePtr); + + SetDlgItemText(hWnd, IDC_NETWORK_SERVER, HostPort.c_str()); + SetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, RoomName.c_str()); + SetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, NickName.c_str()); + } FFT::EnumerateDevices( FFTDeviceEnum, this ); return true; @@ -187,17 +192,23 @@ class CSetupDialog setup->sFFT.bUseRecordingDevice = gaAudioDevices[ SendDlgItemMessage( hWnd, IDC_AUDIOSOURCE, CB_GETCURSEL, 0, 0 ) ].bIsCapture; setup->sFFT.pDeviceID = gaAudioDevices[ SendDlgItemMessage( hWnd, IDC_AUDIOSOURCE, CB_GETCURSEL, 0, 0 ) ].pDeviceID; - int ServerLen = SendDlgItemMessage(hWnd, IDC_NETWORK_SERVER, WM_GETTEXTLENGTH, 0, 0); - char ServerName[512]; - GetDlgItemText(hWnd, IDC_NETWORK_SERVER, ServerName, min(ServerLen + 1, 511)); - - int RoomLen = SendDlgItemMessage(hWnd, IDC_NETWORK_ROOMNAME, WM_GETTEXTLENGTH, 0, 0); - char RoomName[512]; - GetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, RoomName, min(RoomLen + 1, 511)); - - int NickLen = SendDlgItemMessage(hWnd, IDC_NETWORK_NICKNAME, WM_GETTEXTLENGTH, 0, 0); - char NickName[512]; - GetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, NickName, min(NickLen + 1, 511)); + {// Network + int ServerLen = SendDlgItemMessage(hWnd, IDC_NETWORK_SERVER, WM_GETTEXTLENGTH, 0, 0); + char ServerName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_SERVER, ServerName, min(ServerLen + 1, 511)); + + int RoomLen = SendDlgItemMessage(hWnd, IDC_NETWORK_ROOMNAME, WM_GETTEXTLENGTH, 0, 0); + char RoomName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, RoomName, min(RoomLen + 1, 511)); + + int NickLen = SendDlgItemMessage(hWnd, IDC_NETWORK_NICKNAME, WM_GETTEXTLENGTH, 0, 0); + char NickName[512]; + GetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, NickName, min(NickLen + 1, 511)); + std::string FullUrl = std::string(ServerName) + "/" + RoomName + "/" + NickName; + + Network::SetUrl(strdup(FullUrl.c_str())); + + } EndDialog( hWnd, TRUE ); } break; case IDC_NETWORK: // Combo Box Click From f8b6d6a026de77162ef93ea0f92985d4c304e844 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Fri, 20 Sep 2024 21:31:35 +0200 Subject: [PATCH 07/29] Complete Dialog setup + Working sender grabber --- src/Network.h | 7 +++ src/main.cpp | 6 +- src/platform_common/Network.cpp | 45 ++++++++++++-- src/platform_w32_common/SetupDialog.cpp | 78 ++++++++++++++++++------- 4 files changed, 105 insertions(+), 31 deletions(-) diff --git a/src/Network.h b/src/Network.h index 0fa8dc46..dc1c70c7 100644 --- a/src/Network.h +++ b/src/Network.h @@ -7,6 +7,7 @@ #include #include "ShaderEditor.h" namespace Network { + enum NetworkMode { OFFLINE, SENDER, @@ -43,5 +44,11 @@ namespace Network { void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime); char* GetUrl(); void SetUrl(char*); + Network::NetworkMode GetNetworkMode(); + void SetNetworkMode(Network::NetworkMode mode); + void SetNeedRecompile(bool needToRecompile); + void UpdateShaderFileName( const char** shaderName); + void SplitUrl(std::string *host,std::string *roomname,std::string* name); + } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f65524f4..54ab124d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -328,6 +328,8 @@ int main( int argc, const char * argv[] ) Renderer::Texture * texFFTSmoothed = Renderer::Create1DR32Texture( FFT_SIZE ); Renderer::Texture * texFFTIntegrated = Renderer::Create1DR32Texture( FFT_SIZE ); + // Overriding Name if we are in SENDER or GRABBER Network mode + Network::UpdateShaderFileName(&Renderer::szDefaultShaderFilename); bool shaderInitSuccessful = false; char szShader[ 65535 ]; char szError[ 4096 ]; @@ -412,7 +414,6 @@ int main( int argc, const char * argv[] ) float fLastTimeMS = Timer::GetTime(); Network::Init(); - Network::PrintConfig(); while ( !Renderer::WantsToQuit() ) { @@ -472,7 +473,7 @@ int main( int argc, const char * argv[] ) } else if ( Renderer::keyEventBuffer[ i ].scanCode == FKEY( 5 ) || ( Renderer::keyEventBuffer[ i ].ctrl && Renderer::keyEventBuffer[ i ].scanCode == 'r' ) ) // F5 { - //Network::shaderMessage.NeedRecompile= true; + Network::SetNeedRecompile(true); mShaderEditor.GetText( szShader, 65535 ); if ( Renderer::ReloadShader( szShader, (int) strlen( szShader ), szError, 4096 ) ) { @@ -521,7 +522,6 @@ int main( int argc, const char * argv[] ) } } - // Network Update here Network::UpdateShader(&mShaderEditor, time); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index c48813b5..6941b333 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -1,5 +1,5 @@ #include "Network.h" - +#define SHADER_FILENAME(mode) (std::string(mode)+ "_" + RoomName + "_" + NickName + ".glsl") namespace Network { Network::NetworkConfig config; @@ -20,6 +20,12 @@ namespace Network { void SetUrl( char* url) { config.Url =url; } + Network::NetworkMode GetNetworkMode() { + return config.Mode; + } + void SetNetworkMode(NetworkMode mode) { + config.Mode = mode; + } void PrintConfig() { std::cout << "******************* Network Config ********************" << std::endl; std::cout << config.Url << std::endl; @@ -130,9 +136,14 @@ namespace Network { } void Init() { - std::thread network(Create); - tNetwork = &network; - tNetwork->detach(); + if(config.Mode != OFFLINE){ + std::thread network(Create); + tNetwork = &network; + tNetwork->detach(); + } + else { + fprintf(stdout, "[Network]: OFFLINE Mode, not starting Network loop\n"); + } } bool ReloadShader() { if (config.Mode == GRABBER && shaderMessage.NeedRecompile) { @@ -141,9 +152,31 @@ namespace Network { } return false; } - + void SetNeedRecompile(bool needToRecompile) { + shaderMessage.NeedRecompile = needToRecompile; + } + void SplitUrl(std::string* host, std::string* roomname, std::string* name) { + std::string FullUrl((const char*)Network::GetUrl()); + std::size_t PathPartPtr = FullUrl.find('/', 6); + std::size_t HandlePtr = FullUrl.find('/', PathPartPtr + 1); + *host = FullUrl.substr(0, PathPartPtr); + *roomname = FullUrl.substr(PathPartPtr + 1, HandlePtr - PathPartPtr - 1); + *name = FullUrl.substr(HandlePtr + 1, FullUrl.size() - HandlePtr); + } + void UpdateShaderFileName(const char** shaderName) { + if (config.Mode == OFFLINE) return; + std::string HostPort, RoomName, NickName, filename; + Network::SplitUrl(&HostPort, &RoomName, &NickName); + if (config.Mode == SENDER) { + filename = SHADER_FILENAME("sender"); + } + else if(config.Mode == GRABBER) { + filename = SHADER_FILENAME("grabber"); + } + *shaderName = strdup(filename.c_str()); + } void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime) { - if (Network::config.Mode != Network::NetworkMode::OFFLINE) { + if (Network::config.Mode != Network::NetworkMode::OFFLINE) { // If we arn't offline mode if (config.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); diff --git a/src/platform_w32_common/SetupDialog.cpp b/src/platform_w32_common/SetupDialog.cpp index f8984d09..fce64314 100644 --- a/src/platform_w32_common/SetupDialog.cpp +++ b/src/platform_w32_common/SetupDialog.cpp @@ -143,30 +143,53 @@ class CSetupDialog SendDlgItemMessage( hWnd, IDC_RESOLUTION, CB_SETCURSEL, i, 0 ); } } - - TCHAR s[50]; - _sntprintf(s, 50, _T("SENDER")); - SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); - _sntprintf(s, 50, _T("GRABBER")); - SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); - SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_SETCURSEL, 0, 0); - if ( setup->sRenderer.windowMode == Renderer::WINDOWMODE_FULLSCREEN ) - { - SendDlgItemMessage( hWnd, IDC_FULLSCREEN, BM_SETCHECK, 1, 1 ); - } - if ( setup->sRenderer.bVsync ) - { - SendDlgItemMessage( hWnd, IDC_VSYNC, BM_SETCHECK, 1, 1 ); - } + + + if ( setup->sRenderer.windowMode == Renderer::WINDOWMODE_FULLSCREEN ) + { + SendDlgItemMessage( hWnd, IDC_FULLSCREEN, BM_SETCHECK, 1, 1 ); + } + if ( setup->sRenderer.bVsync ) + { + SendDlgItemMessage( hWnd, IDC_VSYNC, BM_SETCHECK, 1, 1 ); + } { // Parsing url, could everthing be inside Network.h ? - std::string FullUrl((const char*)Network::GetUrl()); - std::size_t PathPartPtr = FullUrl.find('/',6); - std::size_t HandlePtr = FullUrl.find('/',PathPartPtr+1); - std::string HostPort = FullUrl.substr(0,PathPartPtr); - std::string RoomName = FullUrl.substr(PathPartPtr+1,HandlePtr-PathPartPtr-1); - std::string NickName = FullUrl.substr(HandlePtr+1,FullUrl.size()-HandlePtr); + TCHAR s[50]; + _sntprintf(s, 50, _T("SENDER")); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); + _sntprintf(s, 50, _T("GRABBER")); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); + + + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_SETCURSEL, 0, 0); + switch (Network::GetNetworkMode()) { + case Network::OFFLINE: + SendDlgItemMessage(hWnd, IDC_NETWORK, BM_SETCHECK, 0, 0); + // Activate + break; + case Network::SENDER: + SendDlgItemMessage(hWnd, IDC_NETWORK, BM_SETCHECK, 1, 1); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_SETCURSEL, 0, 0); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_MODE), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_NICKNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_ROOMNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_SERVER), TRUE); + break; + case Network::GRABBER: + SendDlgItemMessage(hWnd, IDC_NETWORK, BM_SETCHECK, 1, 1); + SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_SETCURSEL, 1, 0); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_MODE), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_NICKNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_ROOMNAME), TRUE); + EnableWindow(GetDlgItem(hWnd, IDC_NETWORK_SERVER), TRUE); + break; + } + + + std::string HostPort, RoomName, NickName; + Network::SplitUrl(&HostPort, &RoomName, &NickName); SetDlgItemText(hWnd, IDC_NETWORK_SERVER, HostPort.c_str()); SetDlgItemText(hWnd, IDC_NETWORK_ROOMNAME, RoomName.c_str()); @@ -207,7 +230,18 @@ class CSetupDialog std::string FullUrl = std::string(ServerName) + "/" + RoomName + "/" + NickName; Network::SetUrl(strdup(FullUrl.c_str())); - + if (SendDlgItemMessage(hWnd, IDC_NETWORK, BM_GETCHECK, 0, 0) == false) { // Offline + Network::SetNetworkMode(Network::OFFLINE); + } + else { + if (SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_GETCURSEL, 0, 0) == 0) { //SENDER + Network::SetNetworkMode(Network::SENDER); + } + else { + Network::SetNetworkMode(Network::GRABBER); + } + } + } EndDialog( hWnd, TRUE ); } break; From 147c4180be7c93eb09a0885d2c04aa8917019446 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sat, 21 Sep 2024 11:14:05 +0200 Subject: [PATCH 08/29] Midi sender only + review parsing json + Network Name display --- src/MIDI.h | 18 +-- src/Network.h | 13 +- src/main.cpp | 36 ++++- src/platform_common/Network.cpp | 166 ++++++++++++++++++++---- src/platform_glfw/Renderer.cpp | 7 +- src/platform_w32_common/SetupDialog.cpp | 10 +- 6 files changed, 193 insertions(+), 57 deletions(-) diff --git a/src/MIDI.h b/src/MIDI.h index 68cea303..ba2cf995 100644 --- a/src/MIDI.h +++ b/src/MIDI.h @@ -1,22 +1,16 @@ +#ifndef BONZOMATIC_MIDI_H +#define BONZOMATIC_MIDI_H +#pragma once namespace MIDI { ////////////////////////////////////////////////////////////////////////// -enum -{ - MIDIMSG_NOTE_OFF = 8, - MIDIMSG_NOTE_ON = 9, - MIDIMSG_POLYPHONIC_KEY = 10, - MIDIMSG_CONTROL_CHANGE = 11, - MIDIMSG_PROGRAM_CHANGE = 12, - MIDIMSG_CHANNEL_PRESSURE = 13, - MIDIMSG_PITCH_BEND = 14, - MIDIMSG_SYSTEM = 15, -} MIDI_MESSAGE_TYPE; + bool Open(); bool Close(); float GetCCValue( unsigned char cc ); ////////////////////////////////////////////////////////////////////////// -}; \ No newline at end of file +}; +#endif \ No newline at end of file diff --git a/src/Network.h b/src/Network.h index dc1c70c7..280d9ac0 100644 --- a/src/Network.h +++ b/src/Network.h @@ -2,6 +2,7 @@ #define BONZOMATIC_NETWORK_H #pragma once #include + #include "mongoose.h" #include #include @@ -14,10 +15,13 @@ namespace Network { GRABBER }; + struct NetworkConfig { char* Url; NetworkMode Mode; - float updateInterval = 0.3; + float updateInterval = 0.3f; + bool sendMidiControls; + bool grabMidiControls; }; struct ShaderMessage { std::string Code; @@ -41,7 +45,7 @@ namespace Network { void Init(); void ParseSettings(jsonxx::Object* options); - void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime); + void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime, std::map *midiRoutes); char* GetUrl(); void SetUrl(char*); Network::NetworkMode GetNetworkMode(); @@ -49,6 +53,9 @@ namespace Network { void SetNeedRecompile(bool needToRecompile); void UpdateShaderFileName( const char** shaderName); void SplitUrl(std::string *host,std::string *roomname,std::string* name); - + bool IsGrabber(); + bool IsSender(); + bool IsOffline(); + void GenerateWindowsTitle(char** originalTitle); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 54ab124d..b8a9a019 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -398,6 +398,7 @@ int main( int argc, const char * argv[] ) mDebugOutput.SetText( "" ); mDebugOutput.SetReadOnly( true ); + static float fftData[ FFT_SIZE ]; memset( fftData, 0, sizeof( float ) * FFT_SIZE ); static float fftDataSmoothed[ FFT_SIZE ]; @@ -414,6 +415,20 @@ int main( int argc, const char * argv[] ) float fLastTimeMS = Timer::GetTime(); Network::Init(); + ShaderEditor mNetworkStatus(surface); + if (!Network::IsOffline()) { + // Network Handle + editorOptions.rect = Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - 100, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin); + editorOptions.nFontSize *= 2.5; + mNetworkStatus.Initialise(editorOptions); + mNetworkStatus.SetReadOnly(true); + + std::string handle = "totetmatt"; + mNetworkStatus.SetText(handle.c_str()); + int fontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), handle.c_str(), (int)handle.length()) * 1.1; + mNetworkStatus.SetPosition(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - fontWidth, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize)); + + } while ( !Renderer::WantsToQuit() ) { @@ -426,16 +441,17 @@ int main( int argc, const char * argv[] ) { if ( bShowGui ) { + switch ( Renderer::mouseEventBuffer[ i ].eventType ) { case Renderer::MOUSEEVENTTYPE_MOVE: mShaderEditor.ButtonMovePublic( Scintilla::Point( Renderer::mouseEventBuffer[ i ].x, Renderer::mouseEventBuffer[ i ].y ) ); break; case Renderer::MOUSEEVENTTYPE_DOWN: - mShaderEditor.ButtonDown( Scintilla::Point( Renderer::mouseEventBuffer[ i ].x, Renderer::mouseEventBuffer[ i ].y ), time * 1000, false, false, false ); + if(!Network::IsGrabber()) mShaderEditor.ButtonDown( Scintilla::Point( Renderer::mouseEventBuffer[ i ].x, Renderer::mouseEventBuffer[ i ].y ), time * 1000, false, false, false ); break; case Renderer::MOUSEEVENTTYPE_UP: - mShaderEditor.ButtonUp( Scintilla::Point( Renderer::mouseEventBuffer[ i ].x, Renderer::mouseEventBuffer[ i ].y ), time * 1000, false ); + if(!Network::IsGrabber()) mShaderEditor.ButtonUp( Scintilla::Point( Renderer::mouseEventBuffer[ i ].x, Renderer::mouseEventBuffer[ i ].y ), time * 1000, false ); break; case Renderer::MOUSEEVENTTYPE_SCROLL: if ( Renderer::mouseEventBuffer[ i ].ctrl ) @@ -445,7 +461,7 @@ int main( int argc, const char * argv[] ) } else { - mShaderEditor.WndProc( SCI_LINESCROLL, (int) ( -Renderer::mouseEventBuffer[ i ].x * fScrollXFactor ), (int) ( -Renderer::mouseEventBuffer[ i ].y * fScrollYFactor ) ); + if (!Network::IsGrabber()) mShaderEditor.WndProc( SCI_LINESCROLL, (int) ( -Renderer::mouseEventBuffer[ i ].x * fScrollXFactor ), (int) ( -Renderer::mouseEventBuffer[ i ].y * fScrollYFactor ) ); } break; } @@ -470,6 +486,7 @@ int main( int argc, const char * argv[] ) mDebugOutput.SetPosition( Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin ) ); bTexPreviewVisible = true; } + } else if ( Renderer::keyEventBuffer[ i ].scanCode == FKEY( 5 ) || ( Renderer::keyEventBuffer[ i ].ctrl && Renderer::keyEventBuffer[ i ].scanCode == 'r' ) ) // F5 { @@ -500,7 +517,7 @@ int main( int argc, const char * argv[] ) { bShowGui = !bShowGui; } - else if ( bShowGui ) + else if ( bShowGui && !Network::IsGrabber()) { bool consumed = false; if ( Renderer::keyEventBuffer[ i ].scanCode ) @@ -523,7 +540,7 @@ int main( int argc, const char * argv[] ) } } - Network::UpdateShader(&mShaderEditor, time); + Network::UpdateShader(&mShaderEditor, time, &midiRoutes); Renderer::keyEventBufferCount = 0; if (Network::ReloadShader()) { @@ -595,11 +612,13 @@ int main( int argc, const char * argv[] ) { mShaderEditor.Tick(); mDebugOutput.Tick(); + if(!Network::IsOffline()) mNetworkStatus.Tick(); fNextTick = time + 0.1; } mShaderEditor.Paint(); mDebugOutput.Paint(); + if (!Network::IsOffline()) mNetworkStatus.Paint(); Renderer::SetTextRenderingViewport( Scintilla::PRectangle( 0, 0, Renderer::nWidth, Renderer::nHeight ) ); @@ -629,8 +648,13 @@ int main( int argc, const char * argv[] ) sHelp += szLayout; surface->DrawTextNoClip( Scintilla::PRectangle( 20, Renderer::nHeight - 20, 100, Renderer::nHeight ), *mShaderEditor.GetTextFont(), Renderer::nHeight - 5.0, sHelp.c_str(), (int) sHelp.length(), 0x80FFFFFF, 0x00000000 ); } + if(bShowGui && !Network::IsOffline()){ // Activity Square + int TexPreviewOffset = bTexPreviewVisible ? nTexPreviewWidth + nMargin : 0; + std::string Status = "totetmatt"; + int fontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), Status.c_str(), (int)Status.length()) * 1.1; + surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - fontWidth-10, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - fontWidth, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); - + } Renderer::EndTextRendering(); Renderer::EndFrame(); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 6941b333..2a0214d4 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -1,5 +1,7 @@ #include "Network.h" +#include "MIDI.h" #define SHADER_FILENAME(mode) (std::string(mode)+ "_" + RoomName + "_" + NickName + ".glsl") +#define LOG(header,message) printf("[" header "] " message " \n") namespace Network { Network::NetworkConfig config; @@ -173,9 +175,9 @@ namespace Network { else if(config.Mode == GRABBER) { filename = SHADER_FILENAME("grabber"); } - *shaderName = strdup(filename.c_str()); + *shaderName = _strdup(filename.c_str()); } - void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime) { + void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime, std::map *midiRoutes) { if (Network::config.Mode != Network::NetworkMode::OFFLINE) { // If we arn't offline mode if (config.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode @@ -216,10 +218,18 @@ namespace Network { Data << "NickName" << "NickName"; Data << "ShaderTime" << shaderMessage.shaderTime; + if(config.sendMidiControls) { // Sending Midi Controls + jsonxx::Object networkShaderParameters; + for (std::map::iterator it = midiRoutes->begin(); it != midiRoutes->end(); it++) + { + networkShaderParameters << it->second << MIDI::GetCCValue(it->first); + } + Data << "Parameters" << networkShaderParameters; + } jsonxx::Object Message = jsonxx::Object("Data", Data); std::string TextJson = Message.json(); - if (connected) { + if (connected) { mg_ws_send(c, TextJson.c_str(), TextJson.length(), WEBSOCKET_OP_TEXT); shaderMessage.NeedRecompile = false; } @@ -227,39 +237,139 @@ namespace Network { } } } + bool IsGrabber() { + return config.Mode == GRABBER; + } + bool IsSender() { + return config.Mode == SENDER; + } + bool IsOffline() { + return config.Mode == OFFLINE; + } + void GenerateWindowsTitle(char** originalTitle) { + if (IsOffline()) { + return; + } + std::string host, roomname, user, title(*originalTitle), newName; + Network::SplitUrl(&host, &roomname, &user); + if (IsGrabber()) { + newName = title + " grabber " + user; + } + if (IsSender()) { + newName = title + " sender " + user; + } + *originalTitle = _strdup(newName.c_str()); + } + /* From here are methods for parsing json */ + void ParseNetworkGrabMidiControls(jsonxx::Object * network) { + if (!network->has("grabMidiControls")) { + LOG("Network Configuration", "Can't find 'grabMidiControls', set to false"); + config.grabMidiControls = false; + return; + } + config.grabMidiControls = network->get("grabMidiControls"); + } + void ParseNetworkSendMidiControls(jsonxx::Object* network) { + if (!network->has("sendMidiControls")) { + LOG("Network Configuration", "Can't find 'sendMidiControls', set to false"); + config.sendMidiControls = false; + return; + } + config.sendMidiControls = network->get("sendMidiControls"); + } + void ParseNetworkUpdateInterval(jsonxx::Object* network) { + if (!network->has("updateInterval")) { + LOG("Network Configuration", "Can't find 'updateInterval', set to 0.3"); + config.updateInterval = 0.3f; + return; + } + + config.updateInterval = network->get("updateInterval"); + } + void ParseNetworkMode(jsonxx::Object* network) { + if (!network->has("networkMode")) { + LOG("Network Configuration", "Can't find 'networkMode' Set to OFFLINE"); + config.Mode = OFFLINE; + return; + } - void ParseSettings(jsonxx::Object* options) { + const char* mode = network->get("networkMode").c_str(); + bool isSenderMode = strcmp(mode, "sender"); + bool isGrabberMode = strcmp(mode, "grabber"); + if (!isSenderMode && !isGrabberMode) { + LOG("Network Configuration", "networkMode is neither SENDER or GRABBER, fallback config to OFFLINE"); + config.Mode = OFFLINE; + return; + } + if(isSenderMode){ + config.Mode = SENDER; + } + if(isGrabberMode){ + config.Mode = GRABBER; + } - if (options->has("network")) { - jsonxx::Object network = options->get("network"); - if (network.has("serverURL")) { - config.Url = strdup(network.get("serverURL").c_str()); - } - if (network.get("enabled")) { + // From now on, we have a minimal config working we can try to parse extra option + ParseNetworkGrabMidiControls(network); + ParseNetworkSendMidiControls(network); + ParseNetworkUpdateInterval(network); + } + void ParseNetworkUrl(jsonxx::Object* network) { + if (!network->has("serverURL")) { + LOG("Network Configuration", "Can't find 'serverURL', set to 'OFFLINE'"); + config.Mode = OFFLINE; + config.Url = ""; + return; + } + + config.Url = _strdup(network->get("serverURL").c_str()); - if (network.has("networkMode")) { - const char* mode = network.get("networkMode").c_str(); - if (strcmp(mode, "sender") == 0) { - config.Mode = SENDER; - } - else if (strcmp(mode, "grabber") == 0) { - config.Mode = GRABBER; - } - else { - config.Mode = GRABBER; - printf("Can't find 'networkMode', set to 'GRABBER'\n"); - } - } - else { - printf("Can't find 'networkMode', set to 'OFFLINE'\n"); - } - } + ParseNetworkMode(network); + } + void ParseNetworkEnabled(jsonxx::Object* network) { + + if (!network->has("enabled")) { + LOG("Network Configuration", "Can't find 'enabled', set to 'OFFLINE'"); + config.Mode = OFFLINE; + config.Url = ""; + return; + } + if (!network->get("enabled")) { + LOG("Network Configuration", "Set to 'OFFLINE'"); + config.Mode = OFFLINE; + config.Url = ""; + // As we can activate this on setup dialog, let's try to get serverURL + if (network->has("serverURL")) { + config.Url = _strdup(network->get("serverURL").c_str()); + } + return; } - else { + ParseNetworkUrl(network); + + + } + /* + Parse the json settings. Cascading calls, not perfect but keep clear code + - Check that Network block exists on json + - Check that 'enabled' exists and is true + - Check that 'serverUrl' exists + - Check that 'networkMode' exists + + If something doesn't match above path, will fallback to OFFLINE Mode + */ + void ParseSettings(jsonxx::Object* options) { + + LOG("Network Configuration", "Parsing network configuration data from json"); + if (!options->has("network")) { + LOG("Network Configuration", "Can't find 'network' block, set to 'OFFLINE'"); config.Mode = OFFLINE; + config.Url = ""; + return; } + jsonxx::Object network = options->get("network"); + ParseNetworkEnabled(&network); + } } \ No newline at end of file diff --git a/src/platform_glfw/Renderer.cpp b/src/platform_glfw/Renderer.cpp index ee7840f5..d8e10a0a 100644 --- a/src/platform_glfw/Renderer.cpp +++ b/src/platform_glfw/Renderer.cpp @@ -1,6 +1,6 @@ #include - +#include "Network.h" #ifdef _WIN32 #include #endif @@ -260,8 +260,9 @@ bool Open( Renderer::Settings * settings ) glfwWindowHint( GLFW_AUTO_ICONIFY, GL_FALSE ); GLFWmonitor * monitor = settings->windowMode == WINDOWMODE_FULLSCREEN ? glfwGetPrimaryMonitor() : NULL; - - mWindow = glfwCreateWindow( nWidth, nHeight, "BONZOMATIC - GLFW edition", monitor, NULL ); + char * windowsTitle = "BONZOMATIC - GLFW edition"; + Network::GenerateWindowsTitle(&windowsTitle); + mWindow = glfwCreateWindow( nWidth, nHeight, windowsTitle, monitor, NULL ); if ( !mWindow ) { printf( "[GLFW] Window creation failed\n" ); diff --git a/src/platform_w32_common/SetupDialog.cpp b/src/platform_w32_common/SetupDialog.cpp index fce64314..5c18d02e 100644 --- a/src/platform_w32_common/SetupDialog.cpp +++ b/src/platform_w32_common/SetupDialog.cpp @@ -69,7 +69,7 @@ class CSetupDialog void FFTDeviceEnum( const bool bIsCaptureDevice, const char * szDeviceName, void * pDeviceID ) { TCHAR sz[ 512 ]; - _sntprintf( sz, 512, _T( "[%hs] %hs" ), bIsCaptureDevice ? "in" : "out", szDeviceName ); + _sntprintf_s( sz, 512, _T( "[%hs] %hs" ), bIsCaptureDevice ? "in" : "out", szDeviceName ); SendDlgItemMessage( hWndSetupDialog, IDC_AUDIOSOURCE, CB_ADDSTRING, 0, (LPARAM) sz ); if ( !pDeviceID ) @@ -129,7 +129,7 @@ class CSetupDialog for ( i = 0; i < gaResolutions.size(); i++ ) { TCHAR s[ 50 ]; - _sntprintf( s, 50, _T( "%d * %d" ), gaResolutions[ i ].nWidth, gaResolutions[ i ].nHeight ); + _sntprintf_s( s, 50, _T( "%d * %d" ), gaResolutions[ i ].nWidth, gaResolutions[ i ].nHeight ); SendDlgItemMessage( hWnd, IDC_RESOLUTION, CB_ADDSTRING, 0, (LPARAM) s ); @@ -157,9 +157,9 @@ class CSetupDialog { // Parsing url, could everthing be inside Network.h ? TCHAR s[50]; - _sntprintf(s, 50, _T("SENDER")); + _sntprintf_s(s, 50, _T("SENDER")); SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); - _sntprintf(s, 50, _T("GRABBER")); + _sntprintf_s(s, 50, _T("GRABBER")); SendDlgItemMessage(hWnd, IDC_NETWORK_MODE, CB_ADDSTRING, 0, (LPARAM)s); @@ -229,7 +229,7 @@ class CSetupDialog GetDlgItemText(hWnd, IDC_NETWORK_NICKNAME, NickName, min(NickLen + 1, 511)); std::string FullUrl = std::string(ServerName) + "/" + RoomName + "/" + NickName; - Network::SetUrl(strdup(FullUrl.c_str())); + Network::SetUrl(_strdup(FullUrl.c_str())); if (SendDlgItemMessage(hWnd, IDC_NETWORK, BM_GETCHECK, 0, 0) == false) { // Offline Network::SetNetworkMode(Network::OFFLINE); } From 39a03ecd65bb7134f1e3a4934582fbc9cdafab5a Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 22 Sep 2024 22:00:57 +0200 Subject: [PATCH 09/29] Improvement --- src/Network.h | 1 + src/main.cpp | 4 ++-- src/platform_common/Network.cpp | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Network.h b/src/Network.h index 280d9ac0..fbaceef4 100644 --- a/src/Network.h +++ b/src/Network.h @@ -56,6 +56,7 @@ namespace Network { bool IsGrabber(); bool IsSender(); bool IsOffline(); + bool IsConnected(); void GenerateWindowsTitle(char** originalTitle); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b8a9a019..b971729b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -385,7 +385,7 @@ int main( int argc, const char * argv[] ) int nMargin = 20; - bool bTexPreviewVisible = true; + bool bTexPreviewVisible = false; editorOptions.rect = Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ); ShaderEditor mShaderEditor( surface ); @@ -648,7 +648,7 @@ int main( int argc, const char * argv[] ) sHelp += szLayout; surface->DrawTextNoClip( Scintilla::PRectangle( 20, Renderer::nHeight - 20, 100, Renderer::nHeight ), *mShaderEditor.GetTextFont(), Renderer::nHeight - 5.0, sHelp.c_str(), (int) sHelp.length(), 0x80FFFFFF, 0x00000000 ); } - if(bShowGui && !Network::IsOffline()){ // Activity Square + if(bShowGui && !Network::IsOffline() && !Network::IsConnected()){ // Activity Square int TexPreviewOffset = bTexPreviewVisible ? nTexPreviewWidth + nMargin : 0; std::string Status = "totetmatt"; int fontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), Status.c_str(), (int)Status.length()) * 1.1; diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 2a0214d4..1279bbb4 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -117,10 +117,10 @@ namespace Network { // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); } - - /*/if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) { - *(bool*)c->fn_data = true; // Signal that we're done - }*/ + else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE ) { + connected = false; + printf("Error\n"); + } } void Create() { @@ -246,6 +246,9 @@ namespace Network { bool IsOffline() { return config.Mode == OFFLINE; } + bool IsConnected() { + return connected; + } void GenerateWindowsTitle(char** originalTitle) { if (IsOffline()) { return; From 93f219008466d4f3c52fa71a6037d1ffcb278e88 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 22 Sep 2024 22:03:54 +0200 Subject: [PATCH 10/29] revert _strdup as non linux ok --- src/platform_common/Network.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 1279bbb4..445bfd6f 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -175,7 +175,7 @@ namespace Network { else if(config.Mode == GRABBER) { filename = SHADER_FILENAME("grabber"); } - *shaderName = _strdup(filename.c_str()); + *shaderName = strdup(filename.c_str()); } void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime, std::map *midiRoutes) { if (Network::config.Mode != Network::NetworkMode::OFFLINE) { // If we arn't offline mode @@ -261,7 +261,7 @@ namespace Network { if (IsSender()) { newName = title + " sender " + user; } - *originalTitle = _strdup(newName.c_str()); + *originalTitle = strdup(newName.c_str()); } /* From here are methods for parsing json */ void ParseNetworkGrabMidiControls(jsonxx::Object * network) { @@ -324,7 +324,7 @@ namespace Network { return; } - config.Url = _strdup(network->get("serverURL").c_str()); + config.Url = strdup(network->get("serverURL").c_str()); ParseNetworkMode(network); @@ -344,7 +344,7 @@ namespace Network { config.Url = ""; // As we can activate this on setup dialog, let's try to get serverURL if (network->has("serverURL")) { - config.Url = _strdup(network->get("serverURL").c_str()); + config.Url = strdup(network->get("serverURL").c_str()); } return; } From eb2ba1656ad5edad0c441ce848f2f64dcc897ebc Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Thu, 26 Sep 2024 17:22:00 +0200 Subject: [PATCH 11/29] Sync time from sender in grabber mode --- src/Network.h | 5 +++++ src/main.cpp | 23 +++++++++---------- src/platform_common/Network.cpp | 40 +++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/Network.h b/src/Network.h index fbaceef4..5c11986b 100644 --- a/src/Network.h +++ b/src/Network.h @@ -22,6 +22,7 @@ namespace Network { float updateInterval = 0.3f; bool sendMidiControls; bool grabMidiControls; + bool syncTimeWithSender; }; struct ShaderMessage { std::string Code; @@ -57,6 +58,10 @@ namespace Network { bool IsSender(); bool IsOffline(); bool IsConnected(); + std::string* GetHandle(); void GenerateWindowsTitle(char** originalTitle); + void SyncTimeWithSender(float* time); + float TimeOffset(); + void ResetTimeOffset(float* time); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b971729b..b7bb479b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -416,6 +416,7 @@ int main( int argc, const char * argv[] ) Network::Init(); ShaderEditor mNetworkStatus(surface); + int networkHandleFontWidth; if (!Network::IsOffline()) { // Network Handle editorOptions.rect = Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - 100, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin); @@ -423,18 +424,19 @@ int main( int argc, const char * argv[] ) mNetworkStatus.Initialise(editorOptions); mNetworkStatus.SetReadOnly(true); - std::string handle = "totetmatt"; - mNetworkStatus.SetText(handle.c_str()); - int fontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), handle.c_str(), (int)handle.length()) * 1.1; - mNetworkStatus.SetPosition(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - fontWidth, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize)); - + std::string* handle = Network::GetHandle(); + mNetworkStatus.SetText(handle->c_str()); + networkHandleFontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), handle->c_str(), (int)handle->length()) * 1.1; + mNetworkStatus.SetPosition(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize)); + } while ( !Renderer::WantsToQuit() ) { bool newShader = false; + float time = Timer::GetTime() / 1000.0; // seconds - + Network::SyncTimeWithSender(&time); Renderer::StartFrame(); for ( int i = 0; i < Renderer::mouseEventBufferCount; i++ ) @@ -556,7 +558,7 @@ int main( int argc, const char * argv[] ) mDebugOutput.SetText(szError); } } - Renderer::SetShaderConstant( "fGlobalTime", time ); + Renderer::SetShaderConstant( "fGlobalTime", time + Network::TimeOffset()); Renderer::SetShaderConstant( "v2Resolution", settings.sRenderer.nWidth, settings.sRenderer.nHeight ); float fTime = Timer::GetTime(); @@ -648,15 +650,12 @@ int main( int argc, const char * argv[] ) sHelp += szLayout; surface->DrawTextNoClip( Scintilla::PRectangle( 20, Renderer::nHeight - 20, 100, Renderer::nHeight ), *mShaderEditor.GetTextFont(), Renderer::nHeight - 5.0, sHelp.c_str(), (int) sHelp.length(), 0x80FFFFFF, 0x00000000 ); } - if(bShowGui && !Network::IsOffline() && !Network::IsConnected()){ // Activity Square + if(bShowGui && !Network::IsOffline() && !Network::IsConnected()){ // Activity Square, might store data to avoid recalculating font widht int TexPreviewOffset = bTexPreviewVisible ? nTexPreviewWidth + nMargin : 0; - std::string Status = "totetmatt"; - int fontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), Status.c_str(), (int)Status.length()) * 1.1; - surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - fontWidth-10, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - fontWidth, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); + surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth-10, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - networkHandleFontWidth, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); } Renderer::EndTextRendering(); - Renderer::EndFrame(); Capture::CaptureFrame(); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 445bfd6f..4460f65a 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -1,5 +1,7 @@ #include "Network.h" #include "MIDI.h" +#include +#include #define SHADER_FILENAME(mode) (std::string(mode)+ "_" + RoomName + "_" + NickName + ".glsl") #define LOG(header,message) printf("[" header "] " message " \n") namespace Network { @@ -7,6 +9,7 @@ namespace Network { Network::NetworkConfig config; Network::ShaderMessage shaderMessage; + float timeOffset=0.f; struct mg_mgr mgr; struct mg_connection* c; bool done = false; @@ -14,7 +17,7 @@ namespace Network { bool IsNewShader = false; char szShader[65535]; bool connected = false; - + std::string handle; char* GetUrl() { return config.Url; @@ -84,7 +87,7 @@ namespace Network { } if (Data.has("ShaderTime")) { - float t = Data.get("ShaderTime"); + shaderMessage.shaderTime = Data.get("ShaderTime"); shaderMessage.Code = Data.get < jsonxx::String>("Code"); shaderMessage.AnchorPosition = Data.get("Anchor"); shaderMessage.CaretPosition = Data.get("Caret"); @@ -255,6 +258,7 @@ namespace Network { } std::string host, roomname, user, title(*originalTitle), newName; Network::SplitUrl(&host, &roomname, &user); + handle = user; if (IsGrabber()) { newName = title + " grabber " + user; } @@ -263,7 +267,37 @@ namespace Network { } *originalTitle = strdup(newName.c_str()); } + + std::string* GetHandle() { + return &handle; + } + void SyncTimeWithSender(float* time) { + if (!IsConnected || !IsGrabber() || !config.syncTimeWithSender) return; + + // 5 - 3 => 2 + if (IsNewShader && abs(*time + timeOffset - shaderMessage.shaderTime) > 1.f) { + timeOffset = shaderMessage.shaderTime - *time; + printf("%f\n", (timeOffset)); + } + + } + float TimeOffset() { + return timeOffset; + } + void ResetTimeOffset(float *time) { + timeOffset = -*time; + } /* From here are methods for parsing json */ + void ParseSyncTimeWithSender(jsonxx::Object* network) { + if (!network->has("syncTimeWithSender")) { + LOG("Network Configuration", "Can't find 'syncTimeWithSender', set to true"); + config.syncTimeWithSender = true; + return; + } + LOG("Network Configuration", "ParseSyncTimeWithSender"); + config.syncTimeWithSender = network->get("syncTimeWithSender"); + printf("%i\n", config.syncTimeWithSender); + } void ParseNetworkGrabMidiControls(jsonxx::Object * network) { if (!network->has("grabMidiControls")) { LOG("Network Configuration", "Can't find 'grabMidiControls', set to false"); @@ -315,6 +349,7 @@ namespace Network { ParseNetworkGrabMidiControls(network); ParseNetworkSendMidiControls(network); ParseNetworkUpdateInterval(network); + ParseSyncTimeWithSender(network); } void ParseNetworkUrl(jsonxx::Object* network) { if (!network->has("serverURL")) { @@ -352,6 +387,7 @@ namespace Network { } + /* Parse the json settings. Cascading calls, not perfect but keep clear code - Check that Network block exists on json From 1a0df8424614c13dc027437ac820f1c83c7d0936 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sat, 28 Sep 2024 22:36:43 +0200 Subject: [PATCH 12/29] Borderless + FFT Initial preprocessing review --- src/FFT.h | 3 + src/Renderer.h | 1 + src/main.cpp | 9 +++ src/platform_common/FFT.cpp | 107 +++++++++++++++++++++++++++++--- src/platform_common/Network.cpp | 24 +++---- src/platform_glfw/Renderer.cpp | 1 + 6 files changed, 126 insertions(+), 19 deletions(-) diff --git a/src/FFT.h b/src/FFT.h index 41a01231..51ab6e3c 100644 --- a/src/FFT.h +++ b/src/FFT.h @@ -13,6 +13,9 @@ struct Settings typedef void ( *FFT_ENUMERATE_FUNC )( const bool bIsCaptureDevice, const char * szDeviceName, void * pDeviceID, void * pUserContext ); extern float fAmplification; +extern bool bPeakNormalization; +extern float fPeakMinValue; +extern float fPeakSmoothing; void EnumerateDevices( FFT_ENUMERATE_FUNC pEnumerationFunction, void * pUserContext ); diff --git a/src/Renderer.h b/src/Renderer.h index 8a2d24a6..91c0ed72 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -17,6 +17,7 @@ struct Settings int nHeight; WINDOWMODE windowMode; bool bVsync; + bool borderless = false; }; struct KeyEvent diff --git a/src/main.cpp b/src/main.cpp index b7bb479b..23928d79 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -137,6 +137,9 @@ int main( int argc, const char * argv[] ) settings.sRenderer.nHeight = options.get( "window" ).get( "height" ); if ( options.get( "window" ).has( "fullscreen" ) ) settings.sRenderer.windowMode = options.get( "window" ).get( "fullscreen" ) ? Renderer::WINDOWMODE_FULLSCREEN : Renderer::WINDOWMODE_WINDOWED; + if (options.get("window").has( "borderless" )) + settings.sRenderer.borderless = options.get( "window" ).get( "borderless" ); + } if ( !skipSetupDialog ) { @@ -203,6 +206,12 @@ int main( int argc, const char * argv[] ) fFFTSmoothingFactor = options.get( "rendering" ).get( "fftSmoothFactor" ); if ( options.get( "rendering" ).has( "fftAmplification" ) ) FFT::fAmplification = options.get( "rendering" ).get( "fftAmplification" ); + if (options.get("rendering").has("fftPeakNormalization")) + FFT::bPeakNormalization = options.get("rendering").get("fftPeakNormalization"); + if (options.get("rendering").has("fftPeakMinValue")) + FFT::fPeakMinValue = options.get("rendering").get("fftPeakMinValue"); + if (options.get("rendering").has("fftPeakSmoothing")) + FFT::fPeakSmoothing = options.get("rendering").get("fftPeakSmoothing"); } if ( options.has( "textures" ) ) diff --git a/src/platform_common/FFT.cpp b/src/platform_common/FFT.cpp index e7888c53..1d87ebe4 100644 --- a/src/platform_common/FFT.cpp +++ b/src/platform_common/FFT.cpp @@ -6,7 +6,7 @@ #include #include #include "FFT.h" - +# define M_PI 3.14159265358979323846 /* pi */ namespace FFT { ////////////////////////////////////////////////////////////////////////// @@ -15,7 +15,14 @@ kiss_fftr_cfg fftcfg; ma_context context; ma_device captureDevice; float sampleBuf[ FFT_SIZE * 2 ]; +float sampleBufWin[FFT_SIZE * 2]; float fAmplification = 1.0f; + +bool bPeakNormalization = true; +float fPeakSmoothValue = 0.0f; +float fPeakMinValue = 0.01f; +float fPeakSmoothing = 0.995f; + bool bCreated = false; void OnLog( ma_context * pContext, ma_device * pDevice, ma_uint32 logLevel, const char * message ) @@ -24,7 +31,7 @@ void OnLog( ma_context * pContext, ma_device * pDevice, ma_uint32 logLevel, cons } void OnReceiveFrames( ma_device * pDevice, void * pOutput, const void * pInput, ma_uint32 frameCount ) -{ +{ frameCount = frameCount < FFT_SIZE * 2 ? frameCount : FFT_SIZE * 2; // Just rotate the buffer; copy existing, append new @@ -143,20 +150,106 @@ bool Open( FFT::Settings * pSettings ) return true; } +float aweight(float hz) { + if(hz<=10) return - 70.4; + if (hz <= 12.5)return -63.4; + if (hz <= 16)return -56.7; + if (hz <= 20)return -50.5; + if (hz <= 25)return -44.7; + if (hz <= 31.5)return -39.4; + if (hz <= 40)return -34.6; + if (hz <= 50)return -30.2; + if (hz <= 63)return -26.2; + if (hz <= 80)return -22.5; + if (hz <= 100)return -19.1; + if (hz <= 125)return -16.1; + if (hz <= 160)return -13.4; + if (hz <= 200)return -10.9; + if (hz <= 250)return -8.6; + if (hz <= 315)return -6.6; + if (hz <= 400) return-4.8; + if (hz <= 500)return -3.2; + if (hz <= 630)return -1.9; + if (hz <= 800)return -0.8; + if (hz <= 1000)return 0; + if (hz <= 1250)return 0.6; + if (hz <= 1600)return 1; + if (hz <= 2000)return 1.2; + if (hz <= 2500)return 1.3; + if (hz <= 3150)return 1.2; + if (hz <= 4000)return 1; + if (hz <= 5000) return 0.5; + if (hz <= 6300)return 0.1; + if (hz <= 8000)return -1.1; + if (hz <= 10000)return -2.5; + if (hz <= 12500)return -4.3; + if (hz <= 16000)return -6.6; + if (hz <= 20000)return -9.3; + return 9.3; +} bool GetFFT( float * _samples ) { + memset(sampleBufWin, 0, sizeof(float) * FFT_SIZE * 2); if ( !bCreated ) { return false; } + + for (size_t i = 0; i < FFT_SIZE*2; i++) { + float t = (float)i / (FFT_SIZE*2 - 1); + float hann = 0.5 - 0.5 * cosf(2 * M_PI * t); + sampleBufWin[i] = sampleBuf[i] *20.f * hann;// powf(sinf(M_PI * i / FFT_SIZE), 2.f); + } kiss_fft_cpx out[ FFT_SIZE + 1 ]; - kiss_fftr( fftcfg, sampleBuf, out ); + kiss_fftr( fftcfg, sampleBufWin, out ); + if (bPeakNormalization) { + float peakValue = fPeakMinValue; + for (int i = 0; i < FFT_SIZE; i++) + { + float val = 2.0f * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i); + if (val > peakValue) peakValue = val; + _samples[i] = val * fAmplification; + } + if (peakValue > fPeakSmoothValue) { + fPeakSmoothValue = peakValue; + } + if (peakValue < fPeakSmoothValue) { + fPeakSmoothValue = fPeakSmoothValue * fPeakSmoothing + peakValue * (1 - fPeakSmoothing); + } + fAmplification = 1.0f / fPeakSmoothValue; + } + else { + float fftResolution = 44100.0f / ((float)FFT_SIZE*2.f); + float sumAmp = 0.f; + for (int i = 0; i < FFT_SIZE; i++) + { + sumAmp += _samples[i]; - for ( int i = 0; i < FFT_SIZE; i++ ) - { - static const float scaling = 1.0f / (float) FFT_SIZE; - _samples[ i ] = 2.0 * sqrtf( out[ i ].r * out[ i ].r + out[ i ].i * out[ i ].i ) * scaling; + } + float maxAmp = 0.1f; + for (int i = 0; i < FFT_SIZE; i++) + { + static const float scaling = 1.0f / (float)FFT_SIZE; + + float amp = 2.0 * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i); + + if (amp > 0.f) { + amp = 20.0f*logf(amp); + float currentFreq = fftResolution * (float)i; + amp += aweight(currentFreq); + + } + //printf("[%zu] - %f\n",i, amp); + _samples[i] = (amp < 0.0f ? 0.0f:amp) * scaling; + maxAmp = _samples[i] > maxAmp ? _samples[i] : maxAmp; + + } + for (int i = 0; i < FFT_SIZE; i++) + { + _samples[i] /= maxAmp; + + } } return true; diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 4460f65a..63364979 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -290,17 +290,17 @@ namespace Network { /* From here are methods for parsing json */ void ParseSyncTimeWithSender(jsonxx::Object* network) { if (!network->has("syncTimeWithSender")) { - LOG("Network Configuration", "Can't find 'syncTimeWithSender', set to true"); + LOG("JSON Network Configuration Parsing", "Can't find 'syncTimeWithSender', set to true"); config.syncTimeWithSender = true; return; } - LOG("Network Configuration", "ParseSyncTimeWithSender"); + LOG("JSON Network Configuration Parsing", "ParseSyncTimeWithSender"); config.syncTimeWithSender = network->get("syncTimeWithSender"); printf("%i\n", config.syncTimeWithSender); } void ParseNetworkGrabMidiControls(jsonxx::Object * network) { if (!network->has("grabMidiControls")) { - LOG("Network Configuration", "Can't find 'grabMidiControls', set to false"); + LOG("JSON Network Configuration Parsing", "Can't find 'grabMidiControls', set to false"); config.grabMidiControls = false; return; } @@ -308,7 +308,7 @@ namespace Network { } void ParseNetworkSendMidiControls(jsonxx::Object* network) { if (!network->has("sendMidiControls")) { - LOG("Network Configuration", "Can't find 'sendMidiControls', set to false"); + LOG("JSON Network Configuration Parsing", "Can't find 'sendMidiControls', set to false"); config.sendMidiControls = false; return; } @@ -316,7 +316,7 @@ namespace Network { } void ParseNetworkUpdateInterval(jsonxx::Object* network) { if (!network->has("updateInterval")) { - LOG("Network Configuration", "Can't find 'updateInterval', set to 0.3"); + LOG("JSON Network Configuration Parsing", "Can't find 'updateInterval', set to 0.3"); config.updateInterval = 0.3f; return; } @@ -325,7 +325,7 @@ namespace Network { } void ParseNetworkMode(jsonxx::Object* network) { if (!network->has("networkMode")) { - LOG("Network Configuration", "Can't find 'networkMode' Set to OFFLINE"); + LOG("JSON Network Configuration Parsing", "Can't find 'networkMode' Set to OFFLINE"); config.Mode = OFFLINE; return; } @@ -334,7 +334,7 @@ namespace Network { bool isSenderMode = strcmp(mode, "sender"); bool isGrabberMode = strcmp(mode, "grabber"); if (!isSenderMode && !isGrabberMode) { - LOG("Network Configuration", "networkMode is neither SENDER or GRABBER, fallback config to OFFLINE"); + LOG("JSON Network Configuration Parsing", "networkMode is neither SENDER or GRABBER, fallback config to OFFLINE"); config.Mode = OFFLINE; return; } @@ -353,7 +353,7 @@ namespace Network { } void ParseNetworkUrl(jsonxx::Object* network) { if (!network->has("serverURL")) { - LOG("Network Configuration", "Can't find 'serverURL', set to 'OFFLINE'"); + LOG("JSON Network Configuration Parsing", "Can't find 'serverURL', set to 'OFFLINE'"); config.Mode = OFFLINE; config.Url = ""; return; @@ -367,14 +367,14 @@ namespace Network { void ParseNetworkEnabled(jsonxx::Object* network) { if (!network->has("enabled")) { - LOG("Network Configuration", "Can't find 'enabled', set to 'OFFLINE'"); + LOG("JSON Network Configuration Parsing", "Can't find 'enabled', set to 'OFFLINE'"); config.Mode = OFFLINE; config.Url = ""; return; } if (!network->get("enabled")) { - LOG("Network Configuration", "Set to 'OFFLINE'"); + LOG("JSON Network Configuration Parsing", "Set to 'OFFLINE'"); config.Mode = OFFLINE; config.Url = ""; // As we can activate this on setup dialog, let's try to get serverURL @@ -399,9 +399,9 @@ namespace Network { */ void ParseSettings(jsonxx::Object* options) { - LOG("Network Configuration", "Parsing network configuration data from json"); + LOG("JSON Network Configuration Parsing", "Parsing network configuration data from json"); if (!options->has("network")) { - LOG("Network Configuration", "Can't find 'network' block, set to 'OFFLINE'"); + LOG("JSON Network Configuration Parsing", "Can't find 'network' block, set to 'OFFLINE'"); config.Mode = OFFLINE; config.Url = ""; return; diff --git a/src/platform_glfw/Renderer.cpp b/src/platform_glfw/Renderer.cpp index d8e10a0a..c0de22cf 100644 --- a/src/platform_glfw/Renderer.cpp +++ b/src/platform_glfw/Renderer.cpp @@ -255,6 +255,7 @@ bool Open( Renderer::Settings * settings ) // TODO: change in case of resize support glfwWindowHint( GLFW_RESIZABLE, GLFW_TRUE ); + glfwWindowHint(GLFW_DECORATED, settings->borderless ? GLFW_FALSE : GLFW_TRUE); // Prevent fullscreen window minimize on focus loss glfwWindowHint( GLFW_AUTO_ICONIFY, GL_FALSE ); From 1a29bf83d3dd65a23aa10153b55ade2c3c4f120e Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sat, 28 Sep 2024 23:03:21 +0200 Subject: [PATCH 13/29] Add FFT Option --- src/FFT.h | 2 +- src/main.cpp | 4 +++- src/platform_common/FFT.cpp | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/FFT.h b/src/FFT.h index 51ab6e3c..4fec32f3 100644 --- a/src/FFT.h +++ b/src/FFT.h @@ -16,7 +16,7 @@ extern float fAmplification; extern bool bPeakNormalization; extern float fPeakMinValue; extern float fPeakSmoothing; - +extern bool bPreProcessing; void EnumerateDevices( FFT_ENUMERATE_FUNC pEnumerationFunction, void * pUserContext ); bool Create(); diff --git a/src/main.cpp b/src/main.cpp index 23928d79..188bc1e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -208,12 +208,14 @@ int main( int argc, const char * argv[] ) FFT::fAmplification = options.get( "rendering" ).get( "fftAmplification" ); if (options.get("rendering").has("fftPeakNormalization")) FFT::bPeakNormalization = options.get("rendering").get("fftPeakNormalization"); + if (options.get("rendering").has("fftPreProcessing")) + FFT::bPreProcessing = options.get("rendering").get("fftPreProcessing"); if (options.get("rendering").has("fftPeakMinValue")) FFT::fPeakMinValue = options.get("rendering").get("fftPeakMinValue"); if (options.get("rendering").has("fftPeakSmoothing")) FFT::fPeakSmoothing = options.get("rendering").get("fftPeakSmoothing"); } - + printf("Preprox %i\n", FFT::bPreProcessing); if ( options.has( "textures" ) ) { printf( "Loading textures...\n" ); diff --git a/src/platform_common/FFT.cpp b/src/platform_common/FFT.cpp index 1d87ebe4..a86278bd 100644 --- a/src/platform_common/FFT.cpp +++ b/src/platform_common/FFT.cpp @@ -19,6 +19,7 @@ float sampleBufWin[FFT_SIZE * 2]; float fAmplification = 1.0f; bool bPeakNormalization = true; +bool bPreProcessing = false; float fPeakSmoothValue = 0.0f; float fPeakMinValue = 0.01f; float fPeakSmoothing = 0.995f; @@ -203,7 +204,8 @@ bool GetFFT( float * _samples ) } kiss_fft_cpx out[ FFT_SIZE + 1 ]; kiss_fftr( fftcfg, sampleBufWin, out ); - if (bPeakNormalization) { + if (!bPreProcessing && bPeakNormalization) { // Nusan's peakNormalization + float peakValue = fPeakMinValue; for (int i = 0; i < FFT_SIZE; i++) { @@ -219,7 +221,8 @@ bool GetFFT( float * _samples ) } fAmplification = 1.0f / fPeakSmoothValue; } - else { + else if(bPreProcessing) // Totetmatt and Cacaooo Pre Processing + { float fftResolution = 44100.0f / ((float)FFT_SIZE*2.f); float sumAmp = 0.f; for (int i = 0; i < FFT_SIZE; i++) @@ -251,6 +254,14 @@ bool GetFFT( float * _samples ) } } + else // Original behaviour + { + for (int i = 0; i < FFT_SIZE; i++) + { + static const float scaling = 1.0f / (float)FFT_SIZE; + _samples[i] = 2.0 * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i) * scaling; + } + } return true; } From 1e451849548daa6387eb7fb73056ba3cbeee4b26 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 29 Sep 2024 17:01:27 +0200 Subject: [PATCH 14/29] Fix aweight --- src/platform_common/FFT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_common/FFT.cpp b/src/platform_common/FFT.cpp index a86278bd..bd9cc2e7 100644 --- a/src/platform_common/FFT.cpp +++ b/src/platform_common/FFT.cpp @@ -186,7 +186,7 @@ float aweight(float hz) { if (hz <= 12500)return -4.3; if (hz <= 16000)return -6.6; if (hz <= 20000)return -9.3; - return 9.3; + return -9.3; } bool GetFFT( float * _samples ) { From 37ac63bad6e281672c3dac6bcd6baa2752d3cfc4 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 30 Sep 2024 23:23:00 +0200 Subject: [PATCH 15/29] Grabber ping back --- src/Network.h | 1 + src/main.cpp | 8 ++++++++ src/platform_common/Network.cpp | 28 ++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Network.h b/src/Network.h index 5c11986b..3b67c4f2 100644 --- a/src/Network.h +++ b/src/Network.h @@ -63,5 +63,6 @@ namespace Network { void SyncTimeWithSender(float* time); float TimeOffset(); void ResetTimeOffset(float* time); + bool IsPinged(); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 188bc1e4..b8edb31e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -658,14 +658,22 @@ int main( int argc, const char * argv[] ) char szLayout[ 255 ]; Misc::GetKeymapName( szLayout ); std::string sHelp = "F2 - toggle texture preview F5 or Ctrl-R - recompile shader F11 - hide GUI Current keymap: "; + sHelp += szLayout; surface->DrawTextNoClip( Scintilla::PRectangle( 20, Renderer::nHeight - 20, 100, Renderer::nHeight ), *mShaderEditor.GetTextFont(), Renderer::nHeight - 5.0, sHelp.c_str(), (int) sHelp.length(), 0x80FFFFFF, 0x00000000 ); + } if(bShowGui && !Network::IsOffline() && !Network::IsConnected()){ // Activity Square, might store data to avoid recalculating font widht int TexPreviewOffset = bTexPreviewVisible ? nTexPreviewWidth + nMargin : 0; surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth-10, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - networkHandleFontWidth, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); } + if (Network::IsSender()) { + if (!Network::IsPinged()) { + surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth - 21, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - networkHandleFontWidth - 11, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xffFF8080); + } + } + Renderer::EndTextRendering(); Renderer::EndFrame(); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 63364979..f6e4b56f 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -19,6 +19,7 @@ namespace Network { bool connected = false; std::string handle; + float pingTime = 0.f; char* GetUrl() { return config.Url; } @@ -98,6 +99,8 @@ namespace Network { } static void fn(struct mg_connection* c, int ev, void* ev_data) { + + //printf("ev: %i\n", ev); if (ev == MG_EV_OPEN) { c->is_hexdumping = 0; } @@ -105,27 +108,40 @@ namespace Network { // On error, log error message MG_ERROR(("%p %s", c->fd, (char*)ev_data)); } + else if (ev == MG_EV_WS_OPEN) { fprintf(stdout, "[Network]: Connected\n"); connected = true; // When websocket handshake is successful, send message // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); } - else if (ev == MG_EV_WS_MSG && config.Mode == GRABBER) { - // When we get echo response, print it + else if (config.Mode == SENDER && ev == MG_EV_WS_MSG) { struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; - RecieveShader((int)wm->data.len, wm->data.buf); + if (wm->data.len == 0) { // Message 0 len = PING from Grabber + pingTime = shaderMessage.shaderTime; + } - // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); } + else if (ev == MG_EV_WS_MSG && config.Mode == GRABBER) { + // When we get echo response, print it + + struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; + + if(wm->data.len>0) { + RecieveShader((int)wm->data.len, wm->data.buf); + } + // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); + } else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE ) { connected = false; printf("Error\n"); } } - + bool IsPinged() { + return abs(pingTime - shaderMessage.shaderTime) < 1.f; + } void Create() { fprintf(stdout, "[Network]: Try to connect to %s\n", config.Url); mg_mgr_init(&mgr); @@ -197,7 +213,7 @@ namespace Network { //mShaderEditor.WndProc(SCI_SETFIRSTVISIBLELINE, NewMessage.FirstVisibleLine, 0); mShaderEditor->WndProc(SCI_SCROLLCARET, 0, 0); //} - + mg_ws_send(c, 0, 0, WEBSOCKET_OP_BINARY); // Send Ping to Sender to notify received } else if (config.Mode == Network::SENDER && shaderTime - shaderMessage.shaderTime > 0.1) { From 98f47329d73e3a4f6b2fd463f8501ca5e30cc6e8 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 30 Sep 2024 23:30:58 +0200 Subject: [PATCH 16/29] Explicit FFT input and FFT output size --- src/FFT.h | 4 ++-- src/main.cpp | 24 ++++++++++++------------ src/platform_common/FFT.cpp | 36 ++++++++++++++++++------------------ 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/FFT.h b/src/FFT.h index 4fec32f3..03466f2f 100644 --- a/src/FFT.h +++ b/src/FFT.h @@ -1,5 +1,5 @@ -#define FFT_SIZE 1024 - +#define FFT_BIN_SIZE 1024 +#define FFT_INPUT_LENGTH (FFT_BIN_SIZE*2) namespace FFT { ////////////////////////////////////////////////////////////////////////// diff --git a/src/main.cpp b/src/main.cpp index b8edb31e..f05f5d67 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -335,9 +335,9 @@ int main( int argc, const char * argv[] ) } Renderer::Texture * texPreviousFrame = Renderer::CreateRGBA8Texture(); - Renderer::Texture * texFFT = Renderer::Create1DR32Texture( FFT_SIZE ); - Renderer::Texture * texFFTSmoothed = Renderer::Create1DR32Texture( FFT_SIZE ); - Renderer::Texture * texFFTIntegrated = Renderer::Create1DR32Texture( FFT_SIZE ); + Renderer::Texture * texFFT = Renderer::Create1DR32Texture(FFT_BIN_SIZE); + Renderer::Texture * texFFTSmoothed = Renderer::Create1DR32Texture(FFT_BIN_SIZE); + Renderer::Texture * texFFTIntegrated = Renderer::Create1DR32Texture(FFT_BIN_SIZE); // Overriding Name if we are in SENDER or GRABBER Network mode Network::UpdateShaderFileName(&Renderer::szDefaultShaderFilename); @@ -410,15 +410,15 @@ int main( int argc, const char * argv[] ) mDebugOutput.SetReadOnly( true ); - static float fftData[ FFT_SIZE ]; - memset( fftData, 0, sizeof( float ) * FFT_SIZE ); - static float fftDataSmoothed[ FFT_SIZE ]; - memset( fftDataSmoothed, 0, sizeof( float ) * FFT_SIZE ); + static float fftData[FFT_BIN_SIZE]; + memset( fftData, 0, sizeof( float ) * FFT_BIN_SIZE); + static float fftDataSmoothed[FFT_BIN_SIZE]; + memset( fftDataSmoothed, 0, sizeof( float ) * FFT_BIN_SIZE); - static float fftDataSlightlySmoothed[ FFT_SIZE ]; - memset( fftDataSlightlySmoothed, 0, sizeof( float ) * FFT_SIZE ); - static float fftDataIntegrated[ FFT_SIZE ]; - memset( fftDataIntegrated, 0, sizeof( float ) * FFT_SIZE ); + static float fftDataSlightlySmoothed[FFT_BIN_SIZE]; + memset( fftDataSlightlySmoothed, 0, sizeof( float ) * FFT_BIN_SIZE); + static float fftDataIntegrated[FFT_BIN_SIZE]; + memset( fftDataIntegrated, 0, sizeof( float ) * FFT_BIN_SIZE); bool bShowGui = true; Timer::Start(); @@ -587,7 +587,7 @@ int main( int argc, const char * argv[] ) Renderer::UpdateR32Texture( texFFT, fftData ); const static float maxIntegralValue = 1024.0f; - for ( int i = 0; i < FFT_SIZE; i++ ) + for ( int i = 0; i < FFT_BIN_SIZE; i++ ) { fftDataSmoothed[ i ] = fftDataSmoothed[ i ] * fFFTSmoothingFactor + ( 1 - fFFTSmoothingFactor ) * fftData[ i ]; diff --git a/src/platform_common/FFT.cpp b/src/platform_common/FFT.cpp index bd9cc2e7..66fc57fb 100644 --- a/src/platform_common/FFT.cpp +++ b/src/platform_common/FFT.cpp @@ -14,8 +14,8 @@ namespace FFT kiss_fftr_cfg fftcfg; ma_context context; ma_device captureDevice; -float sampleBuf[ FFT_SIZE * 2 ]; -float sampleBufWin[FFT_SIZE * 2]; +float sampleBuf[FFT_INPUT_LENGTH ]; +float sampleBufWin[FFT_INPUT_LENGTH]; float fAmplification = 1.0f; bool bPeakNormalization = true; @@ -33,12 +33,12 @@ void OnLog( ma_context * pContext, ma_device * pDevice, ma_uint32 logLevel, cons void OnReceiveFrames( ma_device * pDevice, void * pOutput, const void * pInput, ma_uint32 frameCount ) { - frameCount = frameCount < FFT_SIZE * 2 ? frameCount : FFT_SIZE * 2; + frameCount = frameCount < FFT_INPUT_LENGTH ? frameCount : FFT_INPUT_LENGTH; // Just rotate the buffer; copy existing, append new const float * samples = (const float *) pInput; float * p = sampleBuf; - for ( int i = 0; i < FFT_SIZE * 2 - frameCount; i++ ) + for ( int i = 0; i < FFT_INPUT_LENGTH - frameCount; i++ ) { *( p++ ) = sampleBuf[ i + frameCount ]; } @@ -119,9 +119,9 @@ bool Open( FFT::Settings * pSettings ) return false; } - memset( sampleBuf, 0, sizeof( float ) * FFT_SIZE * 2 ); + memset( sampleBuf, 0, sizeof( float ) * FFT_INPUT_LENGTH); - fftcfg = kiss_fftr_alloc( FFT_SIZE * 2, false, NULL, NULL ); + fftcfg = kiss_fftr_alloc(FFT_INPUT_LENGTH, false, NULL, NULL ); bool useLoopback = ma_is_loopback_supported( context.backend ) && !pSettings->bUseRecordingDevice; ma_device_config config = ma_device_config_init( useLoopback ? ma_device_type_loopback : ma_device_type_capture ); @@ -190,24 +190,24 @@ float aweight(float hz) { } bool GetFFT( float * _samples ) { - memset(sampleBufWin, 0, sizeof(float) * FFT_SIZE * 2); + memset(sampleBufWin, 0, sizeof(float) * FFT_INPUT_LENGTH); if ( !bCreated ) { return false; } - for (size_t i = 0; i < FFT_SIZE*2; i++) { - float t = (float)i / (FFT_SIZE*2 - 1); + for (size_t i = 0; i < FFT_INPUT_LENGTH; i++) { + float t = (float)i / (FFT_INPUT_LENGTH - 1); float hann = 0.5 - 0.5 * cosf(2 * M_PI * t); sampleBufWin[i] = sampleBuf[i] *20.f * hann;// powf(sinf(M_PI * i / FFT_SIZE), 2.f); } - kiss_fft_cpx out[ FFT_SIZE + 1 ]; + kiss_fft_cpx out[ FFT_BIN_SIZE + 1 ]; kiss_fftr( fftcfg, sampleBufWin, out ); if (!bPreProcessing && bPeakNormalization) { // Nusan's peakNormalization float peakValue = fPeakMinValue; - for (int i = 0; i < FFT_SIZE; i++) + for (int i = 0; i < FFT_BIN_SIZE; i++) { float val = 2.0f * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i); if (val > peakValue) peakValue = val; @@ -223,17 +223,17 @@ bool GetFFT( float * _samples ) } else if(bPreProcessing) // Totetmatt and Cacaooo Pre Processing { - float fftResolution = 44100.0f / ((float)FFT_SIZE*2.f); + float fftResolution = 44100.0f / ((float)FFT_INPUT_LENGTH); float sumAmp = 0.f; - for (int i = 0; i < FFT_SIZE; i++) + for (int i = 0; i < FFT_BIN_SIZE; i++) { sumAmp += _samples[i]; } float maxAmp = 0.1f; - for (int i = 0; i < FFT_SIZE; i++) + for (int i = 0; i < FFT_BIN_SIZE; i++) { - static const float scaling = 1.0f / (float)FFT_SIZE; + static const float scaling = 1.0f / (float)FFT_BIN_SIZE; float amp = 2.0 * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i); @@ -248,7 +248,7 @@ bool GetFFT( float * _samples ) maxAmp = _samples[i] > maxAmp ? _samples[i] : maxAmp; } - for (int i = 0; i < FFT_SIZE; i++) + for (int i = 0; i < FFT_BIN_SIZE; i++) { _samples[i] /= maxAmp; @@ -256,9 +256,9 @@ bool GetFFT( float * _samples ) } else // Original behaviour { - for (int i = 0; i < FFT_SIZE; i++) + for (int i = 0; i < FFT_BIN_SIZE; i++) { - static const float scaling = 1.0f / (float)FFT_SIZE; + static const float scaling = 1.0f / (float)FFT_BIN_SIZE; _samples[i] = 2.0 * sqrtf(out[i].r * out[i].r + out[i].i * out[i].i) * scaling; } } From e271596af0457efbe28383f6bb03948a0aef26ca Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 30 Sep 2024 23:41:30 +0200 Subject: [PATCH 17/29] First shader from grabber is compiled --- src/platform_common/Network.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index f6e4b56f..7125c89e 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -20,6 +20,7 @@ namespace Network { std::string handle; float pingTime = 0.f; + bool isFirstShaderCompile = true; char* GetUrl() { return config.Url; } @@ -49,6 +50,7 @@ namespace Network { bool HasNewShader() { if (IsNewShader) { IsNewShader = false; + return true; } return false; @@ -92,8 +94,9 @@ namespace Network { shaderMessage.Code = Data.get < jsonxx::String>("Code"); shaderMessage.AnchorPosition = Data.get("Anchor"); shaderMessage.CaretPosition = Data.get("Caret"); - shaderMessage.NeedRecompile = Data.get("Compile"); + shaderMessage.NeedRecompile = Data.get("Compile") || isFirstShaderCompile; IsNewShader = true; + isFirstShaderCompile = false; } From 9348d9aa611c742b4ea5caa8376eee2fb1f756b9 Mon Sep 17 00:00:00 2001 From: Gargaj Date: Sat, 5 Oct 2024 15:26:29 +0200 Subject: [PATCH 18/29] remove travis --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f3ed36cc..9adce980 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Github workflow status](https://github.com/Gargaj/Bonzomatic/actions/workflows/main.yml/badge.svg)](https://github.com/Gargaj/Bonzomatic/actions/workflows/main.yml) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/ix6fwi6nym1tu4e7?svg=true)](https://ci.appveyor.com/project/Gargaj/bonzomatic) -[![Travis build status](https://img.shields.io/travis/Gargaj/Bonzomatic?logo=travis)](https://travis-ci.org/Gargaj/Bonzomatic) ## What's this? This is a live-coding tool, where you can write a 2D fragment/pixel shader while it is running in the background. From 87dbe108412e6ea57b60a3a32d459335e92a077d Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 6 Oct 2024 18:35:42 +0200 Subject: [PATCH 19/29] Resize windwos --- src/Renderer.h | 1 + src/ShaderEditor.cpp | 2 ++ src/main.cpp | 36 ++++++++++++++++++++-------------- src/platform_glfw/Renderer.cpp | 11 +++++++++-- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Renderer.h b/src/Renderer.h index 91c0ed72..1e7f9aa8 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -84,6 +84,7 @@ extern const char * shaderBuiltin; extern int nWidth; extern int nHeight; +extern bool sizeChanged; bool OpenSetupDialog( Settings * settings ); bool Open( Settings * settings ); diff --git a/src/ShaderEditor.cpp b/src/ShaderEditor.cpp index 06f6e3eb..bf392ad8 100644 --- a/src/ShaderEditor.cpp +++ b/src/ShaderEditor.cpp @@ -103,6 +103,8 @@ void ShaderEditor::Initialise() WndProc( SCI_SETFOLDMARGINHICOLOUR, 1, BACKGROUND( 0x1A1A1A ) ); WndProc( SCI_SETSELBACK, 1, theme.selection ); + WndProc(SCI_ASSIGNCMDKEY, Platform::LongFromTwoShorts('W', SCMOD_CTRL), SCI_UNDO); + SetReadOnly( false ); for ( int i = 0; i < NB_FOLDER_STATE; i++ ) diff --git a/src/main.cpp b/src/main.cpp index f05f5d67..4cc9162f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -398,12 +398,12 @@ int main( int argc, const char * argv[] ) bool bTexPreviewVisible = false; - editorOptions.rect = Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ); + editorOptions.rect = Scintilla::PRectangle( nMargin, nMargin, Renderer::nWidth - nMargin - nTexPreviewWidth - nMargin, Renderer::nHeight - nMargin * 2 - nDebugOutputHeight ); ShaderEditor mShaderEditor( surface ); mShaderEditor.Initialise( editorOptions ); mShaderEditor.SetText( szShader ); - editorOptions.rect = Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin ); + editorOptions.rect = Scintilla::PRectangle( nMargin, Renderer::nHeight - nMargin - nDebugOutputHeight, Renderer::nWidth - nMargin - nTexPreviewWidth - nMargin, Renderer::nHeight - nMargin ); ShaderEditor mDebugOutput( surface ); mDebugOutput.Initialise( editorOptions ); mDebugOutput.SetText( "" ); @@ -430,7 +430,7 @@ int main( int argc, const char * argv[] ) int networkHandleFontWidth; if (!Network::IsOffline()) { // Network Handle - editorOptions.rect = Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - 100, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin); + editorOptions.rect = Scintilla::PRectangle(Renderer::nWidth - nMargin - 100, Renderer::nHeight - nMargin - 50, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin); editorOptions.nFontSize *= 2.5; mNetworkStatus.Initialise(editorOptions); mNetworkStatus.SetReadOnly(true); @@ -438,7 +438,7 @@ int main( int argc, const char * argv[] ) std::string* handle = Network::GetHandle(); mNetworkStatus.SetText(handle->c_str()); networkHandleFontWidth = surface->WidthText(*mNetworkStatus.GetTextFont(), handle->c_str(), (int)handle->length()) * 1.1; - mNetworkStatus.SetPosition(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize)); + mNetworkStatus.SetPosition(Scintilla::PRectangle(Renderer::nWidth - nMargin - networkHandleFontWidth, Renderer::nHeight - nMargin - 50, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin - 50 + editorOptions.nFontSize)); } @@ -489,14 +489,14 @@ int main( int argc, const char * argv[] ) { if ( bTexPreviewVisible ) { - mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ) ); - mDebugOutput.SetPosition( Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin ) ); + mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin * 2 - nDebugOutputHeight ) ); + mDebugOutput.SetPosition( Scintilla::PRectangle( nMargin, Renderer::nHeight - nMargin - nDebugOutputHeight, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin ) ); bTexPreviewVisible = false; } else { - mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ) ); - mDebugOutput.SetPosition( Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin ) ); + mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, Renderer::nWidth - nMargin - nTexPreviewWidth - nMargin, Renderer::nHeight - nMargin * 2 - nDebugOutputHeight ) ); + mDebugOutput.SetPosition( Scintilla::PRectangle( nMargin, Renderer::nHeight - nMargin - nDebugOutputHeight, Renderer::nWidth - nMargin - nTexPreviewWidth - nMargin, Renderer::nHeight - nMargin ) ); bTexPreviewVisible = true; } @@ -570,7 +570,7 @@ int main( int argc, const char * argv[] ) } } Renderer::SetShaderConstant( "fGlobalTime", time + Network::TimeOffset()); - Renderer::SetShaderConstant( "v2Resolution", settings.sRenderer.nWidth, settings.sRenderer.nHeight ); + Renderer::SetShaderConstant( "v2Resolution", Renderer::nWidth, Renderer::nHeight); float fTime = Timer::GetTime(); Renderer::SetShaderConstant( "fFrameTime", ( fTime - fLastTimeMS ) / 1000.0f ); @@ -615,6 +615,7 @@ int main( int argc, const char * argv[] ) Renderer::RenderFullscreenQuad(); + Renderer::CopyBackbufferToTexture( texPreviousFrame ); Renderer::StartTextRendering(); @@ -638,8 +639,8 @@ int main( int argc, const char * argv[] ) if ( bTexPreviewVisible ) { int y1 = nMargin; - int x1 = settings.sRenderer.nWidth - nMargin - nTexPreviewWidth; - int x2 = settings.sRenderer.nWidth - nMargin; + int x1 = Renderer::nWidth - nMargin - nTexPreviewWidth; + int x2 = Renderer::nWidth - nMargin; for ( std::map::iterator it = textures.begin(); it != textures.end(); it++ ) { int y2 = y1 + nTexPreviewWidth * ( it->second->height / (float) it->second->width ); @@ -665,15 +666,20 @@ int main( int argc, const char * argv[] ) } if(bShowGui && !Network::IsOffline() && !Network::IsConnected()){ // Activity Square, might store data to avoid recalculating font widht int TexPreviewOffset = bTexPreviewVisible ? nTexPreviewWidth + nMargin : 0; - surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth-10, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - networkHandleFontWidth, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); + surface->RectangleDraw(Scintilla::PRectangle(Renderer::nWidth - nMargin - networkHandleFontWidth-10, Renderer::nHeight - nMargin - 50, Renderer::nWidth - nMargin - networkHandleFontWidth, Renderer::nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xff8080FF); } - if (Network::IsSender()) { + if (bShowGui && Network::IsSender()) { if (!Network::IsPinged()) { - surface->RectangleDraw(Scintilla::PRectangle(settings.sRenderer.nWidth - nMargin - networkHandleFontWidth - 21, settings.sRenderer.nHeight - nMargin - 50, settings.sRenderer.nWidth - nMargin - networkHandleFontWidth - 11, settings.sRenderer.nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xffFF8080); + surface->RectangleDraw(Scintilla::PRectangle(Renderer::nWidth - nMargin - networkHandleFontWidth - 21, Renderer::nHeight - nMargin - 50, Renderer::nWidth - nMargin - networkHandleFontWidth - 11, Renderer::nHeight - nMargin - 50 + editorOptions.nFontSize), 0x00000000, 0xffFF8080); } } - + if (Renderer::sizeChanged) { + mShaderEditor.SetPosition(Scintilla::PRectangle(nMargin, nMargin, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin * 2 - nDebugOutputHeight)); + mDebugOutput.SetPosition(Scintilla::PRectangle(nMargin, Renderer::nHeight - nMargin - nDebugOutputHeight, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin)); + mNetworkStatus.SetPosition(Scintilla::PRectangle(Renderer::nWidth - nMargin - networkHandleFontWidth, Renderer::nHeight - nMargin - 50, Renderer::nWidth - nMargin, Renderer::nHeight - nMargin - 50 + editorOptions.nFontSize)); + Renderer::sizeChanged = false; + } Renderer::EndTextRendering(); Renderer::EndFrame(); diff --git a/src/platform_glfw/Renderer.cpp b/src/platform_glfw/Renderer.cpp index c0de22cf..26eac96c 100644 --- a/src/platform_glfw/Renderer.cpp +++ b/src/platform_glfw/Renderer.cpp @@ -179,7 +179,13 @@ GLuint glhGUIProgram = 0; int nWidth = 0; int nHeight = 0; - +bool sizeChanged = false; +void window_size_callback(GLFWwindow* window, int width, int height) +{ + nWidth = width; + nHeight = height; + sizeChanged = true; +} void MatrixOrthoOffCenterLH( float * pout, float l, float r, float b, float t, float zn, float zf ) { memset( pout, 0, sizeof( float ) * 4 * 4 ); @@ -285,7 +291,7 @@ bool Open( Renderer::Settings * settings ) glfwSetCursorPosCallback( mWindow, cursor_position_callback ); glfwSetMouseButtonCallback( mWindow, mouse_button_callback ); glfwSetScrollCallback( mWindow, scroll_callback ); - + glfwSetWindowSizeCallback(mWindow, window_size_callback); glewExperimental = GL_TRUE; GLenum err = glewInit(); if ( GLEW_OK != err ) @@ -593,6 +599,7 @@ void StartFrame() { glClearColor( 0.08f, 0.18f, 0.18f, 1.0f ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + glViewport(0, 0, nWidth, nHeight); } void EndFrame() { From 3979482ce6ceb73da3e17d927137b0751d899003 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 7 Oct 2024 22:03:00 +0200 Subject: [PATCH 20/29] Dx9/11 resize --- src/platform_w32_dx11/Renderer.cpp | 2 +- src/platform_w32_dx9/Renderer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform_w32_dx11/Renderer.cpp b/src/platform_w32_dx11/Renderer.cpp index 200acfae..6e0eeb92 100644 --- a/src/platform_w32_dx11/Renderer.cpp +++ b/src/platform_w32_dx11/Renderer.cpp @@ -166,7 +166,7 @@ char defaultVertexShader[ 65536 ] = "}\n"; bool run = true; - +bool sizeChanged =false; IDXGISwapChain * pSwapChain = NULL; ID3D11Device * pDevice = NULL; ID3D11DeviceContext * pContext = NULL; diff --git a/src/platform_w32_dx9/Renderer.cpp b/src/platform_w32_dx9/Renderer.cpp index d1277701..f826702d 100644 --- a/src/platform_w32_dx9/Renderer.cpp +++ b/src/platform_w32_dx9/Renderer.cpp @@ -170,7 +170,7 @@ char defaultVertexShader[ 65536 ] = "}\n"; bool run = true; - +bool sizeChanged =false; LPDIRECT3D9 pD3D = NULL; LPDIRECT3DDEVICE9 pDevice = NULL; LPD3DXCONSTANTTABLE pConstantTable = NULL; From ee6dbac7abf50d51695167be3e96f7d51b7d91d8 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 7 Oct 2024 22:49:39 +0200 Subject: [PATCH 21/29] Default config --- package/common/config.json | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/package/common/config.json b/package/common/config.json index dc80335a..ff47ac3b 100644 --- a/package/common/config.json +++ b/package/common/config.json @@ -1,10 +1,18 @@ { "font":{ "file":"ProFontWindows.ttf", - "size":18, + "size":18 + }, + "window":{ + "width":1280, + "height":720, + "fullscreen":false, + "borderless":false }, "rendering":{ "fftSmoothFactor": 0.9, + "fftPreProcessing": true, + "fftPeakNormalization": false }, "textures":{ "texChecker":"textures/checker.png", @@ -12,11 +20,27 @@ "texTex1":"textures/tex1.jpg", "texTex2":"textures/tex2.jpg", "texTex3":"textures/tex3.jpg", - "texTex4":"textures/tex4.jpg", + "texTex4":"textures/tex4.jpg" }, "gui":{ "outputHeight": 100, "opacity": 192, - "texturePreviewWidth": 100, + "texturePreviewWidth": 100 + }, + "network": { + "enabled": true, + "serverURL": "ws://drone.alkama.com:9000/roomname/handle", + "networkMode": "grabber", + "updateInterval": 0.5, + "syncTimeWithSender": true, + "sendMidiControls": false, + "grabMidiControls": false + }, + "ndi":{ + "enabled": false, + "connectionString": "", + "identifier": "hello!", + "frameRate": 60.0, + "progressive": true } } \ No newline at end of file From e4317807e962fb5ea5abd82bfdc4380ce74de498 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Tue, 8 Oct 2024 23:11:14 +0200 Subject: [PATCH 22/29] BugFix parsing network --- package/common/config.json | 15 +++++- src/CommandLineArgs.h | 10 ++-- src/Network.h | 2 +- src/main.cpp | 6 ++- src/platform_common/Network.cpp | 89 +++++++++++++++------------------ 5 files changed, 62 insertions(+), 60 deletions(-) diff --git a/package/common/config.json b/package/common/config.json index ff47ac3b..6c11d31b 100644 --- a/package/common/config.json +++ b/package/common/config.json @@ -22,9 +22,22 @@ "texTex3":"textures/tex3.jpg", "texTex4":"textures/tex4.jpg" }, + "theme": { + "text": "FFEFE1", + "comment": "B362FF", + "number": "FF628C", + "op": "CCEEFF", + "keyword": "FFFF60", + "type": "80BBFF", + "builtin": "FF9D00", + "preprocessor": "FF80FF", + "selection": "C06699CC", + "codername": "80FFFFFF", + "charBackground": "A0000000" + }, "gui":{ "outputHeight": 100, - "opacity": 192, + "opacity": 0, "texturePreviewWidth": 100 }, "network": { diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index a478b196..5a2e7309 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -14,6 +14,7 @@ namespace CommandLineArgs bool skipDialog; const char* configFile; const char* shaderFile; + } Args; @@ -55,16 +56,13 @@ namespace CommandLineArgs if(strcmp(argv[i],"networkMode")==0) { i++; assert_tuple_arg; - if(strcmp(argv[i],"grabber")){ - //Network::config.Mode = Network::NetworkMode::GRABBER; + if(strcmp(argv[i],"grabber") == 0){ continue; } - if(strcmp(argv[i],"sender")){ - //Network::config.Mode = Network::NetworkMode::SENDER; + if(strcmp(argv[i],"sender") == 0){ continue; } - if(strcmp(argv[i],"offline")){ - // Network::config.Mode = Network::NetworkMode::OFFLINE; + if(strcmp(argv[i],"offline") == 0){ continue; } } diff --git a/src/Network.h b/src/Network.h index 3b67c4f2..16211061 100644 --- a/src/Network.h +++ b/src/Network.h @@ -36,7 +36,6 @@ namespace Network { - void PrintConfig(); bool HasNewShader(); bool ReloadShader(); void RecieveShader(size_t size, char* data); @@ -64,5 +63,6 @@ namespace Network { float TimeOffset(); void ResetTimeOffset(float* time); bool IsPinged(); + void ChecktNetwork(); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4cc9162f..4f6efecf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -215,7 +215,7 @@ int main( int argc, const char * argv[] ) if (options.get("rendering").has("fftPeakSmoothing")) FFT::fPeakSmoothing = options.get("rendering").get("fftPeakSmoothing"); } - printf("Preprox %i\n", FFT::bPreProcessing); + if ( options.has( "textures" ) ) { printf( "Loading textures...\n" ); @@ -444,8 +444,10 @@ int main( int argc, const char * argv[] ) while ( !Renderer::WantsToQuit() ) { + + Network::ChecktNetwork(); bool newShader = false; - + float time = Timer::GetTime() / 1000.0; // seconds Network::SyncTimeWithSender(&time); Renderer::StartFrame(); diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 7125c89e..de4ac2f5 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -3,7 +3,6 @@ #include #include #define SHADER_FILENAME(mode) (std::string(mode)+ "_" + RoomName + "_" + NickName + ".glsl") -#define LOG(header,message) printf("[" header "] " message " \n") namespace Network { Network::NetworkConfig config; @@ -13,7 +12,7 @@ namespace Network { struct mg_mgr mgr; struct mg_connection* c; bool done = false; - std::thread* tNetwork; + std::thread* tNetwork = NULL; bool IsNewShader = false; char szShader[65535]; bool connected = false; @@ -33,24 +32,9 @@ namespace Network { void SetNetworkMode(NetworkMode mode) { config.Mode = mode; } - void PrintConfig() { - std::cout << "******************* Network Config ********************" << std::endl; - std::cout << config.Url << std::endl; - if (config.Mode == NetworkMode::OFFLINE) { - std::cout << "OFFLINE" << std::endl; - } - else if (config.Mode == NetworkMode::SENDER) { - std::cout << "SENDER" << std::endl; - } - else if (config.Mode == NetworkMode::GRABBER) { - std::cout << "GRABBER" << std::endl; - } - - } bool HasNewShader() { if (IsNewShader) { IsNewShader = false; - return true; } return false; @@ -103,20 +87,18 @@ namespace Network { } static void fn(struct mg_connection* c, int ev, void* ev_data) { - //printf("ev: %i\n", ev); if (ev == MG_EV_OPEN) { c->is_hexdumping = 0; + connected = true; } else if (ev == MG_EV_ERROR) { // On error, log error message MG_ERROR(("%p %s", c->fd, (char*)ev_data)); + connected = false; } - else if (ev == MG_EV_WS_OPEN) { fprintf(stdout, "[Network]: Connected\n"); connected = true; - // When websocket handshake is successful, send message - // mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT); } else if (config.Mode == SENDER && ev == MG_EV_WS_MSG) { @@ -128,14 +110,12 @@ namespace Network { } else if (ev == MG_EV_WS_MSG && config.Mode == GRABBER) { - // When we get echo response, print it struct mg_ws_message* wm = (struct mg_ws_message*)ev_data; if(wm->data.len>0) { RecieveShader((int)wm->data.len, wm->data.buf); } - // printf("GOT ECHO REPLY: [%.*s]\n", (int)wm->data.len, wm->data.buf); } else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE ) { connected = false; @@ -159,11 +139,19 @@ namespace Network { mg_mgr_free(&mgr); } + void ChecktNetwork() { + if (!IsConnected() && !IsOffline()) { + connected = true; + fprintf(stdout, "[Network]: Starting Thread\n"); + std::thread network(Create); + tNetwork = &network; + tNetwork->detach(); + + } + } void Init() { if(config.Mode != OFFLINE){ - std::thread network(Create); - tNetwork = &network; - tNetwork->detach(); + ChecktNetwork(); } else { fprintf(stdout, "[Network]: OFFLINE Mode, not starting Network loop\n"); @@ -188,20 +176,20 @@ namespace Network { *name = FullUrl.substr(HandlePtr + 1, FullUrl.size() - HandlePtr); } void UpdateShaderFileName(const char** shaderName) { - if (config.Mode == OFFLINE) return; + if (IsOffline()) return; std::string HostPort, RoomName, NickName, filename; Network::SplitUrl(&HostPort, &RoomName, &NickName); - if (config.Mode == SENDER) { + if (IsSender()) { filename = SHADER_FILENAME("sender"); } - else if(config.Mode == GRABBER) { + else if(IsGrabber()) { filename = SHADER_FILENAME("grabber"); } *shaderName = strdup(filename.c_str()); } void UpdateShader(ShaderEditor* mShaderEditor, float shaderTime, std::map *midiRoutes) { - if (Network::config.Mode != Network::NetworkMode::OFFLINE) { // If we arn't offline mode - if (config.Mode == Network::GRABBER && Network::HasNewShader()) { // Grabber mode + if (!IsOffline()) { // If we arn't offline mode + if (IsGrabber() && HasNewShader()) { // Grabber mode int PreviousTopLine = mShaderEditor->WndProc(SCI_GETFIRSTVISIBLELINE, 0, 0); int PreviousTopDocLine = mShaderEditor->WndProc(SCI_DOCLINEFROMVISIBLE, PreviousTopLine, 0); @@ -219,7 +207,7 @@ namespace Network { mg_ws_send(c, 0, 0, WEBSOCKET_OP_BINARY); // Send Ping to Sender to notify received } - else if (config.Mode == Network::SENDER && shaderTime - shaderMessage.shaderTime > 0.1) { + else if (IsSender() && shaderTime - shaderMessage.shaderTime > 0.1) { //std::cout << shaderTime<<"-"<%f\n", (timeOffset)); + // printf("%f\n", (timeOffset)); } } @@ -309,17 +296,17 @@ namespace Network { /* From here are methods for parsing json */ void ParseSyncTimeWithSender(jsonxx::Object* network) { if (!network->has("syncTimeWithSender")) { - LOG("JSON Network Configuration Parsing", "Can't find 'syncTimeWithSender', set to true"); + fprintf(stderr,"[JSON Network Configuration Parsing] " "Can't find 'syncTimeWithSender', set to true\n"); config.syncTimeWithSender = true; return; } - LOG("JSON Network Configuration Parsing", "ParseSyncTimeWithSender"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "ParseSyncTimeWithSender"); config.syncTimeWithSender = network->get("syncTimeWithSender"); printf("%i\n", config.syncTimeWithSender); } void ParseNetworkGrabMidiControls(jsonxx::Object * network) { if (!network->has("grabMidiControls")) { - LOG("JSON Network Configuration Parsing", "Can't find 'grabMidiControls', set to false"); + fprintf(stderr,"[JSON Network Configuration Parsing] " "Can't find 'grabMidiControls', set to false\n"); config.grabMidiControls = false; return; } @@ -327,7 +314,7 @@ namespace Network { } void ParseNetworkSendMidiControls(jsonxx::Object* network) { if (!network->has("sendMidiControls")) { - LOG("JSON Network Configuration Parsing", "Can't find 'sendMidiControls', set to false"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Can't find 'sendMidiControls', set to false\n"); config.sendMidiControls = false; return; } @@ -335,7 +322,7 @@ namespace Network { } void ParseNetworkUpdateInterval(jsonxx::Object* network) { if (!network->has("updateInterval")) { - LOG("JSON Network Configuration Parsing", "Can't find 'updateInterval', set to 0.3"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Can't find 'updateInterval', set to 0.3\n"); config.updateInterval = 0.3f; return; } @@ -344,23 +331,25 @@ namespace Network { } void ParseNetworkMode(jsonxx::Object* network) { if (!network->has("networkMode")) { - LOG("JSON Network Configuration Parsing", "Can't find 'networkMode' Set to OFFLINE"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Can't find 'networkMode' Set to OFFLINE\n"); config.Mode = OFFLINE; return; } const char* mode = network->get("networkMode").c_str(); - bool isSenderMode = strcmp(mode, "sender"); - bool isGrabberMode = strcmp(mode, "grabber"); + bool isSenderMode = strcmp(mode, "sender") == 0; + bool isGrabberMode = strcmp(mode, "grabber") == 0; if (!isSenderMode && !isGrabberMode) { - LOG("JSON Network Configuration Parsing", "networkMode is neither SENDER or GRABBER, fallback config to OFFLINE"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "networkMode is neither SENDER or GRABBER, fallback config to OFFLINE\n"); config.Mode = OFFLINE; return; } if(isSenderMode){ + fprintf(stderr, "[JSON Network Configuration Parsing] " "networkMode is set to SENDER\n"); config.Mode = SENDER; } if(isGrabberMode){ + fprintf(stderr, "[JSON Network Configuration Parsing] " "networkMode is set to GRABBER\n"); config.Mode = GRABBER; } @@ -372,7 +361,7 @@ namespace Network { } void ParseNetworkUrl(jsonxx::Object* network) { if (!network->has("serverURL")) { - LOG("JSON Network Configuration Parsing", "Can't find 'serverURL', set to 'OFFLINE'"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Can't find 'serverURL', set to 'OFFLINE'\n"); config.Mode = OFFLINE; config.Url = ""; return; @@ -386,14 +375,14 @@ namespace Network { void ParseNetworkEnabled(jsonxx::Object* network) { if (!network->has("enabled")) { - LOG("JSON Network Configuration Parsing", "Can't find 'enabled', set to 'OFFLINE'"); + fprintf(stderr,"[JSON Network Configuration Parsing] " "Can't find 'enabled', set to 'OFFLINE'\n"); config.Mode = OFFLINE; config.Url = ""; return; } if (!network->get("enabled")) { - LOG("JSON Network Configuration Parsing", "Set to 'OFFLINE'"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Set to 'OFFLINE'\n"); config.Mode = OFFLINE; config.Url = ""; // As we can activate this on setup dialog, let's try to get serverURL @@ -418,9 +407,9 @@ namespace Network { */ void ParseSettings(jsonxx::Object* options) { - LOG("JSON Network Configuration Parsing", "Parsing network configuration data from json"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Parsing network configuration data from json\n"); if (!options->has("network")) { - LOG("JSON Network Configuration Parsing", "Can't find 'network' block, set to 'OFFLINE'"); + fprintf(stderr, "[JSON Network Configuration Parsing] " "Can't find 'network' block, set to 'OFFLINE'\n"); config.Mode = OFFLINE; config.Url = ""; return; From 49244362a0ad9b9de39edc5dba93b76854173cc5 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sat, 12 Oct 2024 23:16:14 +0200 Subject: [PATCH 23/29] CommandArgs Override --- src/CommandLineArgs.h | 48 +++++++++++++++++++++++++++---------------- src/main.cpp | 14 +++++++++---- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index 5a2e7309..c8184ee6 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -10,59 +10,78 @@ namespace CommandLineArgs { - struct { + bool skipDialog; const char* configFile; const char* shaderFile; - - } Args; - + bool optServerURL = false; + char serverURL[512]; + + bool optNetworkMode = false; + Network::NetworkMode networkMode; + + void replace() { + + if (optServerURL) { + Network::SetUrl(serverURL); + } + + if (optNetworkMode) { + Network::SetNetworkMode(networkMode); + } + } void parse_args(int argc,const char *argv[]) { - Args.skipDialog = false; - Args.configFile = "config.json"; - Args.shaderFile = "shader.glsl"; + skipDialog = false; + configFile = "config.json"; + shaderFile = "shader.glsl"; //Network::config.Mode = Network::NetworkMode::OFFLINE; //Network::config.Url = "ws://drone.alkama.com:9000/roomname/username"; for(size_t i=0;i 1 ) { - configFile = CommandLineArgs::Args.configFile; + configFile = CommandLineArgs::configFile; printf( "Loading config file '%s'...\n", configFile ); } else @@ -141,13 +142,18 @@ int main( int argc, const char * argv[] ) settings.sRenderer.borderless = options.get( "window" ).get( "borderless" ); } - if ( !skipSetupDialog ) + + if ( !skipSetupDialog && !CommandLineArgs::skipDialog) { if ( !SetupDialog::Open( &settings ) ) { return -1; } } + else { + printf("lol"); + CommandLineArgs::replace(); + } #endif if ( !Renderer::Open( &settings.sRenderer ) ) From 76b797d1102e49b6259e34c8c0e4087811be60ec Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 13 Oct 2024 00:28:25 +0200 Subject: [PATCH 24/29] borderless --- src/CommandLineArgs.h | 14 +++++++++++++- src/main.cpp | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index c8184ee6..619a0671 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -20,8 +20,11 @@ namespace CommandLineArgs bool optNetworkMode = false; Network::NetworkMode networkMode; + + bool optBorderless = false; + bool borderless = false; - void replace() { + void replace(Renderer::Settings* settings) { if (optServerURL) { Network::SetUrl(serverURL); @@ -30,6 +33,9 @@ namespace CommandLineArgs if (optNetworkMode) { Network::SetNetworkMode(networkMode); } + if (optBorderless) { + settings->borderless = borderless; + } } void parse_args(int argc,const char *argv[]) { skipDialog = false; @@ -59,6 +65,12 @@ namespace CommandLineArgs continue; } + if (strcmp(argv[i], "borderless") == 0) { + borderless = true; + optBorderless = true; + continue; + } + if(strcmp(argv[i],"serverURL")==0) { i++; assert_tuple_arg; diff --git a/src/main.cpp b/src/main.cpp index 44431e79..2e3d7266 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -152,7 +152,7 @@ int main( int argc, const char * argv[] ) } else { printf("lol"); - CommandLineArgs::replace(); + CommandLineArgs::replace(&settings.sRenderer); } #endif From 8c9a0ea26bb6d9dc38e6525c6a0c93c62147b0a1 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 13 Oct 2024 18:15:47 +0200 Subject: [PATCH 25/29] COmmandLineArgs parsing compatible with launcher.exe --- src/CommandLineArgs.h | 40 +++++++++++++++++---------------- src/main.cpp | 1 - src/platform_common/Network.cpp | 17 +++++++------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/CommandLineArgs.h b/src/CommandLineArgs.h index 619a0671..c7f538f0 100644 --- a/src/CommandLineArgs.h +++ b/src/CommandLineArgs.h @@ -44,55 +44,57 @@ namespace CommandLineArgs //Network::config.Mode = Network::NetworkMode::OFFLINE; //Network::config.Url = "ws://drone.alkama.com:9000/roomname/username"; - for(size_t i=0;i Date: Mon, 14 Oct 2024 19:01:02 +0200 Subject: [PATCH 26/29] Default values --- package/common/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/common/config.json b/package/common/config.json index 6c11d31b..34e8c6af 100644 --- a/package/common/config.json +++ b/package/common/config.json @@ -41,9 +41,9 @@ "texturePreviewWidth": 100 }, "network": { - "enabled": true, + "enabled": false, "serverURL": "ws://drone.alkama.com:9000/roomname/handle", - "networkMode": "grabber", + "networkMode": "sender", "updateInterval": 0.5, "syncTimeWithSender": true, "sendMidiControls": false, From bb900fa87bd49e5a42dfb9070460cfa690b34ec7 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Tue, 15 Oct 2024 19:03:37 +0200 Subject: [PATCH 27/29] MacOSX:Code Entitlements --- CMakeLists.txt | 1 + data/macosx/entitlements.plist | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 data/macosx/entitlements.plist diff --git a/CMakeLists.txt b/CMakeLists.txt index b472f324..6e25b2ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -469,6 +469,7 @@ if (WIN32) # MacOS elseif (APPLE) set_target_properties(${BZC_EXE_NAME} PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_BUNDLE_NAME "Bonzomatic" MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/data/macosx/MacOSXBundleInfo.plist.in) + set_target_properties(${BZC_EXE_NAME} PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_SOURCE_DIR}/data/macosx/entitlements.plist) if (BONZOMATIC_TOUCHBAR) target_compile_definitions(${BZC_EXE_NAME} PUBLIC -DBONZOMATIC_ENABLE_TOUCHBAR) endif () diff --git a/data/macosx/entitlements.plist b/data/macosx/entitlements.plist new file mode 100644 index 00000000..d98266ee --- /dev/null +++ b/data/macosx/entitlements.plist @@ -0,0 +1,18 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.device.audio-input + + com.apple.security.personal-information.addressbook + + com.apple.security.get-task-allow + + + \ No newline at end of file From f80a5e306b89d37b2eac5cf8ac2d9558f75543aa Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Sun, 20 Oct 2024 23:39:28 +0200 Subject: [PATCH 28/29] AudioSelect --- src/FFT.h | 1 + src/main.cpp | 3 +++ src/platform_common/Network.cpp | 24 +++++++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/FFT.h b/src/FFT.h index 03466f2f..9c70abbd 100644 --- a/src/FFT.h +++ b/src/FFT.h @@ -8,6 +8,7 @@ struct Settings { bool bUseRecordingDevice; void * pDeviceID; + const char * sCaptureDeviceSearchString; }; typedef void ( *FFT_ENUMERATE_FUNC )( const bool bIsCaptureDevice, const char * szDeviceName, void * pDeviceID, void * pUserContext ); diff --git a/src/main.cpp b/src/main.cpp index 36ce1e04..42b486fc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,10 +114,13 @@ int main( int argc, const char * argv[] ) SetupDialog::SETTINGS settings; settings.sFFT.bUseRecordingDevice = true; settings.sFFT.pDeviceID = NULL; + settings.sFFT.sCaptureDeviceSearchString = ""; if ( options.has( "audio" ) ) { if ( options.get( "audio" ).has( "useInput" ) ) settings.sFFT.bUseRecordingDevice = options.get( "audio" ).get( "useInput" ); + if (options.get("audio").has("captureDeviceSearchString")) + settings.sFFT.sCaptureDeviceSearchString = options.get("audio").get("captureDeviceSearchString").c_str(); } settings.sRenderer.bVsync = false; diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index 54012a49..c1834d09 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -179,7 +179,7 @@ namespace Network { void UpdateShaderFileName(const char** shaderName) { if (IsOffline()) return; std::string filename; - + Network::SplitUrl(&HostPort, &RoomName, &NickName); if (IsSender()) { filename = SHADER_FILENAME("sender"); } @@ -205,6 +205,28 @@ namespace Network { //mShaderEditor.WndProc(SCI_SETFIRSTVISIBLELINE, NewMessage.FirstVisibleLine, 0); mShaderEditor->WndProc(SCI_SCROLLCARET, 0, 0); //} + + /*if (config.grabMidiControls && Data.has("Parameters")) { + const std::map& shadParams = Data.get("Parameters").kv_map(); + for (auto it = shadParams.begin(); it != shadParams.end(); it++) + { + float goalValue = it->second->number_value_; + auto cache = networkParamCache.find(it->first); + if (cache == networkParamCache.end()) { + ShaderParamCache newCache; + newCache.lastValue = goalValue; + newCache.currentValue = goalValue; + newCache.goalValue = goalValue; + newCache.duration = duration; + networkParamCache[it->first] = newCache; + cache = networkParamCache.find(it->first); + } + ShaderParamCache& cur = cache->second; + cur.lastValue = cur.currentValue; + cur.goalValue = goalValue; + cur.duration = duration; + } + }*/ mg_ws_send(c, 0, 0, WEBSOCKET_OP_BINARY); // Send Ping to Sender to notify received } From 9b89fd185d27e7a70392f3443bacc11dfb436f86 Mon Sep 17 00:00:00 2001 From: Matthieu Totet Date: Mon, 9 Dec 2024 22:02:47 +0100 Subject: [PATCH 29/29] network enhancement - retry on disconnect ? --- src/Network.h | 2 +- src/main.cpp | 2 +- src/platform_common/Network.cpp | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Network.h b/src/Network.h index 16211061..eada9ebb 100644 --- a/src/Network.h +++ b/src/Network.h @@ -63,6 +63,6 @@ namespace Network { float TimeOffset(); void ResetTimeOffset(float* time); bool IsPinged(); - void ChecktNetwork(); + void CheckNetwork(); } #endif // BONZOMATIC_NETWORK_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 42b486fc..36851b43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -453,7 +453,7 @@ int main( int argc, const char * argv[] ) while ( !Renderer::WantsToQuit() ) { - Network::ChecktNetwork(); + //Network::CheckNetwork(); bool newShader = false; float time = Timer::GetTime() / 1000.0; // seconds diff --git a/src/platform_common/Network.cpp b/src/platform_common/Network.cpp index c1834d09..442fb685 100644 --- a/src/platform_common/Network.cpp +++ b/src/platform_common/Network.cpp @@ -2,6 +2,7 @@ #include "MIDI.h" #include #include +#include #define SHADER_FILENAME(mode) (std::string(mode)+ "_" + RoomName + "_" + NickName + ".glsl") namespace Network { @@ -13,6 +14,7 @@ namespace Network { struct mg_connection* c; bool done = false; std::thread* tNetwork = NULL; + std::mutex network_interface_access_mutex ; bool IsNewShader = false; char szShader[65535]; bool connected = false; @@ -128,7 +130,7 @@ namespace Network { } void Create() { fprintf(stdout, "[Network]: Try to connect to %s\n", config.Url); - mg_mgr_init(&mgr); + c = mg_ws_connect(&mgr, config.Url, fn, &done, NULL); if (c == NULL) { fprintf(stderr, "Invalid address\n"); @@ -140,7 +142,7 @@ namespace Network { mg_mgr_free(&mgr); } - void ChecktNetwork() { + void CheckNetwork() { if (!IsConnected() && !IsOffline()) { connected = true; fprintf(stdout, "[Network]: Starting Thread\n"); @@ -150,9 +152,17 @@ namespace Network { } } + void timer_fn(void* arg) { + + if (c == NULL) { + c = mg_ws_connect(&mgr, config.Url, fn, &done, NULL); + } + } void Init() { + mg_mgr_init(&mgr); + mg_timer_add(&mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr); if(config.Mode != OFFLINE){ - ChecktNetwork(); + CheckNetwork(); } else { fprintf(stdout, "[Network]: OFFLINE Mode, not starting Network loop\n");