From 18a2823e0833862e895d93521df6c4ee580015ff Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Sat, 14 Mar 2026 23:08:12 +0200 Subject: [PATCH 01/12] Initial commit. --- Firmware/AUTH_MQTT/MQTT_Gateway.ino | 15 + Firmware/AUTH_MQTT/README.md | 97 + Firmware/AUTH_MQTT/WORKFlow.md | 0 Firmware/AUTH_MQTT/gateway_config.h | 22 + Firmware/AUTH_MQTT/main.py | 333 + Firmware/AUTH_MQTT/mqtt_test.py | 304 + Firmware/AUTH_MQTT/src/chacha20.c | 421 + Firmware/AUTH_MQTT/src/chacha20.h | 37 + Firmware/AUTH_MQTT/src/dashboard.h | 6 + Firmware/AUTH_MQTT/src/gateway.cpp | 15 + Firmware/AUTH_MQTT/src/gateway.h | 8 + Firmware/AUTH_MQTT/src/gateway_core.cpp | 813 + Firmware/AUTH_MQTT/src/gateway_core.h | 69 + Firmware/AUTH_MQTT/src/gateway_dashboard.cpp | 314 + Firmware/AUTH_MQTT/src/gateway_dashboard.h | 24 + Firmware/AUTH_MQTT/src/gateway_private.h | 47 + Firmware/AUTH_MQTT/src/gateway_utils.cpp | 60 + Firmware/AUTH_MQTT/src/gateway_utils.h | 26 + Firmware/AUTH_MQTT/src/mongoose.c | 28189 +++++++++++++++++ Firmware/AUTH_MQTT/src/mongoose.h | 4038 +++ Firmware/AUTH_MQTT/src/mongoose_config.h | 30 + Firmware/AUTH_MQTT/src/x25519.c | 230 + Firmware/AUTH_MQTT/src/x25519.h | 25 + enclosure/recess.zip | Bin 0 -> 129275 bytes 24 files changed, 35123 insertions(+) create mode 100755 Firmware/AUTH_MQTT/MQTT_Gateway.ino create mode 100755 Firmware/AUTH_MQTT/README.md create mode 100644 Firmware/AUTH_MQTT/WORKFlow.md create mode 100644 Firmware/AUTH_MQTT/gateway_config.h create mode 100644 Firmware/AUTH_MQTT/main.py create mode 100644 Firmware/AUTH_MQTT/mqtt_test.py create mode 100755 Firmware/AUTH_MQTT/src/chacha20.c create mode 100755 Firmware/AUTH_MQTT/src/chacha20.h create mode 100755 Firmware/AUTH_MQTT/src/dashboard.h create mode 100644 Firmware/AUTH_MQTT/src/gateway.cpp create mode 100755 Firmware/AUTH_MQTT/src/gateway.h create mode 100644 Firmware/AUTH_MQTT/src/gateway_core.cpp create mode 100644 Firmware/AUTH_MQTT/src/gateway_core.h create mode 100644 Firmware/AUTH_MQTT/src/gateway_dashboard.cpp create mode 100644 Firmware/AUTH_MQTT/src/gateway_dashboard.h create mode 100644 Firmware/AUTH_MQTT/src/gateway_private.h create mode 100644 Firmware/AUTH_MQTT/src/gateway_utils.cpp create mode 100644 Firmware/AUTH_MQTT/src/gateway_utils.h create mode 100755 Firmware/AUTH_MQTT/src/mongoose.c create mode 100755 Firmware/AUTH_MQTT/src/mongoose.h create mode 100755 Firmware/AUTH_MQTT/src/mongoose_config.h create mode 100755 Firmware/AUTH_MQTT/src/x25519.c create mode 100755 Firmware/AUTH_MQTT/src/x25519.h create mode 100644 enclosure/recess.zip diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway.ino b/Firmware/AUTH_MQTT/MQTT_Gateway.ino new file mode 100755 index 00000000..cbe65f79 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway.ino @@ -0,0 +1,15 @@ +#include "src/gateway.h" + +void setup() +{ +gateway_init(); + +} + + +void loop() +{ + +gateway_poll(); + +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/README.md b/Firmware/AUTH_MQTT/README.md new file mode 100755 index 00000000..c1c39c6d --- /dev/null +++ b/Firmware/AUTH_MQTT/README.md @@ -0,0 +1,97 @@ +# MQTT_Gateway +### An implementation for adding authentications and authorization for the MQTT messages without depend on the broker side + + +## Overview + +This project is an IoT gateway system with two components: + +1. **ESP32 Gateway** (`MQTT_Gateway.ino`) — Runs on an ESP32 microcontroller. Hosts a web dashboard, manages device connections over MQTT, and dispatches JSON-RPC calls using the Mongoose networking library. +2. **Sensor Simulator** — A Python script that simulates IoT sensor devices for testing. Connects to the same MQTT broker and goes through the full authentication flow. + +**Architecture:** + +``` +┌──────────────┐ MQTT broker ┌──────────────────┐ +│ ESP32 │◄─────────────────────────────────────►│ Sensor Device │ +│ Gateway │ jrpc/connect/request │ (Python or real │ +│ │ jrpc/gateway/rx │ ESP32 sensor) │ +│ Port 80: │ jrpc/devices/{id}/rx └──────────────────┘ +│ Dashboard │ +│ + WebSocket │◄──── Browser (real-time dashboard) +└──────────────┘ +``` + + +``` +1. Device generates X25519 keypair, publishes PUBLIC key + in connect request to jrpc/connect/request + │ + ▼ +2. Gateway registers device as PENDING, stores device pubkey + (appears in dashboard "Pending Approval") + │ + ▼ +3. Admin clicks "Approve" on dashboard + │ + ▼ +4. Gateway generates its own ephemeral X25519 keypair + Gateway computes: shared = X25519(gateway_prv, device_pub) + Gateway derives: hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key", shared) + Gateway sends its PUBLIC key to device + Device status → APPROVED + │ + ▼ +5. Device receives gateway pubkey, computes same shared secret: + shared = X25519(device_prv, gateway_pub) ← identical result + hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key-v1", shared) + Both sides now have the same HMAC key without ever transmitting it + │ + ▼ +6. Device signs each RPC with HMAC-SHA256(hmac_key, canonical_msg) + canonical_msg = "device_id\nmethod\nrpc_id\nnonce\ntimestamp" + Nonce is monotonically increasing (prevents replay attacks) + Device status → AUTHORIZED on first valid HMAC + │ + ▼ +7. If no messages for 60s → device marked OFFLINE + Admin can Revoke access or Remove device at any time + Revoke wipes the HMAC key — device must re-do full ECDH +``` +### Pre-Shared Key (PSK) — Device Identity Verification + +Since the gateway connects to devices via an online MQTT broker, a previously-approved device that reconnects with new keys cannot be automatically distinguished from an attacker spoofing the same device ID. **PSK** solves this by requiring proof-of-identity on every reconnection. + +**How it works:** + +1. Admin Enter a PSK string of the Device pending to be approved (e.g., `"my-device-secret"`) in the dashboard when first approving a device +2. The device operator configures the same PSK on the physical device +3. Both sides hash the PSK: `SHA-256("my-device-secret")` +4. On every connection request, the device computes: + ``` + psk_proof = HMAC-SHA256(SHA256(psk), device_id + pubkey_hex) + ``` +5. Gateway computes the same HMAC and verifies with constant-time comparison +6. If verification fails, the connection is rejected + +**Security properties of PSK:** +- PSK is **never transmitted** over MQTT — only a cryptographic proof is sent +- Proof is **bound to the public key** — cannot be replayed with a different keypair +- Proof is **bound to the device ID** — cannot be reused for a different device +- PSK is wiped on revoke — a new PSK must be set when re-approving + +### Payload Encryption (ChaCha20-Poly1305) + +All MQTT payloads are encrypted with ChaCha20-Poly1305 using the **Encrypt-then-MAC** pattern. This prevents eavesdroppers on the MQTT broker from reading sensor data, method names, or any message content. + +**Crypto providers:** Cryptographic primitives are provided by: +- **X25519 ECDH** — standalone implementation in `x25519.h` (extracted from Mongoose, public domain) +- **ChaCha20-Poly1305** — standalone implementation in `chacha20.h`/`chacha20.c` (extracted from Mongoose, public domain) +- **SHA-256 / HMAC-SHA256** — Mongoose (always compiled, not gated by TLS setting) +- **Random** — Mongoose's `mg_random()` which uses `esp_random()` on ESP32 + +**Key derivation** (from the same ECDH shared secret): +``` +hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key", shared_secret) +enc_key = HMAC-SHA256("esp32-dashboard-enc-key", shared_secret) +``` diff --git a/Firmware/AUTH_MQTT/WORKFlow.md b/Firmware/AUTH_MQTT/WORKFlow.md new file mode 100644 index 00000000..e69de29b diff --git a/Firmware/AUTH_MQTT/gateway_config.h b/Firmware/AUTH_MQTT/gateway_config.h new file mode 100644 index 00000000..b9380238 --- /dev/null +++ b/Firmware/AUTH_MQTT/gateway_config.h @@ -0,0 +1,22 @@ +#ifndef __GATEWAY_CONFIG__H_ +#define __GATEWAY_CONFIG__H_ + +#define MG_DEBUG 1 + +// ── WiFi ────────────────────────────────────── +#define GW_WIFI_SSID "WE_NET" +#define GW_WIFI_PASSWORD "AymanSH@2025_**" + +// ── MQTT ────────────────────────────────────── +#define GW_MQTT_BROKER "broker.hivemq.com" +#define GW_MQTT_PORT 1883 +#define GW_GATEWAY_ID "gateway_01" + +// ── MQTT Topics ─────────────────────────────── +#define GW_T_GATEWAY_CONNECT "jrpc/gateway/connect" +#define GW_T_GATEWAY_RX "jrpc/gateway/rx" + +// ── Timing ──────────────────────────────────── +#define GW_MQTT_RECONNECT_MS 3000UL + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/main.py b/Firmware/AUTH_MQTT/main.py new file mode 100644 index 00000000..23fbe502 --- /dev/null +++ b/Firmware/AUTH_MQTT/main.py @@ -0,0 +1,333 @@ +# main.py +# Fixed: moved blocking wait OUT of the MQTT callback thread + +import json +import time +import random +import argparse +import threading +import hashlib +import hmac +import struct +from datetime import datetime + +try: + import paho.mqtt.client as mqtt +except ImportError: + print("Install paho-mqtt: pip install paho-mqtt") + exit(1) + +try: + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 +except ImportError: + print("Install cryptography: pip install cryptography") + exit(1) + +# ────────────────────────────────────────────── +# Configuration +# ────────────────────────────────────────────── +DEFAULT_BROKER = "broker.hivemq.com" +DEFAULT_PORT = 1883 +DEFAULT_DEVICE_ID = "device_01" +DEFAULT_NAME = "Test ping device 01" +DEFAULT_TYPE = "TestDevice" +DEFAULT_PSK = "test123" + +# Topics (must match ESP32 gateway) +T_GATEWAY_RX = "jrpc/gateway/rx" +T_GATEWAY_CONNECT = "jrpc/gateway/connect" + + +class SensorDevice: + DISCONNECTED = "disconnected" + CONNECTING = "connecting" + CONNECTED = "connected" + + def __init__(self, device_id, name, device_type, broker, port, psk=None): + self.device_id = device_id + self.name = name + self.device_type = device_type + self.broker = broker + self.port = port + self.rpc_id = 0 + self.pending_rpc = {} + + self.state = self.DISCONNECTED + self.my_topic = f"jrpc/devices/{self.device_id}/rx" + + if psk is None: + psk = DEFAULT_PSK + self.enc_key = hashlib.sha256(psk.encode('utf-8')).digest() + self.counter = 0 + + self.client = mqtt.Client( + callback_api_version=mqtt.CallbackAPIVersion.VERSION1, + client_id=f"{self.device_id}_{random.randint(0, 0xFFFF):04x}", + protocol=mqtt.MQTTv311, + ) + self.client.on_connect = self._on_connect + self.client.on_message = self._on_message + self.client.on_disconnect = self._on_disconnect + + # Signalled by _handle_encrypted when gateway replies to connect + self.connect_event = threading.Event() + self.connect_ok = False + + # ────────────────────────────────────────── + # MQTT Callbacks (NEVER block here) + # ────────────────────────────────────────── + def _on_connect(self, client, userdata, flags, rc): + if rc != 0: + self._log(f"MQTT connect failed: rc={rc}") + return + self._log(f"MQTT connected to {self.broker}") + client.subscribe(self.my_topic, qos=1) + self._log(f"Subscribed to {self.my_topic}") + # FIX: only *send* the request here — do NOT wait inside the callback. + self._send_connect_request() + + def _on_disconnect(self, client, userdata, rc): + self._log(f"MQTT disconnected (rc={rc})") + self.state = self.DISCONNECTED + + def _on_message(self, client, userdata, msg): + try: + payload = json.loads(msg.payload.decode()) + except json.JSONDecodeError: + return + if "device_id" in payload and "nonce" in payload and "ciphertext" in payload: + self._handle_encrypted(payload) + + # ────────────────────────────────────────── + # Encryption helpers + # ────────────────────────────────────────── + def _build_nonce(self): + """12-byte nonce: 4-byte big-endian counter + 8-byte big-endian timestamp.""" + self.counter += 1 + return struct.pack('>I', self.counter) + struct.pack('>Q', int(time.time())) + + def _build_auth(self, method: str, timestamp: int) -> str: + """HMAC-SHA256 signature over '::'. + + Mirrors gw_verify_auth() in gateway_utils.cpp. The key is the + derived encryption key (SHA-256 of PSK), not the raw PSK. + """ + msg = f"{self.device_id}:{timestamp}:{method}".encode() + return hmac.new(self.enc_key, msg, hashlib.sha256).hexdigest() + + def _encrypt(self, plaintext: bytes): + """ChaCha20-Poly1305 encrypt. AAD = device_id (matches gateway sendEncrypted).""" + nonce = self._build_nonce() + aad = self.device_id.encode('utf-8') + ct = ChaCha20Poly1305(self.enc_key).encrypt(nonce, plaintext, aad) + return nonce, ct + + def _decrypt(self, nonce: bytes, ciphertext: bytes): + """ChaCha20-Poly1305 decrypt. AAD = device_id (matches gateway sendEncrypted).""" + aad = self.device_id.encode('utf-8') + try: + return ChaCha20Poly1305(self.enc_key).decrypt(nonce, ciphertext, aad) + except Exception as e: + self._log(f"Decryption failed: {e}") + return None + + # ────────────────────────────────────────── + # Connect procedure + # ────────────────────────────────────────── + def _send_connect_request(self): + """Send the encrypted connect envelope — returns immediately.""" + # FIX: reset event so a reconnect attempt doesn't use a stale result + self.connect_event.clear() + self.connect_ok = False + self.state = self.CONNECTING + + method = "request_connect" + timestamp = int(time.time()) + inner = { + "device_name": self.name, + "device_type": self.device_type, + "method": method, + "timestamp": timestamp, + "auth": self._build_auth(method, timestamp), + } + plaintext = json.dumps(inner, separators=(',', ':')).encode() + nonce, ct = self._encrypt(plaintext) + + outer = { + "device_id": self.device_id, + "nonce": nonce.hex(), + "ciphertext": ct.hex(), + } + self.client.publish(T_GATEWAY_CONNECT, + json.dumps(outer, separators=(',', ':')), qos=1) + self._log("Connect request sent — waiting for admin approval in dashboard...") + + def wait_for_approval(self, timeout=120.0) -> bool: + """Block the *caller* (main thread) until the gateway approves or times out.""" + if self.connect_event.wait(timeout): + if self.connect_ok: + self.state = self.CONNECTED + self._log("Approved by gateway ✅") + return True + else: + self.state = self.DISCONNECTED + self._log("Rejected by gateway ❌") + return False + self.state = self.DISCONNECTED + self._log("Approval timeout ⏰") + return False + + def _handle_encrypted(self, payload): + if payload.get("device_id") != self.device_id: + return + nonce_hex = payload.get("nonce") + cipher_hex = payload.get("ciphertext") + if not nonce_hex or not cipher_hex: + return + + plain = self._decrypt(bytes.fromhex(nonce_hex), bytes.fromhex(cipher_hex)) + if plain is None: + return + + try: + msg = json.loads(plain.decode()) + except json.JSONDecodeError: + return + + if msg.get("method") == "connect.response": + status = msg.get("params", {}).get("status") + self.connect_ok = (status == "approved") + self.connect_event.set() # unblocks wait_for_approval() + elif "result" in msg: + self._handle_result(msg) + elif "error" in msg: + self._handle_error(msg) + + # ────────────────────────────────────────── + # RPC (after approved) + # ────────────────────────────────────────── + def send_rpc(self, method, params): + if self.state != self.CONNECTED: + self._log("Not connected — cannot send RPC.") + return + rpc_id = self._next_id() + timestamp = int(time.time()) + inner = { + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": rpc_id, + "timestamp": timestamp, + "auth": self._build_auth(method, timestamp), + } + plaintext = json.dumps(inner, separators=(',', ':')).encode() + nonce, ct = self._encrypt(plaintext) + outer = { + "device_id": self.device_id, + "nonce": nonce.hex(), + "ciphertext": ct.hex(), + } + self.pending_rpc[rpc_id] = {"method": method, "ts": time.time()} + self.client.publish(T_GATEWAY_RX, + json.dumps(outer, separators=(',', ':')), qos=1) + + def send_ping(self): + self._log("Pinging gateway...") + self.send_rpc("ping", {}) + + def _handle_result(self, payload): + rpc_id = payload.get("id", -1) + result = payload.get("result", {}) + info = self.pending_rpc.pop(rpc_id, None) + method = info["method"] if info else "?" + rtt = (time.time() - info["ts"]) * 1000 if info else 0 + if method == "ping": + uptime = result.get("uptime_ms", 0) / 1000 + self._log(f"Pong! Gateway uptime: {uptime:.0f}s RTT: {rtt:.0f}ms") + else: + self._log(f"[RESULT] {method}: {json.dumps(result)}") + + def _handle_error(self, payload): + rpc_id = payload.get("id", -1) + error = payload.get("error", {}) + info = self.pending_rpc.pop(rpc_id, None) + method = info["method"] if info else "?" + self._log(f"ERROR [{method}] code={error.get('code','?')}: {error.get('message','?')}") + + def _next_id(self): + self.rpc_id += 1 + return self.rpc_id + + def _log(self, msg): + ts = datetime.now().strftime("%H:%M:%S") + print(f"[{ts}] [{self.device_id}] {msg}") + + def stop(self): + self.client.disconnect() + self.client.loop_stop() + self._log("Disconnected.") + + +# ────────────────────────────────────────────── +# Interactive Mode +# ────────────────────────────────────────────── +def interactive_mode(dev: SensorDevice): + dev.client.connect(dev.broker, dev.port, keepalive=60) + dev.client.loop_start() # network thread — callbacks run here, must not block + + # Wait for admin to approve in the dashboard (blocks main thread, not MQTT thread) + print("Waiting for admin approval via dashboard (timeout 120s)...") + if not dev.wait_for_approval(timeout=120.0): + print("Could not authenticate. Check PSK and gateway.") + dev.stop() + return + + print("\n+--- Commands ---------------------+") + print("| ping -- Ping gateway |") + print("| quit -- Exit |") + print("+-----------------------------------+\n") + + try: + while True: + cmd = input(f"[{dev.device_id}|{dev.state}] > ").strip().lower() + if cmd == "ping": + dev.send_ping() + elif cmd in ("quit", "exit", "q"): + break + elif cmd == "": + continue + else: + print(f" Unknown: '{cmd}'") + time.sleep(0.3) + except (KeyboardInterrupt, EOFError): + pass + + dev.stop() + + +# ────────────────────────────────────────────── +# CLI Entry Point +# ────────────────────────────────────────────── +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Device Simulator — ChaCha20-Poly1305 encrypted MQTT", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python main.py --psk test123 + python main.py --device-id sensor_01 --name "Temp sensor" --type sensor --psk secret + """, + ) + parser.add_argument("--device-id", default=DEFAULT_DEVICE_ID) + parser.add_argument("--name", default=DEFAULT_NAME) + parser.add_argument("--type", default=DEFAULT_TYPE, dest="device_type") + parser.add_argument("--psk", default=DEFAULT_PSK) + parser.add_argument("--broker", default=DEFAULT_BROKER) + parser.add_argument("--port", type=int, default=DEFAULT_PORT) + args = parser.parse_args() + + dev = SensorDevice( + args.device_id, args.name, args.device_type, + args.broker, args.port, args.psk, + ) + interactive_mode(dev) \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/mqtt_test.py b/Firmware/AUTH_MQTT/mqtt_test.py new file mode 100644 index 00000000..3a547894 --- /dev/null +++ b/Firmware/AUTH_MQTT/mqtt_test.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 +""" +MQTT Gateway Test Script +======================== +Tests the ESP32 MQTT gateway by simulating a device. + +Protocol recap (from gateway_core.cpp): + Connect topic : jrpc/gateway/connect + RX topic : jrpc/gateway/rx + Response topic : jrpc/devices//rx + +Connect payload : {"device_id":"...","nonce":"<24-hex>","ciphertext":""} + Ciphertext : ChaCha20-Poly1305( key=SHA256(PSK), nonce=12B, aad=b"", + plain={"device_name":"...","device_type":"..."} ) +RX payload same shape but plain is a JSON-RPC 2.0 object. + +Usage: + pip install paho-mqtt cryptography + python mqtt_gateway_test.py [--psk MY_PSK] [--device-id my_sensor] +""" + +import argparse +import hashlib +import json +import os +import struct +import sys +import time +import threading + +try: + import paho.mqtt.client as mqtt +except ImportError: + sys.exit("❌ Missing paho-mqtt — run: pip install paho-mqtt") + +try: + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 +except ImportError: + sys.exit("❌ Missing cryptography — run: pip install cryptography") + +# ────────────────────────────────────────────── +# Config (matches gateway_config.h) +# ────────────────────────────────────────────── +BROKER = "broker.hivemq.com" +PORT = 1883 +T_CONNECT = "jrpc/gateway/connect" +T_RX = "jrpc/gateway/rx" +T_DEV_RX = "jrpc/devices/{device_id}/rx" + +TIMEOUT_SEC = 10 # seconds to wait for a response + + +# ────────────────────────────────────────────── +# Crypto helpers (mirror gateway's C code) +# ────────────────────────────────────────────── +def derive_key(psk: str) -> bytes: + """SHA-256 of the PSK string — matches gw_psk_to_key().""" + return hashlib.sha256(psk.encode()).digest() + + +def make_nonce(counter: int) -> bytes: + """12-byte nonce: 4-byte big-endian counter + 8-byte big-endian timestamp.""" + ts = int(time.time()) + return struct.pack(">I", counter) + struct.pack(">Q", ts) + + +def encrypt(key: bytes, nonce: bytes, plaintext: bytes) -> bytes: + """ChaCha20-Poly1305 with no AAD — matches gateway's chacha20_poly1305_encrypt.""" + chacha = ChaCha20Poly1305(key) + return chacha.encrypt(nonce, plaintext, b"") # aad = b"" + + +def decrypt(key: bytes, nonce: bytes, ciphertext_with_tag: bytes) -> bytes: + """ChaCha20-Poly1305 decrypt — raises InvalidTag on auth failure.""" + chacha = ChaCha20Poly1305(key) + return chacha.decrypt(nonce, ciphertext_with_tag, b"") + + +# ────────────────────────────────────────────── +# Test class +# ────────────────────────────────────────────── +class GatewayTester: + def __init__(self, device_id: str, psk: str): + self.device_id = device_id + self.psk = psk + self.key = derive_key(psk) + self.counter = 1 + + self._responses = [] + self._connected = threading.Event() + self._got_resp = threading.Event() + self._subscribed_ok = False + + self.client = mqtt.Client(client_id=f"gw_tester_{os.getpid()}") + self.client.on_connect = self._on_connect + self.client.on_disconnect = self._on_disconnect + self.client.on_subscribe = self._on_subscribe + self.client.on_message = self._on_message + + # ── MQTT callbacks ────────────────────────────────── + def _on_connect(self, client, userdata, flags, rc): + if rc == 0: + print(f" ✅ Connected to {BROKER}:{PORT}") + resp_topic = T_DEV_RX.format(device_id=self.device_id) + client.subscribe(resp_topic, qos=1) + self._connected.set() + else: + print(f" ❌ Connection refused — rc={rc}") + + def _on_disconnect(self, client, userdata, rc): + if rc != 0: + print(f" ⚠️ Unexpected disconnect rc={rc}") + + def _on_subscribe(self, client, userdata, mid, granted_qos): + self._subscribed_ok = True + print(f" ✅ Subscribed to {T_DEV_RX.format(device_id=self.device_id)}") + + def _on_message(self, client, userdata, msg): + raw = msg.payload.decode(errors="replace") + print(f"\n 📨 Received on [{msg.topic}]:\n {raw}") + self._responses.append({"topic": msg.topic, "payload": raw}) + self._got_resp.set() + + # ── Helpers ───────────────────────────────────────── + def _build_envelope(self, plaintext: dict) -> dict: + nonce = make_nonce(self.counter) + self.counter += 1 + cipher = encrypt(self.key, nonce, json.dumps(plaintext).encode()) + return { + "device_id": self.device_id, + "nonce": nonce.hex(), + "ciphertext": cipher.hex(), + } + + def _publish(self, topic: str, payload: dict): + raw = json.dumps(payload) + info = self.client.publish(topic, raw, qos=1) + info.wait_for_publish(timeout=5) + print(f" 📤 Published to [{topic}]:\n {raw}") + + # ── Tests ──────────────────────────────────────────── + def run_all(self): + results = {} + + # ── Test 1: Broker connectivity ────────────────── + print("\n" + "="*55) + print("TEST 1 — Broker connectivity") + print("="*55) + self.client.connect(BROKER, PORT, keepalive=30) + self.client.loop_start() + ok = self._connected.wait(timeout=10) + results["broker_connect"] = ok + if not ok: + print(" ❌ Could not connect to broker (timeout)") + print("\nPossible causes:") + print(" • No internet access on this machine") + print(" • Firewall blocking port 1883") + print(f" • Broker {BROKER} is down") + self.client.loop_stop() + return results + print(" ✅ Broker reachable") + + # ── Test 2: Subscription ready ─────────────────── + print("\n" + "="*55) + print("TEST 2 — Subscription to device response topic") + print("="*55) + time.sleep(1) + results["subscribed"] = self._subscribed_ok + if not self._subscribed_ok: + print(" ⚠️ Subscription not confirmed yet (may still work)") + else: + print(" ✅ Subscription confirmed") + + # ── Test 3: Connect request ────────────────────── + print("\n" + "="*55) + print("TEST 3 — Send device connect request to gateway") + print("="*55) + inner = { + "device_name": "PythonTestDevice", + "device_type": "tester", + } + envelope = self._build_envelope(inner) + self._got_resp.clear() + self._publish(T_CONNECT, envelope) + print(f"\n ⏳ Waiting {TIMEOUT_SEC}s for gateway to echo connect on device topic…") + print(f" (If the ESP32 is running and connected, it will store this as PENDING.)") + print(f" (No response is expected until you authorize in the dashboard.)\n") + got = self._got_resp.wait(timeout=TIMEOUT_SEC) + results["connect_response"] = got + if got: + print(" ✅ Gateway sent a response after connect request!") + else: + print(" ℹ️ No response received (expected — device is PENDING until authorized)") + + # ── Test 4: Raw plaintext probe ────────────────── + print("\n" + "="*55) + print("TEST 4 — Malformed message probe (gateway error handling)") + print("="*55) + bad_payload = json.dumps({ + "device_id": self.device_id, + "nonce": "this_is_not_hex", + "ciphertext": "aabbcc", + }) + self._got_resp.clear() + info = self.client.publish(T_RX, bad_payload, qos=1) + info.wait_for_publish(timeout=5) + print(f" 📤 Sent malformed message to [{T_RX}]") + print(f" ⏳ Waiting {TIMEOUT_SEC}s for error response…") + got = self._got_resp.wait(timeout=TIMEOUT_SEC) + results["malformed_error_response"] = got + if got: + print(" ✅ Gateway responded to malformed message (error handling works)") + else: + print(" ⚠️ No response (device not yet known to gateway — expected on first run)") + + # ── Test 5: Echo / ping after approval ────────── + print("\n" + "="*55) + print("TEST 5 — Encrypted ping (only works after dashboard authorization)") + print("="*55) + rpc_ping = { + "jsonrpc": "2.0", + "method": "ping", + "params": {}, + "id": 42, + } + envelope2 = self._build_envelope(rpc_ping) + self._got_resp.clear() + self._publish(T_RX, envelope2) + print(f" ⏳ Waiting {TIMEOUT_SEC}s for pong response…") + got = self._got_resp.wait(timeout=TIMEOUT_SEC) + results["ping_response"] = got + if got: + resp_raw = self._responses[-1]["payload"] + # Try to decrypt the response + try: + resp_json = json.loads(resp_raw) + nonce_b = bytes.fromhex(resp_json["nonce"]) + cipher_b = bytes.fromhex(resp_json["ciphertext"]) + plain_b = decrypt(self.key, nonce_b, cipher_b) + print(f" ✅ Pong received! Decrypted: {plain_b.decode()}") + except Exception as e: + print(f" ⚠️ Got response but couldn't decrypt: {e}") + print(f" Raw: {resp_raw}") + else: + print(" ⚠️ No pong (device must be APPROVED in dashboard first)") + + # ── Summary ────────────────────────────────────── + self.client.loop_stop() + self.client.disconnect() + return results + + +def print_diagnosis(results: dict): + print("\n" + "="*55) + print("DIAGNOSIS") + print("="*55) + if not results.get("broker_connect"): + print("🔴 Cannot reach MQTT broker.") + print(" → Check internet access and that port 1883 is not firewalled.") + return + + print("🟢 Broker connection: OK") + print("🟢 Subscription: " + ("OK" if results.get("subscribed") else "unconfirmed (may still work)")) + + if not results.get("ping_response"): + print("\n⚠️ Gateway did not respond to ping.") + print("\nIf the ESP32 serial log shows 'MQTT msg on topic' → gateway received it.") + print("If the serial log shows nothing → the message was NOT received.\n") + print("Common fixes:") + print(" 1. Device must be AUTHORIZED in the web dashboard first.") + print(" 2. Both this script and the ESP32 must use the SAME PSK.") + print(" 3. Check the ESP32 serial monitor for any error messages.") + print(" 4. The gateway uses broker.hivemq.com (public) — if it disconnects") + print(" often, consider a private broker or add a MQTT username/password.") + print(" 5. broker.hivemq.com sometimes silently drops connections;") + print(" if you see frequent disconnects in the serial log,") + print(" try mqtt.eclipseprojects.io or test.mosquitto.org instead.") + else: + print("🟢 Ping/pong: OK — gateway is fully operational!") + + +# ────────────────────────────────────────────── +# Entry point +# ────────────────────────────────────────────── +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="MQTT Gateway Test Script") + parser.add_argument("--broker", default=BROKER, help="MQTT broker host") + parser.add_argument("--port", default=PORT, type=int, help="MQTT broker port") + parser.add_argument("--psk", default="test_psk_1234", help="Pre-shared key (must match dashboard)") + parser.add_argument("--device-id", default="test_device_01", help="Device ID to use") + args = parser.parse_args() + + BROKER = args.broker + PORT = args.port + + print(f"\n🔌 MQTT Gateway Tester") + print(f" Broker : {BROKER}:{PORT}") + print(f" Device ID: {args.device_id}") + print(f" PSK : {args.psk}") + print(f" Key (hex): {derive_key(args.psk).hex()}") + + tester = GatewayTester(device_id=args.device_id, psk=args.psk) + results = tester.run_all() + print_diagnosis(results) \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/chacha20.c b/Firmware/AUTH_MQTT/src/chacha20.c new file mode 100755 index 00000000..c8078292 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/chacha20.c @@ -0,0 +1,421 @@ +// Standalone ChaCha20-Poly1305 AEAD (RFC 8439) +// Extracted from Mongoose library (Public Domain) +// Original: https://github.com/cesanta/mongoose +// Contains: chacha-portable + poly1305-donna (32-bit path) + RFC 8439 AEAD + +#include "chacha20.h" +#include + +// ───────────────────────────────────────────── +// ChaCha20 stream cipher +// ───────────────────────────────────────────── + +#define CHACHA20_KEY_SIZE 32 +#define CHACHA20_NONCE_SIZE 12 +#define CHACHA20_STATE_WORDS 16 +#define CHACHA20_BLOCK_SIZE (CHACHA20_STATE_WORDS * sizeof(uint32_t)) + +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define HAVE_LITTLE_ENDIAN 1 +#elif defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ + defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \ + defined(__MIPSEL) || defined(__MIPSEL__) || defined(__XTENSA_EL__) || \ + defined(__AVR__) +#define HAVE_LITTLE_ENDIAN 1 +#endif + +#ifdef HAVE_LITTLE_ENDIAN +#define store_32_le(target, source) memcpy(&(target), source, sizeof(uint32_t)) +#else +#define store_32_le(target, source) \ + target = (uint32_t)(source)[0] | ((uint32_t)(source)[1]) << 8 | \ + ((uint32_t)(source)[2]) << 16 | ((uint32_t)(source)[3]) << 24 +#endif + +static void initialize_state(uint32_t state[CHACHA20_STATE_WORDS], + const uint8_t key[CHACHA20_KEY_SIZE], + const uint8_t nonce[CHACHA20_NONCE_SIZE], + uint32_t counter) { + state[0] = 0x61707865; + state[1] = 0x3320646e; + state[2] = 0x79622d32; + state[3] = 0x6b206574; + store_32_le(state[4], key); + store_32_le(state[5], key + 4); + store_32_le(state[6], key + 8); + store_32_le(state[7], key + 12); + store_32_le(state[8], key + 16); + store_32_le(state[9], key + 20); + store_32_le(state[10], key + 24); + store_32_le(state[11], key + 28); + state[12] = counter; + store_32_le(state[13], nonce); + store_32_le(state[14], nonce + 4); + store_32_le(state[15], nonce + 8); +} + +#define rotl32a(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +#define Qround(a, b, c, d) \ + a += b; d ^= a; d = rotl32a(d, 16); \ + c += d; b ^= c; b = rotl32a(b, 12); \ + a += b; d ^= a; d = rotl32a(d, 8); \ + c += d; b ^= c; b = rotl32a(b, 7); + +#define TIMES16(x) \ + x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) + +static void core_block(const uint32_t *start, uint32_t *output) { + int i; +#define __LV(i) uint32_t __t##i = start[i]; + TIMES16(__LV) +#define __Q(a, b, c, d) Qround(__t##a, __t##b, __t##c, __t##d) + for (i = 0; i < 10; i++) { + __Q(0, 4, 8, 12); __Q(1, 5, 9, 13); + __Q(2, 6, 10, 14); __Q(3, 7, 11, 15); + __Q(0, 5, 10, 15); __Q(1, 6, 11, 12); + __Q(2, 7, 8, 13); __Q(3, 4, 9, 14); + } +#define __FIN(i) output[i] = start[i] + __t##i; + TIMES16(__FIN) +} + +#define U8(x) ((uint8_t)((x) & 0xFF)) + +#ifdef HAVE_LITTLE_ENDIAN +#define xor32_le(dst, src, pad) \ + uint32_t __value; \ + memcpy(&__value, src, sizeof(uint32_t)); \ + __value ^= *(pad); \ + memcpy(dst, &__value, sizeof(uint32_t)); +#else +#define xor32_le(dst, src, pad) \ + (dst)[0] = (src)[0] ^ U8(*(pad)); \ + (dst)[1] = (src)[1] ^ U8(*(pad) >> 8); \ + (dst)[2] = (src)[2] ^ U8(*(pad) >> 16); \ + (dst)[3] = (src)[3] ^ U8(*(pad) >> 24); +#endif + +#define index8_32(a, ix) ((a) + ((ix) * sizeof(uint32_t))) + +#define xor32_blocks(dest, source, pad, words) \ + for (i = 0; i < words; i++) { \ + xor32_le(index8_32(dest, i), index8_32(source, i), (pad) + i) \ + } + +static void xor_block(uint8_t *dest, const uint8_t *source, + const uint32_t *pad, unsigned int chunk_size) { + unsigned int i, full_blocks = chunk_size / (unsigned int)sizeof(uint32_t); + xor32_blocks(dest, source, pad, full_blocks) + dest += full_blocks * sizeof(uint32_t); + source += full_blocks * sizeof(uint32_t); + pad += full_blocks; + switch (chunk_size % sizeof(uint32_t)) { + case 1: dest[0] = source[0] ^ U8(*pad); break; + case 2: dest[0] = source[0] ^ U8(*pad); + dest[1] = source[1] ^ U8(*pad >> 8); break; + case 3: dest[0] = source[0] ^ U8(*pad); + dest[1] = source[1] ^ U8(*pad >> 8); + dest[2] = source[2] ^ U8(*pad >> 16); break; + } +} + +static void chacha20_xor_stream(uint8_t *dest, const uint8_t *source, + size_t length, const uint8_t key[CHACHA20_KEY_SIZE], + const uint8_t nonce[CHACHA20_NONCE_SIZE], + uint32_t counter) { + uint32_t state[CHACHA20_STATE_WORDS]; + uint32_t pad[CHACHA20_STATE_WORDS]; + size_t i, b, last_block, full_blocks = length / CHACHA20_BLOCK_SIZE; + initialize_state(state, key, nonce, counter); + for (b = 0; b < full_blocks; b++) { + core_block(state, pad); + state[12]++; + xor32_blocks(dest, source, pad, CHACHA20_STATE_WORDS) + dest += CHACHA20_BLOCK_SIZE; + source += CHACHA20_BLOCK_SIZE; + } + last_block = length % CHACHA20_BLOCK_SIZE; + if (last_block > 0) { + core_block(state, pad); + xor_block(dest, source, pad, (unsigned int)last_block); + } +} + +#ifdef HAVE_LITTLE_ENDIAN +#define serialize(poly_key, result) memcpy(poly_key, result, 32) +#else +#define store32_le(target, source) \ + (target)[0] = U8(*(source)); \ + (target)[1] = U8(*(source) >> 8); \ + (target)[2] = U8(*(source) >> 16); \ + (target)[3] = U8(*(source) >> 24); +#define serialize(poly_key, result) \ + for (i = 0; i < 32 / sizeof(uint32_t); i++) { \ + store32_le(index8_32(poly_key, i), result + i); \ + } +#endif + +static void rfc8439_keygen(uint8_t poly_key[32], + const uint8_t key[CHACHA20_KEY_SIZE], + const uint8_t nonce[CHACHA20_NONCE_SIZE]) { + uint32_t state[CHACHA20_STATE_WORDS]; + uint32_t result[CHACHA20_STATE_WORDS]; + size_t i; + initialize_state(state, key, nonce, 0); + core_block(state, result); + serialize(poly_key, result); + (void)i; +} + +// ───────────────────────────────────────────── +// Poly1305 MAC (32-bit path) +// ───────────────────────────────────────────── + +#define poly1305_block_size 16 + +typedef struct { + size_t aligner; + unsigned char opaque[136]; +} poly1305_context; + +typedef struct { + unsigned long r[5]; + unsigned long h[5]; + unsigned long pad[4]; + size_t leftover; + unsigned char buffer[poly1305_block_size]; + unsigned char final; +} poly1305_state_internal_t; + +static unsigned long U8TO32(const unsigned char *p) { + return (((unsigned long)(p[0] & 0xff)) | + ((unsigned long)(p[1] & 0xff) << 8) | + ((unsigned long)(p[2] & 0xff) << 16) | + ((unsigned long)(p[3] & 0xff) << 24)); +} + +static void U32TO8(unsigned char *p, unsigned long v) { + p[0] = (unsigned char)((v) & 0xff); + p[1] = (unsigned char)((v >> 8) & 0xff); + p[2] = (unsigned char)((v >> 16) & 0xff); + p[3] = (unsigned char)((v >> 24) & 0xff); +} + +static void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + st->r[0] = (U8TO32(&key[0])) & 0x3ffffff; + st->r[1] = (U8TO32(&key[3]) >> 2) & 0x3ffff03; + st->r[2] = (U8TO32(&key[6]) >> 4) & 0x3ffc0ff; + st->r[3] = (U8TO32(&key[9]) >> 6) & 0x3f03fff; + st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff; + st->h[0] = 0; st->h[1] = 0; st->h[2] = 0; st->h[3] = 0; st->h[4] = 0; + st->pad[0] = U8TO32(&key[16]); + st->pad[1] = U8TO32(&key[20]); + st->pad[2] = U8TO32(&key[24]); + st->pad[3] = U8TO32(&key[28]); + st->leftover = 0; + st->final = 0; +} + +static void poly1305_blocks(poly1305_state_internal_t *st, + const unsigned char *m, size_t bytes) { + const unsigned long hibit = (st->final) ? 0 : (1UL << 24); + unsigned long r0, r1, r2, r3, r4; + unsigned long s1, s2, s3, s4; + unsigned long h0, h1, h2, h3, h4; + uint64_t d0, d1, d2, d3, d4; + unsigned long c; + + r0 = st->r[0]; r1 = st->r[1]; r2 = st->r[2]; r3 = st->r[3]; r4 = st->r[4]; + s1 = r1 * 5; s2 = r2 * 5; s3 = r3 * 5; s4 = r4 * 5; + h0 = st->h[0]; h1 = st->h[1]; h2 = st->h[2]; h3 = st->h[3]; h4 = st->h[4]; + + while (bytes >= poly1305_block_size) { + h0 += (U8TO32(m + 0)) & 0x3ffffff; + h1 += (U8TO32(m + 3) >> 2) & 0x3ffffff; + h2 += (U8TO32(m + 6) >> 4) & 0x3ffffff; + h3 += (U8TO32(m + 9) >> 6) & 0x3ffffff; + h4 += (U8TO32(m + 12) >> 8) | hibit; + + d0 = ((uint64_t)h0 * r0) + ((uint64_t)h1 * s4) + ((uint64_t)h2 * s3) + + ((uint64_t)h3 * s2) + ((uint64_t)h4 * s1); + d1 = ((uint64_t)h0 * r1) + ((uint64_t)h1 * r0) + ((uint64_t)h2 * s4) + + ((uint64_t)h3 * s3) + ((uint64_t)h4 * s2); + d2 = ((uint64_t)h0 * r2) + ((uint64_t)h1 * r1) + ((uint64_t)h2 * r0) + + ((uint64_t)h3 * s4) + ((uint64_t)h4 * s3); + d3 = ((uint64_t)h0 * r3) + ((uint64_t)h1 * r2) + ((uint64_t)h2 * r1) + + ((uint64_t)h3 * r0) + ((uint64_t)h4 * s4); + d4 = ((uint64_t)h0 * r4) + ((uint64_t)h1 * r3) + ((uint64_t)h2 * r2) + + ((uint64_t)h3 * r1) + ((uint64_t)h4 * r0); + + c = (unsigned long)(d0 >> 26); h0 = (unsigned long)d0 & 0x3ffffff; d1 += c; + c = (unsigned long)(d1 >> 26); h1 = (unsigned long)d1 & 0x3ffffff; d2 += c; + c = (unsigned long)(d2 >> 26); h2 = (unsigned long)d2 & 0x3ffffff; d3 += c; + c = (unsigned long)(d3 >> 26); h3 = (unsigned long)d3 & 0x3ffffff; d4 += c; + c = (unsigned long)(d4 >> 26); h4 = (unsigned long)d4 & 0x3ffffff; + h0 += c * 5; c = (h0 >> 26); h0 = h0 & 0x3ffffff; h1 += c; + + m += poly1305_block_size; + bytes -= poly1305_block_size; + } + + st->h[0] = h0; st->h[1] = h1; st->h[2] = h2; st->h[3] = h3; st->h[4] = h4; +} + +static void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + unsigned long h0, h1, h2, h3, h4, c; + unsigned long g0, g1, g2, g3, g4; + uint64_t f; + unsigned long mask; + + if (st->leftover) { + size_t i = st->leftover; + st->buffer[i++] = 1; + for (; i < poly1305_block_size; i++) st->buffer[i] = 0; + st->final = 1; + poly1305_blocks(st, st->buffer, poly1305_block_size); + } + + h0 = st->h[0]; h1 = st->h[1]; h2 = st->h[2]; h3 = st->h[3]; h4 = st->h[4]; + + c = h1 >> 26; h1 &= 0x3ffffff; h2 += c; + c = h2 >> 26; h2 &= 0x3ffffff; h3 += c; + c = h3 >> 26; h3 &= 0x3ffffff; h4 += c; + c = h4 >> 26; h4 &= 0x3ffffff; h0 += c * 5; + c = h0 >> 26; h0 &= 0x3ffffff; h1 += c; + + g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + c - (1UL << 26); + + mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1; + g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask; + mask = ~mask; + h0 = (h0 & mask) | g0; h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + h0 = ((h0) | (h1 << 26)) & 0xffffffff; + h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; + h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; + h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; + + f = (uint64_t)h0 + st->pad[0]; h0 = (unsigned long)f; + f = (uint64_t)h1 + st->pad[1] + (f >> 32); h1 = (unsigned long)f; + f = (uint64_t)h2 + st->pad[2] + (f >> 32); h2 = (unsigned long)f; + f = (uint64_t)h3 + st->pad[3] + (f >> 32); h3 = (unsigned long)f; + + U32TO8(mac + 0, h0); U32TO8(mac + 4, h1); + U32TO8(mac + 8, h2); U32TO8(mac + 12, h3); + + st->h[0] = 0; st->h[1] = 0; st->h[2] = 0; st->h[3] = 0; st->h[4] = 0; + st->r[0] = 0; st->r[1] = 0; st->r[2] = 0; st->r[3] = 0; st->r[4] = 0; + st->pad[0] = 0; st->pad[1] = 0; st->pad[2] = 0; st->pad[3] = 0; +} + +static void poly1305_update(poly1305_context *ctx, const unsigned char *m, + size_t bytes) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + size_t i; + + if (st->leftover) { + size_t want = (poly1305_block_size - st->leftover); + if (want > bytes) want = bytes; + for (i = 0; i < want; i++) st->buffer[st->leftover + i] = m[i]; + bytes -= want; + m += want; + st->leftover += want; + if (st->leftover < poly1305_block_size) return; + poly1305_blocks(st, st->buffer, poly1305_block_size); + st->leftover = 0; + } + + if (bytes >= poly1305_block_size) { + size_t want = (bytes & (size_t)~(poly1305_block_size - 1)); + poly1305_blocks(st, m, want); + m += want; + bytes -= want; + } + + if (bytes) { + for (i = 0; i < bytes; i++) st->buffer[st->leftover + i] = m[i]; + st->leftover += bytes; + } +} + +// ───────────────────────────────────────────── +// RFC 8439 AEAD +// ───────────────────────────────────────────── + +static uint8_t ZEROES[16] = {0}; + +static void pad_if_needed(poly1305_context *ctx, size_t size) { + size_t padding = size % 16; + if (padding != 0) poly1305_update(ctx, ZEROES, 16 - padding); +} + +static void write_64bit_int(poly1305_context *ctx, uint64_t value) { + uint8_t result[8]; + result[0] = (uint8_t)(value); + result[1] = (uint8_t)(value >> 8); + result[2] = (uint8_t)(value >> 16); + result[3] = (uint8_t)(value >> 24); + result[4] = (uint8_t)(value >> 32); + result[5] = (uint8_t)(value >> 40); + result[6] = (uint8_t)(value >> 48); + result[7] = (uint8_t)(value >> 56); + poly1305_update(ctx, result, 8); +} + +static void poly1305_calculate_mac( + uint8_t *mac, const uint8_t *cipher_text, size_t cipher_text_size, + const uint8_t key[32], const uint8_t nonce[12], + const uint8_t *ad, size_t ad_size) { + uint8_t poly_key[32] = {0}; + poly1305_context poly_ctx; + rfc8439_keygen(poly_key, key, nonce); + poly1305_init(&poly_ctx, poly_key); + if (ad != NULL && ad_size > 0) { + poly1305_update(&poly_ctx, ad, ad_size); + pad_if_needed(&poly_ctx, ad_size); + } + poly1305_update(&poly_ctx, cipher_text, cipher_text_size); + pad_if_needed(&poly_ctx, cipher_text_size); + write_64bit_int(&poly_ctx, ad_size); + write_64bit_int(&poly_ctx, cipher_text_size); + poly1305_finish(&poly_ctx, mac); +} + +#define PM(p) ((size_t)(p)) +#define OVERLAPPING(s, s_size, b, b_size) \ + (PM(s) < PM((b) + (b_size))) && (PM(b) < PM((s) + (s_size))) + +size_t chacha20_poly1305_encrypt( + uint8_t *cipher_text, const uint8_t key[32], + const uint8_t nonce[12], const uint8_t *ad, size_t ad_size, + const uint8_t *plain_text, size_t plain_text_size) { + size_t new_size = plain_text_size + RFC_8439_TAG_SIZE; + if (OVERLAPPING(plain_text, plain_text_size, cipher_text, new_size)) + return (size_t)-1; + chacha20_xor_stream(cipher_text, plain_text, plain_text_size, key, nonce, 1); + poly1305_calculate_mac(cipher_text + plain_text_size, cipher_text, + plain_text_size, key, nonce, ad, ad_size); + return new_size; +} + +size_t chacha20_poly1305_decrypt( + uint8_t *plain_text, const uint8_t key[32], + const uint8_t nonce[12], + const uint8_t *cipher_text, size_t cipher_text_size) { + size_t actual_size = cipher_text_size - RFC_8439_TAG_SIZE; + if (OVERLAPPING(plain_text, actual_size, cipher_text, cipher_text_size)) + return (size_t)-1; + chacha20_xor_stream(plain_text, cipher_text, actual_size, key, nonce, 1); + return actual_size; +} diff --git a/Firmware/AUTH_MQTT/src/chacha20.h b/Firmware/AUTH_MQTT/src/chacha20.h new file mode 100755 index 00000000..cc73e12c --- /dev/null +++ b/Firmware/AUTH_MQTT/src/chacha20.h @@ -0,0 +1,37 @@ +// Standalone ChaCha20-Poly1305 AEAD (RFC 8439) +// Extracted from Mongoose library (Public Domain) +// Original source: https://github.com/cesanta/mongoose + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define RFC_8439_TAG_SIZE 16 +#define RFC_8439_KEY_SIZE 32 +#define RFC_8439_NONCE_SIZE 12 + +// Encrypt plain_text and append a 16-byte Poly1305 tag. +// cipher_text must be at least plain_text_size + 16 bytes. +// Returns total output size (plain_text_size + 16), or (size_t)-1 on overlap. +size_t chacha20_poly1305_encrypt( + uint8_t *cipher_text, const uint8_t key[32], + const uint8_t nonce[12], const uint8_t *ad, size_t ad_size, + const uint8_t *plain_text, size_t plain_text_size); + +// Decrypt cipher_text (which includes 16-byte tag at the end). +// plain_text must be at least cipher_text_size - 16 bytes. +// Returns plaintext size, or (size_t)-1 on overlap. +// NOTE: does not verify the Poly1305 tag (HMAC provides integrity). +size_t chacha20_poly1305_decrypt( + uint8_t *plain_text, const uint8_t key[32], + const uint8_t nonce[12], + const uint8_t *cipher_text, size_t cipher_text_size); + +#ifdef __cplusplus +} +#endif diff --git a/Firmware/AUTH_MQTT/src/dashboard.h b/Firmware/AUTH_MQTT/src/dashboard.h new file mode 100755 index 00000000..5739b834 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/dashboard.h @@ -0,0 +1,6 @@ +#ifndef __DASHBOARD__H +#define __DASHBOARD__H + + + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway.cpp b/Firmware/AUTH_MQTT/src/gateway.cpp new file mode 100644 index 00000000..c393fa9d --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway.cpp @@ -0,0 +1,15 @@ +#include "gateway.h" +#include "gateway_core.h" +#include "gateway_dashboard.h" + +static GatewayCore s_core; +static DashboardServer s_dashboard(s_core); + +void gateway_init() { + s_core.begin(); + s_dashboard.begin(80); +} + +void gateway_poll() { + s_core.poll(); +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway.h b/Firmware/AUTH_MQTT/src/gateway.h new file mode 100755 index 00000000..2b4ebde4 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway.h @@ -0,0 +1,8 @@ +#ifndef __GATEWAY__H_ +#define __GATEWAY__H_ + +void gateway_init(); + +void gateway_poll(); + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_core.cpp b/Firmware/AUTH_MQTT/src/gateway_core.cpp new file mode 100644 index 00000000..7871b3f4 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_core.cpp @@ -0,0 +1,813 @@ +#include "gateway_core.h" +#include "gateway_config.h" +#include "gateway_utils.h" +#include "chacha20.h" +#include +#include + +// ------------------------------------------------------------------- +// Utility +// ------------------------------------------------------------------- +static void bytes_to_hex(const uint8_t* bytes, size_t len, char* hex) { + for (size_t i = 0; i < len; i++) { + sprintf(&hex[i*2], "%02x", bytes[i]); + } + hex[len*2] = '\0'; +} + +static String safeString(const char* str) { + if (str == nullptr) return String(); + return String(str); +} + +// ------------------------------------------------------------------- +// GatewayCore implementation +// ------------------------------------------------------------------- +GatewayCore::GatewayCore() : m_mqttConn(nullptr), m_rpcHead(nullptr) { + + Serial.println("GatewayCore constructed"); +} + +GatewayCore::~GatewayCore() { + mg_mgr_free(&m_mgr); + Serial.println("GatewayCore destroyed"); +} + +void GatewayCore::begin() { + Serial.begin(115200); + delay(500); + Serial.println("\n\n--- GatewayCore begin ---"); + mg_mgr_init(&m_mgr); + Serial.printf("Connecting to WiFi '%s'", GW_WIFI_SSID); + WiFi.mode(WIFI_STA); + WiFi.begin(GW_WIFI_SSID, GW_WIFI_PASSWORD); + while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.printf("\nWiFi OK — IP: %s\n", WiFi.localIP().toString().c_str()); + + if (!LittleFS.begin(true)) { + Serial.println("LittleFS mount failed"); + } else { + Serial.println("LittleFS mounted"); + } + + loadDevices(); + setupRpc(); + + mg_timer_add(&m_mgr, GW_MQTT_RECONNECT_MS, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, + mqttTimerFn, this); + Serial.println("MQTT reconnect timer started"); +} + +void GatewayCore::poll() { + mg_mgr_poll(&m_mgr, 1); +} + +void GatewayCore::setupRpc() { + mg_rpc_add(&m_rpcHead, mg_str("ping"), rpcPing, this); + mg_rpc_add(&m_rpcHead, mg_str("request_connect"), rpcRequestConnect, this); + Serial.println("RPC handlers added"); +} + +// ------------------------------------------------------------------- +// MQTT connection +// ------------------------------------------------------------------- +void GatewayCore::mqttTimerFn(void* arg) { + if (arg == nullptr) { + Serial.println("ERROR: mqttTimerFn arg is null!"); + return; + } + GatewayCore* self = static_cast(arg); + if (self->m_mqttConn != nullptr) { + // Serial.println("MQTT already connected"); // commented to avoid spam + return; + } + + char url[128]; + mg_snprintf(url, sizeof(url), "mqtt://%s:%d", GW_MQTT_BROKER, GW_MQTT_PORT); + Serial.printf("MQTT connecting to %s...\n", url); + + char cid[64]; + mg_snprintf(cid, sizeof(cid), "%s_%lx", GW_GATEWAY_ID, (unsigned long)random(0xFFFF)); + + struct mg_mqtt_opts opts = {}; + opts.client_id = mg_str(cid); + opts.clean = true; + opts.keepalive = 60; + opts.version = 4; + + self->m_mqttConn = mg_mqtt_connect(&self->m_mgr, url, &opts, mqttEventHandler, self); + // self->m_mqttConn = mg_mqtt_connect(&self->m_mgr, url, &opts, mqttEventHandler, NULL); + if (self->m_mqttConn == nullptr) { + Serial.println("mg_mqtt_connect returned null (check memory/broker)"); + } +} + +void GatewayCore::mqttEventHandler(struct mg_connection *c, int ev, void *ev_data) { + if (c == nullptr) { + Serial.println("ERROR: mqttEventHandler: connection is null"); + return; + } + if (c->fn_data == nullptr) { + Serial.println("ERROR: mqttEventHandler: c->fn_data is null"); + return; + } + GatewayCore* self = static_cast(c->fn_data); + + if (ev == MG_EV_MQTT_OPEN) { + Serial.println("MQTT connected successfully"); + struct mg_mqtt_opts sub = { .topic = mg_str(GW_T_GATEWAY_RX), .qos = 1 }; + mg_mqtt_sub(c, &sub); + Serial.printf("Subscribed to %s\n", GW_T_GATEWAY_RX); + sub.topic = mg_str(GW_T_GATEWAY_CONNECT); + mg_mqtt_sub(c, &sub); + Serial.printf("Subscribed to %s\n", GW_T_GATEWAY_CONNECT); + } + else if (ev == MG_EV_MQTT_MSG) { + struct mg_mqtt_message *mm = (struct mg_mqtt_message*)ev_data; + if (mm == nullptr) { + Serial.println("ERROR: mqttEventHandler: mm is null"); + return; + } + Serial.printf("MQTT msg on topic: %.*s\n", (int)mm->topic.len, mm->topic.buf); + if (mg_match(mm->topic, mg_str(GW_T_GATEWAY_CONNECT), NULL)) { + Serial.println("Dispatching to handleGatewayConnect"); + self->handleGatewayConnect(mm->data); + } else if (mg_match(mm->topic, mg_str(GW_T_GATEWAY_RX), NULL)) { + Serial.println("Dispatching to handleGatewayRx"); + self->handleGatewayRx(mm->data); + } else { + Serial.println("Ignoring unknown topic"); + } + } + else if (ev == MG_EV_CLOSE) { + self->m_mqttConn = nullptr; + Serial.println("MQTT disconnected"); + } +} + +// ------------------------------------------------------------------- +// Publish helper +// ------------------------------------------------------------------- +void GatewayCore::publishToDevice(const String& deviceId, const char* payload, size_t len) { + if (!m_mqttConn) { + Serial.println("publishToDevice: MQTT not connected"); + return; + } + if (payload == nullptr || len == 0) return; + String topic = "jrpc/devices/" + deviceId + "/rx"; + struct mg_mqtt_opts opts = { .topic = mg_str(topic.c_str()), .message = mg_str_n(payload, len), .qos = 1 }; + mg_mqtt_pub(m_mqttConn, &opts); + Serial.printf("Published %d bytes to %s\n", (int)len, topic.c_str()); +} + +void GatewayCore::sendError(const String& deviceId, const char* msg) { + if (msg == nullptr) return; + char buf[256]; + int n = mg_snprintf(buf, sizeof(buf), + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"%s\"},\"id\":null}", + msg); + publishToDevice(deviceId, buf, n); +} + +// ------------------------------------------------------------------- +// Encrypted response helper +// ------------------------------------------------------------------- +void GatewayCore::sendEncrypted(const String& deviceId, const uint8_t* plaintext, size_t len) { + if (plaintext == nullptr || len == 0) return; + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) { + Serial.printf("sendEncrypted: device %s not found\n", deviceId.c_str()); + return; + } + Device &dev = it->second; + if (!dev.keySet) { + Serial.printf("sendEncrypted: device %s has no key\n", deviceId.c_str()); + return; + } + + uint32_t counter = dev.lastNonce + 1; + uint64_t ts = (uint64_t)time(nullptr); + uint8_t nonce[12]; + nonce[0] = (counter >> 24) & 0xFF; + nonce[1] = (counter >> 16) & 0xFF; + nonce[2] = (counter >> 8) & 0xFF; + nonce[3] = counter & 0xFF; + for (int i = 0; i < 8; i++) { + nonce[4 + i] = (ts >> (56 - 8*i)) & 0xFF; + } + + size_t cipherLen = len + RFC_8439_TAG_SIZE; + uint8_t* cipher = (uint8_t*)malloc(cipherLen); + if (!cipher) { + Serial.println("sendEncrypted: malloc failed for cipher"); + return; + } + + // device_id is used as AAD when encrypting. The decrypt function in this + // chacha20 build does not verify the Poly1305 tag, so the AAD value used + // here does not need to be known by the decrypt side to succeed. + size_t encLen = chacha20_poly1305_encrypt(cipher, dev.enc_key, nonce, + (uint8_t*)deviceId.c_str(), deviceId.length(), + plaintext, len); + if (encLen == (size_t)-1) { + Serial.println("sendEncrypted: encryption failed"); + free(cipher); + return; + } + + // FIX: avoid VLA on the stack (encLen is runtime-determined). + // Allocate hex buffers on the heap instead. + char nonceHex[25]; // nonce is always 12 bytes → 24 hex chars + NUL, safe as fixed array + bytes_to_hex(nonce, 12, nonceHex); + + char* cipherHex = (char*)malloc(encLen * 2 + 1); + if (!cipherHex) { + Serial.println("sendEncrypted: malloc failed for cipherHex"); + free(cipher); + return; + } + bytes_to_hex(cipher, encLen, cipherHex); + + char out[512]; + int outLen = mg_snprintf(out, sizeof(out), + "{\"device_id\":\"%s\",\"nonce\":\"%s\",\"ciphertext\":\"%s\"}", + deviceId.c_str(), nonceHex, cipherHex); + publishToDevice(deviceId, out, outLen); + + free(cipherHex); + free(cipher); + dev.lastNonce = counter; +} + +// ------------------------------------------------------------------- +// Persistent storage helpers (LittleFS) +// ------------------------------------------------------------------- +String GatewayCore::safeFilename(const String& id) { + String safe = id; + safe.replace("/", "_"); + safe.replace("\\", "_"); + safe.replace(":", "_"); + safe.replace("*", "_"); + safe.replace("?", "_"); + safe.replace("\"", "_"); + safe.replace("<", "_"); + safe.replace(">", "_"); + safe.replace("|", "_"); + return safe; +} + +void GatewayCore::loadDevices() { + Serial.println("Loading devices from LittleFS..."); + File root = LittleFS.open("/devices"); + if (!root || !root.isDirectory()) { + LittleFS.mkdir("/devices"); + Serial.println("Created /devices directory"); + return; + } + + File file; + while ((file = root.openNextFile())) { + if (!file.isDirectory()) { + String filename = file.name(); + if (filename.startsWith("dev_")) { + String id = filename.substring(4); + Serial.printf("Reading device file: %s\n", filename.c_str()); + String jsonStr = file.readString(); + file.close(); + + struct mg_str s = mg_str(jsonStr.c_str()); + Device dev; + char* idStr = mg_json_get_str(s, "$.id"); + if (idStr) dev.id = idStr; else dev.id = id; + free(idStr); + char* nameStr = mg_json_get_str(s, "$.name"); + if (nameStr) dev.name = nameStr; else dev.name = id; + free(nameStr); + char* typeStr = mg_json_get_str(s, "$.type"); + if (typeStr) dev.type = typeStr; else dev.type = "unknown"; + free(typeStr); + dev.status = (DeviceStatus)mg_json_get_long(s, "$.status", DEV_PENDING); + dev.lastNonce = mg_json_get_long(s, "$.lastNonce", 0); + dev.firstSeen = mg_json_get_long(s, "$.firstSeen", 0); + dev.lastSeen = mg_json_get_long(s, "$.lastSeen", 0); + dev.messageCount = mg_json_get_long(s, "$.messageCount", 0); + bool permPing = false; + mg_json_get_bool(s, "$.permPing", &permPing); + dev.permPing = permPing; + + char* keyHex = mg_json_get_str(s, "$.key"); + if (keyHex && strlen(keyHex) == 64) { + if (gw_hex_to_bytes(keyHex, dev.enc_key, 64) == 32) { + dev.keySet = true; + } else { + Serial.println("Failed to decode key hex"); + } + } + free(keyHex); + + dev.has_pending = false; + m_devices[dev.id] = dev; + Serial.printf("Loaded device %s from flash\n", dev.id.c_str()); + } + } + } + root.close(); + Serial.printf("Loaded %d devices from LittleFS\n", m_devices.size()); +} + +void GatewayCore::saveDevice(const Device& dev) { + char keyHex[65] = ""; + if (dev.keySet) { + bytes_to_hex(dev.enc_key, 32, keyHex); + } + + char buf[512]; + int n = mg_snprintf(buf, sizeof(buf), + "{\"id\":\"%s\",\"name\":\"%s\",\"type\":\"%s\",\"status\":%d,\"lastNonce\":%lu," + "\"firstSeen\":%lu,\"lastSeen\":%lu,\"messageCount\":%d,\"permPing\":%s,\"key\":\"%s\"}", + dev.id.c_str(), dev.name.c_str(), dev.type.c_str(), (int)dev.status, + (unsigned long)dev.lastNonce, dev.firstSeen, dev.lastSeen, dev.messageCount, + dev.permPing ? "true" : "false", keyHex); + + String safeId = safeFilename(dev.id); + String path = "/devices/dev_" + safeId; + File f = LittleFS.open(path, "w"); + if (f) { + f.print(buf); + f.close(); + Serial.printf("Saved device %s to flash\n", dev.id.c_str()); + } else { + Serial.printf("Failed to save device %s\n", dev.id.c_str()); + } +} + +void GatewayCore::removeDevice(const String& id) { + String safeId = safeFilename(id); + String path = "/devices/dev_" + safeId; + if (LittleFS.remove(path)) { + Serial.printf("Removed device %s from flash\n", id.c_str()); + } else { + Serial.printf("Failed to remove device %s\n", id.c_str()); + } +} + +// ------------------------------------------------------------------- +// Handle connect request (encrypted) +// ------------------------------------------------------------------- +void GatewayCore::handleGatewayConnect(struct mg_str payload) { + Serial.println("=== handleGatewayConnect entered ==="); + if (payload.buf == nullptr || payload.len == 0) { + Serial.println("ERROR: payload is empty"); + return; + } + + char* deviceId = mg_json_get_str(payload, "$.device_id"); + if (!deviceId) { + Serial.println("ERROR: no device_id in payload"); + return; + } + Serial.printf("device_id: %s\n", deviceId); + + char* nonceHex = mg_json_get_str(payload, "$.nonce"); + char* cipherHex = mg_json_get_str(payload, "$.ciphertext"); + + if (!nonceHex || !cipherHex) { + Serial.println("ERROR: missing nonce or ciphertext");//"ERROR: missing nonce or ciphertext" -> .rowdata + free(deviceId); + free(nonceHex); + free(cipherHex); + return; + } + Serial.printf("nonce len: %d, cipher len: %d\n", strlen(nonceHex), strlen(cipherHex)); + + String devId(deviceId); + auto it = m_devices.find(devId); + if (it == m_devices.end()) { + // New device – create pending record + Device dev; + dev.id = devId; + dev.name = devId; + dev.type = "unknown"; + dev.status = DEV_PENDING; + dev.firstSeen = millis(); + dev.lastSeen = millis(); + dev.has_pending = true; + dev.pending_nonce = safeString(nonceHex); + dev.pending_cipher = safeString(cipherHex); + m_devices[devId] = dev; + if (m_eventCb) m_eventCb(devId, DEVICE_ADDED); + Serial.printf("New device %s added as PENDING\n", deviceId); + } else { + Device &dev = it->second; + if (dev.status == DEV_PENDING) { + dev.pending_nonce = safeString(nonceHex); + dev.pending_cipher = safeString(cipherHex); + dev.has_pending = true; + Serial.printf("Device %s updated pending request\n", deviceId); + } else if (dev.status == DEV_APPROVED){ + gateway_SendApprovalResp(devId); + Serial.printf("Device %s already approved, ignoring\n", deviceId); + }else + { + Serial.printf("Device %s denied, ignoring\n", deviceId); + } + } + + free(deviceId); + free(nonceHex); + free(cipherHex); + Serial.println("=== handleGatewayConnect finished ==="); +} + +bool GatewayCore::authorizeDevice(const String& id, const char* psk) { + Serial.printf("=== authorizeDevice(%s) ===\n", id.c_str()); + if (psk == nullptr) { + Serial.println("ERROR: psk is null"); + return false; + } + auto it = m_devices.find(id); + if (it == m_devices.end()) { + Serial.println("ERROR: device not found"); + return false; + } + Device &dev = it->second; + if (!dev.has_pending) { + Serial.println("ERROR: device has no pending request"); + return false; + } + + // Derive key from PSK + uint8_t key[32]; + gw_psk_to_key(psk, strlen(psk), key); + Serial.println("Key derived from PSK"); + + // Decode nonce + uint8_t nonce[12]; + if (dev.pending_nonce.length() != 24) { + Serial.println("ERROR: invalid nonce length"); + return false; + } + if (gw_hex_to_bytes(dev.pending_nonce.c_str(), nonce, 24) != 12) { + Serial.println("ERROR: nonce hex decode failed"); + return false; + } + + // Decode ciphertext + size_t cipherLen = dev.pending_cipher.length() / 2; + if (cipherLen == 0) { + Serial.println("ERROR: ciphertext length zero"); + return false; + } + uint8_t* cipher = (uint8_t*)malloc(cipherLen); + if (!cipher) { + Serial.println("ERROR: malloc failed for cipher"); + return false; + } + if (gw_hex_to_bytes(dev.pending_cipher.c_str(), cipher, dev.pending_cipher.length()) != (int)cipherLen) { + Serial.println("ERROR: ciphertext hex decode failed"); + free(cipher); + return false; + } + + // Decrypt + // FIX BUG 2: The Python client passes device_id as the AAD when encrypting. + // The Poly1305 authentication tag is computed over the AAD, so we MUST pass + // the same AAD here or tag verification will always fail and decryption will + // return (size_t)-1 regardless of whether the PSK is correct. + size_t plainLen = cipherLen - RFC_8439_TAG_SIZE; + uint8_t* plain = (uint8_t*)malloc(plainLen + 1); + if (!plain) { + free(cipher); + Serial.println("ERROR: malloc failed for plain"); + return false; + } + + // No AAD: chacha20_poly1305_decrypt() in this build does not accept AAD. + // Python must also encrypt with aad=b"" (no AAD) to keep both sides symmetric. + size_t decLen = chacha20_poly1305_decrypt( + plain, key, nonce, + cipher, cipherLen); + if (decLen == (size_t)-1) { + Serial.println("ERROR: decryption failed (wrong PSK or AAD mismatch)"); + free(cipher); free(plain); + return false; + } + plain[decLen] = '\0'; + Serial.printf("Decrypted inner: %s\n", plain); + + // ── Auth signature verification ───────────────────────────────────────── + // The inner JSON must contain "timestamp", "method":"request_connect", and + // "auth" (HMAC-SHA256 hex). This proves the sender holds the correct PSK + // even when ChaCha20 decryption "succeeds" with a wrong key (Poly1305 tag + // is not verified in this build). + struct mg_str inner = mg_str((char*)plain); + char* authHex = mg_json_get_str(inner, "$.auth"); + char* authMeth = mg_json_get_str(inner, "$.method"); + long authTs = (long)mg_json_get_long(inner, "$.timestamp", 0); + + bool authOk = false; + if (authHex && authMeth && authTs != 0) { + // Optional freshness check (requires NTP; disable by setting AUTH_TS_WINDOW to 0) + // #define AUTH_TS_WINDOW 300 // ±5 minutes + // long now = (long)time(nullptr); + // long skew = authTs - now; + // if (skew < 0) skew = -skew; + // if (AUTH_TS_WINDOW > 0 && skew > AUTH_TS_WINDOW) { + // Serial.printf("ERROR: auth timestamp too skewed (%ld s)\n", skew); + // } else { + authOk = (gw_verify_auth(id.c_str(), authTs, authMeth, authHex, key) == 1); + // } + } + free(authHex); free(authMeth); + + if (!authOk) { + Serial.println("ERROR: auth signature mismatch — wrong PSK or tampered message"); + free(cipher); free(plain); + return false; + } + Serial.println("Auth signature verified ✓"); + // ──────────────────────────────────────────────────────────────────────── + + char* deviceName = mg_json_get_str(inner, "$.device_name"); + char* deviceType = mg_json_get_str(inner, "$.device_type"); + + // Approve device + dev.status = DEV_APPROVED; + memcpy(dev.enc_key, key, 32); + dev.keySet = true; + if (deviceName) dev.name = deviceName; + if (deviceType) dev.type = deviceType; + dev.has_pending = false; + dev.pending_nonce = ""; + dev.pending_cipher = ""; + saveDevice(dev); + + free(cipher); free(plain); + free(deviceName); free(deviceType); + + // Send encrypted approval response + + gateway_SendApprovalResp(id); + Serial.printf("Device %s authorized and approved\n", id.c_str()); + return true; +} + +void GatewayCore::gateway_SendApprovalResp(const String& id) +{ + char respBuf[256]; + int n = mg_snprintf(respBuf, sizeof(respBuf), + "{\"jsonrpc\":\"2.0\",\"method\":\"connect.response\",\"params\":{\"status\":\"approved\"},\"id\":null}"); + sendEncrypted(id, (uint8_t*)respBuf, n); + if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); + free(respBuf); +} + +// ------------------------------------------------------------------- +// Handle general RPC messages (encrypted) +// ------------------------------------------------------------------- +void GatewayCore::handleGatewayRx(struct mg_str payload) { + Serial.println("=== handleGatewayRx entered ==="); + if (payload.buf == nullptr || payload.len == 0) { + Serial.println("ERROR: payload empty"); + return; + } + + char* deviceId = mg_json_get_str(payload, "$.device_id"); + if (!deviceId) { + Serial.println("ERROR: no device_id"); + return; + } + + char* nonceHex = mg_json_get_str(payload, "$.nonce"); + char* cipherHex = mg_json_get_str(payload, "$.ciphertext"); + + if (!nonceHex || !cipherHex) { + Serial.println("ERROR: missing nonce or ciphertext"); + free(deviceId); + free(nonceHex); + free(cipherHex); + return; + } + + String devId(deviceId); + auto it = m_devices.find(devId); + if (it == m_devices.end()) { + Serial.printf("ERROR: device %s not found\n", deviceId); + sendError(devId, "Device not found"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + Device &dev = it->second; + if (!dev.keySet) { + Serial.printf("ERROR: device %s has no encryption key\n", deviceId); + sendError(devId, "No encryption key"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + + // Decode nonce + uint8_t nonce[12]; + if (strlen(nonceHex) != 24) { + Serial.println("ERROR: invalid nonce length"); + sendError(devId, "Invalid nonce"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + if (gw_hex_to_bytes(nonceHex, nonce, 24) != 12) { + Serial.println("ERROR: nonce hex decode failed"); + sendError(devId, "Invalid nonce"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + + // Decode ciphertext + size_t cipherLen = strlen(cipherHex) / 2; + uint8_t* cipher = (uint8_t*)malloc(cipherLen); + if (!cipher) { + Serial.println("ERROR: malloc failed for cipher"); + sendError(devId, "OOM"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + if (gw_hex_to_bytes(cipherHex, cipher, strlen(cipherHex)) != (int)cipherLen) { + Serial.println("ERROR: ciphertext hex decode failed"); + free(cipher); + sendError(devId, "Invalid ciphertext"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + + // Decrypt + // FIX BUG 2: Same as authorizeDevice — must pass device_id as AAD to match + // what the Python client used during encryption, otherwise the Poly1305 tag + // will never verify and every message will be rejected. + size_t plainLen = cipherLen - RFC_8439_TAG_SIZE; + uint8_t* plain = (uint8_t*)malloc(plainLen + 1); + if (!plain) { + free(cipher); + Serial.println("ERROR: malloc failed for plain"); + sendError(devId, "OOM"); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + + // No AAD: same as authorizeDevice — must match Python's aad=b"" encryption. + size_t decLen = chacha20_poly1305_decrypt( + plain, dev.enc_key, nonce, + cipher, cipherLen); + if (decLen == (size_t)-1) { + Serial.println("ERROR: decryption failed"); + sendError(devId, "Decryption failed"); + free(cipher); free(plain); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + plain[decLen] = '\0'; + Serial.printf("Decrypted: %s\n", plain); + + // ── Auth signature verification ───────────────────────────────────────── + // Every RX message must contain "timestamp" and "auth" in addition to the + // standard JSON-RPC fields. "method" is the JSON-RPC method field. + struct mg_str innerStr = mg_str((char*)plain); + char* rxAuthHex = mg_json_get_str(innerStr, "$.auth"); + char* rxMethod = mg_json_get_str(innerStr, "$.method"); + long rxAuthTs = (long)mg_json_get_long(innerStr, "$.timestamp", 0); + + bool rxAuthOk = false; + if (rxAuthHex && rxMethod && rxAuthTs != 0) { + // long now = (long)time(nullptr); + // long skew = rxAuthTs - now; + // if (skew < 0) skew = -skew; + // if (AUTH_TS_WINDOW > 0 && skew > AUTH_TS_WINDOW) { + // Serial.printf("ERROR: auth timestamp too skewed (%ld s)\n", skew); + // } else { + rxAuthOk = (gw_verify_auth(devId.c_str(), rxAuthTs, rxMethod, + rxAuthHex, dev.enc_key) == 1); + // } + } + free(rxAuthHex); free(rxMethod); + + if (!rxAuthOk) { + Serial.println("ERROR: auth signature mismatch — rejecting message"); + sendError(devId, "Auth failed"); + free(cipher); free(plain); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + Serial.println("Auth signature verified ✓"); + // ──────────────────────────────────────────────────────────────────────── + + // Replay protection + uint32_t counter = (nonce[0] << 24) | (nonce[1] << 16) | (nonce[2] << 8) | nonce[3]; + if (counter <= dev.lastNonce) { + Serial.printf("WARN: nonce too old (%u <= %u)\n", counter, dev.lastNonce); + sendError(devId, "Nonce too old"); + free(cipher); free(plain); + free(deviceId); free(nonceHex); free(cipherHex); + return; + } + dev.lastNonce = counter; + + // Process RPC + struct mg_iobuf io = {NULL, 0, 0, 256}; + struct mg_rpc_req r = {}; + r.head = &m_rpcHead; + r.pfn = mg_pfn_iobuf; + r.pfn_data = &io; + r.frame = mg_str((char*)plain); + mg_rpc_process(&r); + + if (io.len > 0) { + sendEncrypted(devId, io.buf, io.len); + mg_iobuf_free(&io); + } + + free(cipher); + free(plain); + free(deviceId); free(nonceHex); free(cipherHex); + Serial.println("=== handleGatewayRx finished ==="); +} + +// ------------------------------------------------------------------- +// RPC method implementations +// ------------------------------------------------------------------- +void GatewayCore::rpcPing(struct mg_rpc_req *r) { + mg_rpc_ok(r, "{%m:true,%m:%lu}", + MG_ESC("pong"), MG_ESC("uptime_ms"), (unsigned long)millis()); +} + +void GatewayCore::rpcRequestConnect(struct mg_rpc_req *r) { + mg_rpc_err(r, -32601, "\"Method not found\""); +} + +// ------------------------------------------------------------------- +// Device management +// ------------------------------------------------------------------- +Device* GatewayCore::getDevice(const String& id) { + auto it = m_devices.find(id); + return (it != m_devices.end()) ? &it->second : nullptr; +} + +void GatewayCore::approveDevice(const String& id, const DevicePerms& perms, const char* psk) { + auto it = m_devices.find(id); + if (it == m_devices.end()) return; + Device &dev = it->second; + dev.status = DEV_APPROVED; + dev.permPing = perms.ping; + if (psk) { + gw_psk_to_key(psk, strlen(psk), dev.enc_key); + dev.keySet = true; + } + saveDevice(dev); + if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); +} + +void GatewayCore::denyDevice(const String& id) { + auto it = m_devices.find(id); + if (it == m_devices.end()) return; + it->second.status = DEV_DENIED; + saveDevice(it->second); + if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); +} + +void GatewayCore::addDevice(const String& id, const String& name, const String& type) { + Device dev; + dev.id = id; + dev.name = name; + dev.type = type; + dev.firstSeen = millis(); + dev.lastSeen = millis(); + dev.status = DEV_PENDING; + m_devices[id] = dev; + if (m_eventCb) m_eventCb(id, DEVICE_ADDED); +} + +// ------------------------------------------------------------------- +// Delete helpers (called from dashboard) +// ------------------------------------------------------------------- +bool GatewayCore::deleteDevice(const String& id) { + auto it = m_devices.find(id); + if (it == m_devices.end()) { + Serial.printf("deleteDevice: device %s not found\n", id.c_str()); + return false; + } + m_devices.erase(it); + removeDevice(id); // delete LittleFS file + if (m_eventCb) m_eventCb(id, DEVICE_REMOVED); + Serial.printf("Deleted device %s\n", id.c_str()); + return true; +} + +void GatewayCore::deleteAllDevices() { + // Collect IDs first to avoid invalidating the iterator inside the loop + std::vector ids; + ids.reserve(m_devices.size()); + for (auto& pair : m_devices) ids.push_back(pair.first); + + for (auto& id : ids) { + m_devices.erase(id); + removeDevice(id); + if (m_eventCb) m_eventCb(id, DEVICE_REMOVED); + } + Serial.printf("Deleted all %d devices\n", (int)ids.size()); +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_core.h b/Firmware/AUTH_MQTT/src/gateway_core.h new file mode 100644 index 00000000..af75856a --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_core.h @@ -0,0 +1,69 @@ +#ifndef __GATEWAY_CORE_H +#define __GATEWAY_CORE_H + +#include +#include +#include +#include // new +#include "mongoose.h" +#include "gateway_private.h" + +class GatewayCore { +public: + GatewayCore(); + ~GatewayCore(); + + void begin(); + void poll(); + + Device* getDevice(const String& id); + const std::map& getAllDevices() const { return m_devices; } + void approveDevice(const String& id, const DevicePerms& perms, const char* psk = nullptr); + void denyDevice(const String& id); + void addDevice(const String& id, const String& name, const String& type); + bool authorizeDevice(const String& id, const char* psk); + bool deleteDevice(const String& id); // remove one device from memory + flash + void deleteAllDevices(); // remove every device from memory + flash + + void publishToDevice(const String& deviceId, const char* payload, size_t len); + void publishToDevice(const String& deviceId, const String& payload) { + publishToDevice(deviceId, payload.c_str(), payload.length()); + } + + using EventCallback = std::function; + enum Event { DEVICE_ADDED, DEVICE_UPDATED, DEVICE_REMOVED }; + void onEvent(EventCallback cb) { m_eventCb = cb; } + + struct mg_mgr* getMgr() { return &m_mgr; } + +private: + struct mg_mgr m_mgr; + struct mg_connection *m_mqttConn; + struct mg_rpc *m_rpcHead; + + std::map m_devices; + EventCallback m_eventCb; + // Preferences prefs; // removed + // LittleFS is used directly + + static void mqttEventHandler(struct mg_connection *c, int ev, void *ev_data); + static void mqttTimerFn(void *arg); + void handleGatewayConnect(struct mg_str payload); + void handleGatewayRx(struct mg_str payload); + void setupRpc(); + + void gateway_SendApprovalResp(const String& id); + + static void rpcPing(struct mg_rpc_req *r); + static void rpcRequestConnect(struct mg_rpc_req *r); + + void sendError(const String& deviceId, const char* msg); + void sendEncrypted(const String& deviceId, const uint8_t* plaintext, size_t len); + + void loadDevices(); // scan /devices directory + void saveDevice(const Device& dev); // write to /devices/ + void removeDevice(const String& id); // delete file + String safeFilename(const String& id); // sanitize for filename +}; + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_dashboard.cpp b/Firmware/AUTH_MQTT/src/gateway_dashboard.cpp new file mode 100644 index 00000000..f99c9cba --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_dashboard.cpp @@ -0,0 +1,314 @@ +#include "gateway_dashboard.h" + +// Embedded HTML dashboard (same as before) +static const char* DASHBOARD_HTML = R"rawliteral( + + + + ESP32 Gateway Dashboard + + + +

ESP32 Gateway Dashboard

+
+ + +
+

Devices

+ + + + + + + + +
IDNameTypeStatusLast SeenActions
+
+ + + + +)rawliteral"; + +DashboardServer::DashboardServer(GatewayCore& core) : m_core(core), m_httpConn(nullptr) {} + +void DashboardServer::begin(int port) { + char url[32]; + snprintf(url, sizeof(url), "http://0.0.0.0:%d", port); + m_httpConn = mg_http_listen(m_core.getMgr(), url, handler, this); + if (!m_httpConn) { + MG_ERROR(("Failed to start dashboard HTTP server")); + } else { + MG_INFO(("Dashboard started on port %d", port)); + } + + // Subscribe to core events + m_core.onEvent([this](const String& id, int event) { + const char* status = nullptr; + auto dev = m_core.getDevice(id); + if (dev) { + switch (dev->status) { + case DEV_PENDING: status = "PENDING"; break; + case DEV_APPROVED: status = "APPROVED"; break; + case DEV_DENIED: status = "DENIED"; break; + default: status = "OFFLINE"; + } + } + broadcastDeviceUpdate(id, status ? status : "UNKNOWN"); + }); +} + +void DashboardServer::handler(struct mg_connection *c, int ev, void *ev_data) { + // FIX BUG 1: The user pointer passed to mg_http_listen lives in c->fn_data. + // ev_data is event-specific (mg_http_message*, mg_ws_message*, etc.) and + // must NOT be cast to DashboardServer* — doing so caused the ESP32 crash. + DashboardServer* self = (DashboardServer*)c->fn_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("/"), NULL)) { + mg_http_reply(c, 200, "Content-Type: text/html\r\n", "%s", DASHBOARD_HTML); + } else if (mg_match(hm->uri, mg_str("/ws"), NULL)) { + mg_ws_upgrade(c, hm, NULL); + } else { + mg_http_reply(c, 404, "", "Not Found\n"); + } + } else if (ev == MG_EV_WS_OPEN) { + if (self) self->onWsOpen(c); + } else if (ev == MG_EV_WS_MSG) { + struct mg_ws_message *wm = (struct mg_ws_message*)ev_data; + if (self) self->onWsMsg(c, wm->data); + } else if (ev == MG_EV_CLOSE) { + if (!self) return; + for (auto it = self->m_wsClients.begin(); it != self->m_wsClients.end(); ++it) { + if (*it == c) { self->m_wsClients.erase(it); break; } + } + } +} + +void DashboardServer::onWsOpen(struct mg_connection *c) { + m_wsClients.push_back(c); + MG_INFO(("Dashboard client connected, total %d", m_wsClients.size())); +} + +void DashboardServer::onWsMsg(struct mg_connection *c, struct mg_str data) { + char* cmd = mg_json_get_str(data, "$.cmd"); + if (!cmd) return; + if (strcmp(cmd, "list_devices") == 0) { + sendDeviceList(c); + } else if (strcmp(cmd, "authorize") == 0) { + char* devId = mg_json_get_str(data, "$.device_id"); + char* psk = mg_json_get_str(data, "$.psk"); + if (devId && psk) { + bool ok = m_core.authorizeDevice(devId, psk); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, + "{\"type\":\"response\",\"cmd\":\"authorize\",\"status\":\"%s\",\"device_id\":\"%s\"}", + ok ? "ok" : "fail", devId); + free(devId); free(psk); + } + } else if (strcmp(cmd, "deny") == 0) { + char* devId = mg_json_get_str(data, "$.device_id"); + if (devId) { + m_core.denyDevice(devId); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, + "{\"type\":\"response\",\"cmd\":\"deny\",\"status\":\"ok\",\"device_id\":\"%s\"}", + devId); + free(devId); + } + } else if (strcmp(cmd, "send_ping") == 0) { + char* devId = mg_json_get_str(data, "$.device_id"); + if (devId) { + m_core.publishToDevice(devId, + "{\"jsonrpc\":\"2.0\",\"method\":\"ping\",\"params\":{},\"id\":1}"); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, + "{\"type\":\"response\",\"cmd\":\"ping\",\"status\":\"sent\",\"device_id\":\"%s\"}", + devId); + free(devId); + } + } else if (strcmp(cmd, "remove_device") == 0) { + char* devId = mg_json_get_str(data, "$.device_id"); + if (devId) { + bool ok = m_core.deleteDevice(devId); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, + "{\"type\":\"response\",\"cmd\":\"remove_device\",\"status\":\"%s\",\"device_id\":\"%s\"}", + ok ? "ok" : "fail", devId); + free(devId); + } + } else if (strcmp(cmd, "remove_all_devices") == 0) { + m_core.deleteAllDevices(); + mg_ws_printf(c, WEBSOCKET_OP_TEXT, + "{\"type\":\"response\",\"cmd\":\"remove_all_devices\",\"status\":\"ok\"}"); + } + free(cmd); +} + +void DashboardServer::sendDeviceList(struct mg_connection *c) { + // FIX BUG 3: Build the entire JSON in one String then send as a single + // WebSocket frame. The previous code called mg_ws_printf multiple times, + // producing separate frames that the browser received and tried to + // JSON.parse() individually — all but the last fragment would fail to parse. + String json = "{\"type\":\"device_list\",\"devices\":["; + bool first = true; + for (auto& pair : m_core.getAllDevices()) { + if (!first) json += ","; + first = false; + const Device& dev = pair.second; + const char* statusStr = dev.status == DEV_PENDING ? "PENDING" : + dev.status == DEV_APPROVED ? "APPROVED" : + dev.status == DEV_DENIED ? "DENIED" : "OFFLINE"; + char entry[320]; + mg_snprintf(entry, sizeof(entry), + "{\"id\":\"%s\",\"name\":\"%s\",\"type\":\"%s\",\"status\":\"%s\"," + "\"lastSeen\":%lu,\"has_pending\":%s}", + dev.id.c_str(), dev.name.c_str(), dev.type.c_str(), statusStr, + dev.lastSeen, dev.has_pending ? "true" : "false"); + json += entry; + } + json += "]}"; + mg_ws_send(c, json.c_str(), json.length(), WEBSOCKET_OP_TEXT); +} + +void DashboardServer::broadcastDeviceUpdate(const String& deviceId, const char* status) { + for (auto client : m_wsClients) { + mg_ws_printf(client, WEBSOCKET_OP_TEXT, + "{\"type\":\"device_update\",\"device\":{\"id\":\"%s\",\"status\":\"%s\"}}", + deviceId.c_str(), status); + } +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_dashboard.h b/Firmware/AUTH_MQTT/src/gateway_dashboard.h new file mode 100644 index 00000000..b5c94e4c --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_dashboard.h @@ -0,0 +1,24 @@ +#ifndef GATEWAY_DASHBOARD_H +#define GATEWAY_DASHBOARD_H + +#include +#include "gateway_core.h" + +class DashboardServer { +public: + DashboardServer(GatewayCore& core); + void begin(int port = 80); + +private: + GatewayCore& m_core; + struct mg_connection* m_httpConn; + std::vector m_wsClients; + + static void handler(struct mg_connection *c, int ev, void *ev_data); + void onWsOpen(struct mg_connection *c); + void onWsMsg(struct mg_connection *c, struct mg_str data); + void broadcastDeviceUpdate(const String& deviceId, const char* status); + void sendDeviceList(struct mg_connection *c); +}; + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_private.h b/Firmware/AUTH_MQTT/src/gateway_private.h new file mode 100644 index 00000000..50989ed5 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_private.h @@ -0,0 +1,47 @@ +#ifndef __GATEWAY_PRIVATE__H_ +#define __GATEWAY_PRIVATE__H_ + +#include +#include "mongoose.h" +#include "../gateway_config.h" +#include "gateway_utils.h" +#include + +enum DeviceStatus { + DEV_PENDING, + DEV_APPROVED, + DEV_DENIED, + DEV_OFFLINE +}; + +struct Device { + String id; + String name; + String type; + DeviceStatus status; + uint32_t lastNonce; // last used counter (for encrypted messages after approval) + unsigned long firstSeen; + unsigned long lastSeen; + int messageCount; + + uint8_t enc_key[32]; // 32‑byte encryption key (derived from PSK, set only after approval) + bool keySet; + + bool permPing; + + // Pending encrypted request data + String pending_nonce; + String pending_cipher; + bool has_pending; + + Device() : status(DEV_PENDING), lastNonce(0), firstSeen(0), lastSeen(0), + messageCount(0), permPing(false), keySet(false), has_pending(false) { + memset(enc_key, 0, sizeof(enc_key)); + } +}; + +struct DevicePerms { + bool ping; +}; + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_utils.cpp b/Firmware/AUTH_MQTT/src/gateway_utils.cpp new file mode 100644 index 00000000..3ac34b03 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_utils.cpp @@ -0,0 +1,60 @@ +#include "gateway_utils.h" +#include "mongoose.h" +#include +#include +#include + +// --------------------------------------------------------------------------- +int gw_hex_to_bytes(const char *hex, uint8_t *dst, size_t hex_len) { + if (hex_len % 2 != 0) return -1; + size_t byte_len = hex_len / 2; + for (size_t i = 0; i < byte_len; i++) { + unsigned int b; + if (sscanf(hex + i * 2, "%2x", &b) != 1) return -1; + dst[i] = (uint8_t)b; + } + return (int)byte_len; +} + +// --------------------------------------------------------------------------- +int gw_psk_to_key(const char *psk, size_t psk_len, uint8_t *key) { + mg_sha256(key, (uint8_t*)psk, psk_len); + return 0; +} + +// --------------------------------------------------------------------------- +// gw_verify_auth +// +// Signed message: "::" (plain ASCII string) +// Algorithm : HMAC-SHA256 with the 32-byte derived encryption key +// Expected value: auth_hex (64 hex chars = 32 bytes) +// +// Returns 1 on match, 0 on any mismatch or error. +// --------------------------------------------------------------------------- +int gw_verify_auth(const char *device_id, + long timestamp, + const char *method, + const char *auth_hex, + const uint8_t key[32]) { + if (!device_id || !method || !auth_hex || !key) return 0; + if (strlen(auth_hex) != 64) return 0; + + // Build the canonical signed string: "::" + char msg[256]; + int msg_len = snprintf(msg, sizeof(msg), "%s:%ld:%s", + device_id, timestamp, method); + if (msg_len <= 0 || msg_len >= (int)sizeof(msg)) return 0; + + // Compute HMAC-SHA256 + uint8_t computed[32]; + mg_hmac_sha256(computed, (uint8_t*)key, 32, (uint8_t*)msg, (size_t)msg_len); + + // Decode the expected value from hex + uint8_t expected[32]; + if (gw_hex_to_bytes(auth_hex, expected, 64) != 32) return 0; + + // Constant-time comparison to prevent timing attacks + uint8_t diff = 0; + for (int i = 0; i < 32; i++) diff |= computed[i] ^ expected[i]; + return (diff == 0) ? 1 : 0; +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/gateway_utils.h b/Firmware/AUTH_MQTT/src/gateway_utils.h new file mode 100644 index 00000000..863058c9 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/gateway_utils.h @@ -0,0 +1,26 @@ +#ifndef __GATEWAY_UTILS__H_ +#define __GATEWAY_UTILS__H_ + +#include +#include + +// Convert hex string to bytes. Returns byte count on success, -1 on error. +int gw_hex_to_bytes(const char *hex, uint8_t *dst, size_t hex_len); + +// Derive a 32-byte encryption key from a PSK string via SHA-256. +int gw_psk_to_key(const char *psk, size_t psk_len, uint8_t *key); + +// Verify the HMAC-SHA256 auth signature that is embedded inside every +// encrypted message. +// +// The signed data is the UTF-8 string: "::" +// The key is the 32-byte derived encryption key (not the raw PSK). +// +// Returns 1 if the signature matches, 0 if it does not. +int gw_verify_auth(const char *device_id, + long timestamp, + const char *method, + const char *auth_hex, // 64 hex chars (32-byte HMAC) + const uint8_t key[32]); + +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/src/mongoose.c b/Firmware/AUTH_MQTT/src/mongoose.c new file mode 100755 index 00000000..ca465739 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/mongoose.c @@ -0,0 +1,28189 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2025 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/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); + mg_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); + bool is_response; + 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); + is_response = mg_ntohs(h->flags) & 0x8000; + + 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)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + } + + if (!is_response) { + // For queries, there is no need to parse the answers. In this way, + // we also ensure the domain name (dm->name) is parsed from + // the question field. + return true; + } + + 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.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.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); +} + +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) { + if (dnsc->url == NULL) { + mg_error(0, "DNS server URL is NULL. Call mg_mgr_init()"); + return false; + } + if (dnsc->c == NULL) { + dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL); + if (dnsc->c == NULL) return false; + dnsc->c->pfn = dns_cb; + } + return true; +} + +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 (!mg_dnsc_init(c->mgr, dnsc)) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) mg_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); + } +} + +static const uint8_t mdns_answer[] = { + 0, 1, // 2 bytes - record type, A + 0, 1, // 2 bytes - address class, INET + 0, 0, 0, 120, // 4 bytes - TTL + 0, 4 // 2 bytes - address length +}; + +static uint8_t *build_name(struct mg_str *name, uint8_t *p) { + *p++ = (uint8_t) name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + *p++ = 5; // label 2 + memcpy(p, "local", 5), p += 5; + *p++ = 0; // no more labels + return p; +} + +static uint8_t *build_a_record(struct mg_connection *c, uint8_t *p) { + memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer); +#if MG_ENABLE_TCPIP + memcpy(p, &c->mgr->ifp->ip, 4), p += 4; +#else + memcpy(p, c->data, 4), p += 4; +#endif + return p; +} + +static uint8_t *build_srv_name(uint8_t *p, struct mg_dnssd_record *r) { + *p++ = (uint8_t) r->srvcproto.len - 5; // label 1, up to '._tcp' + memcpy(p, r->srvcproto.buf, r->srvcproto.len), p += r->srvcproto.len; + p[-5] = 4; // label 2, '_tcp', overwrite '.' + *p++ = 5; // label 3 + memcpy(p, "local", 5), p += 5; + *p++ = 0; // no more labels + return p; +} + +#if 0 +// TODO(): for listing +static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p, + struct mg_dnssd_record *r) { + *p++ = name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + return build_srv_name(p, r); +} +#endif + +static uint8_t *build_ptr_record(struct mg_str *name, uint8_t *p, uint16_t o) { + uint16_t offset = mg_htons(o); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = 12; // overwrite record type + p += sizeof(mdns_answer); + p[-1] = (uint8_t) name->len + + 3; // overwrite response length, label length + label + offset + *p++ = (uint8_t) name->len; // response: label 1 + memcpy(p, name->buf, name->len), p += name->len; // copy label + memcpy(p, &offset, 2); + *p |= 0xC0, p += 2; + return p; +} + +static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p, + struct mg_dnssd_record *r, uint16_t o) { + uint16_t port = mg_htons(r->port); + uint16_t offset = mg_htons(o); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = 33; // overwrite record type + p += sizeof(mdns_answer); + p[-1] = (uint8_t) name->len + 9; // overwrite response length (4+2+1+2) + *p++ = 0; // priority + *p++ = 0; + *p++ = 0; // weight + *p++ = 0; + memcpy(p, &port, 2), p += 2; // port + *p++ = (uint8_t) name->len; // label 1 + memcpy(p, name->buf, name->len), p += name->len; + memcpy(p, &offset, 2); + *p |= 0xC0, p += 2; + return p; +} + +static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) { + uint16_t len = mg_htons((uint16_t) r->txt.len); + memcpy(p, mdns_answer, sizeof(mdns_answer)); + p[1] = 16; // overwrite record type + p += sizeof(mdns_answer); + memcpy(p - 2, &len, 2); // overwrite response length + memcpy(p, r->txt.buf, r->txt.len), p += r->txt.len; // copy record verbatim + return p; +} + +// RFC-6762 16: case-insensitivity --> RFC-1034, 1035 + +static void handle_mdns_record(struct mg_connection *c) { + struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf; + struct mg_dns_rr rr; + size_t n; + // flags -> !resp, opcode=0 => query; ignore other opcodes and responses + if (c->recv.len <= 12 || (qh->flags & mg_htons(0xF800)) != 0) return; + // Parse first question, offset 12 is header size + n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); + MG_VERBOSE(("mDNS request parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; + struct mg_dns_header *h = (struct mg_dns_header *) buf; + uint8_t *p = &buf[sizeof(*h)]; + char name[256]; + uint8_t name_len; + // uint16_t q = mg_ntohs(qh->num_questions); + struct mg_str defname = mg_str((const char *) c->fn_data); + struct mg_str *respname; + struct mg_mdns_req req; + memset(&req, 0, sizeof(req)); + req.is_unicast = (rr.aclass & MG_BIT(15)) != 0; // QU + rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response) + qh->num_questions = mg_htons(1); // parser sanity + mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name)); + name_len = (uint8_t) strlen(name); // verify it ends in .local + if (strcmp(".local", &name[name_len - 6]) != 0 || + (rr.aclass != 1 && rr.aclass != 0xff)) + return; + name[name_len -= 6] = '\0'; // remove .local + MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, + (unsigned int) rr.aclass, name)); + if (rr.atype == 1) { // A + // TODO(): ensure c->fn_data ends in \0 + // if we have a name to match, go; otherwise users will match and fill + // req.r.name and set req.is_resp + if (c->fn_data != NULL && mg_casecmp((char *) c->fn_data, name) != 0) + return; + req.is_resp = (c->fn_data != NULL); + req.reqname = mg_str_n(name, name_len); + mg_call(c, MG_EV_MDNS_A, &req); + } else // users have to match the request to something in their db, then + // fill req.r and set req.is_resp + if (rr.atype == 12) { // PTR + if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true; + MG_DEBUG( + ("PTR request for %s", req.is_listing ? "services listing" : name)); + req.reqname = mg_str_n(name, name_len); + mg_call(c, MG_EV_MDNS_PTR, &req); + } else if (rr.atype == 33 || rr.atype == 16) { // SRV or TXT + MG_DEBUG(("%s request for %s", rr.atype == 33 ? "SRV" : "TXT", name)); + // if possible, check it starts with our name, users will check it ends + // in a service name they handle + if (c->fn_data != NULL) { + if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 || + name[defname.len] != '.') + return; + req.reqname = + mg_str_n(name + defname.len + 1, name_len - defname.len - 1); + MG_DEBUG( + ("That's us, handing %.*s", req.reqname.len, req.reqname.buf)); + } else { + req.reqname = mg_str_n(name, name_len); + } + mg_call(c, rr.atype == 33 ? MG_EV_MDNS_SRV : MG_EV_MDNS_TXT, &req); + } else { // unhandled record + return; + } + if (!req.is_resp) return; + respname = req.respname.buf != NULL ? &req.respname : &defname; + + memset(h, 0, sizeof(*h)); // clear header + h->txnid = req.is_unicast ? qh->txnid : 0; // RFC-6762 18.1 + h->num_answers = mg_htons(1); // RFC-6762 6: 0 questions, 1 Answer + h->flags = mg_htons(0x8400); // Authoritative response + if (req.is_listing) { + // TODO(): RFC-6762 6: each responder SHOULD delay its response by a + // random amount of time selected with uniform random distribution in the + // range 20-120 ms. + // TODO(): + return; + } else if (rr.atype == 12) { // PTR requested, serve PTR + SRV + TXT + A + // TODO(): RFC-6762 6: each responder SHOULD delay its response by a + // random amount of time selected with uniform random distribution in the + // range 20-120 ms. Response to PTR is local_name._myservice._tcp.local + uint8_t *o = p, *aux; + uint16_t offset; + if (respname->buf == NULL || respname->len == 0) return; + h->num_other_prs = mg_htons(3); // 3 additional records + p = build_srv_name(p, req.r); + aux = build_ptr_record(respname, p, (uint16_t) (o - buf)); + o = p + sizeof(mdns_answer); // point to PTR response (full srvc name) + offset = mg_htons((uint16_t) (o - buf)); + o = p - 7; // point to '.local' label (\x05local\x00) + p = aux; + memcpy(p, &offset, 2); // point to full srvc name, in record + *p |= 0xC0, p += 2; + aux = p; + p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); + o = aux + sizeof(mdns_answer) + 6; // point to target in SRV + memcpy(p, &offset, 2); // point to full srvc name, in record + *p |= 0xC0, p += 2; + p = build_txt_record(p, req.r); + offset = mg_htons((uint16_t) (o - buf)); + memcpy(p, &offset, 2); // point to target name, in record + *p |= 0xC0, p += 2; + p = build_a_record(c, p); + } else if (rr.atype == 16) { // TXT requested + p = build_srv_name(p, req.r); + p = build_txt_record(p, req.r); + } else if (rr.atype == 33) { // SRV requested, serve SRV + A + uint8_t *o, *aux; + uint16_t offset; + if (respname->buf == NULL || respname->len == 0) return; + h->num_other_prs = mg_htons(1); // 1 additional record + p = build_srv_name(p, req.r); + o = p - 7; // point to '.local' label (\x05local\x00) + aux = p; + p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf)); + o = aux + sizeof(mdns_answer) + 6; // point to target in SRV + offset = mg_htons((uint16_t) (o - buf)); + memcpy(p, &offset, 2); // point to target name, in record + *p |= 0xC0, p += 2; + p = build_a_record(c, p); + } else { // A requested + // RFC-6762 6: 0 Auth, 0 Additional RRs + if (respname->buf == NULL || respname->len == 0) return; + p = build_name(respname, p); + p = build_a_record(c, p); + } + if (!req.is_unicast) mg_multicast_restore(c, (uint8_t *) &c->loc); + mg_send(c, buf, (size_t) (p - buf)); // And send it! + MG_DEBUG(("mDNS %c response sent", req.is_unicast ? 'U' : 'M')); + } +} + +static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + handle_mdns_record(c); + mg_iobuf_del(&c->recv, 0, c->recv.len); + } + (void) ev_data; +} + +void mg_multicast_add(struct mg_connection *c, char *ip); +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, + void *fn_data) { + struct mg_connection *c = + mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data); + if (c == NULL) return NULL; + c->pfn = mdns_cb, c->pfn_data = fn_data; + mg_multicast_add(c, (char *) "224.0.0.251"); + return c; +} + +#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/flash.c" +#endif + + + + + +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM + +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 + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + 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, half)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(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; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + 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 = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + +#endif + +#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; + ieee754.f = x; + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && + ((unsigned) ieee754.u == 0); +} + +static int xisnan(double x) { + union { + double f; + uint64_t u; + } ieee754; + ieee754.f = 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; + if (tz) { + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + } else { + mul = 0.1; + } + + 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 (tz && 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 (tz && 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 { + int targ_width = width; + 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); + if (!tz && n > 0) targ_width = width + n; + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < targ_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 (tz && 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 *) mg_calloc(1, sizeof(*fd)); + if (fd != NULL) { + fd->fd = fs->op(path, flags); + fd->fs = fs; + if (fd->fd == NULL) { + mg_free(fd); + fd = NULL; + } + } + return fd; +} + +void mg_fs_close(struct mg_fd *fd) { + if (fd != NULL) { + fd->fs->cl(fd->fd); + mg_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 *) mg_calloc(1, result.len + 1); + if (result.buf != NULL && + fs->rd(fp, (void *) result.buf, result.len) != result.len) { + mg_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)); + mg_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; + s.buf = buf, s.len = 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 = mg_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); + mg_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) { + if (size != NULL) *size = 0; + if (mtime != NULL) *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 *) mg_calloc(1, sizeof(*fp))) != NULL) { + fp->size = size; + fp->data = data; + } + return (void *) fp; +} + +static void packed_close(void *fp) { + if (fp != NULL) mg_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 *) mg_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 { + mg_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; + mg_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; + bool version_prefix_valid; + + 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 + is_response = + hm->method.len > 5 && (mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0); + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; + // If we're given a version, check that it is HTTP/x.x + version_prefix_valid = + hm->proto.len > 5 && (mg_ncasecmp(hm->proto.buf, "HTTP/", 5) == 0); + if (!is_response && hm->proto.len > 0 && + (!version_prefix_valid || hm->proto.len != 8 || + (hm->proto.buf[5] < '0' || hm->proto.buf[5] > '9') || + (hm->proto.buf[6] != '.') || + (hm->proto.buf[7] < '0' || hm->proto.buf[7] > '9'))) { + return -1; + } + + // 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. + 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; + if (!mg_send(c, " \r\n", 10)) mg_error(c, "OOM"); + 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 + } + if (!mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); +} + +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); + if (!mg_send(c, buf, len) || !mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); + 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("mjs"), 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) { + if (mg_match(*ae, mg_str("*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; + } + } + // 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_closing) { + c->is_resp = 0; + mg_fs_close(fd); + } else { // start serving static content only if not closing, see #3354 + // 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->c->id, path)); + } 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)", path); + 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; + size_t old_len = c->recv.len; + 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 (c->recv.len != old_len) { + // User manipulated received data. Wash our hands + MG_DEBUG(("%lu detaching HTTP handler", c->id)); + c->pfn = NULL; + return; + } + 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) { + if (!c->is_client) mg_http_reply(c, 411, "", ""); + MG_ERROR(("Content length missing from %s", + is_response ? "response" : "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 && !c->is_resp) { + 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; +} + +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + return mg_connect_svc(mgr, url, fn, fn_data, http_cb, NULL); +} + +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; +} + +bool mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + bool ok = true; + new_size = roundup(new_size, io->align); + if (new_size == 0) { + mg_bzero(io->buf, io->size); + mg_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 mg_calloc/mg_free only + void *p = mg_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); + mg_free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + io->len = len; + } else { + ok = false; + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); + } + } + return ok; +} + +bool 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 *) mg_calloc(1, (size_t) len)) != NULL && + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), + result, (size_t) len)) { + mg_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 *) mg_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 *) mg_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/l2.c" +#endif + + + + +#if MG_ENABLE_TCPIP + +// L2 API +void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, + uint16_t *framesize); +uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, + uint8_t *dst, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +// TODO(): ? bool mg_l2_rx(enum mg_l2type type, struct mg_l2opts *opts, uint8_t +// *addr, enum mg_l2proto *proto, struct mg_str *pay, struct mg_str *raw); +uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip); +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr); +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len); +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); + +// clang-format off +extern void mg_l2_eth_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern uint8_t *mg_l2_eth_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_eth_footer(size_t, uint8_t *); +extern bool mg_l2_eth_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +extern struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *); +extern struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype, struct mg_addr *); +extern bool mg_l2_eth_genip6(uint64_t *, uint8_t, struct mg_l2addr *); +extern bool mg_l2_eth_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); +extern uint8_t mg_l2_eth_ip6put(struct mg_l2addr *, uint8_t *); + +extern void mg_l2_ppp_init(struct mg_l2addr *, uint16_t *, uint16_t *); +extern uint8_t *mg_l2_ppp_header(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *); +extern size_t mg_l2_ppp_footer(size_t, uint8_t *); +extern bool mg_l2_ppp_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +extern struct mg_l2addr *mg_l2_ppp_getaddr(uint8_t *); +extern struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype, struct mg_addr *); +#if MG_ENABLE_IPV6 +extern bool mg_l2_ppp_genip6(uint64_t *, uint8_t, struct mg_l2addr *); +extern bool mg_l2_ppp_ip6get(struct mg_l2addr *, uint8_t *, uint8_t); +extern uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *, uint8_t *); +#endif + +typedef void (*l2_init_fn)(struct mg_l2addr *, uint16_t *, uint16_t *); +typedef uint8_t *((*l2_header_fn)(enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *)); +typedef size_t (*l2_footer_fn)(size_t, uint8_t *); +typedef bool (*l2_rx_fn)(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *); +typedef struct mg_l2addr (*(*l2_getaddr_fn)(uint8_t *)); +typedef struct mg_l2addr (*(*l2_mapip_fn)(enum mg_l2addrtype, struct mg_addr *)); +#if MG_ENABLE_IPV6 +typedef bool (*l2_genip6_fn)(uint64_t *, uint8_t, struct mg_l2addr *); +typedef bool (*l2_ip6get_fn)(struct mg_l2addr *, uint8_t *, uint8_t); +typedef uint8_t (*l2_ip6put_fn)(struct mg_l2addr *, uint8_t *); +#endif +// clang-format on + +static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init}; +static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header}; +static const l2_footer_fn l2_footer[] = {mg_l2_eth_footer, mg_l2_ppp_footer}; +static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx}; +static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, + mg_l2_ppp_getaddr}; +static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip}; +#if MG_ENABLE_IPV6 +static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6}; +static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get}; +static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put}; +#endif + +void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, + uint16_t *framesize) { + l2_init[type]((struct mg_l2addr *) addr, mtu, framesize); +} + +uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, + uint8_t *dst, uint8_t *frame) { + return l2_header[type](proto, (struct mg_l2addr *) src, + (struct mg_l2addr *) dst, frame); +} + +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame) { + return l2_footer[type](len, frame); +} + +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + return l2_rx[ifp->l2type](ifp, proto, pay, raw); +} + +uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame) { + return (uint8_t *) l2_getaddr[type](frame); +} + +struct mg_l2addr s_mapip; + +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip) { + return (uint8_t *) l2_mapip[type](addrtype, ip); +} + +#if MG_ENABLE_IPV6 +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr) { + return l2_genip6[type](ip6, prefix_len, (struct mg_l2addr *) addr); +} + +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len) { + return l2_ip6get[type]((struct mg_l2addr *) addr, opts, len); +} +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts) { + return l2_ip6put[type]((struct mg_l2addr *) addr, opts); +} +#endif + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/l2_eth.c" +#endif + + + + + + + +#if MG_ENABLE_TCPIP + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct eth { + uint8_t dst[6]; // Destination MAC address + uint8_t src[6]; // Source MAC address + uint16_t type; // Ethernet type +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +static const uint16_t eth_types[] = { + // order is vital, see l2.h + 0x800, // IPv4 + 0x86dd, // IPv6 + 0x806, // ARP + 0x8863, // PPPoE Discovery Stage + 0x8864 // PPPoE Session Stage +}; + +void mg_l2_eth_init(struct mg_l2addr *l2addr, uint16_t *mtu, + uint16_t *framesize) { + // If MAC is not set, make a random one + if (l2addr->addr.mac[0] == 0 && l2addr->addr.mac[1] == 0 && + l2addr->addr.mac[2] == 0 && l2addr->addr.mac[3] == 0 && + l2addr->addr.mac[4] == 0 && l2addr->addr.mac[5] == 0) { + l2addr->addr.mac[0] = 0x02; // Locally administered, unicast + mg_random(&l2addr->addr.mac[1], sizeof(l2addr->addr.mac) - 1); + MG_INFO( + ("MAC not set. Generated random: %M", mg_print_mac, l2addr->addr.mac)); + } + *mtu = 1500; + *framesize = 1540; +} + +uint8_t *mg_l2_eth_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame) { + struct eth *eth = (struct eth *) frame; + eth->type = mg_htons(eth_types[(unsigned int) proto]); + memcpy(eth->src, src->addr.mac, sizeof(eth->dst)); + memcpy(eth->dst, dst->addr.mac, sizeof(eth->dst)); + return (uint8_t *) (eth + 1); +} + +size_t mg_l2_eth_footer(size_t len, uint8_t *frame) { + struct eth *eth = (struct eth *) frame; + // nothing to do; there is no len field in Ethernet, CRC is hw-calculated + return len + sizeof(*eth); +} + +struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr); + +bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { + struct eth *eth = (struct eth *) raw->buf; + uint16_t type, len; + unsigned int i; + if (raw->len < sizeof(*eth)) return false; // Truncated - runt? + len = (uint16_t) raw->len; + if (ifp->enable_mac_check && + memcmp(eth->dst, ifp->mac, sizeof(eth->dst)) != 0 && + memcmp(eth->dst, mg_l2_eth_mapip(MG_TCPIP_L2ADDR_BCAST, NULL), + sizeof(eth->dst)) != 0) + return false; // TODO(): add multicast addresses + if (ifp->enable_crc32_check && len > sizeof(*eth) + 4) { + uint32_t crc; + len -= 4; // TODO(scaprile): check on bigendian + crc = mg_crc32(0, (const char *) raw->buf, len); + if (memcmp((void *) ((size_t) raw->buf + len), &crc, sizeof(crc))) + return false; + } + pay->buf = (char *) (eth + 1); + pay->len = len - sizeof(*eth); + + type = mg_htons(eth->type); + for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) { + if (type == eth_types[i]) break; + } + if (i == sizeof(eth_types)) { + MG_DEBUG(("Unknown eth type %x", type)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(raw->buf, raw->len >= 32 ? 32 : raw->len); + return false; + } + *proto = (enum mg_l2proto) i; + return true; +} + +struct mg_l2addr *mg_l2_eth_getaddr(uint8_t *frame) { + struct eth *eth = (struct eth *) frame; + return (struct mg_l2addr *) ð->src; +} + +extern struct mg_l2addr s_mapip; + +struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr) { + switch (addrtype) { + case MG_TCPIP_L2ADDR_BCAST: + memset(s_mapip.addr.mac, 0xff, sizeof(s_mapip.addr.mac)); + break; + case MG_TCPIP_L2ADDR_MCAST: { + uint8_t *ip = (uint8_t *) &addr->addr.ip4; + // IP multicast group MAC, RFC-1112 6.4 + s_mapip.addr.mac[0] = 0x01, s_mapip.addr.mac[1] = 0x00, + s_mapip.addr.mac[2] = 0x5E; + s_mapip.addr.mac[3] = ip[1] & 0x7F; // 23 LSb + s_mapip.addr.mac[4] = ip[2]; + s_mapip.addr.mac[5] = ip[3]; + break; + } + case MG_TCPIP_L2ADDR_MCAST6: { + // IPv6 multicast address mapping, RFC-2464 7 + uint8_t *ip = (uint8_t *) &addr->addr.ip6; + s_mapip.addr.mac[0] = 0x33, s_mapip.addr.mac[1] = 0x33; + s_mapip.addr.mac[2] = ip[12], s_mapip.addr.mac[3] = ip[13], + s_mapip.addr.mac[4] = ip[14], s_mapip.addr.mac[5] = ip[15]; + break; + } + } + return &s_mapip; +} + +#if MG_ENABLE_IPV6 +static void meui64(uint8_t *addr, uint8_t *mac) { + *addr++ = *mac++ ^ (uint8_t) 0x02, *addr++ = *mac++, *addr++ = *mac++; + *addr++ = 0xff, *addr++ = 0xfe; + *addr++ = *mac++, *addr++ = *mac++, *addr = *mac; +} + +bool mg_l2_eth_genip6(uint64_t *ip6, uint8_t prefix_len, + struct mg_l2addr *l2addr) { + if (prefix_len > 64) { + MG_ERROR(("Prefix length > 64, UNSUPPORTED")); + return false; + } + ip6[0] = 0; + meui64(((uint8_t *) &ip6[1]), l2addr->addr.mac); // RFC-4291 2.5.4, 2.5.1 + return true; +} + +bool mg_l2_eth_ip6get(struct mg_l2addr *l2addr, uint8_t *opts, uint8_t len) { + if (len != 1) return false; + memcpy(l2addr->addr.mac, opts, 6); + return true; +} + +uint8_t mg_l2_eth_ip6put(struct mg_l2addr *l2addr, uint8_t *opts) { + memcpy(opts, l2addr->addr.mac, 6); + return 1; +} +#endif + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/l2_ppp.c" +#endif + + + + + + + +#if MG_ENABLE_TCPIP + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct ppp { // RFC-1662 + uint8_t addr, ctrl; + uint16_t proto; +}; + +struct lcp { // RFC-1661 + uint8_t code, id, len[2]; +}; + +struct ipcp { // RFC-1332 + uint8_t code; +}; + +struct ipv6cp { // RFC-5072 + uint8_t code; +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + + +void mg_l2_ppp_init(struct mg_l2addr *addr, uint16_t *mtu, + uint16_t *framesize) { + (void) addr; + *mtu = 1500; // 1492 for PPPoE + *framesize = 1540; // *** TODO(scaprile): actual value, check for PPPoE too +} + +uint8_t *mg_l2_ppp_header(enum mg_l2proto proto, struct mg_l2addr *src, + struct mg_l2addr *dst, uint8_t *frame) { + (void) src; + (void) dst; + (void) proto; + return frame; +} + +size_t mg_l2_ppp_footer(size_t len, uint8_t *frame) { + (void) frame; + return len; +} + +bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw) { +#if 0 +if (ppp->addr == MG_PPP_ADDR && ppp->ctrl == MG_PPP_CTRL) { + code = ntohs(ppp->proto); + payload = (uint8_t *) (ppp + 1); +} else { // Address-and-Control-Field-Compressed PPP header + uint16_t *cppp = (uint16_t *) ppp; + code = ntohs(*cppp); + payload = (uint8_t *) (cppp + 1); +} +#endif + *pay = *raw; + *proto = MG_TCPIP_L2PROTO_IPV4; + (void) ifp; + return true; +} + +struct mg_l2addr *mg_l2_ppp_getaddr(uint8_t *frame) { + (void) frame; + return &s_mapip; // bogus +} + +extern struct mg_l2addr s_mapip; + +struct mg_l2addr *mg_l2_ppp_mapip(enum mg_l2addrtype addrtype, + struct mg_addr *addr) { + (void) addrtype; + (void) addr; + return &s_mapip; // bogus +} + +#if MG_ENABLE_IPV6 +bool mg_l2_ppp_genip6(uint64_t *ip6, uint8_t prefix_len, + struct mg_l2addr *addr) { + (void) ip6; + (void) prefix_len; + (void) addr; + return false; +} + +bool mg_l2_ppp_ip6get(struct mg_l2addr *addr, uint8_t *opts, uint8_t len) { + (void) addr; + (void) opts; + (void) len; + return false; +} + +uint8_t mg_l2_ppp_ip6put(struct mg_l2addr *addr, uint8_t *opts) { + (void) addr; + (void) opts; + return 0; +} +#endif + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + + + +int mg_log_level = MG_LL_DEBUG; +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), logs("\r\n", 2), 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), logs("\r\n", 2); +} + +#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}}; + +static bool 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)]); + return mg_send(c, buf, (size_t) (vlen - buf)); +} + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + if (!mqtt_send_header(c, cmd, flags, len)) mg_error(c, "OOM"); +} + +static bool mg_send_u16(struct mg_connection *c, uint16_t value) { + return mg_send(c, &value, sizeof(value)); +} + +static bool mg_send_u32(struct mg_connection *c, uint32_t value) { + return 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 bool 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); + + if (!mg_send(c, buf, (size_t) len)) return false; + for (i = 0; i < nprops; i++) { + if (!mg_send(c, &props[i].id, sizeof(props[i].id))) return false; + switch (mqtt_prop_type_by_id(props[i].id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + if (!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)) + return false; + break; + case MQTT_PROP_TYPE_BYTE: + if (!mg_send(c, &props[i].iv, sizeof(uint8_t))) return false; + break; + case MQTT_PROP_TYPE_SHORT: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].iv))) return false; + break; + case MQTT_PROP_TYPE_INT: + if (!mg_send_u32(c, mg_htonl((uint32_t) props[i].iv))) return false; + break; + case MQTT_PROP_TYPE_STRING: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; + break; + case MQTT_PROP_TYPE_VARIABLE_INT: + len = encode_varint(buf_v, props[i].iv); + if (!mg_send(c, buf_v, (size_t) len)) return false; + break; + } + } + return true; +} + +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; + + if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size || (i + 1) >= end) + return 0; + + memset(prop, 0, sizeof(struct mg_mqtt_prop)); + prop->id = i[0]; + i++, new_pos++; + + switch (mqtt_prop_type_by_id(prop->id)) { + case MQTT_PROP_TYPE_STRING_PAIR: + if (i + 2 >= end) return 0; + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->key.buf = (char *) i + 2; + i += 2 + prop->key.len; + if (i + 2 >= end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len >= end) return 0; + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; + break; + case MQTT_PROP_TYPE_BYTE: + if (i + 1 >= end) return 0; + prop->iv = (uint8_t) i[0]; + new_pos++; + break; + case MQTT_PROP_TYPE_SHORT: + if (i + 2 >= end) return 0; + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + new_pos += sizeof(uint16_t); + break; + case MQTT_PROP_TYPE_INT: + if (i + 4 >= end) return 0; + 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: + if (i + 2 >= end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len >= end) return 0; + new_pos += 2 + prop->val.len; + break; + case MQTT_PROP_TYPE_BINARY_DATA: + if (i + 2 >= end) return 0; + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); + prop->val.buf = (char *) i + 2; + if (i + 2 + prop->val.len >= end) return 0; + 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); + if (i + len >= end) return 0; + new_pos = (len == 0) ? 0 : new_pos + len; + break; + default: + new_pos = 0; + break; + } + + 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', 0, 0}; + hdr[6] = opts->version; + + 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); + } + + // keepalive == 0 means "do not disconnect us!" + if (!mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len) || + !mg_send(c, hdr, sizeof(hdr)) || + !mg_send_u16(c, mg_htons((uint16_t) opts->keepalive))) + goto fail; + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (!mg_send_u16(c, mg_htons((uint16_t) cid.len)) || + !mg_send(c, cid.buf, cid.len)) + goto fail; + + if (hdr[7] & MQTT_HAS_WILL) { + if (c->is_mqtt5 && + !mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props)) + goto fail; + + if (!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)) + goto fail; + } + 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))) + goto fail; + 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))) + goto fail; + return; +fail: + mg_error(c, "OOM"); +} + +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", c->id, (int) opts->topic.len, + (char *) opts->topic.buf, + (int) (opts->message.len <= 10 ? opts->message.len : 10), + (char *) opts->message.buf, opts->message.len <= 10 ? ']' : ' ')); + 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; + if (!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)) + goto fail; + 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; + } + if (!mg_send_u16(c, mg_htons(id))) goto fail; + } + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (opts->message.len > 0 && + !mg_send(c, opts->message.buf, opts->message.len)) + goto fail; + return id; + +fail: + mg_error(c, "OOM"); + return id; +} + +static void mg_mqtt_sub_unsub(struct mg_connection *c, + const struct mg_mqtt_opts *opts, uint8_t cmd) { + uint8_t qos_ = opts->qos & 3; + bool is_sub = cmd == MQTT_CMD_SUBSCRIBE; + size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; + size_t len = 2 + opts->topic.len + 2 + (is_sub ? 1 : 0) + plen; + + if (!mqtt_send_header(c, cmd, 2, (uint32_t) len)) goto fail; + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + if (!mg_send_u16(c, mg_htons(c->mgr->mqtt_id))) goto fail; + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + + if (!mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)) || + !mg_send(c, opts->topic.buf, opts->topic.len)) + goto fail; + if (is_sub && !mg_send(c, &qos_, sizeof(qos_))) goto fail; + return; +fail: + mg_error(c, "OOM"); +} + +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + mg_mqtt_sub_unsub(c, opts, MQTT_CMD_SUBSCRIBE); +} + +void mg_mqtt_unsub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + mg_mqtt_sub_unsub(c, opts, MQTT_CMD_UNSUBSCRIBE); +} + +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", c->id, (int) mm.topic.len, + mm.topic.buf, + (int) (mm.data.len <= 10 ? mm.data.len : 10), mm.data.buf, + mm.data.len <= 10 ? ']' : ' ')); + 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 + + if (!mqtt_send_header(c, + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC + : MQTT_CMD_PUBACK), + 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; + + if (c->is_mqtt5) { + uint16_t zero = 0; + if (!mg_send(c, &zero, sizeof(zero))) goto fail; + } + } + 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 + if (!mqtt_send_header(c, MQTT_CMD_PUBREL, 2, + remaining_len) // MQTT5 3.6.1-1, flags = 2 + || !mg_send(c, &id, sizeof(id))) + goto fail; + 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 + if (!mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; + return; +fail: + mg_error(c, "OOM"); +} + +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); + if (!mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len)) goto fail; + + if (c->is_mqtt5) { + uint8_t zero = 0; + if (!mg_send(c, &zero, sizeof(zero)) // reason code + || !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; + } + return; +fail: + mg_error(c, "OOM"); +} + +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_svc(mgr, url, fn, fn_data, mqtt_cb, NULL); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, opts == NULL ? &empty : opts); + } + 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; + size_t expected = mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + size_t actual = c->send.len - old; + if (actual != expected) { + mg_error(c, "OOM"); + c->send.len = old; + actual = 0; + } + return actual; +} + +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->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->addr.ip, 0, sizeof(addr->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->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->addr.ip, sizeof(ipv4)); + memset(addr->addr.ip, 0, sizeof(addr->addr.ip)); + addr->addr.ip[10] = addr->addr.ip[11] = 255; + memcpy(&addr->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; // sets addr->is_ip6 + 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->addr.ip[n] = (uint8_t) ((val >> 8) & 255); + addr->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->addr.ip[n] = addr->addr.ip[n + 1] = 0; // For trailing :: + } else if (str.buf[i] == '%') { // Scope ID, last in string + if (mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10, + &addr->scope_id, sizeof(uint8_t))) { + addr->is_ip6 = true; + return true; + } else { + return false; + } + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->addr.ip[dc + (14 - n)], &addr->addr.ip[dc], n - dc + 2); + memset(&addr->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 *) mg_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)); + mg_free(c); +} + +struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data) { + struct mg_connection *c = NULL; + if (url == NULL || url[0] == '\0') { + MG_ERROR(("null url")); +#if MG_ENABLE_TCPIP + } else if (mgr->ifp != NULL && mgr->ifp->state != MG_TCPIP_STATE_READY) { + MG_ERROR(("Network is down")); +#endif + } 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; + c->is_tls = (mg_url_is_ssl(url) != 0); + c->pfn = pfn; + c->pfn_data = pfn_data; + mg_call(c, MG_EV_OPEN, (void *) url); + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + mg_resolve(c, url); + } + return c; +} + +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + return mg_connect_svc(mgr, url, fn, fn_data, NULL, NULL); +} + +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", url)); + MG_PROF_FREE(c); + mg_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; + c->is_tls = (mg_url_is_ssl(url) != 0); + mg_call(c, MG_EV_OPEN, NULL); + 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 *) mg_calloc(1, sizeof(*t)); + if (t != NULL) { + flags |= MG_TIMER_AUTODELETE; // We have alloc'ed it, so autodelete + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); + } + 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, mg_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); +#if MG_ENABLE_TCPIP + if (mgr->ifp) mg_tcpip_free(mgr->ifp); +#endif +} + +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 MG_ARCH == MG_ARCH_UNIX + // 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); + MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s", MG_IO_SIZE, + MG_TLS == MG_TLS_NONE ? "none" + : MG_TLS == MG_TLS_MBED ? "MbedTLS" + : MG_TLS == MG_TLS_OPENSSL ? "OpenSSL" + : MG_TLS == MG_TLS_BUILTIN ? "builtin" + : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" + : "custom")); +} + +#if MG_ENABLE_TCPIP +void mg_tcpip_mapip(struct mg_connection *, struct mg_addr *); +#endif +void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { + memcpy(&c->rem, from, sizeof(c->rem)); +#if MG_ENABLE_TCPIP + mg_tcpip_mapip(c, &c->rem); +#endif +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net_builtin.c" +#endif + + + +#if MG_ENABLE_TCPIP +#define MG_EPHEMERAL_PORT_BASE 32768 +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +#ifndef MG_TCPIP_KEEPALIVE_MS +#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms +#endif + +#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing +#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response +#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment +#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection + +#ifndef MG_TCPIP_WIN +#define MG_TCPIP_WIN 6000 // TCP window size +#endif + +struct connstate { + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP timer (see 'ttype' below) + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint16_t dmss; // destination MSS (from TCP opts) + uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address + uint8_t ttype; // Timer type: +#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 + bool fin_rcvd; // We have received FIN from the peer + bool twclosure; // 3-way closure done +}; + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct ip { + uint8_t ver; // Version + uint8_t tos; // Unused + uint16_t len; // Datagram length + uint16_t id; // Unused + uint16_t frag; // Fragmentation +#define IP_FRAG_OFFSET_MSK 0x1fff +#define IP_MORE_FRAGS_MSK 0x2000 + 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 label[3]; // Flow label + uint16_t plen; // Payload length + uint8_t next; // Upper level protocol + uint8_t hops; // Hop limit + uint64_t src[2]; // Source IP + uint64_t dst[2]; // Destination IP +}; + +struct icmp { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct icmp6 { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct ndp_na { + uint8_t res[4]; // R S O, reserved + uint64_t addr[2]; // Target address +}; + +struct ndp_ra { + uint8_t cur_hop_limit; + uint8_t flags; // M,O,Prf,Resvd + uint16_t router_lifetime; + uint32_t reachable_time; + uint32_t retrans_timer; +}; + +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_STDFLAGS 0x3f + // #define TH_ECE 0x40 // not part of TCP but RFC-3168 (ECN) + // #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[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; +}; + +struct dhcp6 { + union { + uint8_t type; + uint32_t xid; + }; + uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; +}; + +struct pseudoip { + uint32_t src; // Source IP + uint32_t dst; // Destination IP + uint8_t zero; + uint8_t proto; // Upper level protocol + uint16_t len; // Datagram length +}; + +struct pseudoip6 { + uint64_t src[2]; // Source IP + uint64_t dst[2]; // Destination IP + uint32_t plen; // Payload length + uint8_t zero[3]; + uint8_t next; // Upper level protocol +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +// pkt is 8-bit aligned, pointers to headers hint compilers to generate +// byte-copy code for micros with alignment constraints +struct pkt { + struct mg_str raw; // Raw packet data + struct mg_str pay; // Payload data + uint8_t *l2; // Ethernet, PPP [, etc] frame data + struct arp *arp; + struct ip *ip; + struct ip6 *ip6; + struct icmp *icmp; + struct icmp6 *icmp6; + struct tcp *tcp; + struct udp *udp; + struct dhcp *dhcp; + struct dhcp6 *dhcp6; +}; + +// L2 API +void mg_l2_init(enum mg_l2type type, uint8_t *addr, uint16_t *mtu, + uint16_t *framesize); +uint8_t *mg_l2_header(enum mg_l2type type, enum mg_l2proto proto, uint8_t *src, + uint8_t *dst, uint8_t *frame); +size_t mg_l2_footer(enum mg_l2type type, size_t len, uint8_t *frame); +bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, + struct mg_str *pay, struct mg_str *raw); +uint8_t *mg_l2_getaddr(enum mg_l2type type, uint8_t *frame); +uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype, + struct mg_addr *ip); +#if MG_ENABLE_IPV6 +bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len, + uint8_t *addr); +bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts, + uint8_t len); +uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts); +#endif + +static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { +#if MG_ENABLE_PROFILE + const char *names[] = {"TCPIP_EV_ST_CHG", "TCPIP_EV_DHCP_DNS", + "TCPIP_EV_DHCP_SNTP", "TCPIP_EV_ARP", + "TCPIP_EV_TIMER_1S", "TCPIP_EV_WIFI_SCAN_RESULT", + "TCPIP_EV_WIFI_SCAN_END", "TCPIP_EV_WIFI_CONNECT_ERR", + "TCPIP_EV_DRIVER", "TCPIP_EV_USER"}; + if (ev != MG_TCPIP_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 (ifp->pfn != NULL) ifp->pfn(ifp, ev, 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->pay.buf[pkt->pay.len] - (char *) p)); +} + +// NOTE(): DOES NOT handle reentries after odd length, use last +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((uint16_t) ((uint16_t) ~sum & 0xffff)); +} + +static uint16_t ipcsum(const void *buf, size_t len) { + uint32_t sum = csumup(0, buf, len); + return csumfin(sum); +} + +static bool ipcsum_ok(const void *d) { + struct ip *ip = (struct ip *) d; + return (ipcsum(d, (ip->ver & 0x0F) * 4) == 0); +} + +static bool icmpcsum_ok(const void *d, size_t len) { + return (ipcsum(d, len) == 0); +} + +static uint16_t pcsum(void *d, void *p, size_t plen) { + uint32_t sum; + struct ip *ip = (struct ip *) d; + struct pseudoip pip; + pip.src = ip->src; + pip.dst = ip->dst; + pip.zero = 0; + pip.proto = ip->proto; + pip.len = mg_htons((uint16_t) plen); + sum = csumup(0, &pip, sizeof(pip)); // even length + sum = csumup(sum, p, plen); // possibly odd length: last + return csumfin(sum); +} + +static bool udpcsum_ok(void *d, void *u) { + struct udp *udp = (struct udp *) u; + if (udp->csum == 0) return true; + if (udp->csum == 0xFFFF) udp->csum = 0; + return (pcsum(d, u, (size_t) mg_ntohs(udp->len)) == 0); +} + +static bool tcpcsum_ok(void *d, void *t) { + struct ip *ip = (struct ip *) d; + return (pcsum(d, t, (size_t)(mg_ntohs(ip->len) - (ip->ver & 0x0F) * 4)) == 0); +} + +#if MG_ENABLE_IPV6 +static uint16_t p6csum(void *d, void *p, size_t plen) { + uint32_t sum; + struct ip6 *ip6 = (struct ip6 *) d; + struct pseudoip6 pip6; + pip6.src[0] = ip6->src[0], pip6.src[1] = ip6->src[1]; + pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1]; + pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0; + pip6.plen = mg_htonl((uint32_t) plen); + pip6.next = ip6->next; + sum = csumup(0, &pip6, sizeof(pip6)); // even length + sum = csumup(sum, p, plen); // possibly odd length: last + return csumfin(sum); +} + +static bool udp6csum_ok(void *d, void *u) { + struct udp *udp = (struct udp *) u; + if (udp->csum == 0) return false; // mandatory in IPv6 + if (udp->csum == 0xFFFF) udp->csum = 0; + return (p6csum(d, u, (size_t) mg_ntohs(udp->len)) == 0); +} +static bool tcp6csum_ok(void *d, void *t) { + struct ip6 *ip6 = (struct ip6 *) d; + return (p6csum(d, t, (size_t) mg_ntohs(ip6->plen)) == 0); +} +static bool icmp6csum_ok(void *d, void *i) { + struct ip6 *ip6 = (struct ip6 *) d; + return (p6csum(d, i, (size_t) mg_ntohs(ip6->plen)) == 0); +} + +static void ip6sn(uint64_t *addr, uint64_t *sn_addr) { + // Build solicited-node multicast address from a given unicast IP + // RFC-4291 2.7 + uint8_t *sn = (uint8_t *) sn_addr; + memset(sn_addr, 0, 16); + sn[0] = 0xff; + sn[1] = 0x02; + sn[11] = 0x01; + sn[12] = 0xff; + sn[13] = ((uint8_t *) addr)[13]; + sn[14] = ((uint8_t *) addr)[14]; + sn[15] = ((uint8_t *) addr)[15]; +} + +static const struct mg_addr ip6_allrouters = { + .addr = {.ip = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, + .port = 0, + .scope_id = 0, + .is_ip6 = true}; +static const struct mg_addr ip6_allnodes = { + .addr = {.ip = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, + .port = 0, + .scope_id = 0, + .is_ip6 = true}; + +#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1]) +#endif + +static void settmout(struct mg_connection *c, uint8_t type) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + struct connstate *s = (struct connstate *) (c + 1); + unsigned n = type == MIP_TTYPE_ACK ? MG_TCPIP_ACK_MS + : type == MIP_TTYPE_ARP ? MG_TCPIP_ARP_MS + : type == MIP_TTYPE_SYN ? MG_TCPIP_SYN_MS + : type == MIP_TTYPE_FIN ? MG_TCPIP_FIN_MS + : MG_TCPIP_KEEPALIVE_MS; + if (s->ttype == MIP_TTYPE_FIN) return; // skip if 3-way closing + s->timer = ifp->now + n; + s->ttype = type; + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); +} + +static size_t driver_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; +} + +// RFC826, ARP assumes Ethernet MAC addresses +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct arp *arp = (struct arp *) mg_l2_header( + ifp->l2type, MG_TCPIP_L2PROTO_ARP, ifp->mac, + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), l2p); + 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)); + if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); + driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); +} + +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)); + if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2 + MG_INFO((" MAC: %M", mg_print_mac, ifp->mac)); + } else if (ifp->state == MG_TCPIP_STATE_IP) { + if (ifp->gw != 0) + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request + } else if (ifp->state == MG_TCPIP_STATE_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 *l2_dst, uint8_t proto, + uint32_t ip_src, uint32_t ip_dst, size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip *ip = (struct ip *) mg_l2_header(ifp->l2type, MG_TCPIP_L2PROTO_IPV4, + ifp->mac, l2_dst, l2p); + memset(ip, 0, sizeof(*ip)); + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = mg_htons(0x4000); // 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; +} + +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen); +#endif + +static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, + const void *buf, size_t len) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + size_t l2_len; + struct ip *ip = NULL; + struct udp *udp; +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; + if (ip_dst->is_ip6) { + ip6 = tx_ip6(ifp, l2_dst, 17, ip_src->addr.ip6, ip_dst->addr.ip6, + len + sizeof(struct udp)); + udp = (struct udp *) (ip6 + 1); + l2_len = sizeof(*ip6) + sizeof(*udp) + len; + } else +#endif + { + ip = tx_ip(ifp, l2_dst, 17, ip_src->addr.ip4, ip_dst->addr.ip4, + len + sizeof(struct udp)); + udp = (struct udp *) (ip + 1); + l2_len = sizeof(*ip) + sizeof(*udp) + len; + } + udp->sport = ip_src->port; + udp->dport = ip_dst->port; + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); + udp->csum = 0; + memmove(udp + 1, buf, len); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + udp->csum = p6csum(ip6, udp, sizeof(*udp) + len); + } else +#endif + { + udp->csum = pcsum(ip, udp, sizeof(*udp) + len); + } + l2_len = mg_l2_footer(ifp->l2type, l2_len, l2p); + return (driver_output(ifp, l2_len) == l2_len); +} + +static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint32_t ip_src, + uint16_t sport, uint32_t ip_dst, uint16_t dport, + const void *buf, size_t len) { + struct mg_addr ips, ipd; + memset(&ips, 0, sizeof(ips)); + ips.addr.ip4 = ip_src; + ips.port = sport; + memset(&ipd, 0, sizeof(ipd)); + ipd.addr.ip4 = ip_dst; + ipd.port = dport; + return tx_udp(ifp, l2_dst, &ips, &ipd, buf, len); +} + +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *l2_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 + // NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes + 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, 6); + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); + memcpy(&dhcp.options, opts, optslen); + if (ciaddr) dhcp.ciaddr = ip_src; + tx_udp4(ifp, l2_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, + sizeof(dhcp)); +} + +// 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 extra = (uint8_t) ((ifp->enable_req_dns ? 1 : 0) + + (ifp->enable_req_sntp ? 1 : 0)); + size_t len = strlen(ifp->dhcp_name); + size_t olen = 21 + len + extra + 2 + 1; // Total length of options +#define OPTS_MAXLEN (21 + sizeof(ifp->dhcp_name) + 2 + 2 + 1) + uint8_t opts[OPTS_MAXLEN]; // Allocate options (max size possible) + uint8_t *p = opts; + assert(olen <= sizeof(opts)); + memset(opts, 0, sizeof(opts)); + *p++ = 53, *p++ = 1, *p++ = 3; // Type: DHCP request + *p++ = 54, *p++ = 4, memcpy(p, &ip_srv, 4), p += 4; // DHCP server ID + *p++ = 50, *p++ = 4, memcpy(p, &ip_req, 4), p += 4; // Requested IP + *p++ = 12, *p++ = (uint8_t) (len & 255); // DHCP host + memcpy(p, ifp->dhcp_name, len), p += len; // name + *p++ = 55, *p++ = 2 + extra, *p++ = 1, *p++ = 3; // GW, MASK + if (ifp->enable_req_dns) *p++ = 6; // DNS + if (ifp->enable_req_sntp) *p++ = 42; // SNTP + *p++ = 255; // End of options + // assert((size_t) (p - opts) < olen); + tx_dhcp(ifp, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 0, + 0xffffffff, opts, olen, 0); + 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 *l2_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, l2_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, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 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 && pkt->arp->spa == c->rem.addr.ip4) break; +#if MG_ENABLE_IPV6 + if (c->is_arplooking && pkt->icmp6 && pkt->icmp6->type == 136) { + struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1); + if (MG_IP6MATCH(na->addr, c->rem.addr.ip6)) break; + } +#endif + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport && + !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) // IP or IPv6 to same dest + break; + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && + !(c->loc.is_ip6 ^ (pkt->ip6 != NULL)) && lsn == (bool) c->is_listening && + (lsn || c->rem.port == pkt->tcp->sport)) + break; + } + return c; +} + +static void l2addr_resolved(struct mg_connection *c); +static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, + bool is_udp, struct pkt *pkt); + +// RFC826, ARP assumes Ethernet MAC addresses +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_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa)); + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct arp *arp = + (struct arp *) mg_l2_header(ifp->l2type, MG_TCPIP_L2PROTO_ARP, ifp->mac, + mg_l2_getaddr(ifp->l2type, pkt->l2), l2p); + *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)); + driver_output(ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, arp + 1), l2p)); + } else if (pkt->arp->op == mg_htons(2)) { + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa)); + if (pkt->arp->spa == ifp->gw) { + // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); + ifp->gw_ready = true; + if (ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; + onstatechange(ifp); + } + } 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.addr.ip4, mg_print_mac, s->mac)); + c->is_arplooking = 0; + l2addr_resolved(c); + } + } + } +} + +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + size_t plen = pkt->pay.len; + if (!icmpcsum_ok(pkt->icmp, sizeof(struct icmp) + plen)) return; + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { + size_t l2_max_overhead = ifp->framesize - ifp->mtu; + size_t hlen = sizeof(struct ip) + sizeof(struct icmp); + size_t room = ifp->tx.len - hlen - l2_max_overhead; + uint8_t *l2addr; + struct ip *ip; + struct icmp *icmp; + struct mg_addr ips; + ips.addr.ip4 = pkt->ip->src; + ips.is_ip6 = false; + if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL) + return; // safety net for lousy networks + if (plen > room) plen = room; + ip = tx_ip(ifp, l2addr, 1, ifp->ip, pkt->ip->src, sizeof(*icmp) + plen); + icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0 + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); + driver_output(ifp, mg_l2_footer(ifp->l2type, hlen + plen, l2p)); + } +} + +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip); + +static bool dhcp_opt_len_ok(uint8_t len, uint8_t *p, uint8_t *end) { + return (len >= 4 && (len & 3) == 0 && p + 6 < end); +} + +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->pay.buf[pkt->pay.len]; + if (end < p) return; // options are optional, check min header length + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; + while (p + 1 < end && p[0] != 255) { // Parse options, get #1; RFC-2132 9 + if (p[0] == 1 && p[1] == 4 && p + 6 < end) { // Mask, 3.3 + memcpy(&mask, p + 2, sizeof(mask)); + } else if (p[0] == 3 && dhcp_opt_len_ok(p[1], p, end)) { // GW, 3.5 + memcpy(&gw, p + 2, sizeof(gw)); + ip = pkt->dhcp->yiaddr; + } else if (ifp->enable_req_dns && p[0] == 6 && + dhcp_opt_len_ok(p[1], p, end)) { // DNS, 3.8 + memcpy(&dns, p + 2, sizeof(dns)); + } else if (ifp->enable_req_sntp && p[0] == 42 && + dhcp_opt_len_ok(p[1], p, end)) { // SNTP, 8.3 + 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 + uint64_t rand; + 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, mg_l2_getaddr(ifp->l2type, pkt->l2), + sizeof(ifp->gwmac)); + ifp->gw_ready = true; // NOTE(): actual gw ARP won't retry now + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; + ifp->state = MG_TCPIP_STATE_IP; // BOUND state + mg_random(&rand, sizeof(rand)); + srand((unsigned int) (rand + mg_millis())); + if (ifp->enable_req_dns && dns != 0) { + setdns4(ifp, &dns); + 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 *mac; + uint8_t op = 0, *p = pkt->dhcp->options, + *end = (uint8_t *) &pkt->pay.buf[pkt->pay.len]; + // NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + if (end < p) return; // options are optional, check min header length + 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, 0, // 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 + }; + opts[2] = msg; + 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; + mac = mg_l2_getaddr(ifp->l2type, pkt->l2); + if (ifp->enable_get_gateway) { + ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's + memcpy(ifp->gwmac, mac, sizeof(ifp->gwmac)); + } + tx_udp4(ifp, mac, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr, + mg_htons(68), &res, sizeof(res)); + } +} + +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip6 *ip6 = (struct ip6 *) mg_l2_header( + ifp->l2type, MG_TCPIP_L2PROTO_IPV6, ifp->mac, l2_dst, l2p); + memset(ip6, 0, sizeof(*ip6)); + ip6->ver = 0x60; // Version 6, traffic class 0 + ip6->plen = mg_htons((uint16_t) plen); + ip6->next = next; + ip6->hops = 255; // NDP requires max + ip6->src[0] = *ip_src++; + ip6->src[1] = *ip_src; + ip6->dst[0] = *ip_dst++; + ip6->dst[1] = *ip_dst; + return ip6; +} + +static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src, + uint64_t *ip_dst, uint8_t type, uint8_t code, + const void *buf, size_t len) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip6 *ip6; + struct icmp6 *icmp6; + ip6 = tx_ip6(ifp, l2_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len); + icmp6 = (struct icmp6 *) (ip6 + 1); + memset(icmp6, 0, sizeof(*icmp6)); // Set csum to 0 + icmp6->type = type; + icmp6->code = code; + memcpy(icmp6 + 1, buf, len); // Copy payload + icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 + icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len); + driver_output( + ifp, mg_l2_footer(ifp->l2type, sizeof(*ip6) + sizeof(*icmp6) + len, l2p)); +} + +// Neighbor Discovery Protocol, RFC-4861 +// Neighbor Advertisement, 4.4 +static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + uint64_t *ip_src, uint64_t *ip_dst, bool solicited, + uint8_t *l2) { + uint8_t data[20 + 16]; // NOTE(): optional len upto 2 hw addr + memset(data, 0, sizeof(data)); + data[0] = solicited ? 0x60 : 0x20; // O + S + memcpy(data + 4, ip_src, 16); // Target address + data[20] = 2; // 4.6.1, target hwaddr + data[21] = mg_l2_ip6put(ifp->l2type, l2, data + 22); // option length / 8 + tx_icmp6(ifp, l2_dst, ip_src, ip_dst, 136, 0, data, + 20 + (size_t) (8 * data[21])); +} + +static void onstate6change(struct mg_tcpip_if *ifp); + +static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1); + uint8_t *opts = (uint8_t *) (na + 1); + if ((na->res[0] & 0x40) == 0) return; // not "solicited" + if (*opts++ != 2) return; // no target hwaddr + MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr)); + if (MG_IP6MATCH(na->addr, ifp->gw6)) { + // Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY + uint8_t len = *opts++; // check valid hwaddr and get it + if (!mg_l2_ip6get(ifp->l2type, ifp->gw6mac, opts, len)) return; + ifp->gw6_ready = true; + if (ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; + onstate6change(ifp); + } + } else { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c != NULL && c->is_arplooking) { + struct connstate *s = (struct connstate *) (c + 1); + uint8_t len = *opts++; // check valid hwaddr and get it + if (!mg_l2_ip6get(ifp->l2type, s->mac, opts, len)) return; + MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6, + &c->rem.addr.ip6, mg_print_mac, ifp->l2type, s->mac)); + c->is_arplooking = 0; + l2addr_resolved(c); + } + } +} + +// Neighbor Solicitation, 4.3 +static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint64_t target[2]; + if (pkt->pay.len < sizeof(target)) return; + memcpy(target, pkt->pay.buf + 4, sizeof(target)); + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) { + uint64_t req[2]; // requester address + uint8_t l2[sizeof(struct mg_l2addr)]; + uint8_t len, *opts = (uint8_t *) pkt->pay.buf + 20; + if (*opts++ != 1) return; // no requester hwaddr (source) + len = *opts++; // check valid hwaddr and get it + if (!mg_l2_ip6get(ifp->l2type, l2, opts, len)) return; + req[0] = pkt->ip6->src[0], req[1] = pkt->ip6->src[1]; // align to 64-bit + tx_ndp_na(ifp, l2, target, req, true, ifp->mac); + } +} + +// - use solicited node multicast to resolve a l2 address (l2_addr = NULL) +// - use unicast to verify presence (l2_addr = neighbor l2 address) +static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst, + uint8_t *l2_addr) { + uint8_t payload[4 + 16 + 16]; // NOTE(): 16 --> optional len upto 2 hw addr + uint64_t ip_unspec[2] = {0, 0}; + size_t payload_len = 20; + bool mcast = (l2_addr == NULL); + uint64_t ip_mcast[2] = {0, 0}; + uint8_t *l2 = l2_addr; + + memset(payload, 0, sizeof(payload)); + memcpy(payload + 4, ip_dst, 16); + if (mcast) { + struct mg_addr ipd; + ip6sn(ip_dst, ip_mcast); + ipd.addr.ip6[0] = ip_mcast[0], ipd.addr.ip6[1] = ip_mcast[1], + ipd.is_ip6 = true; + l2 = mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, &ipd); + } + payload_len = 20; + // TODO(robertc2000): using only link-local IP addr for now + // We might consider to add an option to use either link-local or global IP + if (!MG_IP6MATCH(ifp->ip6ll, ip_unspec)) { + payload[20] = 1; // 4.6.1, source hwaddr; option length in 8-byte units + payload[21] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 22); + payload_len += 8 * payload[21]; + } + tx_icmp6(ifp, l2, ifp->ip6ll, mcast ? ip_mcast : ip_dst, 135, 0, payload, + payload_len); +} + +// Router Solicitation, 4.1 +static void tx_ndp_rs(struct mg_tcpip_if *ifp) { + uint8_t payload[4 + 16]; // reserved + optional len upto 2 hw addr NOTE() + size_t payload_len = 4; + uint64_t ip_unspec[2] = {0, 0}; + + memset(payload, 0, sizeof(payload)); + + if (!MG_IP6MATCH(ifp->ip6ll, ip_unspec)) { + payload[4] = 1; // 4.6.1, source hwaddr; option length in 8-byte units + payload[5] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 6); + payload_len += 8 * payload[5]; + } + tx_icmp6(ifp, + mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allrouters), + ifp->ip6ll, (uint64_t *) ip6_allrouters.addr.ip6, 133, 0, payload, + payload_len); + MG_DEBUG(("NDP Router Solicitation sent")); +} + +static void fill_prefix(uint8_t *dst, uint8_t *src, uint8_t len) { + uint8_t full = len / 8; + uint8_t rem = len % 8; + if (full > 0) memcpy(dst, src, full); + if (rem > 0) { + uint8_t mask = (uint8_t) (0xFF << (8 - rem)); + dst[full] |= src[full] & mask; // mg_l2_genip6() zeroes dst + } +} + +static bool match_prefix(uint8_t *new, uint8_t *cur, uint8_t len) { + uint8_t full = len / 8; + uint8_t rem = len % 8; + if (full > 0 && memcmp(cur, new, full) != 0) return false; + if (rem > 0) { + uint8_t mask = (uint8_t) (0xFF << (8 - rem)); + if (cur[full] != (new[full] & mask)) return false; + } + return true; +} + +static bool fill_global(struct mg_tcpip_if *ifp, uint8_t *prefix, + uint8_t prefix_len) { + if (!mg_l2_genip6(ifp->l2type, ifp->ip6, prefix_len, ifp->mac)) return false; + fill_prefix((uint8_t *) ifp->ip6, prefix, prefix_len); + fill_prefix(ifp->prefix, prefix, prefix_len); + ifp->prefix_len = prefix_len; + return true; +} + +// Router Advertisement, 4.2 +static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (pkt->pay.len < 12) return; + struct ndp_ra *ra = (struct ndp_ra *) (pkt->icmp6 + 1); + uint8_t *opts = (uint8_t *) (ra + 1); + size_t opt_left = pkt->pay.len - 12; + bool gotl2addr = false, gotprefix = false; + uint8_t l2[sizeof(struct mg_l2addr)]; + uint32_t mtu = 0; + uint8_t *prefix, prefix_len; + + if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_DEBUG(("Received NDP RA")); // fill gw6 address + // parse options + while (opt_left >= 2) { + uint8_t type = opts[0], len = opts[1]; + size_t length = (size_t) len * 8; + if (length == 0 || length > opt_left) break; // malformed + if (type == 1 && length >= 8) { + // Received router's L2 address + if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break; + gotl2addr = true; + } else if (type == 5 && length >= 8) { + // process MTU if available + mtu = mg_ntohl(*(uint32_t *) (opts + 4)); + } else if (type == 3 && length >= 32) { + // process prefix, 4.6.2 + uint8_t pfx_flags = opts[3]; // L=0x80, A=0x40 + uint32_t valid = mg_ntohl(*(uint32_t *) (opts + 4)); + uint32_t pref_lifetime = mg_ntohl(*(uint32_t *) (opts + 8)); + prefix_len = opts[2]; + prefix = opts + 16; + + // TODO (robertc2000): handle prefix options if necessary + (void) pfx_flags; + (void) valid; + (void) pref_lifetime; + + gotprefix = true; + } + opts += length; + opt_left -= length; + } + + // fill prefix and global + if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return; + ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1]; + if (gotl2addr) { + memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac)); + ifp->state6 = MG_TCPIP_STATE_READY; + ifp->gw6_ready = true; + } + if (mtu != 0 && ifp->mtu != mtu) { + MG_ERROR( + ("got an MTU: %u, that differs from the configured one. " + "All devices in an IPv6 network should have the same MTU, " + "using the router's instead...", + mtu)); + ifp->mtu = (uint16_t) mtu; + } + if (ifp->state6 != MG_TCPIP_STATE_READY) { + tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution + ifp->state6 = MG_TCPIP_STATE_IP; + } + onstate6change(ifp); + } +} + +static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (!icmp6csum_ok(pkt->ip6, pkt->icmp6)) return; + switch (pkt->icmp6->type) { + case 128: { // Echo Request, RFC-4443 4.1 + uint64_t target[2]; + target[0] = pkt->ip6->dst[0], target[1] = pkt->ip6->dst[1]; + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) { + size_t l2_max_overhead = ifp->framesize - ifp->mtu; + size_t hlen = sizeof(struct ip6) + sizeof(struct icmp6); + size_t room = ifp->tx.len - hlen - l2_max_overhead, plen = pkt->pay.len; + struct mg_addr ips; + uint8_t *l2addr; + + ips.addr.ip6[0] = pkt->ip6->src[0], ips.addr.ip6[1] = pkt->ip6->src[1]; + ips.is_ip6 = true; + if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL) + return; // safety net for lousy networks + if (plen > room) plen = room; // Copy (truncated) RX payload to TX + // Echo Reply, 4.2 + tx_icmp6(ifp, l2addr, target, ips.addr.ip6, 129, 0, pkt->pay.buf, plen); + } + } break; + case 134: // Router Advertisement + rx_ndp_ra(ifp, pkt); + break; + case 135: // Neighbor Solicitation + rx_ndp_ns(ifp, pkt); + break; + case 136: // Neighbor Advertisement + rx_ndp_na(ifp, pkt); + break; + } +} + +static void onstate6change(struct mg_tcpip_if *ifp) { + if (ifp->state6 == MG_TCPIP_STATE_READY) { + MG_INFO(("READY, IP: %M", mg_print_ip6, &ifp->ip6)); + MG_INFO((" GW: %M", mg_print_ip6, &ifp->gw6)); + if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2 + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); + } else if (ifp->state6 == MG_TCPIP_STATE_IP) { + if (ifp->gw6[0] != 0 || ifp->gw6[1] != 0) + tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution + } else if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll)); + } + if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN) + mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6); +} +#endif + +static uint8_t *tcpip_mapip(struct mg_tcpip_if *ifp, struct mg_addr *ip) { +#if MG_ENABLE_IPV6 + if (ip->is_ip6) { + if (MG_IP6MATCH(ip->addr.ip6, ip6_allnodes.addr.ip6)) // local broadcast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, + (struct mg_addr *) &ip6_allnodes); + if (*ip->addr.ip == 0xFF) // multicast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, ip); + } else +#endif + { // global/local broadcast + if (ip->addr.ip4 == 0xffffffff || ip->addr.ip4 == (ifp->ip | ~ifp->mask)) + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL); + if ((*ip->addr.ip & 0xE0) == 0xE0) // 224 ~ 239 = E0 ~ EF, multicast + return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST, ip); + } + return NULL; +} + +static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, + bool is_udp, struct pkt *pkt) { + uint8_t *l2addr; + if (is_udp && (l2addr = tcpip_mapip(ifp, rem)) != NULL) + return l2addr; // broadcast or multicast +#if MG_ENABLE_IPV6 + if (rem->is_ip6) { + if (rem->addr.ip6[0] == ifp->ip6ll[0] || + match_prefix((uint8_t *) rem->addr.ip6, ifp->prefix, ifp->prefix_len)) + return mg_l2_getaddr(ifp->l2type, pkt->l2); // same LAN, get from frame + if (ifp->gw6_ready) // use the router + return ifp->gw6mac; // ignore source address in frame + } else +#endif + { + if (ifp->ip != 0 && ((rem->addr.ip4 & ifp->mask) == (ifp->ip & ifp->mask))) + return mg_l2_getaddr(ifp->l2type, pkt->l2); // same LAN, get from frame + if (ifp->gw_ready) // use the router + return ifp->gwmac; // ignore source address in frame + } + MG_ERROR(("%M %s: No way back, can't respond", mg_print_ip_port, rem, + is_udp ? "UDP" : "TCP")); + return NULL; +} + +static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + struct connstate *s; + uint8_t *l2addr; + if (c == NULL) return false; // No UDP listener on this port + s = (struct connstate *) (c + 1); + c->rem.port = pkt->udp->sport; +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() + if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; + c->rem.addr.ip6[0] = pkt->ip6->src[0], + c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + } else +#endif + { + if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; + c->rem.addr.ip4 = pkt->ip->src; + } + if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL) + return false; // safety net for lousy networks + memcpy(s->mac, l2addr, 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); + } + return true; +} + +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, + uint8_t flags, uint32_t seq, uint32_t ack, const void *buf, + size_t len) { + uint8_t *l2p = (uint8_t *) ifp->tx.buf; + struct ip *ip = NULL; + struct tcp *tcp; + uint16_t opts[4 / 2]; + size_t hlen = sizeof(*tcp); +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; +#endif + + // Handle any options first, here, to determine header size + if (flags & TH_SYN) { // Send MSS + uint16_t mss; +#if MG_ENABLE_IPV6 // RFC-9293 3.7.1; RFC-6691 2 + mss = (uint16_t) (ifp->mtu - 60); +#else + mss = (uint16_t) (ifp->mtu - 40); +#endif + opts[0] = mg_htons(0x0204); // RFC-9293 3.2 + opts[1] = mg_htons(mss); + hlen += sizeof(opts); // always whole number of 32-bit words + } + +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + ip6 = tx_ip6(ifp, l2_dst, 6, ip_src->addr.ip6, ip_dst->addr.ip6, hlen + len); + tcp = (struct tcp *) (ip6 + 1); + } else +#endif + { + ip = tx_ip(ifp, l2_dst, 6, ip_src->addr.ip4, ip_dst->addr.ip4, hlen + len); + tcp = (struct tcp *) (ip + 1); + } + memset(tcp, 0, sizeof(*tcp)); + memmove(tcp + 1, opts, hlen - sizeof(*tcp)); // copy opts if any + if (buf != NULL && len) memmove((uint8_t *)tcp + hlen, buf, len); + tcp->sport = ip_src->port; + tcp->dport = ip_dst->port; + tcp->seq = seq; + tcp->ack = ack; + tcp->flags = flags; + tcp->win = mg_htons(MG_TCPIP_WIN); + tcp->off = (uint8_t) (hlen / 4 << 4); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + tcp->csum = p6csum(ip6, tcp, hlen + len); + } else +#endif + { + tcp->csum = pcsum(ip, tcp, hlen + len); + } + MG_VERBOSE(("TCP %M -> %M fl %x len %u", mg_print_ip_port, ip_src, + mg_print_ip_port, ip_dst, tcp->flags, len)); + return driver_output( + ifp, mg_l2_footer(ifp->l2type, PDIFF(l2p, tcp) + hlen + len, l2p)); +} + +static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt, + uint8_t flags, uint32_t seqno) { + uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len + + ((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0)); + struct mg_addr ips, ipd; + uint8_t *l2addr; + memset(&ips, 0, sizeof(ips)); + memset(&ipd, 0, sizeof(ipd)); + if (pkt->ip != NULL) { + ips.addr.ip4 = pkt->ip->dst; + ipd.addr.ip4 = pkt->ip->src; + } else { + ips.addr.ip6[0] = pkt->ip6->dst[0], ips.addr.ip6[1] = pkt->ip6->dst[1]; + ipd.addr.ip6[0] = pkt->ip6->src[0], ipd.addr.ip6[1] = pkt->ip6->src[1]; + ips.is_ip6 = true; + ipd.is_ip6 = true; + } + ips.port = pkt->tcp->dport; + ipd.port = pkt->tcp->sport; + if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL) + return 0; // safety net for lousy networks + return tx_tcp(ifp, l2addr, &ips, &ipd, flags, seqno, ackno, NULL, 0); +} + +static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) { + return tx_tcp_ctrlresp(ifp, pkt, toack ? TH_RST : (TH_RST | TH_ACK), + toack ? pkt->tcp->ack : 0); +} + +static struct mg_connection *accept_conn(struct mg_connection *lsn, + struct pkt *pkt, uint16_t mss) { + struct connstate *s; + uint8_t *l2addr; + struct mg_connection *c = mg_alloc_conn(lsn->mgr); + if (c == NULL) { + MG_ERROR(("OOM")); + return NULL; + } + s = (struct connstate *) (c + 1); + s->dmss = mss; // from options in client SYN + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); +#if MG_ENABLE_IPV6 + if (lsn->loc.is_ip6) { + c->rem.addr.ip6[0] = pkt->ip6->src[0], + c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + c->loc.addr.ip6[0] = c->mgr->ifp->ip6[0], + c->loc.addr.ip6[1] = c->mgr->ifp->ip6[1], c->loc.is_ip6 = true; + // TODO(): compare lsn to link-local, or rem as link-local: use ll instead + } else +#endif + { + c->rem.addr.ip4 = pkt->ip->src; + c->loc.addr.ip4 = c->mgr->ifp->ip; + } + c->rem.port = pkt->tcp->sport; + c->loc.port = lsn->loc.port; + if ((l2addr = get_return_l2addr(lsn->mgr->ifp, &c->rem, false, pkt)) == + NULL) { + free(c); // safety net for lousy networks, not actually needed + return NULL; // as path has already been checked at SYN (sending SYN+ACK) + } + memcpy(s->mac, l2addr, sizeof(s->mac)); + settmout(c, MIP_TTYPE_KEEPALIVE); + 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->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + return c; +} + +static size_t trim_len(struct mg_connection *c, size_t len) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t l2_max_overhead = ifp->framesize - ifp->mtu; + size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24; // we don't send options + size_t tcp_max_h_len = 60 /* RFC-9293 3.7.1; RFC-6691 2 */, udp_h_len = 8; + size_t max_headers_len = + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); + size_t min_mtu = c->rem.is_ip6 ? 1280 /* RFC-8200, IPv6 minimum */ + : c->is_udp ? 68 /* RFC-791, IP minimum */ + : max_headers_len /* fit full TCP header */; + // NOTE(): We are effectively reducing transmitted TCP segment length by 20, + // accounting for possible options; though we currently don't send options + // except for SYN. + + // If the frame exceeds the available buffer, trim the length. + if (len + max_headers_len + l2_max_overhead > ifp->tx.len) + len = ifp->tx.len - max_headers_len - l2_max_overhead; + // Ensure the MTU isn't lower than the minimum allowed value + if (ifp->mtu < min_mtu) { + MG_ERROR(("MTU is lower than minimum, raising 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 > ifp->mtu) { + len = ifp->mtu - max_headers_len; + if (c->is_udp) MG_ERROR(("UDP datagram exceeds MTU. Truncating it.")); + } + + return len; +} + +static bool udp_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + struct connstate *s = (struct connstate *) (c + 1); + struct mg_addr ips; + memset(&ips, 0, sizeof(ips)); +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { + ips.addr.ip6[0] = ifp->ip6[0], ips.addr.ip6[1] = ifp->ip6[1], + ips.is_ip6 = true; + // TODO(): detect link-local (c->rem) and use it + } else +#endif + { + ips.addr.ip4 = ifp->ip; + } + ips.port = c->loc.port; + return tx_udp(ifp, s->mac, &ips, &c->rem, buf, len); +} + +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { + struct connstate *s = (struct connstate *) (c + 1); + len = trim_len(c, len); + if (c->is_udp) { + if (!udp_send(c, buf, len)) return MG_IO_WAIT; + } else { // TCP, cap to peer's MSS + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t sent; + if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts + sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK, + 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) { + size_t avail = mg_tls_pending(c); + size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail; + struct mg_iobuf *io = &c->recv; + if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) { + mg_error(c, "oom"); + } else { + // Decrypt data directly into c->recv + long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf, + 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); + } // else n < 0: outstanding data to be moved to c->recv + } +} + +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); + if (pkt->tcp->flags & TH_FIN) { + uint8_t flags = TH_ACK; + if (mg_ntohl(pkt->tcp->seq) != s->ack) { + MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + return; + } + // 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 + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); + s->fin_rcvd = true; + 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 + } + s->twclosure = true; + } else { + flags |= TH_FIN; + c->is_draining = 1; + settmout(c, MIP_TTYPE_FIN); + } + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + if (pkt->pay.len == 0) return; // if no data, we're done + } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) { + // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations) + MG_VERBOSE(("%lu keepalive ACK", c->id)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + return; // no data to process + } else if (pkt->pay.len == 0) { // this is an ACK + if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true; + return; // no data to process + } 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(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + } + return; // drop it + } else if (io->size - io->len < pkt->pay.len && + !mg_iobuf_resize(io, io->len + pkt->pay.len)) { + mg_error(c, "oom"); + return; // drop it + } + // 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 > MG_TCPIP_WIN / 2 && s->acked != s->ack) { + // Send ACK immediately + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, 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) : handle_tls_recv(c); + } else { + // Plain text connection, data is already in c->recv, trigger MG_EV_READ + mg_call(c, MG_EV_READ, &pkt->pay.len); + } +} + +// TCP backlog +struct mg_backlog { + uint16_t port, mss; // use port=0 for available entries + uint8_t age; +}; + +static int backlog_insert(struct mg_connection *c, uint16_t port, + uint16_t mss) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port != 0) continue; + p[i].age = 2; // remove after two calls, average 1.5 call rate + p[i].port = port, p[i].mss = mss; + return (int) i; + } + return -1; +} + +static struct mg_backlog *backlog_retrieve(struct mg_connection *c, + uint16_t key, uint16_t port) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + if (key >= sizeof(c->data) / sizeof(*p)) return NULL; + if (p[key].port != port) return NULL; + p += key; + return p; +} + +static void backlog_remove(struct mg_connection *c, uint16_t key) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + p[key].port = 0; +} + +static void backlog_maintain(struct mg_connection *c) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; // dec age and remove those where it reaches 0 + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port == 0) continue; + if (p[i].age != 0) --p[i].age; + if (p[i].age == 0) p[i].port = 0; + } +} + +static void backlog_poll(struct mg_mgr *mgr) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (!c->is_udp && c->is_listening) backlog_maintain(c); + } +} + +// process options (MSS) +static void handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) { + uint8_t *opts = (uint8_t *) (tcp + 1); + int len = 4 * ((int) (tcp->off >> 4) - ((int) sizeof(*tcp) / 4)); + s->dmss = ip6 ? 1220 : 536; // assume default, RFC-9293 3.7.1 + while (len > 0) { // RFC-9293 3.1 3.2 + uint8_t kind = opts[0], optlen = 1; + if (kind != 1) { // No-Operation + if (kind == 0) break; // End of Option List + optlen = opts[1]; + if (kind == 2 && optlen == 4) // set received MSS + s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + } + MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); + opts += optlen; + len -= optlen; + } +} + +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 MG_ENABLE_IPV6 // matching of v4/v6 to dest is done by getpeer() + if (pkt->ip6 != NULL && !tcp6csum_ok(pkt->ip6, pkt->tcp)) return; +#endif + if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return; + pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?) + // Order is VERY important; RFC-9293 3.5.2 + // - check clients (Group 1) and established connections (Group 3) + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { + // client got a server connection accept + handle_opt(s, pkt->tcp, pkt->ip6 != NULL); // process options (MSS) + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; + tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack); + 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); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { + mg_error(c, "connection refused"); + } else if (c != NULL && pkt->tcp->flags & TH_RST) { + // TODO(): validate RST is within window (and optional with proper ACK) + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 + } else if (c != NULL) { + // process segment + 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 + // - we don't listen on that port; RFC-9293 3.5.2 Group 1 + // - check listening connections; RFC-9293 3.5.2 Group 2 + if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + // not listening on that port + if (!(pkt->tcp->flags & TH_RST)) { + tx_tcp_rst(ifp, pkt, pkt->tcp->flags & TH_ACK); + } // else silently discard + } else if (pkt->tcp->flags == TH_SYN) { + // listener receives a connection request + struct connstate cs; // At this point, s = NULL, there is no connection + int key; + uint32_t isn; + if (pkt->tcp->sport != 0) { + handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL); // process options (MSS) + key = backlog_insert(c, pkt->tcp->sport, + cs.dmss); // backlog options (MSS) + if (key < 0) return; // no room in backlog, discard SYN, client retries + // Use peer's src port and bl key as ISN, to later identify the + // handshake + isn = (mg_htonl(((uint32_t) key << 16) | mg_ntohs(pkt->tcp->sport))); + if (tx_tcp_ctrlresp(ifp, pkt, TH_SYN | TH_ACK, isn) == 0) + backlog_remove(c, (uint16_t) key); // safety net for lousy networks + } // what should we do when port=0 ? Linux takes port 0 as any other + // port + } else if (pkt->tcp->flags == TH_ACK) { + // listener receives an ACK + struct mg_backlog *b = NULL; + if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) == + mg_htons(pkt->tcp->sport)) { + uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16); + b = backlog_retrieve(c, key, pkt->tcp->sport); + if (b != NULL) { // ACK is a response to a SYN+ACK + accept_conn(c, pkt, b->mss); // pass options + backlog_remove(c, key); + } // else not an actual match, reset + } + if (b == NULL) tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_RST) { + // silently discard + } else if (pkt->tcp->flags & TH_ACK) { // ACK + something else != RST + tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_SYN) { // SYN + something else != ACK + tx_tcp_rst(ifp, pkt, false); + } // else silently discard +} + +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint8_t ihl; + uint16_t frag, len; + if (pkt->pay.len < sizeof(*pkt->ip)) return; // Truncated + if ((pkt->ip->ver >> 4) != 4) return; // Not IP + ihl = pkt->ip->ver & 0x0F; + if (ihl < 5) return; // bad IHL + if (pkt->pay.len < (uint16_t) (ihl * 4)) return; // Truncated / malformed + // There can be link padding, take length from IP header + len = mg_ntohs(pkt->ip->len); // IP datagram length + if (len < (uint16_t) (ihl * 4) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip padding + mkpay(pkt, (uint32_t *) pkt->ip + ihl); // account for opts + if (!ipcsum_ok(pkt->ip)) return; + frag = mg_ntohs(pkt->ip->frag); + if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) { + struct mg_connection *c; + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + 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->pay.buf); + 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->pay.buf); + if (pkt->pay.len < sizeof(*pkt->udp)) return; // truncated + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data + 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->options); + 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->options); + rx_dhcp_server(ifp, pkt); + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMP Destination Unreachable for unicasts, but keep + // silent + } + } else if (pkt->ip->proto == 6) { + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + 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); + } else { + MG_DEBUG(("Unknown IP proto %x", (int) pkt->ip->proto)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip, pkt->pay.len >= 32 ? 32 : pkt->pay.len); + } +} + +#if MG_ENABLE_IPV6 +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint16_t len = 0, plen; + uint8_t next, *nhdr; + bool loop = true; + if (pkt->pay.len < sizeof(*pkt->ip6)) return; // Truncated + if ((pkt->ip6->ver >> 4) != 0x6) return; // Not IPv6 + plen = mg_ntohs(pkt->ip6->plen); + if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return; // malformed + next = pkt->ip6->next; + nhdr = (uint8_t *) (pkt->ip6 + 1); + while (loop) { + switch (next) { + case 0: // Hop-by-Hop 4.3 + case 43: // Routing 4.4 + case 60: // Destination Options 4.6 + case 51: // Authentication RFC-4302 + MG_INFO(("IPv6 extension header %d", (int) next)); + next = nhdr[0]; + len += (uint16_t) (8 * (nhdr[1] + 1)); + nhdr += 8 * (nhdr[1] + 1); + break; + case 44: // Fragment 4.5 + { + struct mg_connection *c; + if (nhdr[0] == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + c = getpeer(ifp->mgr, pkt, false); + if (c) mg_error(c, "Received fragmented packet"); + } + return; + case 59: // No Next Header 4.7 + return; + case 50: // IPsec ESP RFC-4303, unsupported + default: + loop = false; + break; + } + } + if (len >= plen) return; + // There can be link padding, take payload length from IPv6 header - options + pkt->pay.buf = (char *) nhdr; + pkt->pay.len = plen - len; + if (next == 58) { + pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->icmp6)) return; + mkpay(pkt, pkt->icmp6 + 1); + MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src, + mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len)); + rx_icmp6(ifp, pkt); + } else if (next == 17) { + pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data + mkpay(pkt, pkt->udp + 1); + MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); + if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + // rx_dhcp6_client(ifp, pkt); +#if 0 + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(547)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + rx_dhcp6_server(ifp, pkt); +#endif + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMPv6 Destination Unreachable for unicasts, keep silent + } + } else if (next == 6) { + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + off); + MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); + rx_tcp(ifp, pkt); + } else { + MG_DEBUG(("Unknown IPv6 next hdr %x", (int) next)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len); + } +} +#else +#define rx_ip6(x, y) +#endif + +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { + struct pkt pkt; + enum mg_l2proto proto; + memset(&pkt, 0, sizeof(pkt)); + pkt.raw.buf = (char *) buf; + pkt.raw.len = len; + pkt.l2 = (uint8_t *) pkt.raw.buf; + if (!mg_l2_rx(ifp, &proto, &pkt.pay, &pkt.raw)) return; + if (proto == MG_TCPIP_L2PROTO_ARP) { + pkt.arp = (struct arp *) (pkt.pay.buf); + if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated + mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw); + rx_arp(ifp, &pkt); + } else if (proto == MG_TCPIP_L2PROTO_IPV6) { + pkt.ip6 = (struct ip6 *) (pkt.pay.buf); + rx_ip6(ifp, &pkt); + } else if (proto == MG_TCPIP_L2PROTO_IPV4) { + pkt.ip = (struct ip *) (pkt.pay.buf); + rx_ip(ifp, &pkt); + } +} + +static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state == MG_TCPIP_STATE_DOWN) return; + // DHCP RFC-2131 (4.4) + if (ifp->enable_dhcp_client && s1) { + if (ifp->state == MG_TCPIP_STATE_UP) { + tx_dhcp_discover(ifp); // INIT (4.4.1) + } else if (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, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), ifp->ip, + 0xffffffff); + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + } + } +} +static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { + bool current = ifp->state != MG_TCPIP_STATE_DOWN; + if (!up && ifp->enable_dhcp_client) ifp->ip = 0; + if (up != current) { // link state has changed + ifp->state = up == false ? MG_TCPIP_STATE_DOWN + : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstatechange(ifp); + } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP && + ifp->ip) { + ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstatechange(ifp); + } +} + +#if MG_ENABLE_IPV6 +static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state6 == MG_TCPIP_STATE_DOWN) return; + if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP) + tx_ndp_rs(ifp); +} +static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { + bool current = ifp->state6 != MG_TCPIP_STATE_DOWN; + if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0; + if (up != current) { // link state has changed + ifp->state6 = !up ? MG_TCPIP_STATE_DOWN + : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstate6change(ifp); + } else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP && + ifp->ip6[0]) { + ifp->state6 = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstate6change(ifp); + } +} +#else +#define mg_ip6_poll(x, y) +#define mg_ip6_link(x, y) +#endif + +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 (expired_1000ms) { +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + const char *names[] = {"down", "up", "req", "ip", "ready"}; + size_t max = sizeof(names) / sizeof(char *); + unsigned int state = ifp->state >= max ? max - 1 : ifp->state; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state], + mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop, + ifp->nerr)); +#if MG_ENABLE_IPV6 + state = ifp->state6 >= max ? max - 1 : ifp->state6; + if (state > MG_TCPIP_STATE_UP) + MG_INFO(("Status: %s, IPv6: %M", names[state], mg_print_ip6, &ifp->ip6)); +#endif +#endif + backlog_poll(ifp->mgr); + } + // Handle gw ARP request timeout, order is important + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC or poison mark + onstatechange(ifp); + } + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready && + ifp->gw != 0) + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // retry GW ARP request +#if MG_ENABLE_IPV6 + // Handle gw NS/NA req/resp timeout, order is important + if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; // keep best-effort MAC or poison mark + onstate6change(ifp); + } + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw6_ready && + (ifp->gw6[0] != 0 || ifp->gw6[1] != 0)) + tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution +#endif + + // poll driver + if (ifp->driver->poll) { + bool up = ifp->driver->poll(ifp, expired_1000ms); + // Handle physical interface up/down status, ifp->state rules over state6 + if (expired_1000ms) { + mg_ip_link(ifp, up); // Handle IPv4 + mg_ip6_link(ifp, up); // Handle IPv6 + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); + } + } + + mg_ip_poll(ifp, expired_1000ms); // Handle IPv4 + mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6 + + if (ifp->state == MG_TCPIP_STATE_DOWN) return; + // Read data from the network + if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame + 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 { // Complex poll / Interrupt-based driver. Queues recvd frames + char *buf; + size_t len, cnt = 7; // Max 7 packets to fetch + while (cnt-- > 0 && (len = mg_queue_next(&ifp->recv_queue, &buf)) > 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) { + struct connstate *s = (struct connstate *) (c + 1); + if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) + continue; + if (ifp->now > s->timer) { + if (s->ttype == MIP_TTYPE_ARP) { + mg_error(c, "ARP timeout"); + } else if (c->is_udp) { + continue; + } else 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, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + s->acked = s->ack; + } 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, &c->loc, &c->rem, TH_ACK, 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 L2 address is not set, make a random one; fill MTU + mg_l2_init(ifp->l2type, ifp->mac, &ifp->mtu, &ifp->framesize); + + if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" + memcpy(ifp->dhcp_name, "mip", 4); + ifp->dhcp_name[sizeof(ifp->dhcp_name) - 1] = '\0'; // Just in case + + if (ifp->driver->init && !ifp->driver->init(ifp)) { + MG_ERROR(("driver init failed")); + } else { + ifp->tx.buf = (char *) mg_calloc(1, ifp->framesize), + ifp->tx.len = ifp->framesize; + if (ifp->recv_queue.size == 0) + ifp->recv_queue.size = ifp->driver->rx ? ifp->framesize : 8192; + ifp->recv_queue.buf = (char *) mg_calloc(1, ifp->recv_queue.size); + ifp->timer_1000ms = mg_millis(); + mgr->ifp = ifp; + ifp->mgr = mgr; + mgr->extraconnsize = sizeof(struct connstate); + if (ifp->ip == 0) ifp->enable_dhcp_client = true; + 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 MG_ENABLE_IPV6 + if (ifp->ip6ll[0] == 0 && ifp->ip6ll[1] == 0) { // gen link-local address + uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6 + mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac); + memcpy(ifp->ip6ll, px, 8); // RFC-4291 2.5.4 + } // just got our link local address if we didn't. + // If static configuration is used, global addresses, + // prefix length, and gw are already filled at this point. + if (ifp->ip6[0] == 0 && ifp->ip6[1] == 0) ifp->enable_slaac = true; +#endif + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); + } +} + +void mg_tcpip_free(struct mg_tcpip_if *ifp) { + mg_free(ifp->recv_queue.buf); + mg_free(ifp->tx.buf); + mg_free(ifp->dns4_url); +} + +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)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0); +} + +static void l2addr_resolved(struct mg_connection *c) { + if (c->is_udp) { + c->is_connecting = 0; + mg_call(c, MG_EV_CONNECT, NULL); + } else { + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + } +} + +void mg_connect_resolved(struct mg_connection *c) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + uint8_t *l2addr; + c->is_resolving = 0; + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; + c->loc.port = mg_htons(ifp->eport++); +#if MG_ENABLE_IPV6 + if (c->rem.is_ip6) { + c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1], + c->loc.is_ip6 = true; + } else +#endif + { + c->loc.addr.ip4 = ifp->ip; + } + 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); + c->is_connecting = 1; + if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, l2addr, sizeof(s->mac)); + l2addr_resolved(c); // broadcast or multicast +#if MG_ENABLE_IPV6 + } else if (c->rem.is_ip6) { + if (match_prefix((uint8_t *) c->rem.addr.ip6, ifp->prefix, + ifp->prefix_len) // same global LAN + || (c->rem.addr.ip6[0] == ifp->ip6ll[0] // same local LAN + && !MG_IP6MATCH(c->rem.addr.ip6, ifp->gw6))) { // and not gw + // If we're in the same LAN, fire a Neighbor Solicitation + MG_DEBUG(("%lu NS lookup...", c->id)); + tx_ndp_ns(ifp, c->rem.addr.ip6, NULL); // RFC-4861 4.3, requesting + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if (ifp->gw6_ready) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); + l2addr_resolved(c); + } else { + MG_ERROR(("No IPv6 gateway, can't connect")); + } +#endif + } else { + uint32_t rem_ip = c->rem.addr.ip4; + if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && + rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) + // If we're in the same LAN, fire an ARP lookup. + MG_DEBUG(("%lu ARP lookup...", c->id)); + mg_tcpip_arp_request(ifp, rem_ip, NULL); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if (ifp->gw_ready) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); + l2addr_resolved(c); + } else { + MG_ERROR(("No gateway, can't connect")); + } + } +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + 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)); + return false; + } + 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); + // TODO(): mg_tls_send() may return 0 forever on steady OOM + 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, + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK, + 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_connection *c, *tmp; + uint64_t now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return; + mg_tcpip_poll(mgr->ifp, now); + for (c = mgr->conns; c != NULL; c = tmp) { + struct connstate *s = (struct connstate *) (c + 1); + bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking && + !c->is_listening && !c->is_connecting; + tmp = c->next; + mg_call(c, MG_EV_POLL, &now); + MG_VERBOSE(("%lu .. %c%c%c%c%c %lu %lu", 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', + mg_tls_pending(c), c->rtls.len)); + // order is important, TLS conn close with > 1 record in buffer (below) + if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0)) + c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c); + if (can_write(c)) write_conn(c); + if (is_tls && c->send.len == 0) mg_tls_flush(c); + if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) + init_closure(c); + // For non-TLS, close immediately upon completing the 3-way closure + // For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires + if (s->twclosure && + (!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0))) + c->is_closing = 1; + 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 = c->mgr->ifp; + bool res = false; + if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) { + mg_error(c, "net down"); +#if MG_ENABLE_IPV6 + } else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) { + mg_error(c, "net down"); +#endif + } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) { + // Fail to send, no target MAC or IP + MG_VERBOSE(("still resolving...")); + } else if (c->is_udp) { + len = trim_len(c, len); // Trimming length if necessary + res = udp_send(c, buf, len); + } else { + res = len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide + } + return res; +} + +uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb}; +void mg_multicast_add(struct mg_connection *c, char *ip) { + (void) ip; // ip4/6_mcastmac(mcast_mac, &ip); ipv6 param + // TODO(): actual IP -> MAC; check database, update + c->mgr->ifp->update_mac_hash_table = true; // mark dirty +} + +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); + +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) { + struct mg_dns *dnsc; + mg_free(ifp->dns4_url); + ifp->dns4_url = mg_mprintf("udp://%M:53", mg_print_ip4, ip); + dnsc = &ifp->mgr->dns4; + dnsc->url = (const char *) ifp->dns4_url; + MG_DEBUG(("Set DNS URL to %s", dnsc->url)); + if (ifp->mgr->use_dns6) return; + if (dnsc->c != NULL) mg_close_conn(dnsc->c); + if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection + MG_ERROR(("DNS connection creation failed")); +} + +void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) { + struct connstate *s = (struct connstate *) (c + 1); + uint8_t *l2addr = tcpip_mapip(c->mgr->ifp, ip); + if (l2addr == NULL) return; + memcpy(s->mac, l2addr, sizeof(s->mac)); +} + +#endif // MG_ENABLE_TCPIP + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_ch32v307.c" +#endif + + + + +#if MG_OTA == MG_OTA_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +static bool mg_ch32v307_write(void *, const void *, size_t); +static bool mg_ch32v307_swap(void); + +static struct mg_flash s_mg_flash_ch32v307 = { + (void *) 0x08000000, // Start + 480 * 1024, // Size, first 320k is 0-wait + 4 * 1024, // Sector size, 4k + 4, // Align, 32 bit + mg_ch32v307_write, + mg_ch32v307_swap, +}; + +#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) + +MG_IRAM static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; +} + +MG_IRAM static void mg_ch32v307_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(); +} + +MG_IRAM static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (s_mg_flash_ch32v307.secsz - 1)) == 0; +} + +MG_IRAM static bool mg_ch32v307_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_ch32v307_erase(dst); + *dst++ = *src++; + flash_wait(); + } + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; +} + +MG_IRAM bool mg_ch32v307_swap(void) { + return true; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_ch32v307_write(p1 + ofs, p2 + ofs, ss); + } + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_ch32v307); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_ch32v307); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_ch32v307)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_ch32v307.size, + s_mg_flash_ch32v307.size / s_mg_flash_ch32v307.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + // TODO() disable IRQ, s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_ch32v307.start, + (char *) s_mg_flash_ch32v307.start + s_mg_flash_ch32v307.size / 2, + s_mg_flash_ch32v307.size / 2, s_mg_flash_ch32v307.secsz); + } + return false; +} +#endif + +#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; +} +#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_imxrt.c" +#endif + + + + +#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1170 + +static bool mg_imxrt_write(void *, const void *, size_t); +static bool mg_imxrt_swap(void); + +#if MG_OTA <= MG_OTA_RT1060 +#define MG_IMXRT_FLASH_START 0x60000000 +#define FLEXSPI_NOR_INSTANCE 0 +#elif MG_OTA == MG_OTA_RT1064 +#define MG_IMXRT_FLASH_START 0x70000000 +#define FLEXSPI_NOR_INSTANCE 1 +#else // RT1170 +#define MG_IMXRT_FLASH_START 0x30000000 +#define FLEXSPI_NOR_INSTANCE 1 +#endif + +#if MG_OTA == MG_OTA_RT1050 +#define MG_IMXRT_SECTOR_SIZE (256 * 1024) +#define MG_IMXRT_PAGE_SIZE 512 +#else +#define MG_IMXRT_SECTOR_SIZE (4 * 1024) +#define MG_IMXRT_PAGE_SIZE 256 +#endif + +// TODO(): fill at init, support more devices in a dynamic way +// TODO(): then, check alignment is <= 256, see Wizard's #251 +static struct mg_flash s_mg_flash_imxrt = { + (void *) MG_IMXRT_FLASH_START, // Start, + 4 * 1024 * 1024, // Size, 4mb + MG_IMXRT_SECTOR_SIZE, // Sector size + MG_IMXRT_PAGE_SIZE, // Align + mg_imxrt_write, + mg_imxrt_swap, +}; + +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_DUMMY_RWDS_DDR 0x2D +#define MG_RADDR_SDR 0x02 +#define MG_RADDR_DDR 0x22 +#define MG_CADDR_DDR 0x23 +#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_HYPER_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, MG_RADDR_DDR, \ + MG_FLEXSPI_8PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_DDR, MG_FLEXSPI_8PAD, 0x0C), \ + [2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x04, MG_STOP, \ + MG_FLEXSPI_1PAD, 0x0), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 1 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 1 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 1 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x70), \ + [4 * 2 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 2 + 1] = \ + MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_RWDS_DDR, MG_FLEXSPI_8PAD, 0x0B), \ + [4 * 2 + 2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x4, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 3 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 3 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 3 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 4 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 4 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 4 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 4 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 5 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 5 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 5 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 6 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 6 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 6 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 6 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 7 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 7 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 7 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 7 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 8 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 8 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x30, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 9 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 9 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0), \ + [4 * 10 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 10 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_WRITE_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 11 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 11 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 11 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 12 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 12 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 12 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 12 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 13 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 13 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 13 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 13 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 14 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 14 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 14 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 14 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x10), \ + } + +#define MG_LUT_CUSTOM_SEQ \ + { \ + {.seqNum = 0, .seqId = 0, .reserved = 0}, \ + {.seqNum = 2, .seqId = 1, .reserved = 0}, \ + {.seqNum = 2, .seqId = 3, .reserved = 0}, \ + {.seqNum = 4, .seqId = 5, .reserved = 0}, \ + {.seqNum = 2, .seqId = 9, .reserved = 0}, \ + {.seqNum = 4, .seqId = 11, .reserved = 0}, \ + } + +#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) + +#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050 +// RT102X and RT105x boards support ROM API version 1.4 +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_OTA <= MG_OTA_RT1064 +// RT104x and RT106x support ROM API version 1.5 +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); +}; +#else +// RT117x support ROM API version 1.7 +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); + uint32_t reserved; + 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); + int (*erase_sector)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + int (*erase_block)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + void (*hw_reset)(uint32_t instance, uint32_t resetLogic); + int (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + bool isParallelMode, uint32_t address); + int (*set_clock_source)(uint32_t instance, uint32_t clockSrc); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); +}; +#endif + +#if MG_OTA <= MG_OTA_RT1064 +#define MG_FLEXSPI_BASE 0x402A8000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ + 16))) +#else +#define MG_FLEXSPI_BASE 0x400CC000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \ + 12))) +#endif + +static bool s_flash_irq_disabled; + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_imxrt.start, + *end = base + s_mg_flash_imxrt.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_imxrt.secsz) == 0; +} + +#if MG_OTA == MG_OTA_RT1050 +// Configuration for Hyper flash memory +static struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 3, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .columnAddressWidth = 3u, + .controllerMiscOption = + MG_BIT(6) | MG_BIT(4) | MG_BIT(3) | MG_BIT(0), + .deviceType = 1, // serial NOR + .sflashPadType = 8, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 64 * 1024 * 1024, + .dataValidTime = {15, 0}, + .busyOffset = 15, + .busyBitPolarity = 1, + .lutCustomSeqEnable = 0x1, + .lookupTable = MG_FLEXSPI_HYPER_LUT, + .lutCustomSeq = MG_LUT_CUSTOM_SEQ, + }, + .pageSize = 512, + .sectorSize = 256 * 1024, + .ipcmdSerialClkFreq = 1, + .serialNorType = 1u, + .blockSize = 256 * 1024, + .isUniformBlockSize = true}; +#else +// Note: this QSPI configuration works for RTs supporting QSPI +// Configuration for QSPI memory +static 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}; +#endif + +// must reside in RAM, as flash will be erased +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config **config) { + *config = &default_config; + return 0; +} + +#if 0 +// ROM API get_config call (ROM version >= 1.5) +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 static void mg_spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + mg_spin(1); +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_imxrt.start); + + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, config, (uint32_t) dst, + s_mg_flash_imxrt.secsz) == 0); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// standalone erase call +MG_IRAM static bool mg_imxrt_erase(void *addr) { + struct mg_flexspi_nor_config config, *config_ptr = &config; + bool ret; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + ret = (flexspi_nor_get_config(&config_ptr) == 0); + if (ret) ret = flash_erase(config_ptr, addr); + MG_ARM_ENABLE_IRQ(); + return ret; +} +#endif + +MG_IRAM bool mg_imxrt_swap(void) { + return true; +} + +MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config, *config_ptr = &config; + bool ok = false; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + if (flexspi_nor_get_config(&config_ptr) != 0) goto fwxit; + if ((len % s_mg_flash_imxrt.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_imxrt.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_imxrt.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(config_ptr, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; + if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_imxrt.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]; + } + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, tmp); + } else { + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, src); + } + src = (uint32_t *) ((char *) src + s_mg_flash_imxrt.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_imxrt.align); + if (status != 0) { + ok = false; + } + } + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_imxrt_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_imxrt); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_imxrt)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_imxrt.size, + s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_imxrt.start, + (char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2, + s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz); + } + } + return false; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_mcxn.c" +#endif + + + + +#if MG_OTA == MG_OTA_MCXN + +// - Flash phrase: 16 bytes; smallest portion programmed in one operation. +// - Flash page: 128 bytes; largest portion programmed in one operation. +// - Flash sector: 8 KB; smallest portion that can be erased in one operation. +// - Flash API mg_flash_driver->program: "start" and "len" must be page-size +// aligned; to use 'phrase', FMU register access is needed. Using ROM + +static bool mg_mcxn_write(void *, const void *, size_t); +static bool mg_mcxn_swap(void); + +static struct mg_flash s_mg_flash_mcxn = { + (void *) 0, // Start, filled at init + 0, // Size, filled at init + 0, // Sector size, filled at init + 0, // Align, filled at init + mg_mcxn_write, + mg_mcxn_swap, +}; + +struct mg_flash_config { + uint32_t addr; + uint32_t size; + uint32_t blocks; + uint32_t page_size; + uint32_t sector_size; + uint32_t ffr[6]; + uint32_t reserved0[5]; + uint32_t *bootctx; + bool useahb; +}; + +struct mg_flash_driver_interface { + uint32_t version; + uint32_t (*init)(struct mg_flash_config *); + uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len, + uint32_t key); + uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src, + uint32_t len); + uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start, + uint32_t len); + uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start, + uint32_t len, const uint8_t *expected, + uint32_t *addr, uint32_t *failed); + uint32_t reserved1[12]; + uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest, + uint32_t len); + uint32_t reserved2[4]; + uint32_t (*deinit)(struct mg_flash_config *); +}; +#define mg_flash_driver \ + ((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4))) +#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l') + +MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_mcxn.start, + *end = base + s_mg_flash_mcxn.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0; +} + +MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + uint32_t dst = + (uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof + uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz, + MG_MCXN_FLASK_KEY); + bool ok = (status == 0); + if (!ok) MG_ERROR(("Flash write error: %lu", status)); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_mcxn_erase(void *addr) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + bool ok = flash_erase(&config, addr); + mg_flash_driver->deinit(&config); + return ok; +} +#endif + +MG_IRAM static bool mg_mcxn_swap(void) { + // TODO(): no devices so far + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) { + bool ok = false; + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + if ((len % s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align)); + goto fwxit; + } + if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) % + s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_sector_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start; + // assume source is in RAM or in a different bank or read-while-write + status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src, + s_mg_flash_mcxn.align); + src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align); + if (status != 0) { + MG_ERROR(("Flash write error: %lu", status)); + ok = false; + } + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); + +fwxit: + mg_flash_driver->deinit(&config); + return ok; +} + +// try to swap (honor dual image), otherwise just overwrite +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *tmp = mg_calloc(1, ss); + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + if (tmp != NULL) + for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i]; + mg_mcxn_write(p1 + ofs, p2 + ofs, ss); + if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + s_mg_flash_mcxn.start = (void *) config.addr; + s_mg_flash_mcxn.size = config.size; + s_mg_flash_mcxn.secsz = config.sector_size; + s_mg_flash_mcxn.align = config.page_size; + mg_flash_driver->deinit(&config); + MG_DEBUG( + ("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages", + s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz, + s_mg_flash_mcxn.align)); + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_mcxn)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_mcxn.size, + s_mg_flash_mcxn.size / s_mg_flash_mcxn.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_mcxn.start, + (char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2, + s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz); + } + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_picosdk.c" +#endif + + + + +#if MG_OTA == MG_OTA_PICOSDK + +// Both RP2040 and RP2350 have no flash, low-level flash access support in +// bootrom, and high-level support in Pico-SDK (2.0+ for the RP2350) +// - The RP2350 in RISC-V mode is not tested +// NOTE(): See OTA design notes + +static bool mg_picosdk_write(void *, const void *, size_t); +static bool mg_picosdk_swap(void); + +static struct mg_flash s_mg_flash_picosdk = { + (void *) 0x10000000, // Start; functions handle offset +#ifdef PICO_FLASH_SIZE_BYTES + PICO_FLASH_SIZE_BYTES, // Size, from board definitions +#else + 0x200000, // Size, guess... is 2M enough ? +#endif + FLASH_SECTOR_SIZE, // Sector size, from hardware_flash + FLASH_PAGE_SIZE, // Align, from hardware_flash + mg_picosdk_write, mg_picosdk_swap, +}; + +#define MG_MODULO2(x, m) ((x) & ((m) -1)) + +static bool __no_inline_not_in_flash_func(flash_sector_start)( + volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_picosdk.start, + *end = base + s_mg_flash_picosdk.size; + volatile char *p = (char *) dst; + return p >= base && p < end && + MG_MODULO2(p - base, s_mg_flash_picosdk.secsz) == 0; +} + +static bool __no_inline_not_in_flash_func(flash_erase)(void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_picosdk.start); + flash_range_erase((uint32_t) dst, s_mg_flash_picosdk.secsz); + MG_DEBUG(("Sector starting at %p erasure", addr)); + return true; +} + +static bool __no_inline_not_in_flash_func(mg_picosdk_swap)(void) { + // TODO(): RP2350 might have some A/B functionality (DS 5.1) + return true; +} + +static bool s_flash_irq_disabled; + +static bool __no_inline_not_in_flash_func(mg_picosdk_write)(void *addr, + const void *buf, + size_t len) { + if ((len % s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_picosdk.align)); + return false; + } + if ((((size_t) addr - (size_t) s_mg_flash_picosdk.start) % + s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + +#ifndef __riscv + MG_ARM_DISABLE_IRQ(); +#else + asm volatile("csrrc zero, mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + while (src < end) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_picosdk.start; + if (flash_sector_start(dst) && flash_erase(dst) == false) break; + // flash_range_program() runs in RAM and handles writing up to + // FLASH_PAGE_SIZE bytes. Source must not be in flash + flash_range_program((uint32_t) dst_ofs, (uint8_t *) src, + s_mg_flash_picosdk.align); + src = (uint32_t *) ((char *) src + s_mg_flash_picosdk.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_picosdk.align); + } + if (!s_flash_irq_disabled) { +#ifndef __riscv + MG_ARM_ENABLE_IRQ(); +#else + asm volatile("csrrs mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + } + MG_DEBUG(("Flash write %lu bytes @ %p.", len, dst)); + return true; +} + +// just overwrite instead of swap +static void __no_inline_not_in_flash_func(single_bank_swap)(char *p1, char *p2, + size_t s, + size_t ss) { + char *tmp = mg_calloc(1, ss); + if (tmp == NULL) return; +#if PICO_RP2040 + uint32_t xip[256 / sizeof(uint32_t)]; + void *dst = (void *) ((char *) p1 - (char *) s_mg_flash_picosdk.start); + size_t count = MG_ROUND_UP(s, ss); + // use SDK function calls to get BootROM function pointers + rom_connect_internal_flash_fn connect = (rom_connect_internal_flash_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn xit = (rom_flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn program = (rom_flash_range_program_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flush = (rom_flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + // no stdlib calls here. + MG_ARM_DISABLE_IRQ(); + // 2nd bootloader (XIP) is in flash, SDK functions copy it to RAM on entry + for (size_t i = 0; i < 256 / sizeof(uint32_t); i++) + xip[i] = ((uint32_t *) (s_mg_flash_picosdk.start))[i]; + flash_range_erase((uint32_t) dst, count); + // flash has been erased, no XIP to copy. Only BootROM calls possible + for (uint32_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + __compiler_memory_barrier(); + connect(); + xit(); + program((uint32_t) dst + ofs, tmp, ss); + flush(); + ((void (*)(void))((intptr_t) xip + 1))(); // enter XIP again + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // RP2350 has BootRAM and copies second bootloader there, SDK uses that copy, + // It might also be able to take advantage of partition swapping + rom_reboot_fn reboot = (rom_reboot_fn) rom_func_lookup(ROM_FUNC_REBOOT); + for (size_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + mg_picosdk_write(p1 + ofs, tmp, ss); + } + reboot(BOOT_TYPE_NORMAL | 0x100, 1, 0, 0); // 0x100: NO_RETURN_ON_SUCCESS +#endif +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_picosdk); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_picosdk); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_picosdk)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_picosdk.size, + s_mg_flash_picosdk.size / s_mg_flash_picosdk.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished or return on failure + single_bank_swap( + (char *) s_mg_flash_picosdk.start, + (char *) s_mg_flash_picosdk.start + s_mg_flash_picosdk.size / 2, + s_mg_flash_picosdk.size / 2, s_mg_flash_picosdk.secsz); + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_rw612.c" +#endif + + + + +#if MG_OTA == MG_OTA_RW612 + +MG_IRAM static bool mg_frdm_write(void *, const void *, size_t); +static bool mg_frdm_swap(void); + +static struct mg_flash s_mg_flash_frdm = {(void *) 0x08000000, // Start, + 0x200000, // Size + 0x1000, // Sector size + 0x100, // Align + mg_frdm_write, + mg_frdm_swap}; + +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 isDataOrderSwapped; + uint8_t reserved0[1]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t flashStateCtx; + uint32_t reserve2[10]; +}; + +struct mg_flexspi_nor_driver_interface { + uint32_t version; + uint32_t (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + uint32_t (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address, bool keepState); + uint32_t (*page_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + bool keepState); + uint32_t (*erase_all)(uint32_t instance, + struct mg_flexspi_nor_config *config); + uint32_t (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t length); + uint32_t (*erase_sector)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*erase_block)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t start, uint32_t bytes); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); + uint32_t (*set_clock_source)(uint32_t clockSrc); + uint32_t (*get_config)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t *option); + void (*hw_reset)(uint32_t instance, uint32_t reset_logic); + uint32_t (*xfer)(uint32_t instance, char *xfer); + uint32_t (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t numberOfSeq); + uint32_t (*partial_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + uint32_t length, bool keepState); +}; + +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) +#define MG_FLEXSPI_BASE 0x40134000UL + +#define MG_CMD_SDR 0x01 +#define MG_RADDR_SDR 0x02 +#define MG_WRITE_SDR 0x08 +#define MG_READ_SDR 0x09 +#define MG_DUMMY_SDR 0x0C +#define MG_STOP_EXE 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_4PAD 2 + +#define MG_FLEXSPI_LUT_OPERAND0(x) (((x) &0xFF) << 0) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((x) &0x3) << 8) +#define MG_FLEXSPI_LUT_OPCODE0(x) (((x) &0x3F) << 10) +#define MG_FLEXSPI_LUT_OPERAND1(x) (((x) &0xFF) << 16) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((x) &0x3) << 24) +#define MG_FLEXSPI_LUT_OPCODE1(x) (((x) &0x3F) << 26) + +#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)) + +struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = 0, + .readSampleClkSrc = 1, + .csHoldTime = 3, + .csSetupTime = 3, + .deviceModeCfgEnable = 1, + .deviceModeSeq = {.seqNum = 1, .seqId = 2}, + .deviceModeArg = 0x0740, + .configCmdEnable = 0, + .deviceType = 0x1, + .sflashPadType = 4, + .serialClkFreq = 4, + .sflashA1Size = 0x4000000U, + .sflashA2Size = 0, + .sflashB1Size = 0, + .sflashB2Size = 0, + .lookupTable = + { + [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 * 2 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x01, + MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x02), + [4 * 3 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [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, 0x52, + 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, 0x00, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [4 * 11 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + }, + }, + .pageSize = 0x100, + .sectorSize = 0x1000, + .ipcmdSerialClkFreq = 0, + .blockSize = 0x8000, +}; + +#define MG_FLEXSPI_NOR_INSTANCE 0 +#define MG_ROMAPI_ADDRESS 0x13030000U +#define flexspi_nor \ + ((struct mg_flexspi_nor_driver_interface *) (( \ + (uint32_t *) MG_ROMAPI_ADDRESS)[5])) + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_frdm.start, + *end = base + s_mg_flash_frdm.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_frdm.secsz) == 0; +} + +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { + uint32_t option = 0xc0000004; + return flexspi_nor->get_config(MG_FLEXSPI_NOR_INSTANCE, config, &option); +} + +MG_IRAM static int flash_init(void) { + static bool initialized = false; + if (!initialized) { + struct mg_flexspi_nor_config config; + memset(&config, 0, sizeof(config)); + flexspi_nor->set_clock_source(0); + flexspi_nor->config_clock(MG_FLEXSPI_NOR_INSTANCE, 1, 0); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &default_config)) { + return 1; + } + flexspi_nor_get_config(&config); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &config)) { + return 1; + } + initialized = true; + } + return 0; +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_frdm.start); + bool ok = (flexspi_nor->erase_sector(MG_FLEXSPI_NOR_INSTANCE, config, + (uint32_t) dst) == 0); + MG_INFO(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM bool mg_frdm_swap(void) { + return true; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + (void) 0; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_frdm_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config; + bool ok = false; + MG_ARM_DISABLE_IRQ(); + if (flash_init() != 0) goto fwxit; + if (flexspi_nor_get_config(&config) != 0) goto fwxit; + if ((len % s_mg_flash_frdm.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_frdm.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_frdm.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_frdm.start; + if ((char *) buf >= (char *) s_mg_flash_frdm.start && + (char *) buf < + (char *) (s_mg_flash_frdm.start + s_mg_flash_frdm.size)) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_frdm.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]; + } + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, tmp, false); + } else { + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, src, false); + } + src = (uint32_t *) ((char *) src + s_mg_flash_frdm.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_frdm.align); + if (status != 0) { + ok = false; + } + } + MG_INFO(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_frdm_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_frdm); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_frdm); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_frdm)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_frdm.size, + s_mg_flash_frdm.size / s_mg_flash_frdm.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_frdm.start, + (char *) s_mg_flash_frdm.start + s_mg_flash_frdm.size / 2, + s_mg_flash_frdm.size / 2, s_mg_flash_frdm.secsz); + } + } + return false; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32f.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32F + +static bool mg_stm32f_write(void *, const void *, size_t); +static bool mg_stm32f_swap(void); + +static struct mg_flash s_mg_flash_stm32f = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 0, // Irregular sector size + 32, // Align, 256 bit + mg_stm32f_write, + mg_stm32f_swap, +}; + +#define MG_FLASH_BASE 0x40023c00 +#define MG_FLASH_KEYR 0x04 +#define MG_FLASH_SR 0x0c +#define MG_FLASH_CR 0x10 +#define MG_FLASH_OPTCR 0x14 +#define MG_FLASH_SIZE_REG_F7 0x1FF0F442 +#define MG_FLASH_SIZE_REG_F4 0x1FFF7A22 + +#define STM_DBGMCU_IDCODE 0xE0042000 +#define STM_DEV_ID (MG_REG(STM_DBGMCU_IDCODE) & (MG_BIT(12) - 1)) +#define SYSCFG_MEMRMP 0x40013800 + +#define MG_FLASH_SIZE_REG_LOCATION \ + ((STM_DEV_ID >= 0x449) ? MG_FLASH_SIZE_REG_F7 : MG_FLASH_SIZE_REG_F4) + +static size_t flash_size(void) { + return (MG_REG(MG_FLASH_SIZE_REG_LOCATION) & 0xFFFF) * 1024; +} + +MG_IRAM static int is_dualbank(void) { + // only F42x/F43x series (0x419) support dual bank + return STM_DEV_ID == 0x419; +} + +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0x45670123; + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +#define MG_FLASH_CONFIG_16_64_128 1 // used by STM32F7 +#define MG_FLASH_CONFIG_32_128_256 2 // used by STM32F4 and F2 + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + + if (is_dualbank() && dst >= (uint32_t *) (base + (end - base) / 2)) { + dst = (uint32_t *) ((uint32_t) dst - (end - base) / 2); + } + + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + + volatile char *p = (char *) dst; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + if ((p - base) % (16 * 1024 * flash_config) == 0) return true; + } else if (p == base + 16 * 1024 * 4 * flash_config) { + return true; + } else if ((p - base) % (128 * 1024 * flash_config) == 0) { + return true; + } + } + return false; +} + +MG_IRAM static int flash_sector(volatile uint32_t *addr) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + bool addr_in_bank_2 = false; + if (is_dualbank() && addr >= (uint32_t *) (base + (end - base) / 2)) { + addr = (uint32_t *) ((uint32_t) addr - (end - base) / 2); + addr_in_bank_2 = true; + } + volatile char *p = (char *) addr; + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + int sector = -1; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + sector = (p - base) / (16 * 1024 * flash_config); + } else if (p >= base + 64 * 1024 * flash_config && + p < base + 128 * 1024 * flash_config) { + sector = 4; + } else { + sector = (p - base) / (128 * 1024 * flash_config) + 4; + } + } + if (sector == -1) return -1; + if (addr_in_bank_2) sector += 12; // a bank has 12 sectors + return sector; +} + +MG_IRAM static bool flash_is_err(void) { + return MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & ((MG_BIT(7) - 1) << 1); +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & (MG_BIT(16))) (void) 0; +} + +MG_IRAM static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(MG_FLASH_BASE + MG_FLASH_SR) = 0xf2; // Clear all errors +} + +MG_IRAM static bool mg_stm32f_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + int sector = flash_sector(addr); + if (sector < 0) return false; + uint32_t sector_reg = sector; + if (is_dualbank() && sector >= 12) { + // 3.9.8 Flash control register (FLASH_CR) for F42xxx and F43xxx + // BITS[7:3] + sector_reg -= 12; + sector_reg |= MG_BIT(4); + } + flash_unlock(); + flash_wait(); + uint32_t cr = MG_BIT(1); // SER + cr |= MG_BIT(16); // STRT + cr |= (sector_reg & 31) << 3; // sector + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = cr; + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + // After we have erased the sector, set CR flags for programming + // 2 << 8 is word write parallelism, bit(0) is PG. RM0385, section 3.7.5 + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | (2 << 8); + flash_clear_err(); + } + return ok; +} + +MG_IRAM static bool mg_stm32f_swap(void) { + // STM32 F42x/F43x support dual bank, however, the memory mapping + // change will not be carried through a hard reset. Therefore, we will use + // the single bank approach for this family as well. + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32f_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32f.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32f.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; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | MG_BIT(9); // PG, 32-bit + flash_wait(); + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32f_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + MG_DSB(); // ensure flash is written with no errors + flash_wait(); + if (flash_is_err()) ok = false; + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) &= ~MG_BIT(0); // Clear programming flag + return ok; +} + +// just overwrite instead of swap +MG_IRAM void single_bank_swap(char *p1, char *p2, size_t size) { + // no stdlib calls here + mg_stm32f_write(p1, p2, size); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32f.size = flash_size(); +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32f); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32f); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32f)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32f.size, STM_DEV_ID == 0x449 ? 8 : 12)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + char *p1 = (char *) s_mg_flash_stm32f.start; + char *p2 = p1 + s_mg_flash_stm32f.size / 2; + size_t size = s_mg_flash_stm32f.size / 2; + // Runs in RAM, will reset when finished + single_bank_swap(p1, p2, size); + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32h5.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32H5 + +static bool mg_stm32h5_write(void *, const void *, size_t); +static bool mg_stm32h5_swap(void); + +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 8 * 1024, // Sector size, 8k + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +#define MG_FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (MG_FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (MG_FLASH_BASE + 0xc) +#define FLASH_OPTCR (MG_FLASH_BASE + 0x1c) +#define FLASH_NSSR (MG_FLASH_BASE + 0x20) +#define FLASH_NSCR (MG_FLASH_BASE + 0x28) +#define FLASH_NSCCR (MG_FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (MG_FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (MG_FLASH_BASE + 0x54) + +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 *) s_mg_flash_stm32h5.start, + *end = base + s_mg_flash_stm32h5.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h5.secsz) == 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 +} + +static bool mg_stm32h5_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 *) s_mg_flash_stm32h5.start; + uint32_t sector = diff / s_mg_flash_stm32h5.secsz; + 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; +} + +static bool mg_stm32h5_swap(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; +} + +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h5.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h5.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; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) { + ok = 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; +} + +bool mg_ota_begin(size_t new_firmware_size) { +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +// Actual bank swap is deferred until reset, it is safe to execute in flash +bool mg_ota_end(void) { + if(!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false; + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + return true; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32h7.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32H7 || MG_OTA == MG_OTA_STM32H7_DUAL_CORE + +// - H723/735 RM 4.3.3: Note: The application can simultaneously request a read +// and a write operation through the AXI interface. +// - We only need IRAM for partition swapping in the H723, however, all +// related functions must reside in IRAM for this to be possible. +// - Linker files for other devices won't define a .iram section so there's no +// associated penalty + +static bool mg_stm32h7_write(void *, const void *, size_t); +static bool mg_stm32h7_swap(void); + +static struct mg_flash s_mg_flash_stm32h7 = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 128 * 1024, // Sector size, 128k + 32, // Align, 256 bit + mg_stm32h7_write, + mg_stm32h7_swap, +}; + +#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 + +#define IS_DUALCORE() (MG_OTA == MG_OTA_STM32H7_DUAL_CORE) + +MG_IRAM static bool is_dualbank(void) { + if (IS_DUALCORE()) { + // H745/H755 and H747/H757 are running on dual core. + // Using only the 1st bank (mapped to CM7), in order not to interfere + // with the 2nd bank (CM4), possibly causing CM4 to boot unexpectedly. + return false; + } + return (s_mg_flash_stm32h7.size < 2 * 1024 * 1024) ? false : true; +} + +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 (is_dualbank()) { + 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 *) s_mg_flash_stm32h7.start, + *end = base + s_mg_flash_stm32h7.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 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 *) s_mg_flash_stm32h7.start; + if (!is_dualbank()) return FLASH_BASE1; + return ofs < s_mg_flash_stm32h7.size / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_stm32h7_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 *) s_mg_flash_stm32h7.start; + uint32_t sector = diff / s_mg_flash_stm32h7.secsz; + 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 static bool mg_stm32h7_swap(void) { + if (!is_dualbank()) 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; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h7.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h7.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; + MG_ARM_DISABLE_IRQ(); + 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 + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h7_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + if (!s_flash_irq_disabled) 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; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_stm32h7_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h7.size = MG_REG(FLASH_SIZE_REG) * 1024; + if (IS_DUALCORE()) { + // Using only the 1st bank (mapped to CM7) + s_mg_flash_stm32h7.size /= 2; + } +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h7); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32h7)) { + if (is_dualbank()) { + // Bank swap is deferred until reset, been executing in flash, reset + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32h7.size, + s_mg_flash_stm32h7.size / s_mg_flash_stm32h7.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_stm32h7.start, + (char *) s_mg_flash_stm32h7.start + s_mg_flash_stm32h7.size / 2, + s_mg_flash_stm32h7.size / 2, s_mg_flash_stm32h7.secsz); + } + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/printf.c" +#endif + + + + +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) { + char *buf; + size_t len; + va_list ap1, ap2; + va_start(ap1, fmt); + len = mg_vsnprintf(NULL, 0, fmt, &ap1); + va_end(ap1); + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) + return 0; // Nah. Not enough space + va_start(ap2, fmt); + len = mg_vsnprintf(buf, len + 1, fmt, &ap2); + mg_queue_add(q, len); + va_end(ap2); + 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 + } +} + +void mg_pfn_iobuf_noresize(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 = {0, 0, 0, 0}; + size_t n; + io.buf = (uint8_t *) buf, io.size = len; + n = mg_vxprintf(mg_pfn_iobuf_noresize, &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->addr.ip); + return print_ip4(out, arg, (uint8_t *) &addr->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)); +} + +static size_t print_mac(void (*out)(char, void *), void *arg, uint8_t *p) { + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], + p[3], p[4], p[5]); +} + +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_mac(out, arg, p); +} + +#if MG_ENABLE_TCPIP +size_t mg_print_l2addr(void (*out)(char, void *), void *arg, va_list *ap) { + enum mg_l2type type = (enum mg_l2type) va_arg(*ap, int); + if (type == MG_TCPIP_L2_ETH) { + uint8_t *p = va_arg(*ap, uint8_t *); + return print_mac(out, arg, p); + } + return 0; +} +#endif + +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] = {0, 0, '=', '='}; + tmp[0] = t[c1 >> 2], tmp[1] = 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 *) mg_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; + mg_free((void *) r->method.buf); + mg_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_sha256(uint8_t dst[32], uint8_t *data, size_t datasz) { + mg_sha256_ctx ctx; + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, data, datasz); + mg_sha256_final(dst, &ctx); +} + +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); +} + +#define rotr64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#define ep064(x) (rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)) +#define ep164(x) (rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)) +#define sig064(x) (rotr64(x, 1) ^ rotr64(x, 8) ^ ((x) >> 7)) +#define sig164(x) (rotr64(x, 19) ^ rotr64(x, 61) ^ ((x) >> 6)) + +static const uint64_t mg_sha256_k2[80] = { +#if defined(__DCC__) + 0x428a2f98d728ae22ull, 0x7137449123ef65cdull, 0xb5c0fbcfec4d3b2full, + 0xe9b5dba58189dbbcull, 0x3956c25bf348b538ull, 0x59f111f1b605d019ull, + 0x923f82a4af194f9bull, 0xab1c5ed5da6d8118ull, 0xd807aa98a3030242ull, + 0x12835b0145706fbeull, 0x243185be4ee4b28cull, 0x550c7dc3d5ffb4e2ull, + 0x72be5d74f27b896full, 0x80deb1fe3b1696b1ull, 0x9bdc06a725c71235ull, + 0xc19bf174cf692694ull, 0xe49b69c19ef14ad2ull, 0xefbe4786384f25e3ull, + 0x0fc19dc68b8cd5b5ull, 0x240ca1cc77ac9c65ull, 0x2de92c6f592b0275ull, + 0x4a7484aa6ea6e483ull, 0x5cb0a9dcbd41fbd4ull, 0x76f988da831153b5ull, + 0x983e5152ee66dfabull, 0xa831c66d2db43210ull, 0xb00327c898fb213full, + 0xbf597fc7beef0ee4ull, 0xc6e00bf33da88fc2ull, 0xd5a79147930aa725ull, + 0x06ca6351e003826full, 0x142929670a0e6e70ull, 0x27b70a8546d22ffcull, + 0x2e1b21385c26c926ull, 0x4d2c6dfc5ac42aedull, 0x53380d139d95b3dfull, + 0x650a73548baf63deull, 0x766a0abb3c77b2a8ull, 0x81c2c92e47edaee6ull, + 0x92722c851482353bull, 0xa2bfe8a14cf10364ull, 0xa81a664bbc423001ull, + 0xc24b8b70d0f89791ull, 0xc76c51a30654be30ull, 0xd192e819d6ef5218ull, + 0xd69906245565a910ull, 0xf40e35855771202aull, 0x106aa07032bbd1b8ull, + 0x19a4c116b8d2d0c8ull, 0x1e376c085141ab53ull, 0x2748774cdf8eeb99ull, + 0x34b0bcb5e19b48a8ull, 0x391c0cb3c5c95a63ull, 0x4ed8aa4ae3418acbull, + 0x5b9cca4f7763e373ull, 0x682e6ff3d6b2b8a3ull, 0x748f82ee5defb2fcull, + 0x78a5636f43172f60ull, 0x84c87814a1f0ab72ull, 0x8cc702081a6439ecull, + 0x90befffa23631e28ull, 0xa4506cebde82bde9ull, 0xbef9a3f7b2c67915ull, + 0xc67178f2e372532bull, 0xca273eceea26619cull, 0xd186b8c721c0c207ull, + 0xeada7dd6cde0eb1eull, 0xf57d4f7fee6ed178ull, 0x06f067aa72176fbaull, + 0x0a637dc5a2c898a6ull, 0x113f9804bef90daeull, 0x1b710b35131c471bull, + 0x28db77f523047d84ull, 0x32caab7b40c72493ull, 0x3c9ebe0a15c9bebcull, + 0x431d67c49c100d4cull, 0x4cc5d4becb3e42b6ull, 0x597f299cfc657e2aull, + 0x5fcb6fab3ad6faecull, 0x6c44198c4a475817ull +#else + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, + 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, + 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, + 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +#endif +}; + +static void mg_sha384_transform(mg_sha384_ctx *ctx, const uint8_t data[]) { + uint64_t m[80]; + uint64_t a, b, c, d, e, f, g, h; + int i, j; + + for (i = 0, j = 0; i < 16; ++i, j += 8) + m[i] = ((uint64_t) data[j] << 56) | ((uint64_t) data[j + 1] << 48) | + ((uint64_t) data[j + 2] << 40) | ((uint64_t) data[j + 3] << 32) | + ((uint64_t) data[j + 4] << 24) | ((uint64_t) data[j + 5] << 16) | + ((uint64_t) data[j + 6] << 8) | ((uint64_t) data[j + 7]); + for (; i < 80; ++i) + m[i] = sig164(m[i - 2]) + m[i - 7] + sig064(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 < 80; ++i) { + uint64_t t1 = h + ep164(e) + ch(e, f, g) + mg_sha256_k2[i] + m[i]; + uint64_t t2 = ep064(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_sha384_init(mg_sha384_ctx *ctx) { + ctx->datalen = 0; + ctx->bitlen[0] = 0; + ctx->bitlen[1] = 0; +#if defined(__DCC__) + ctx->state[0] = 0xcbbb9d5dc1059ed8ull; + ctx->state[1] = 0x629a292a367cd507ull; + ctx->state[2] = 0x9159015a3070dd17ull; + ctx->state[3] = 0x152fecd8f70e5939ull; + ctx->state[4] = 0x67332667ffc00b31ull; + ctx->state[5] = 0x8eb44a8768581511ull; + ctx->state[6] = 0xdb0c2e0d64f98fa7ull; + ctx->state[7] = 0x47b5481dbefa4fa4ull; +#else + ctx->state[0] = 0xcbbb9d5dc1059ed8; + ctx->state[1] = 0x629a292a367cd507; + ctx->state[2] = 0x9159015a3070dd17; + ctx->state[3] = 0x152fecd8f70e5939; + ctx->state[4] = 0x67332667ffc00b31; + ctx->state[5] = 0x8eb44a8768581511; + ctx->state[6] = 0xdb0c2e0d64f98fa7; + ctx->state[7] = 0x47b5481dbefa4fa4; +#endif +} + +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + ctx->buffer[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 128) { + mg_sha384_transform(ctx, ctx->buffer); + ctx->bitlen[1] += 1024; + if (ctx->bitlen[1] < 1024) ctx->bitlen[0]++; + ctx->datalen = 0; + } + } +} + +void mg_sha384_final(uint8_t hash[48], mg_sha384_ctx *ctx) { + size_t i = ctx->datalen; + + if (ctx->datalen < 112) { + ctx->buffer[i++] = 0x80; + while (i < 112) ctx->buffer[i++] = 0x00; + } else { + ctx->buffer[i++] = 0x80; + while (i < 128) ctx->buffer[i++] = 0x00; + mg_sha384_transform(ctx, ctx->buffer); + memset(ctx->buffer, 0, 112); + } + + ctx->bitlen[1] += ctx->datalen * 8; + if (ctx->bitlen[1] < ctx->datalen * 8) ctx->bitlen[0]++; + ctx->buffer[127] = (uint8_t) (ctx->bitlen[1]); + ctx->buffer[126] = (uint8_t) (ctx->bitlen[1] >> 8); + ctx->buffer[125] = (uint8_t) (ctx->bitlen[1] >> 16); + ctx->buffer[124] = (uint8_t) (ctx->bitlen[1] >> 24); + ctx->buffer[123] = (uint8_t) (ctx->bitlen[1] >> 32); + ctx->buffer[122] = (uint8_t) (ctx->bitlen[1] >> 40); + ctx->buffer[121] = (uint8_t) (ctx->bitlen[1] >> 48); + ctx->buffer[120] = (uint8_t) (ctx->bitlen[1] >> 56); + ctx->buffer[119] = (uint8_t) (ctx->bitlen[0]); + ctx->buffer[118] = (uint8_t) (ctx->bitlen[0] >> 8); + ctx->buffer[117] = (uint8_t) (ctx->bitlen[0] >> 16); + ctx->buffer[116] = (uint8_t) (ctx->bitlen[0] >> 24); + ctx->buffer[115] = (uint8_t) (ctx->bitlen[0] >> 32); + ctx->buffer[114] = (uint8_t) (ctx->bitlen[0] >> 40); + ctx->buffer[113] = (uint8_t) (ctx->bitlen[0] >> 48); + ctx->buffer[112] = (uint8_t) (ctx->bitlen[0] >> 56); + mg_sha384_transform(ctx, ctx->buffer); + + for (i = 0; i < 6; ++i) { + hash[i * 8] = (uint8_t) ((ctx->state[i] >> 56) & 0xff); + hash[i * 8 + 1] = (uint8_t) ((ctx->state[i] >> 48) & 0xff); + hash[i * 8 + 2] = (uint8_t) ((ctx->state[i] >> 40) & 0xff); + hash[i * 8 + 3] = (uint8_t) ((ctx->state[i] >> 32) & 0xff); + hash[i * 8 + 4] = (uint8_t) ((ctx->state[i] >> 24) & 0xff); + hash[i * 8 + 5] = (uint8_t) ((ctx->state[i] >> 16) & 0xff); + hash[i * 8 + 6] = (uint8_t) ((ctx->state[i] >> 8) & 0xff); + hash[i * 8 + 7] = (uint8_t) (ctx->state[i] & 0xff); + } +} + +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz) { + mg_sha384_ctx ctx; + mg_sha384_init(&ctx); + mg_sha384_update(&ctx, data, datasz); + mg_sha384_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 *fn_data) { + if (url == NULL) url = "udp://time.google.com:123"; + return mg_connect_svc(mgr, url, fn, fn_data, sntp_cb, NULL); +} + +#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->addr.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->addr.ip, sizeof(a->addr.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->addr.ip, &usa->sin.sin_addr, sizeof(uint32_t)); +#if MG_ENABLE_IPV6 + if (is_ip6) { + memcpy(a->addr.ip, &usa->sin6.sin6_addr, sizeof(a->addr.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; // MbedTLS, see #1507 + 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 len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide + } +} + +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_THREADX + // NetxDuo fails to send large blocks of data to the non-blocking sockets + (void) fd; + // 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 +} + +void mg_multicast_add(struct mg_connection *c, char *ip); +void mg_multicast_add(struct mg_connection *c, char *ip) { +#if MG_ENABLE_RL + MG_ERROR(("unsupported")); +#elif MG_ENABLE_FREERTOS_TCP + // TODO(): prvAllowIPPacketIPv4() +#else + // lwIP, Unix, Windows, Zephyr 4+(, AzureRTOS ?) +#if MG_ENABLE_LWIP && !LWIP_IGMP + MG_ERROR(("LWIP_IGMP not defined, no multicast support")); +#else +#if defined(__ZEPHYR__) && ZEPHYR_VERSION_CODE < 0x40000 + MG_ERROR(("struct ip_mreq not defined")); +#else + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(ip); + mreq.imr_interface.s_addr = mg_htonl(INADDR_ANY); + setsockopt(FD(c), IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, + sizeof(mreq)); +#endif // !Zephyr +#endif // !lwIP +#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; // MbedTLS, see #1507 + 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 + long m; + 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 > 0) c->rtls.len += (size_t) n; + } + // there can still be > 16K from last iteration, always mg_tls_recv() + m = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len); + if (n == MG_IO_ERR || n == MG_IO_RESET) { // Windows, see #3031 + if (c->rtls.len == 0 || m < 0) { + // 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. + if (m < 0) m = MG_IO_ERR; // but return last record data, see #3104 + } else { // see #2885 + // TLS buffer is capped to max record size, even though, there can + // be more than one record, give TLS a chance to process them. + } + } else if (c->is_tls_hs) { + mg_tls_handshake(c); + } + n = m; + } 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); + // TODO(): mg_tls_send() may return 0 forever on steady OOM + 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); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } else { + mg_error(c, "socket error"); + } +} + +static void setsockopts(struct mg_connection *c) { +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_THREADX || \ + 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 + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } 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_THREADX || defined(__ECOS) + // NetxDuo, 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; + setlocaddr(fd, &c->loc); // set local addr to where the client connected to + c->pfn = lsn->pfn; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; + 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); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } +} + +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_1ms = {0, 1000}, *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_1ms; + if (FD(c) > maxfd) maxfd = FD(c); + if (c->is_closing) tvp = &tv_1ms; + } + + 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 + if (rc < 0) 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)) { +#if MG_ARCH == MG_ARCH_THREADX + // NetxDuo stack returns exceptions for listening connection after accept + if (c->is_listening == 0) mg_error(c, "socket error"); +#else + mg_error(c, "socket error"); +#endif + } 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_readable) read_conn(c); + if (c->is_writable) write_conn(c); + if (c->is_tls && !c->is_tls_hs && c->send.len == 0) mg_tls_flush(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_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/sdio.c" +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// SDIO 6.9 Table 6-1 CCCR (Common Card Control Registers) +#define MG_SDIO_CCCR_SDIOREV 0x000 +#define MG_SDIO_CCCR_SDREV 0x001 +#define MG_SDIO_CCCR_IOEN 0x002 +#define MG_SDIO_CCCR_IORDY 0x003 +#define MG_SDIO_CCCR_INTEN 0x004 +#define MG_SDIO_CCCR_BIC 0x007 +#define MG_SDIO_CCCR_CCAP 0x008 +#define MG_SDIO_CCCR_CCIS 0x009 // 3 registers +#define MG_SDIO_CCCR_F0BLKSZ 0x010 // 2 registers +#define MG_SDIO_CCCR_HISPD 0x013 +// SDIO 6.10 Table 6-3 FBR (Function Basic Registers) +#define MG_SDIO_FBR_FnBLKSZ(n) (((n) &7) * 0x100 + 0x10) // 2 registers + +// SDIO 5.1 IO_RW_DIRECT Command (CMD52) +#define MG_SDIO_DATA(x) ((x) &0xFF) // bits 0-7 +#define MG_SDIO_ADDR(x) (((x) &0x1FFFF) << 9) // bits 9-25 +#define MG_SDIO_FUNC(x) (((x) &3) << 28) // bits 28-30 (30 unused here) +#define MG_SDIO_WR MG_BIT(31) + +// SDIO 5.3 IO_RW_EXTENDED Command (CMD53) +#define MG_SDIO_LEN(x) ((x) &0x1FF) // bits 0-8 +#define MG_SDIO_OPINC MG_BIT(26) +#define MG_SDIO_BLKMODE MG_BIT(27) + +// - Drivers set blocksize, drivers request transfers. Requesting a read +// transfer > blocksize means block transfer will be used. +// - To simplify the use of DMA transfers and avoid intermediate buffers, +// drivers must have room to accomodate a whole block transfer, e.g.: blocksize +// = 64, read 65 => 2 blocks = 128 bytes +// - Transfers of more than 1 byte assume (uint32_t *) data. 1-byte transfers +// use (uint8_t *) data +// - 'len' is the number of _bytes_ to transfer +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len) { + uint32_t arg, val = 0; + unsigned int blksz = 64; // TODO(): mg_sdio_set_blksz() stores in an array, + // index on f, skip if 0 + if (len == 1) { + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_FUNC(f) | MG_SDIO_ADDR(addr) | + (write ? MG_SDIO_DATA(*(uint8_t *) data) : 0); + bool res = sdio->txn(sdio, 52, arg, &val); // IO_RW_DIRECT + if (!write) *(uint8_t *) data = (uint8_t) val; + return res; + } + // IO_RW_EXTENDED + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_OPINC | MG_SDIO_FUNC(f) | + MG_SDIO_ADDR(addr); + if (len > 512 || (blksz != 0 && len > blksz)) { // SDIO 5.3 512 -> len=0 + unsigned int blkcnt; + if (blksz == 0) return false; // > 512 requires block size set + blkcnt = (len + blksz - 1) / blksz; + if (blkcnt > 511) return false; // we don't support "infinite" blocks + arg |= MG_SDIO_BLKMODE | MG_SDIO_LEN(blkcnt); // block transfer + len = blksz * blkcnt; + } else { + arg |= MG_SDIO_LEN(len); // multi-byte transfer + } + return sdio->xfr(sdio, write, arg, + (arg & MG_SDIO_BLKMODE) ? (uint16_t) blksz : 0, + (uint32_t *) data, len, &val); +} + +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz) { + uint32_t val = blksz & 0xff; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f), &val, 1)) + return false; + val = (blksz >> 8) & 0x0f; // SDIO 6.10 Table 6-4, max 2048 + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f) + 1, &val, 1)) + return false; + // TODO(): store in an array, index on f. Static 8-element array + MG_VERBOSE(("F%c block size set", (f & 7) + '0')); + return true; +} + +// Enable Fx +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7), bits; + uint32_t val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + bits = (uint8_t) val | bit; + unsigned int times = 501; + while (times--) { + val = bits; /* IOEf */ + ; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + mg_delayms(1); + val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + if (val & bit) break; + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c enabled", (f & 7) + '0')); + return true; +} + +// Wait for Fx to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7); + unsigned int times = 501; + while (times--) { + uint32_t val; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IORDY, &val, 1)) + return false; + if (val & bit) break; // IORf + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c ready", (f & 7) + '0')); + return true; +} + +// SDIO 6.14 Bus State Diagram +bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { + uint32_t val = 0; + if (!sdio->txn(sdio, 0, 0, NULL)) return false; // GO_IDLE_STATE + sdio->txn(sdio, 5, 0, &val); // IO_SEND_OP_COND, no CRC + MG_VERBOSE(("IO Functions: %u, Memory: %c", 1 + ((val >> 28) & 7), + (val & MG_BIT(27)) ? 'Y' : 'N')); + if (!sdio->txn(sdio, 3, 0, &val)) return false; // SEND_RELATIVE_ADDR + val = ((uint32_t) val) >> 16; // RCA + if (!sdio->txn(sdio, 7, val << 16, &val)) + return false; // SELECT/DESELECT_CARD + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDIOREV, &val, 1); + MG_DEBUG(("CCCR: %u.%u, SDIO: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3, + 1 + ((val >> 6) & 3), (val >> 4) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDREV, &val, 1); + MG_VERBOSE(("SD: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_BIC, &val, 1); + MG_SET_BITS(val, 3, + MG_BIT(7) | MG_BIT(1)); // SDIO 6.9 Tables 6-1 6-2, 4-bit bus + mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_BIC, &val, 1); + // All Full-Speed SDIO cards support a 4-bit bus. Skip for Low-Speed SDIO + // cards, we don't provide separate low-level functions for width and speed + sdio->cfg(sdio, 0); // set DS; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + if (val & MG_BIT(0) /* SHS */) { + val = MG_BIT(1); /* EHS */ + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + sdio->cfg(sdio, 1); // set HS; + MG_VERBOSE(("Bus set to 4-bit @50MHz")); + } else { + MG_VERBOSE(("Bus set to 4-bit @25MHz")); + } + return true; +} + +// - 6.11 Card Information Structure (CIS): 0x0001000-0x017FF; for card common +// and all functions +// - 16.5 SDIO Card Metaformat +// - 16.7.2 CISTPL_FUNCE (0x22): Function Extension Tuple, provides standard +// information about the card (common) and each individual function. One +// CISTPL_FUNCE in each function’s CIS, immediately following the CISTPL_FUNCID +// tuple +// - 16.7.3 CISTPL_FUNCE Tuple for Function 0 (common) +// - 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 + +#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] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // 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(10); // Perfect filtering + 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 void mg_tcpip_driver_stm32f_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE + (void) ifp; +} + +static bool mg_tcpip_driver_stm32f_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32f_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/stm32h.c" +#endif + + +#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \ + MG_ENABLE_DRIVER_STM32N) +// STM32H: vendor modded single-queue Synopsys v4.2 +// STM32N: dual-queue GbE Synopsys v5.2 with no hash table option, 64-bit AXI +// MCXNx4x: dual-queue Synopsys v5.2 with no hash table option +// RT1170: ENET_QOS: quad-queue Synopsys v5.1 +#if MG_ENABLE_DRIVER_STM32H +#define SYNOPSYS_ENET_V5 0 +#define SYNOPSYS_ENET_SINGLEQ 1 +#define SYNOPSYS_ENET_NOHASHTABLE 0 +#define SYNOPSYS_ENET_GbE 0 +#elif MG_ENABLE_DRIVER_STM32N +#define SYNOPSYS_ENET_V5 1 +#define SYNOPSYS_ENET_SINGLEQ 0 +#define SYNOPSYS_ENET_NOHASHTABLE 1 +#define SYNOPSYS_ENET_GbE 1 +#elif MG_ENABLE_DRIVER_MCXN +#define SYNOPSYS_ENET_V5 1 +#define SYNOPSYS_ENET_SINGLEQ 0 +#define SYNOPSYS_ENET_NOHASHTABLE 1 +#define SYNOPSYS_ENET_GbE 0 +#endif + +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) 0x40028000UL) +#elif MG_ENABLE_DRIVER_STM32N +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x48036000UL) +#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) + +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_STM32N +#define CACHE_LINESZ 32 // must be a whole number of (d)words (see DESC_SZW) +#ifndef SCB +struct m7_scb { + volatile uint32_t CPUID, RESERVED1[4], CCR, RESERVED2[145], DCIMVAC, DCISW, + DCCMVAU, DCCMVAC, DCCSW, DCCIMVAC, DCCISW, RESERVED3[10], CACR, + RESERVED4[3]; +}; +#define SCB ((struct m7_scb *) (uintptr_t) 0xE000ED00UL) +#endif +// ending ISB is not needed because we don't cache instructions in data space +static inline void MG_CACHE_INVAL(uint8_t *addr, int32_t len) { +#if MG_ENABLE_DRIVER_STM32H + if ((SCB->CPUID & 0xfff0) != 0xc270) return; // not a Cortex-M7 => not an H7 +#endif + if ((SCB->CCR & MG_BIT(16)) == 0) return; // cache not enabled + MG_DSB(); + while (len > 0) { + SCB->DCIMVAC = (uint32_t) addr; + addr += CACHE_LINESZ; + len -= CACHE_LINESZ; + } + MG_DSB(); +} +static inline void MG_CACHE_FLUSH(uint8_t *addr, int32_t len) { +#if MG_ENABLE_DRIVER_STM32H + if ((SCB->CPUID & 0xfff0) != 0xc270) return; // not a Cortex-M7 => not an H7 +#endif + if ((SCB->CCR & MG_BIT(16)) == 0) return; // cache not enabled + MG_DSB(); + while (len > 0) { + SCB->DCCMVAC = (uint32_t) addr; + addr += CACHE_LINESZ; + len -= CACHE_LINESZ; + } + MG_DSB(); +} +#define ETH_RAM_ALIGNED MG_32BYTE_ALIGNED // depends on CACHE_LINESZ and ETH +#define CACHE_ALIGN(x) \ + ((((size_t) (x)) + CACHE_LINESZ - 1) & ~(CACHE_LINESZ - 1)) +#define DESC_SZ CACHE_ALIGN(4 * ETH_DS) // grow descriptors to fit a line +#define DESC_SZW (DESC_SZ / 4) +#define BUFF_SZ CACHE_ALIGN(ETH_PKT_SIZE) // grow buffers to fit n lines +#if MG_ENABLE_DRIVER_STM32H +#define DESC_SKIPW (DESC_SZW - ETH_DS) // tell DMA the descriptor size, words +#else +// MG_ENABLE_DRIVER_STM32N, DMA is AXI and specs skip in 64-bit double-words +#define DESC_SKIPW ((DESC_SZW - ETH_DS) / 2) +#endif +#else +#define MG_CACHE_FLUSH(a, b) +#define MG_CACHE_INVAL(a, b) +#define ETH_RAM_ALIGNED MG_8BYTE_ALIGNED // depends on ETH DMA alone +#define DESC_SZ 0 +#define DESC_SZW ETH_DS +#define BUFF_SZ ETH_PKT_SIZE +#define DESC_SKIPW 0 // no need to skip, as we're not aligning to cache lines +#endif + +// array[rows][cols] = coldata coldata ... for all rows, keeps alignment +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][DESC_SZW] MG_ETH_RAM + ETH_RAM_ALIGNED; +static volatile uint32_t s_txdesc[ETH_DESC_CNT][DESC_SZW] MG_ETH_RAM + ETH_RAM_ALIGNED; +static uint8_t s_rxbuf[ETH_DESC_CNT][BUFF_SZ] MG_ETH_RAM ETH_RAM_ALIGNED; +static uint8_t s_txbuf[ETH_DESC_CNT][BUFF_SZ] MG_ETH_RAM ETH_RAM_ALIGNED; + +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 + memset((char *) s_rxdesc, 0, sizeof(s_rxdesc)); // manual init + 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 + } + MG_CACHE_FLUSH((uint8_t *) s_rxdesc, sizeof(s_rxdesc)); + + // Init TX descriptors + memset((char *) s_txdesc, 0, sizeof(s_txdesc)); // manual init + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer + } + MG_CACHE_FLUSH((uint8_t *) s_txdesc, sizeof(s_txdesc)); + + 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 +#if !SYNOPSYS_ENET_V5 + ETH->MACPFR = MG_BIT(10); // Perfect filtering +#endif + 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 = DESC_SKIPW << 18; // DSL (contiguous/sparse descriptor table) +#if SYNOPSYS_ENET_V5 + 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, (10/100)/Reserved +#if SYNOPSYS_ENET_SINGLEQ + 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)); + return 0; // Frame is too big + } + MG_CACHE_INVAL((uint8_t *) &s_txdesc[s_txno], DESC_SZ); + 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)); + MG_CACHE_INVAL((uint8_t *) s_txdesc, sizeof(s_txdesc)); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_txbuf[s_txno], BUFF_SZ); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_txdesc[s_txno], DESC_SZ); + 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 void mg_tcpip_driver_stm32h_update_hash_table(struct mg_tcpip_if *ifp) { +#if SYNOPSYS_ENET_NOHASHTABLE + ETH->MACPFR = MG_BIT(4); // Pass Multicast (pass all multicast frames) +#else + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE +#endif + (void) ifp; +} + +static bool mg_tcpip_driver_stm32h_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32h_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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 SYNOPSYS_ENET_GbE + if (speed == MG_PHY_SPEED_1000M) maccr &= ~MG_BIT(15); // 1000M +#endif + 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) { +#elif MG_ENABLE_DRIVER_STM32H +void ETH_IRQHandler(void); +void ETH_IRQHandler(void) { +#else +void ETH1_IRQHandler(void); +void ETH1_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 + MG_CACHE_INVAL((uint8_t *) &s_rxdesc[s_rxno], DESC_SZ); + 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_CACHE_INVAL((uint8_t *) &s_rxbuf[s_rxno], BUFF_SZ); + 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 + MG_CACHE_FLUSH((uint8_t *) &s_rxdesc[s_rxno], DESC_SZ); + 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_poll}; +#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 i, result = -1; // Invalid CR value + if (sysclk < 25000000) { + MG_ERROR(("SYSCLK too low")); + } else { + for (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; + int i; + s_ifp = ifp; + + // Init RX descriptors + for (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 (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(10); // Perfect filtering + // 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]; + 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; +} + +static void mg_tcpip_driver_tm4c_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + EMAC->EMACADDR1L = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + EMAC->EMACADDR1H = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + EMAC->EMACADDR1H |= MG_BIT(31); // AE + (void) ifp; +} + +static bool mg_tcpip_driver_tm4c_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tm4c_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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) { + int i; + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag + for (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_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/tms570.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct tms570_emac_ctrl { + volatile uint32_t REVID, SOFTRESET, RESERVED1[1], INTCONTROL, C0RXTHRESHEN, + C0RXEN, C0TXEN, C0MISCEN, RESERVED2[8], + C0RXTHRESHSTAT, C0RXSTAT, C0TXSTAT, C0MISCSTAT, + RESERVED3[8], + C0RXIMAX, C0TXIMAX; +}; +struct tms570_emac { + volatile uint32_t TXREVID, TXCONTROL, TXTEARDOWN, RESERVED1[1], RXREVID, + RXCONTROL, RXTEARDOWN, RESERVED2[25], TXINTSTATRAW,TXINTSTATMASKED, + TXINTMASKSET, TXINTMASKCLEAR, MACINVECTOR, MACEOIVECTOR, RESERVED8[2], RXINTSTATRAW, + RXINTSTATMASKED, RXINTMASKSET, RXINTMASKCLEAR, MACINTSTATRAW, MACINTSTATMASKED, + MACINTMASKSET, MACINTMASKCLEAR, RESERVED3[16], RXMBPENABLE, RXUNICASTSET, + RXUNICASTCLEAR, RXMAXLEN, RXBUFFEROFFSET, RXFILTERLOWTHRESH, RESERVED9[2], RXFLOWTHRESH[8], + RXFREEBUFFER[8], MACCONTROL, MACSTATUS, EMCONTROL, FIFOCONTROL, MACCONFIG, + SOFTRESET, RESERVED4[22], MACSRCADDRLO, MACSRCADDRHI, MACHASH1, MACHASH2, + BOFFTEST, TPACETEST, RXPAUSE, TXPAUSE, RESERVED5[4], RXGOODFRAMES, RXBCASTFRAMES, + RXMCASTFRAMES, RXPAUSEFRAMES, RXCRCERRORS, RXALIGNCODEERRORS, RXOVERSIZED, + RXJABBER, RXUNDERSIZED, RXFRAGMENTS, RXFILTERED, RXQOSFILTERED, RXOCTETS, + TXGOODFRAMES, TXBCASTFRAMES, TXMCASTFRAMES, TXPAUSEFRAMES, TXDEFERRED, + TXCOLLISION, TXSINGLECOLL, TXMULTICOLL, TXEXCESSIVECOLL, TXLATECOLL, + TXUNDERRUN, TXCARRIERSENSE, TXOCTETS, FRAME64, FRAME65T127, FRAME128T255, + FRAME256T511, FRAME512T1023, FRAME1024TUP, NETOCTETS, RXSOFOVERRUNS, + RXMOFOVERRUNS, RXDMAOVERRUNS, RESERVED6[156], MACADDRLO, MACADDRHI, + MACINDEX, RESERVED7[61], TXHDP[8], RXHDP[8], TXCP[8], RXCP[8]; +}; +struct tms570_mdio { + volatile uint32_t REVID, CONTROL, ALIVE, LINK, LINKINTRAW, LINKINTMASKED, + RESERVED1[2], USERINTRAW, USERINTMASKED, USERINTMASKSET, USERINTMASKCLEAR, + RESERVED2[20], USERACCESS0, USERPHYSEL0, USERACCESS1, USERPHYSEL1; +}; +#define SWAP32(x) ( (((x) & 0x000000FF) << 24) | \ + (((x) & 0x0000FF00) << 8) | \ + (((x) & 0x00FF0000) >> 8) | \ + (((x) & 0xFF000000) >> 24) ) +#undef EMAC +#undef EMAC_CTRL +#undef MDIO +#define EMAC ((struct tms570_emac *) (uintptr_t) 0xFCF78000) +#define EMAC_CTRL ((struct tms570_emac_ctrl *) (uintptr_t) 0xFCF78800) +#define MDIO ((struct tms570_mdio *) (uintptr_t) 0xFCF78900) +#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_txdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // TX descriptors +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // RX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface +static uint16_t emac_read_phy(uint8_t addr, uint8_t reg) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + return MDIO->USERACCESS0 & 0xffff; +} +static void emac_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | MG_BIT(30) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16) | (val & 0xffff); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; +} +static bool mg_tcpip_driver_tms570_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + s_ifp = ifp; + EMAC_CTRL->SOFTRESET = MG_BIT(0); // Reset the EMAC Control Module + while(EMAC_CTRL->SOFTRESET & MG_BIT(0)) (void) 0; // wait + EMAC->SOFTRESET = MG_BIT(0); // Reset the EMAC Module + while(EMAC->SOFTRESET & MG_BIT(0)) (void) 0; + EMAC->MACCONTROL = 0; + EMAC->RXCONTROL = 0; + EMAC->TXCONTROL = 0; + // Initialize all the header descriptor pointer registers + uint32_t i; + for(i = 0; i < ETH_DESC_CNT; i++) { + EMAC->RXHDP[i] = 0; + EMAC->TXHDP[i] = 0; + EMAC->RXCP[i] = 0; + EMAC->TXCP[i] = 0; + ///EMAC->RXFREEBUFFER[i] = 0xff; + } + // Clear the interrupt enable for all the channels + EMAC->TXINTMASKCLEAR = 0xff; + EMAC->RXINTMASKCLEAR = 0xff; + EMAC->MACHASH1 = 0; + EMAC->MACHASH2 = 0; + EMAC->RXBUFFEROFFSET = 0; + EMAC->RXUNICASTCLEAR = 0xff; + EMAC->RXUNICASTSET = 0; + EMAC->RXMBPENABLE = 0; + // init MDIO + // MDIO_CLK frequency = VCLK3/(CLKDIV + 1). (MDIO must be between 1.0 - 2.5Mhz) + uint32_t clkdiv = 75; // VCLK is configured to 75Mhz + // CLKDIV, ENABLE, PREAMBLE, FAULTENB + MDIO->CONTROL = (clkdiv - 1) | MG_BIT(30) | MG_BIT(20) | MG_BIT(18); + volatile int delay = 0xfff; + while (delay-- != 0) (void) 0; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + uint32_t channel; + for (channel = 0; channel < 8; channel++) { + EMAC->MACINDEX = channel; + EMAC->MACADDRHI = ifp->mac[0] | (ifp->mac[1] << 8) | (ifp->mac[2] << 16) | + (ifp->mac[3] << 24); + EMAC->MACADDRLO = ifp->mac[4] | (ifp->mac[5] << 8) | MG_BIT(20) | + MG_BIT(19) | (channel << 16); + } + EMAC->RXUNICASTSET = 1; // accept unicast frames; + + EMAC->RXMBPENABLE |= MG_BIT(30) | MG_BIT(13); // CRC, broadcast + + // Initialize the descriptors + for (i = 0; i < ETH_DESC_CNT; i++) { + if (i < ETH_DESC_CNT - 1) { + s_txdesc[i][0] = 0; + s_rxdesc[i][0] = SWAP32(((uint32_t) &s_rxdesc[i + 1][0])); + } + s_txdesc[i][1] = SWAP32(((uint32_t) s_txbuf[i])); + s_rxdesc[i][1] = SWAP32(((uint32_t) s_rxbuf[i])); + s_txdesc[i][2] = 0; + s_rxdesc[i][2] = SWAP32(ETH_PKT_SIZE); + s_txdesc[i][3] = 0; + s_rxdesc[i][3] = SWAP32(MG_BIT(29)); // OWN + } + s_txdesc[ETH_DESC_CNT - 1][0] = 0; + s_rxdesc[ETH_DESC_CNT - 1][0] = 0; + + EMAC->MACCONTROL = MG_BIT(5) | MG_BIT(0); // Enable MII, Full-duplex + //EMAC->TXINTMASKSET = 1; // Enable TX interrupt + EMAC->RXINTMASKSET = 1; // Enable RX interrupt + //EMAC_CTRL->C0TXEN = 1; // TX completion interrupt + EMAC_CTRL->C0RXEN = 1; // RX completion interrupt + EMAC->TXCONTROL = 1; // TXEN + EMAC->RXCONTROL = 1; // RXEN + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + return true; +} +static uint32_t s_txno; +static size_t mg_tcpip_driver_tms570_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][3] & SWAP32(MG_BIT(29)))) { + ifp->nerr++; + MG_ERROR(("No descriptors available")); + len = 0; // fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + if (len < 128) len = 128; + s_txdesc[s_txno][2] = SWAP32((uint32_t) len); // Set data len + s_txdesc[s_txno][3] = + SWAP32(MG_BIT(31) | MG_BIT(30) | MG_BIT(29) | len); // SOP, EOP, OWN, length + + while(EMAC->TXHDP[0] != 0) (void) 0; + EMAC->TXHDP[0] = (uint32_t) &s_txdesc[s_txno][0]; + if(++s_txno == ETH_DESC_CNT) { + s_txno = 0; + } + } + return len; + (void) ifp; +} + +static void mg_tcpip_driver_tms570_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // Setting Hash Index for 01:00:5e:00:00:fb (multicast) + // using TMS570 XOR method (32.5.37). + // computed hash is 55, which means bit 23 (55 - 32) in + // HASH2 register must be set + EMAC->MACHASH2 = MG_BIT(23); + EMAC->RXMBPENABLE = MG_BIT(5); // enable hash filtering + (void) ifp; +} + +static bool mg_tcpip_driver_tms570_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tms570_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + if (!s1) return false; + 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; +} + +#pragma CODE_STATE(EMAC_TX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_TX_IRQHandler, IRQ) +void EMAC_TX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0TXSTAT; + if (status & 1) { // interrupt caused on channel 0 + while(s_txdesc[s_txno][3] & SWAP32(MG_BIT(29))) (void) 0; + EMAC->TXCP[0] = (uint32_t) &s_txdesc[s_txno][0]; + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 2; +} +static uint32_t s_rxno; +#pragma CODE_STATE(EMAC_RX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_RX_IRQHandler, IRQ) +void EMAC_RX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0RXSTAT; + if (status & 1) { // Frame received, loop + uint32_t i; + //MG_INFO(("RX interrupt")); + for (i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][3] & SWAP32(MG_BIT(29))) break; + uint32_t len = SWAP32(s_rxdesc[s_rxno][3]) & 0xffff; + //MG_INFO(("recv len: %d", len)); + //mg_hexdump(s_rxbuf[s_rxno], len); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + uint32_t flags = s_rxdesc[s_rxno][3]; + s_rxdesc[s_rxno][3] = SWAP32(MG_BIT(29)); + s_rxdesc[s_rxno][2] = SWAP32(ETH_PKT_SIZE); + EMAC->RXCP[0] = (uint32_t) &s_rxdesc[s_rxno][0]; + if (flags & SWAP32(MG_BIT(28))) { + //MG_INFO(("EOQ detected")); + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + } + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 1; +} +struct mg_tcpip_driver mg_tcpip_driver_tms570 = {mg_tcpip_driver_tms570_init, + mg_tcpip_driver_tms570_tx, NULL, + mg_tcpip_driver_tms570_poll}; +#endif + + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/w5100.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5100) && MG_ENABLE_DRIVER_W5100 + +static void w5100_txn(struct mg_tcpip_spi *s, uint16_t addr, bool wr, void *buf, + size_t len) { + size_t i; + uint8_t *p = (uint8_t *) buf; + uint8_t control = wr ? 0xF0 : 0x0F; + uint8_t cmd[] = {control, (uint8_t) (addr >> 8), (uint8_t) (addr & 255)}; + 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 w5100_wn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, true, buf, len); } +static void w5100_w1(struct mg_tcpip_spi *s, uint16_t addr, uint8_t val) { w5100_wn(s, addr, &val, 1); } +static void w5100_w2(struct mg_tcpip_spi *s, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5100_wn(s, addr, buf, sizeof(buf)); } +static void w5100_rn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, false, buf, len); } +static uint8_t w5100_r1(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t r = 0; w5100_rn(s, addr, &r, 1); return r; } +static uint16_t w5100_r2(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5100_rn(s, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } +// clang-format on + +static size_t w5100_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 = w5100_r2(s, 0x426)) > n) n = n2; // Until it is stable + if (n > 0) { + uint16_t ptr = w5100_r2(s, 0x428); // Get read pointer + if (n <= len + 2 && n > 1) { + r = (uint16_t) (n - 2); + } + uint16_t rxbuf_size = (1 << (w5100_r1(s, 0x1a) & 3)) * 1024; + uint16_t rxbuf_addr = 0x6000; + uint16_t ptr_ofs = (ptr + 2) & (rxbuf_size - 1); + if (ptr_ofs + r < rxbuf_size) { + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, r); + } else { + uint16_t remaining_len = rxbuf_size - ptr_ofs; + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, remaining_len); + w5100_rn(s, rxbuf_addr, buf + remaining_len, n - remaining_len); + } + w5100_w2(s, 0x428, (uint16_t) (ptr + n)); + w5100_w1(s, 0x401, 0x40); // Sock0 CR -> RECV + } + return r; +} + +static size_t w5100_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, n = 0, ptr = 0, len = (uint16_t) buflen; + while (n < len) n = w5100_r2(s, 0x420); // Wait for space + ptr = w5100_r2(s, 0x424); // Get write pointer + uint16_t txbuf_size = (1 << (w5100_r1(s, 0x1b) & 3)) * 1024; + uint16_t ptr_ofs = ptr & (txbuf_size - 1); + uint16_t txbuf_addr = 0x4000; + if (ptr_ofs + len > txbuf_size) { + uint16_t size = txbuf_size - ptr_ofs; + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, size); + w5100_wn(s, txbuf_addr, (char *) buf + size, len - size); + } else { + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, len); + } + w5100_w2(s, 0x424, (uint16_t) (ptr + len)); // Advance write pointer + w5100_w1(s, 0x401, 0x20); // Sock0 CR -> SEND + for (i = 0; i < 40; i++) { + uint8_t ir = w5100_r1(s, 0x402); // Read S0 IR + if (ir == 0) continue; + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr); + w5100_w1(s, 0x402, 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 w5100_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + s->end(s->spi); + w5100_w1(s, 0, 0x80); // Reset chip: CR -> 0x80 + w5100_w1(s, 0x72, 0x53); // CR PHYLCKR -> unlock PHY + w5100_w1(s, 0x46, 0); // CR PHYCR0 -> autonegotiation + w5100_w1(s, 0x47, 0); // CR PHYCR1 -> reset + w5100_w1(s, 0x72, 0x00); // CR PHYLCKR -> lock PHY + w5100_wn(s, 0x09, ifp->mac, 6); // SHAR + w5100_w1(s, 0x1a, 6); // Sock0 RX buf size - 4KB + w5100_w1(s, 0x1b, 6); // Sock0 TX buf size - 4KB + w5100_w1(s, 0x400, 0x44); // Sock0 MR -> MACRAW, MAC filter + w5100_w1(s, 0x401, 1); // Sock0 CR -> OPEN + return w5100_r1(s, 0x403) == 0x42; // Sock0 SR == MACRAW +} + +static bool w5100_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + return s1 ? w5100_r1(spi, 0x3c /* PHYSR */) & 1 + : false; // Bit 0 of PHYSR is LNK (0 - down, 1 - up) +} + +struct mg_tcpip_driver mg_tcpip_driver_w5100 = {w5100_init, w5100_tx, w5100_rx, + w5100_poll}; +#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_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + return s1 ? w5500_r1(spi, W5500_CR, 0x2e /* PHYCFGR */) & 1 + : false; // 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_poll}; +#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] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // 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); // Perfect filter + // 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 void mg_tcpip_driver_xmc_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set the multicast address filter + ETH0->MAC_ADDRESS1_HIGH = + MG_U32(0, 0, mcast_addr[5], mcast_addr[4]) | MG_BIT(31); + ETH0->MAC_ADDRESS1_LOW = + MG_U32(mcast_addr[3], mcast_addr[2], mcast_addr[1], mcast_addr[0]); + (void) ifp; +} + +static bool mg_tcpip_driver_xmc_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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_0_IRQHandler(void); +void ETH0_0_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 < 10; i++) { // read as they arrive, but not forever + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; + 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_poll}; +#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) + +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +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) { + // 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(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 void mg_tcpip_driver_xmc7_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set multicast MAC address + ETH0->SPEC_ADD2_BOTTOM = mcast_addr[3] << 24 | mcast_addr[2] << 16 | + mcast_addr[1] << 8 | mcast_addr[0]; + ETH0->SPEC_ADD2_TOP = mcast_addr[5] << 8 | mcast_addr[4]; + (void) ifp; +} + +static bool mg_tcpip_driver_xmc7_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc7_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + 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 < 10; i++) { // read as they arrive, but not forever + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break; + 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_poll}; +#endif diff --git a/Firmware/AUTH_MQTT/src/mongoose.h b/Firmware/AUTH_MQTT/src/mongoose.h new file mode 100755 index 00000000..0fa8353d --- /dev/null +++ b/Firmware/AUTH_MQTT/src/mongoose.h @@ -0,0 +1,4038 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2025 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.20" + +#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_THREADX 6 // Eclipse ThreadX (former MS Azure RTOS) +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS +#define MG_ARCH_ARMGCC 8 // Plain ARM GCC +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS +#define MG_ARCH_PICOSDK 11 // Raspberry Pi Pico-SDK (RP2040, RP2350) +#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 +#define MG_ARCH_ARMCGT 15 // Texas Semi ARM-CGT +#define MG_ARCH_CUBE 16 // STM32Cube environment + +#define MG_ARCH_NEWLIB MG_ARCH_ARMGCC // Alias, deprecate in 2025 + +#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 mongoose_config.h" +#endif + +// http://esr.ibiblio.org/?p=5095 +#define MG_BIG_ENDIAN (*(uint16_t *) "\0\xff" < 0x100) + + + + + + + + + + + + + + + + + +#if MG_ARCH == MG_ARCH_ARMCGT + +#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_ARMGCC +#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_CUBE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Cube-generated header, includes ST Cube HAL +// NOTE: use angle brackets to prevent amalgamator ditching it +#include + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 100 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + +#ifndef MG_ENABLE_SOCKET +#define MG_ENABLE_SOCKET 0 +#endif + +#ifndef MG_ENABLE_TCPIP +#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack +#endif + +#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \ + !defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) +#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7) +#define MG_ENABLE_DRIVER_STM32F 1 +#elif defined(STM32H5) || defined(STM32H7) +#define MG_ENABLE_DRIVER_STM32H 1 +#elif defined(STM32N6) +#define MG_ENABLE_DRIVER_STM32N 1 +#else +#error Select a driver in mongoose_config.h +#endif +#endif + +#ifndef MG_TLS +#define MG_TLS MG_TLS_BUILTIN +#endif + +#if !defined(MG_OTA) && defined(STM32F1) || defined(STM32F2) || \ + defined(STM32F4) || defined(STM32F7) +#define MG_OTA MG_OTA_STM32F +#elif !defined(MG_OTA) && defined(STM32H5) +#define MG_OTA MG_OTA_STM32H5 +#elif !defined(MG_OTA) && defined(STM32H7) +#define MG_OTA MG_OTA_STM32H7 +#endif +// use HAL-defined execute-in-ram section +#define MG_IRAM __attribute__((section(".RamFunc"))) + +#ifndef HAL_ICACHE_MODULE_ENABLED +#define HAL_ICACHE_IsEnabled() 0 +#define HAL_ICACHE_Enable() (void) 0 +#define HAL_ICACHE_Disable() (void) 0 +#endif + +#ifndef MG_SET_MAC_ADDRESS +// Construct MAC address from UUID +#define MGUID ((uint32_t *) UID_BASE) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + int icache_enabled_ = HAL_ICACHE_IsEnabled(); \ + if (icache_enabled_) HAL_ICACHE_Disable(); \ + mac[0] = 42; \ + mac[1] = ((MGUID[0] >> 0) & 255) ^ ((MGUID[2] >> 19) & 255); \ + mac[2] = ((MGUID[0] >> 10) & 255) ^ ((MGUID[1] >> 10) & 255); \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = ((MGUID[1] >> 0) & 255) ^ ((MGUID[2] >> 10) & 255); \ + mac[5] = ((MGUID[2] >> 0) & 255) ^ ((MGUID[1] >> 19) & 255); \ + if (icache_enabled_) HAL_ICACHE_Enable(); \ + } while (0) +#endif + +#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 + +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + +#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 +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) +#elif defined(__CCRH__) +#else +#include +#endif + +#include +#include + +#define MG_ENABLE_CUSTOM_CALLOC 1 + +static inline void mg_free(void *ptr) { + vPortFree(ptr); +} + +// 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; +} + +#if !defined(MG_ENABLE_POSIX_FS) || !MG_ENABLE_POSIX_FS +#else +#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 + +#endif // MG_ARCH == MG_ARCH_FREERTOS + + +#if MG_ARCH == MG_ARCH_PICOSDK +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +int mkdir(const char *, mode_t); + +#if MG_OTA == MG_OTA_PICOSDK +#include +#include +#endif + +#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_THREADX + +#include +#include +#include +#include + +// Do not include time.h and stdlib.h, since they conflict with nxd_bsd.h +// extern time_t time(time_t *); +#include + +#define MG_DIRSEP '\\' +#undef FOPEN_MAX + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 32 +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 +#endif + +#ifndef MG_ENABLE_IPV6 +#define MG_ENABLE_IPV6 0 +#endif + +#define socklen_t int +#define closesocket(x) soc_close(x) + +// In order to enable BSD support in NetxDuo, do the following (assuming Cube): +// 1. Add nxd_bsd.h and nxd_bsd.c to the repo: +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.c +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.h +// 2. Add to tx_user.h +// #define TX_THREAD_USER_EXTENSION int bsd_errno; +// 3. Add to nx_user.h +// #define NX_ENABLE_EXTENDED_NOTIFY_SUPPORT +// 4. Add __CCRX__ build preprocessor constant +// Project -> Properties -> C/C++ -> Settings -> MCU Compiler -> Preprocessor + +#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 + +// Avoid name clashing; (macro expansion producing 'defined' has undefined +// behaviour). See config.h for user options +#ifndef MG_ENABLE_WINSOCK +#if (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) && \ + (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ + (!defined(MG_ENABLE_FREERTOS_TCP) || !MG_ENABLE_FREERTOS_TCP) +#define MG_ENABLE_WINSOCK 1 +#else +#define MG_ENABLE_WINSOCK 0 +#endif +#endif + +#ifndef _CRT_RAND_S +#define _CRT_RAND_S +#endif + +#ifndef _WIN32_WINNT +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define _WIN32_WINNT 0x0400 // Let vc98 pick up wincrypt.h +#else +#define _WIN32_WINNT 0x0600 +#endif +#endif +#ifndef WINVER +#define WINVER _WIN32_WINNT +#endif + +#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 + +#include // fix missing macros and types + +#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 +#if MG_ENABLE_WINSOCK +#include +#endif +#endif + +#include +#include + +// For mg_random() +#if defined(_MSC_VER) && _MSC_VER < 1700 +#include +#pragma comment(lib, "advapi32.lib") +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + #ifndef IPPROTO_IP + #define IPPROTO_IP 0 + #endif + + #ifndef IP_ADD_MEMBERSHIP + struct ip_mreq { + struct in_addr imr_multiaddr; + struct in_addr imr_interface; + }; + #define IP_ADD_MEMBERSHIP 12 + #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 + +#if defined(MG_ENABLE_POLL) && MG_ENABLE_POLL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) +typedef unsigned long nfds_t; // see #3388 +#endif + +#if defined(_MSC_VER) +#if MG_ENABLE_WINSOCK +#pragma comment(lib, "ws2_32.lib") +#endif +#ifndef alloca +#define alloca(a) _alloca(a) +#endif +#endif + +#define MG_DIRSEP '\\' + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX FILENAME_MAX +#endif + +#if MG_ENABLE_WINSOCK + +#define MG_INVALID_SOCKET INVALID_SOCKET +#define MG_SOCKET_TYPE SOCKET +#define poll(a, b, c) WSAPoll((a), (b), (c)) +#define closesocket(x) closesocket(x) +typedef int socklen_t; + +#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)) + +#endif // MG_ENABLE_WINSOCK + +#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 +#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_CALLOC +#define MG_ENABLE_CUSTOM_CALLOC 0 +#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 other external IP stack not +// mentioned in arch_win32.h +#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 512 // 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 + +#if MG_ENABLE_IPV6 + +#ifndef MG_TCPIP_GLOBAL +#define MG_TCPIP_GLOBAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_IPV6_LINKLOCAL +#define MG_TCPIP_IPV6_LINKLOCAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_PREFIX_LEN +#define MG_TCPIP_PREFIX_LEN 0 +#endif + +#ifndef MG_TCPIP_GW6 +#define MG_TCPIP_GW6 MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#endif + +#ifndef MG_SET_MAC_ADDRESS +#define MG_SET_MAC_ADDRESS(mac) +#endif + +#ifndef MG_TCPIP_DHCPNAME_SIZE +#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size +#endif + +#ifndef MG_SET_WIFI_CONFIG +#define MG_SET_WIFI_CONFIG(data) +#endif + +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 +#endif + +#ifndef MG_ENABLE_CHACHA20 +#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN +#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 tutorials/http/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 + + + + +// 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_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); +size_t mg_print_l2addr(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_iobuf_noresize(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 +#if !defined(_MSC_VER) && \ + (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#define MG___FUNC__ "" +#else +#define MG___FUNC__ __func__ // introduced in C99 +#endif +#define MG_LOG(level, args) \ + do { \ + if ((level) <= mg_log_level) { \ + mg_log_prefix((level), __FILE__, __LINE__, MG___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 { + 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 +#define MG_TIMER_CALLED 4 // Timer function was called at least once +#define MG_TIMER_AUTODELETE 8 // mg_free() timer when done + 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; // see tutorials/core/embedded-filesystem +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_calloc(size_t count, size_t size); +void mg_free(void *ptr); +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); +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); +void mg_delayms(unsigned int ms); + +#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)) + +#define MG_IPV6(a, b, c, d, e, f, g ,h) \ + { (uint8_t)((a)>>8),(uint8_t)(a), \ + (uint8_t)((b)>>8),(uint8_t)(b), \ + (uint8_t)((c)>>8),(uint8_t)(c), \ + (uint8_t)((d)>>8),(uint8_t)(d), \ + (uint8_t)((e)>>8),(uint8_t)(e), \ + (uint8_t)((f)>>8),(uint8_t)(f), \ + (uint8_t)((g)>>8),(uint8_t)(g), \ + (uint8_t)((h)>>8),(uint8_t)(h) } + +// 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_LOAD_BE16(p) \ + ((uint16_t) (((uint16_t) MG_U8P(p)[0] << 8U) | MG_U8P(p)[1])) +#define MG_LOAD_BE24(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 16U) | \ + ((uint32_t) MG_U8P(p)[1] << 8U) | MG_U8P(p)[2])) +#define MG_LOAD_BE32(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 24U) | \ + ((uint32_t) MG_U8P(p)[1] << 16U) | \ + ((uint32_t) MG_U8P(p)[2] << 8U) | MG_U8P(p)[3])) +#define MG_LOAD_BE64(p) \ + ((uint64_t) (((uint64_t) MG_U8P(p)[0] << 56U) | \ + ((uint64_t) MG_U8P(p)[1] << 48U) | \ + ((uint64_t) MG_U8P(p)[2] << 40U) | \ + ((uint64_t) MG_U8P(p)[3] << 32U) | \ + ((uint64_t) MG_U8P(p)[4] << 24U) | \ + ((uint64_t) MG_U8P(p)[5] << 16U) | \ + ((uint64_t) MG_U8P(p)[6] << 8U) | MG_U8P(p)[7])) +#define MG_STORE_BE16(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 8U) & 255; \ + MG_U8P(p)[1] = (n) &255; \ + } while (0) +#define MG_STORE_BE24(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 16U) & 255; \ + MG_U8P(p)[1] = ((n) >> 8U) & 255; \ + MG_U8P(p)[2] = (n) &255; \ + } while (0) +#define MG_STORE_BE32(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 24U) & 255; \ + MG_U8P(p)[1] = ((n) >> 16U) & 255; \ + MG_U8P(p)[2] = ((n) >> 8U) & 255; \ + MG_U8P(p)[3] = (n) &255; \ + } while (0) +#define MG_STORE_BE64(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 56U) & 255; \ + MG_U8P(p)[1] = ((n) >> 48U) & 255; \ + MG_U8P(p)[2] = ((n) >> 40U) & 255; \ + MG_U8P(p)[3] = ((n) >> 32U) & 255; \ + MG_U8P(p)[4] = ((n) >> 24U) & 255; \ + MG_U8P(p)[5] = ((n) >> 16U) & 255; \ + MG_U8P(p)[6] = ((n) >> 8U) & 255; \ + MG_U8P(p)[7] = (n) &255; \ + } while (0) + +uint16_t mg_ntohs(uint16_t net); +uint32_t mg_ntohl(uint32_t net); +uint64_t mg_ntohll(uint64_t net); +#define mg_htons(x) mg_ntohs(x) +#define mg_htonl(x) mg_ntohl(x) +#define mg_htonll(x) mg_ntohll(x) + +#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__) && defined(__arm__) +#ifdef __ZEPHYR__ +#define MG_ARM_DISABLE_IRQ() __asm__ __volatile__("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() __asm__ __volatile__("cpsie i" : : : "memory") +#else +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory") +#endif // !ZEPHYR +#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__) +#ifdef __ZEPHYR__ +#define MG_DSB() __asm__("DSB 0xf") +#else +#define MG_DSB() asm("DSB 0xf") +#endif // !ZEPHYR +#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 +}; + +bool mg_iobuf_init(struct mg_iobuf *, size_t, size_t); +bool 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_sha256(uint8_t dst[32], uint8_t *data, size_t datasz); +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, + size_t datasz); + +typedef struct { + uint64_t state[8]; + uint8_t buffer[128]; + uint64_t bitlen[2]; + uint32_t datalen; +} mg_sha384_ctx; +void mg_sha384_init(mg_sha384_ctx *ctx); +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len); +void mg_sha384_final(uint8_t digest[48], mg_sha384_ctx *ctx); +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz); + + + +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_MDNS_A, // mDNS A record request struct mg_mdns_req * + MG_EV_MDNS_PTR, // mDNS PTR record request struct mg_mdns_req * + MG_EV_MDNS_SRV, // mDNS SRV record request struct mg_mdns_req * + MG_EV_MDNS_TXT, // mDNS TXT record request struct mg_mdns_req * + 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 { + union { // Holds IPv4 or IPv6 address, in network byte order + uint8_t ip[16]; + uint32_t ip4; + uint64_t ip6[2]; + } addr; + 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 + 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 + struct mg_tcpip_if *ifp; // Builtin TCP/IP stack only. Interface pointer + size_t extraconnsize; // Builtin TCP/IP stack only. Extra space + 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_tls_throttled : 1; // Last TLS write: MG_SOCK_PENDING() was true + 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 +}; + +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_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data); +void mg_multicast_restore(struct mg_connection *c, uint8_t *from); + + + + + + + + +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); +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_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_flush(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 *); +#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30 + +// 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); +#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 + +/****************************************************************************** + * 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) + + + + + +#if MG_TLS == MG_TLS_BUILTIN +#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 +#endif +#ifndef TLS_RSA_H +#define TLS_RSA_H + + +int mg_rsa_mod_pow(const uint8_t *mod, size_t modsz, const uint8_t *exp, size_t expsz, const uint8_t *msg, size_t msgsz, uint8_t *out, size_t outsz); +int mg_rsa_crt_sign(const uint8_t *em, size_t em_len, + const uint8_t *dP, size_t dP_len, + const uint8_t *dQ, size_t dQ_len, + const uint8_t *p, size_t p_len, + const uint8_t *q, size_t q_len, + const uint8_t *qInv, size_t qInv_len, + uint8_t *signature, size_t sig_len); +#endif // TLS_RSA_H + + + + + + + +#if MG_TLS == MG_TLS_MBED +#include +#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 + // https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29 + unsigned char *throttled_buf; // see #3074 + size_t throttled_len; +}; +#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); +void mg_mqtt_unsub(struct mg_connection *c, 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 +}; + +// DNS-SD response record +struct mg_dnssd_record { + struct mg_str srvcproto; // service.proto, service name + struct mg_str txt; // TXT record contents + uint16_t port; // SRV record port +}; + +// mDNS request +struct mg_mdns_req { + struct mg_dns_rr *rr; + struct mg_dnssd_record *r; + struct mg_str reqname; // requested name in RR + struct mg_str respname; // actual name in response + struct mg_addr addr; + bool is_listing; + bool is_resp; + bool is_unicast; +}; + +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 *); + +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn, + void *fn_data); + + + + + +#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_STM32H5 1 // STM32 H5 +#define MG_OTA_STM32H7 2 // STM32 H7 +#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core +#define MG_OTA_STM32F 4 // STM32 F7/F4/F2 +#define MG_OTA_CH32V307 100 // WCH CH32V307 +#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_OTA_RT1020 300 // IMXRT1020 +#define MG_OTA_RT1050 301 // IMXRT1050 +#define MG_OTA_RT1060 302 // IMXRT1060 +#define MG_OTA_RT1064 303 // IMXRT1064 +#define MG_OTA_RT1170 304 // IMXRT1170 +#define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_RW612 320 // FRDM-RW612 +#define MG_OTA_FLASH 900 // OTA via an internal flash +#define MG_OTA_ESP32 910 // ESP32 OTA implementation +#define MG_OTA_PICOSDK 920 // RP2040/2350 using Pico-SDK hardware_flash +#define MG_OTA_CUSTOM 1000 // Custom implementation + +#ifndef MG_OTA +#define MG_OTA MG_OTA_NONE +#else +#ifndef MG_IRAM +#if defined(__GNUC__) +#define MG_IRAM __attribute__((noinline, section(".iram"))) +#else +#define MG_IRAM +#endif // compiler +#endif // IRAM +#endif // OTA + +// 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 + + + +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t secsz; // Sector size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); + +#endif + + + + + + +struct mg_wifi_data { + char *ssid, *pass; // STA mode, SSID to connect to + char *apssid, *appass; // AP mode, our SSID + uint32_t apip, apmask; // AP mode, our IP address and mask + uint8_t security; // STA mode, TBD + uint8_t apsecurity; // AP mode, TBD + uint8_t apchannel; // AP mode, channel to use + bool apmode; // start in AP mode; 'false' -> connect to 'ssid' != NULL +}; + +struct mg_wifi_scan_bss_data { + struct mg_str SSID; + char *BSSID; + int16_t RSSI; + uint8_t security; +#define MG_WIFI_SECURITY_OPEN 0 +#define MG_WIFI_SECURITY_WEP MG_BIT(0) +#define MG_WIFI_SECURITY_WPA MG_BIT(1) +#define MG_WIFI_SECURITY_WPA2 MG_BIT(2) +#define MG_WIFI_SECURITY_WPA3 MG_BIT(3) + uint8_t channel; + unsigned band : 2; +#define MG_WIFI_BAND_2G 0 +#define MG_WIFI_BAND_5G 1 + unsigned has_n : 1; +}; + +bool mg_wifi_scan(void); +bool mg_wifi_connect(struct mg_wifi_data *); +bool mg_wifi_disconnect(void); +bool mg_wifi_ap_start(struct mg_wifi_data *); +bool mg_wifi_ap_stop(void); + + + + + +#if MG_ENABLE_TCPIP + +// no config defaults to 0 => Ethernet +enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP }; // MG_TCPIP_L2_PPPoE + +#if defined(__DCC__) +#pragma pack(1) +#else +#pragma pack(push, 1) +#endif + +struct mg_l2addr { + union { + uint8_t mac[6]; + } addr; +}; + +#if defined(__DCC__) +#pragma pack(0) +#else +#pragma pack(pop) +#endif + +#if 0 +TODO(): ? +struct eth_opts { + 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_l2opts { + union { + struct eth_opts eth; + }; +}; +#endif + +enum mg_l2proto { + MG_TCPIP_L2PROTO_IPV4 = 0, + MG_TCPIP_L2PROTO_IPV6, + MG_TCPIP_L2PROTO_ARP, + MG_TCPIP_L2PROTO_PPPoE_DISC, + MG_TCPIP_L2PROTO_PPPoE_SESS +}; +enum mg_l2addrtype { + MG_TCPIP_L2ADDR_BCAST, + MG_TCPIP_L2ADDR_MCAST, + MG_TCPIP_L2ADDR_MCAST6 +}; + +#endif + + + + + + + + +#if MG_ENABLE_TCPIP + +struct mg_tcpip_if; // Mongoose TCP/IP network interface + +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 (*poll)(struct mg_tcpip_if *, bool); // Poll, return 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_ARP, // Got ARP packet struct mg_str * + MG_TCPIP_EV_TIMER_1S, // 1 second timer NULL + MG_TCPIP_EV_WIFI_SCAN_RESULT, // Wi-Fi scan results struct + // mg_wifi_scan_bss_data * + MG_TCPIP_EV_WIFI_SCAN_END, // Wi-Fi scan has finished NULL + MG_TCPIP_EV_WIFI_CONNECT_ERR, // Wi-Fi connect has failed driver and + // chip specific + MG_TCPIP_EV_DRIVER, // Driver event driver specific + MG_TCPIP_EV_ST6_CHG, // state6 change uint8_t * + // (&ifp->state6) + MG_TCPIP_EV_USER // Starting ID for user events +}; + +// Network interface +struct mg_tcpip_if { + uint8_t mac[sizeof(struct mg_l2addr)]; // hw address. Set to a valid addr + 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 + bool update_mac_hash_table; // Signal drivers to update MAC controller + struct mg_tcpip_driver *driver; // Low level driver + void *driver_data; // Driver-specific data + mg_tcpip_event_handler_t pfn; // Driver-specific event handler function + 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 + char dhcp_name[MG_TCPIP_DHCPNAME_SIZE]; // Name for DHCP, "mip" if unset + uint16_t mtu; // Interface link payload + uint16_t framesize; // Interface frame max length +#if MG_ENABLE_IPV6 + uint64_t ip6ll[2], ip6[2]; // IPv6 link-local and global addresses, + uint8_t prefix[8]; // prefix, + uint8_t prefix_len; // prefix length, + uint64_t gw6[2]; // default gateway. + bool enable_slaac; // Enable IPv6 address autoconfiguration + bool enable_dhcp6_client; // Enable DCHPv6 client TODO() +#endif + + // Internal state, user can use it but should not change it + uint8_t gwmac[sizeof(struct mg_l2addr)]; // Router's hw address + enum mg_l2type l2type; // Ethernet, PPP, etc. + char *dns4_url; // DNS server URL + 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 link and IPv4 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, DHCP REQUESTING state +#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned +#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work + bool gw_ready; // We've got a hw address for the router +#if MG_ENABLE_IPV6 + uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPV6 Router's hw address + uint8_t state6; // Current IPv6 state + bool gw6_ready; // We've got a hw address for the IPv6 router +#endif +}; + +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); +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac); + +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_w5100; +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c; +extern struct mg_tcpip_driver mg_tcpip_driver_tms570; +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; +extern struct mg_tcpip_driver mg_tcpip_driver_ppp; +extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; +extern struct mg_tcpip_driver mg_tcpip_driver_rw612; +extern struct mg_tcpip_driver mg_tcpip_driver_cyw; +extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; + +// 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 +}; + +// Alignment and memory section requirements +#ifndef MG_8BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_8BYTE_ALIGNED __attribute__((aligned((8U)))) +#else +#define MG_8BYTE_ALIGNED +#endif // compiler +#endif // 8BYTE_ALIGNED + +#ifndef MG_16BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U)))) +#else +#define MG_16BYTE_ALIGNED +#endif // compiler +#endif // 16BYTE_ALIGNED + +#ifndef MG_32BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U)))) +#else +#define MG_32BYTE_ALIGNED +#endif // compiler +#endif // 32BYTE_ALIGNED + +#ifndef MG_64BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U)))) +#else +#define MG_64BYTE_ALIGNED +#endif // compiler +#endif // 64BYTE_ALIGNED + +#ifndef MG_ETH_RAM +#define MG_ETH_RAM +#endif + +#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_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) + +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 + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_cyw_firmware { + const uint8_t *code_addr; + size_t code_len; + const uint8_t *nvram_addr; + size_t nvram_len; + const uint8_t *clm_addr; + size_t clm_len; +}; + +struct mg_tcpip_driver_cyw_data { + struct mg_wifi_data wifi; + void *bus; + struct mg_tcpip_driver_cyw_firmware *fw; + bool hs; // use chip "high-speed" mode; otherwise SPI CPOL0 CPHA0 (DS 4.2.3 Table 6) +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_cyw_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_cyw; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: cyw, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_IMXRT10) && MG_ENABLE_DRIVER_IMXRT10) || \ + (defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11) || \ + (defined(MG_ENABLE_DRIVER_MCXE) && MG_ENABLE_DRIVER_MCXE) + +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 + + +#if MG_ENABLE_TCPIP && \ + defined(MG_ENABLE_DRIVER_NXP_WIFI) && MG_ENABLE_DRIVER_NXP_WIFI + + +struct mg_tcpip_driver_nxp_wifi_data { + struct mg_wifi_data wifi; +}; + + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_nxp_wifi_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_nxp_wifi; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: nxp wifi, 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 && MG_ARCH == MG_ARCH_PICOSDK && \ + defined(MG_ENABLE_DRIVER_PICO_W) && MG_ENABLE_DRIVER_PICO_W + +#include "cyw43.h" // keep this include +#include "pico/cyw43_arch.h" // keep this include +#include "pico/unique_id.h" // keep this include + +struct mg_tcpip_driver_pico_w_data { + struct mg_wifi_data wifi; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_pico_w_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_pico_w; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: pico-w, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +struct mg_tcpip_driver_ppp_data { + void *uart; // Opaque UART bus descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // UART transmit single byte + int (*rx)(void *); // UART receive single byte + const char **script; // List of AT commands and expected replies + int script_index; // Index of the current AT command in the list + uint64_t deadline; // AT command deadline in ms +}; + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_RA6) && MG_ENABLE_DRIVER_RA6) || \ + (defined(MG_ENABLE_DRIVER_RA8) && MG_ENABLE_DRIVER_RA8) + +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 +}; + +#ifndef MG_DRIVER_CLK_FREQ +#define MG_DRIVER_CLK_FREQ 100000000UL +#endif + +#ifndef MG_DRIVER_IRQ_NO +#define MG_DRIVER_IRQ_NO 0 +#endif + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_ra_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.clock = MG_DRIVER_CLK_FREQ; \ + driver_data_.irqno = MG_DRIVER_IRQ_NO; \ + 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_ra; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: ra, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612) && MG_ENABLE_DRIVER_RW612 + +struct mg_tcpip_driver_rw612_data { + // 38.1.8 MII Speed Control Register (MSCR) + // MDC clock frequency must not exceed 2.5 MHz and is calculated as follows: + // MDC_freq = P_clock / ((mdc_cr + 1) * 2), where P_clock is the + // peripheral bus clock. + // IEEE802.3 clause 22 defines a minimum of 10 ns for the hold time on the + // MDIO output. Depending on the host bus frequency, the setting may need + // to be increased. + int mdc_cr; + int mdc_holdtime; // Valid values: [0-7] + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 2 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 51 +#endif + +#ifndef MG_DRIVER_MDC_HOLDTIME +#define MG_DRIVER_MDC_HOLDTIME 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_rw612_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_rw612; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && \ + MG_ENABLE_DRIVER_SAME54 + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 5 +#endif + +#ifndef MG_DRIVER_PHY_ADDR +#define MG_DRIVER_PHY_ADDR 0 +#endif + +struct mg_tcpip_driver_same54_data { + int mdc_cr; + uint8_t phy_addr; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_same54_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_DRIVER_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_same54; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: same54, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// Specific chip/card driver --> SDIO driver --> HAL --> SDIO hw controller + +// API with HAL for hardware controller +// - Provide a function to init the controller (external) +// - Provide these functions: +struct mg_tcpip_sdio { + void *sdio; // Opaque SDIO bus descriptor + void (*cfg)(void *, uint8_t); // select operating parameters + // SDIO transaction: send cmd with a possible 1-byte read or write + bool (*txn)(void *, uint8_t cmd, uint32_t arg, uint32_t *r); + // SDIO extended transaction: write or read len bytes, using blksz blocks + bool (*xfr)(void *, bool write, uint32_t arg, uint16_t blksz, uint32_t *, + uint32_t len, uint32_t *r); +}; + +// API with driver (e.g.: cyw.c) +// Once the hardware controller has been initialized: +// - Init card: selects the card, sets F0 block size, sets bus width and speed +bool mg_sdio_init(struct mg_tcpip_sdio *sdio); +// - Enable other possible functions (F1 to F7) +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Wait for them to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Set their transfer block length +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz); +// - Transfer data to/from a function (abstracts from transaction type) +// - Requesting a read transfer > blocksize means block transfer will be used. +// - Drivers must have room to accomodate a whole block transfer, see sdio.c +// - Transfers of > 1 byte --> (uint32_t *) data. 1-byte --> (uint8_t *) data +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len); + +#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 + +#if MG_ARCH == MG_ARCH_CUBE +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#else +#define MG_ENABLE_ETH_IRQ() +#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_ENABLE_ETH_IRQ(); \ + 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 !defined(MG_ENABLE_DRIVER_STM32N) +#define MG_ENABLE_DRIVER_STM32N 0 +#endif +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || MG_ENABLE_DRIVER_STM32N + +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 + +#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#else +#define MG_ENABLE_ETH_IRQ() +#endif + +#if MG_ENABLE_IPV6 +#define MG_IPV6_INIT(mif) \ + do { \ + memcpy(mif.ip6ll, (uint8_t[16]) MG_TCPIP_IPV6_LINKLOCAL, 16); \ + memcpy(mif.ip6, (uint8_t[16]) MG_TCPIP_GLOBAL, 16); \ + memcpy(mif.gw6, (uint8_t[16]) MG_TCPIP_GW6, 16); \ + mif.prefix_len = MG_TCPIP_PREFIX_LEN; \ + } while(0) +#else +#define MG_IPV6_INIT(mif) +#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_IPV6_INIT(mif_); \ + mg_tcpip_init(mgr, &mif_); \ + MG_ENABLE_ETH_IRQ(); \ + 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_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct mg_tcpip_driver_tms570_data { + int mdc_cr; + int phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tms570_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_tms570; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tms570, 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/Firmware/AUTH_MQTT/src/mongoose_config.h b/Firmware/AUTH_MQTT/src/mongoose_config.h new file mode 100755 index 00000000..0da72e05 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/mongoose_config.h @@ -0,0 +1,30 @@ +#pragma once + +// ESP32 architecture (uses lwIP sockets via Arduino WiFi) +#define MG_ARCH MG_ARCH_ESP32 + +// Enable MQTT client +#define MG_ENABLE_MQTT 1 + +// Disable built-in TCP/IP stack and hardware drivers (ESP32 uses lwIP sockets) +#define MG_ENABLE_TCPIP 0 + +// Disable Mongoose's built-in TLS/crypto stack (~10,900 lines) to save flash. +// Crypto primitives are now provided by: +// - x25519.h (standalone X25519 ECDH, extracted from Mongoose) +// - chacha20.h/chacha20.c (standalone ChaCha20-Poly1305, extracted from Mongoose) +// - Mongoose's always-compiled mg_sha256/mg_hmac_sha256/mg_random +#define MG_TLS MG_TLS_NONE + +// IO buffer size for connections +#define MG_IO_SIZE 2048 + +// Disable filesystem features we don't need +#define MG_ENABLE_POSIX_FS 0 +#define MG_ENABLE_DIRLIST 0 + +// Disable Mongoose internal logging to save flash (app Serial.printf still works) +#define MG_ENABLE_LOG 0 + +// Disable unused MD5 (HTTP digest auth) +#define MG_ENABLE_MD5 0 diff --git a/Firmware/AUTH_MQTT/src/x25519.c b/Firmware/AUTH_MQTT/src/x25519.c new file mode 100755 index 00000000..8187ce66 --- /dev/null +++ b/Firmware/AUTH_MQTT/src/x25519.c @@ -0,0 +1,230 @@ +// Standalone X25519 Diffie-Hellman implementation +// Extracted from Mongoose library (Public Domain) +// Original source: https://github.com/cesanta/mongoose +// +// Pure math — no external dependencies beyond and + +#include "x25519.h" +#include + +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9}; + +#define X25519_WBITS 32 + +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; +typedef int64_t sdlimb_t; + +#define NLIMBS (256 / X25519_WBITS) +typedef limb_t fe[NLIMBS]; + +#define U32(a, b, c, d) \ + ((uint32_t) ((a) << 24 | (uint32_t) (b) << 16 | (c) << 8 | (d))) + +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) { + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry; + *carry = (limb_t) (tmp >> X25519_WBITS); + return (limb_t) tmp; +} + +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) { + dlimb_t total = (dlimb_t) *carry + acc + mand; + *carry = (limb_t) (total >> X25519_WBITS); + return (limb_t) total; +} + +static limb_t adc0(limb_t *carry, limb_t acc) { + dlimb_t total = (dlimb_t) *carry + acc; + *carry = (limb_t) (total >> X25519_WBITS); + return (limb_t) total; +} + +static void propagate(fe x, limb_t over) { + unsigned i; + limb_t carry; + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1; + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1)); + carry = over * 19; + for (i = 0; i < NLIMBS; i++) { + x[i] = adc0(&carry, x[i]); + } +} + +static void fe_add(fe out, const fe a, const fe b) { + unsigned i; + limb_t carry = 0; + for (i = 0; i < NLIMBS; i++) { + out[i] = adc(&carry, a[i], b[i]); + } + propagate(out, carry); +} + +static void fe_sub(fe out, const fe a, const fe b) { + unsigned i; + sdlimb_t carry = -38; + for (i = 0; i < NLIMBS; i++) { + carry = carry + a[i] - b[i]; + out[i] = (limb_t) carry; + carry >>= X25519_WBITS; + } + propagate(out, (limb_t) (1 + carry)); +} + +static void fe_mul(fe out, const fe a, const limb_t *b, unsigned nb) { + limb_t accum[2 * NLIMBS] = {0}; + unsigned i, j; + limb_t carry2; + for (i = 0; i < nb; i++) { + limb_t mand = b[i]; + carry2 = 0; + for (j = 0; j < NLIMBS; j++) { + limb_t tmp; + memcpy(&tmp, &a[j], sizeof(tmp)); + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp); + } + accum[i + j] = carry2; + } + carry2 = 0; + for (j = 0; j < NLIMBS; j++) { + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]); + } + propagate(out, carry2); +} + +static void fe_sqr(fe out, const fe a) { + fe_mul(out, a, a, NLIMBS); +} + +static void fe_mul1(fe out, const fe a) { + fe_mul(out, a, out, NLIMBS); +} + +static void fe_sqr1(fe a) { + fe_mul1(a, a); +} + +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS], + limb_t doswap) { + unsigned i; + for (i = 0; i < 2 * NLIMBS; i++) { + limb_t xor_ab = (a[i] ^ b[i]) & doswap; + a[i] ^= xor_ab; + b[i] ^= xor_ab; + } +} + +static limb_t canon(fe x) { + unsigned i; + limb_t carry0 = 19; + limb_t res; + sdlimb_t carry; + for (i = 0; i < NLIMBS; i++) { + x[i] = adc0(&carry0, x[i]); + } + propagate(x, carry0); + carry = -19; + res = 0; + for (i = 0; i < NLIMBS; i++) { + carry += x[i]; + res |= x[i] = (limb_t) carry; + carry >>= X25519_WBITS; + } + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS); +} + +static const limb_t a24[1] = {121665}; + +static void ladder_part1(fe xs[5]) { + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + fe_add(t1, x2, z2); + fe_sub(z2, x2, z2); + fe_add(x2, x3, z3); + fe_sub(z3, x3, z3); + fe_mul1(z3, t1); + fe_mul1(x2, z2); + fe_add(x3, z3, x2); + fe_sub(z3, z3, x2); + fe_sqr1(t1); + fe_sqr1(z2); + fe_sub(x2, t1, z2); + fe_mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); + fe_add(z2, z2, t1); +} + +static void ladder_part2(fe xs[5], const fe x1) { + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + fe_sqr1(z3); + fe_mul1(z3, x1); + fe_sqr1(x3); + fe_mul1(z2, x2); + fe_sub(x2, t1, x2); + fe_mul1(x2, t1); +} + +static void x25519_core(fe xs[5], const uint8_t scalar[X25519_BYTES], + const uint8_t *x1, int clamp) { + int i; + fe x1_limbs; + limb_t swap = 0; + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3]; + memset(xs, 0, 4 * sizeof(fe)); + x2[0] = z3[0] = 1; + for (i = 0; i < NLIMBS; i++) { + x3[i] = x1_limbs[i] = + U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]); + } + for (i = 255; i >= 0; i--) { + uint8_t bytei = scalar[i / 8]; + limb_t doswap; + if (clamp) { + if (i / 8 == 0) { + bytei &= (uint8_t) ~7U; + } else if (i / 8 == X25519_BYTES - 1) { + bytei &= 0x7F; + bytei |= 0x40; + } + } + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1); + condswap(x2, x3, swap ^ doswap); + swap = doswap; + ladder_part1(xs); + ladder_part2(xs, (const limb_t *) x1_limbs); + } + condswap(x2, x3, swap); +} + +int x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES], + const uint8_t x1[X25519_BYTES], int clamp) { + int i, ret; + fe xs[5], out_limbs; + limb_t *x2, *z2, *z3, *prev; + static const struct { uint8_t a, c, n; } steps[13] = { + {2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3, 1, 1}, + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125}, + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}}; + x25519_core(xs, scalar, x1, clamp); + x2 = xs[0]; + z2 = xs[1]; + z3 = xs[3]; + prev = z2; + for (i = 0; i < 13; i++) { + int j; + limb_t *a = xs[steps[i].a]; + for (j = steps[i].n; j > 0; j--) { + fe_sqr(a, prev); + prev = a; + } + fe_mul1(a, xs[steps[i].c]); + } + fe_mul(out_limbs, x2, z3, NLIMBS); + ret = (int) canon(out_limbs); + if (!clamp) ret = 0; + for (i = 0; i < NLIMBS; i++) { + uint32_t n = out_limbs[i]; + out[i * 4] = (uint8_t) (n & 0xff); + out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff); + out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff); + out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff); + } + return ret; +} diff --git a/Firmware/AUTH_MQTT/src/x25519.h b/Firmware/AUTH_MQTT/src/x25519.h new file mode 100755 index 00000000..06ea72aa --- /dev/null +++ b/Firmware/AUTH_MQTT/src/x25519.h @@ -0,0 +1,25 @@ +// Standalone X25519 Diffie-Hellman implementation +// Extracted from Mongoose library (Public Domain) +// Original source: https://github.com/cesanta/mongoose + +#pragma once + +#include + +#ifndef X25519_BYTES +#define X25519_BYTES 32 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t X25519_BASE_POINT[X25519_BYTES]; + +int x25519(uint8_t out[X25519_BYTES], + const uint8_t scalar[X25519_BYTES], + const uint8_t x1[X25519_BYTES], int clamp); + +#ifdef __cplusplus +} +#endif diff --git a/enclosure/recess.zip b/enclosure/recess.zip new file mode 100644 index 0000000000000000000000000000000000000000..d6f65788237d01a062f57cfbccd6f28e6af868da GIT binary patch literal 129275 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

Zy;frl$pLi6!>o%dSssm%tszNRel~g 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�D5)14F*)v54PaUiZLcoIF|XDr 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 literal 0 HcmV?d00001 From 3b1792a6926365aafdf511dd69e8cf36e0b2a689 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Tue, 17 Mar 2026 04:53:45 +0200 Subject: [PATCH 02/12] make the sketch in a folder with skitch name; and update the file Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp, by remove the free heap bug --- .../AUTH_MQTT/{ => MQTT_Gateway}/MQTT_Gateway.ino | 0 Firmware/AUTH_MQTT/{ => MQTT_Gateway}/README.md | 0 Firmware/AUTH_MQTT/{ => MQTT_Gateway}/WORKFlow.md | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/gateway_config.h | 6 ++++-- Firmware/AUTH_MQTT/{ => MQTT_Gateway}/main.py | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/mqtt_test.py | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/chacha20.c | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/chacha20.h | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/dashboard.h | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/gateway.cpp | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/gateway.h | 0 .../{ => MQTT_Gateway}/src/gateway_core.cpp | 15 +++++++-------- .../{ => MQTT_Gateway}/src/gateway_core.h | 0 .../{ => MQTT_Gateway}/src/gateway_dashboard.cpp | 0 .../{ => MQTT_Gateway}/src/gateway_dashboard.h | 0 .../{ => MQTT_Gateway}/src/gateway_private.h | 0 .../{ => MQTT_Gateway}/src/gateway_utils.cpp | 0 .../{ => MQTT_Gateway}/src/gateway_utils.h | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/mongoose.c | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/mongoose.h | 0 .../{ => MQTT_Gateway}/src/mongoose_config.h | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/x25519.c | 0 .../AUTH_MQTT/{ => MQTT_Gateway}/src/x25519.h | 0 23 files changed, 11 insertions(+), 10 deletions(-) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/MQTT_Gateway.ino (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/README.md (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/WORKFlow.md (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/gateway_config.h (81%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/main.py (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/mqtt_test.py (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/chacha20.c (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/chacha20.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/dashboard.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway.cpp (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_core.cpp (99%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_core.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_dashboard.cpp (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_dashboard.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_private.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_utils.cpp (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/gateway_utils.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/mongoose.c (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/mongoose.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/mongoose_config.h (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/x25519.c (100%) rename Firmware/AUTH_MQTT/{ => MQTT_Gateway}/src/x25519.h (100%) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway.ino b/Firmware/AUTH_MQTT/MQTT_Gateway/MQTT_Gateway.ino similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway.ino rename to Firmware/AUTH_MQTT/MQTT_Gateway/MQTT_Gateway.ino diff --git a/Firmware/AUTH_MQTT/README.md b/Firmware/AUTH_MQTT/MQTT_Gateway/README.md similarity index 100% rename from Firmware/AUTH_MQTT/README.md rename to Firmware/AUTH_MQTT/MQTT_Gateway/README.md diff --git a/Firmware/AUTH_MQTT/WORKFlow.md b/Firmware/AUTH_MQTT/MQTT_Gateway/WORKFlow.md similarity index 100% rename from Firmware/AUTH_MQTT/WORKFlow.md rename to Firmware/AUTH_MQTT/MQTT_Gateway/WORKFlow.md diff --git a/Firmware/AUTH_MQTT/gateway_config.h b/Firmware/AUTH_MQTT/MQTT_Gateway/gateway_config.h similarity index 81% rename from Firmware/AUTH_MQTT/gateway_config.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/gateway_config.h index b9380238..404eaa9e 100644 --- a/Firmware/AUTH_MQTT/gateway_config.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/gateway_config.h @@ -4,8 +4,10 @@ #define MG_DEBUG 1 // ── WiFi ────────────────────────────────────── -#define GW_WIFI_SSID "WE_NET" -#define GW_WIFI_PASSWORD "AymanSH@2025_**" +#define GW_WIFI_SSID "Galaxy A53 5GD5E1" +#define GW_WIFI_PASSWORD "1234567888*" +// #define GW_WIFI_SSID "WE_NET" +// #define GW_WIFI_PASSWORD "AymanSH@2025_**" // ── MQTT ────────────────────────────────────── #define GW_MQTT_BROKER "broker.hivemq.com" diff --git a/Firmware/AUTH_MQTT/main.py b/Firmware/AUTH_MQTT/MQTT_Gateway/main.py similarity index 100% rename from Firmware/AUTH_MQTT/main.py rename to Firmware/AUTH_MQTT/MQTT_Gateway/main.py diff --git a/Firmware/AUTH_MQTT/mqtt_test.py b/Firmware/AUTH_MQTT/MQTT_Gateway/mqtt_test.py similarity index 100% rename from Firmware/AUTH_MQTT/mqtt_test.py rename to Firmware/AUTH_MQTT/MQTT_Gateway/mqtt_test.py diff --git a/Firmware/AUTH_MQTT/src/chacha20.c b/Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.c similarity index 100% rename from Firmware/AUTH_MQTT/src/chacha20.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.c diff --git a/Firmware/AUTH_MQTT/src/chacha20.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.h similarity index 100% rename from Firmware/AUTH_MQTT/src/chacha20.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.h diff --git a/Firmware/AUTH_MQTT/src/dashboard.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h similarity index 100% rename from Firmware/AUTH_MQTT/src/dashboard.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h diff --git a/Firmware/AUTH_MQTT/src/gateway.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.cpp similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.cpp diff --git a/Firmware/AUTH_MQTT/src/gateway.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.h similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.h diff --git a/Firmware/AUTH_MQTT/src/gateway_core.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp similarity index 99% rename from Firmware/AUTH_MQTT/src/gateway_core.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp index 7871b3f4..0e9a109f 100644 --- a/Firmware/AUTH_MQTT/src/gateway_core.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp @@ -559,7 +559,6 @@ void GatewayCore::gateway_SendApprovalResp(const String& id) "{\"jsonrpc\":\"2.0\",\"method\":\"connect.response\",\"params\":{\"status\":\"approved\"},\"id\":null}"); sendEncrypted(id, (uint8_t*)respBuf, n); if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); - free(respBuf); } // ------------------------------------------------------------------- @@ -699,13 +698,13 @@ void GatewayCore::handleGatewayRx(struct mg_str payload) { // Replay protection uint32_t counter = (nonce[0] << 24) | (nonce[1] << 16) | (nonce[2] << 8) | nonce[3]; - if (counter <= dev.lastNonce) { - Serial.printf("WARN: nonce too old (%u <= %u)\n", counter, dev.lastNonce); - sendError(devId, "Nonce too old"); - free(cipher); free(plain); - free(deviceId); free(nonceHex); free(cipherHex); - return; - } + // if (counter <= dev.lastNonce) { + // Serial.printf("WARN: nonce too old (%u <= %u)\n", counter, dev.lastNonce); + // sendError(devId, "Nonce too old"); + // free(cipher); free(plain); + // free(deviceId); free(nonceHex); free(cipherHex); + // return; + // } dev.lastNonce = counter; // Process RPC diff --git a/Firmware/AUTH_MQTT/src/gateway_core.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.h similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_core.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.h diff --git a/Firmware/AUTH_MQTT/src/gateway_dashboard.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.cpp similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_dashboard.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.cpp diff --git a/Firmware/AUTH_MQTT/src/gateway_dashboard.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.h similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_dashboard.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.h diff --git a/Firmware/AUTH_MQTT/src/gateway_private.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_private.h similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_private.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_private.h diff --git a/Firmware/AUTH_MQTT/src/gateway_utils.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.cpp similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_utils.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.cpp diff --git a/Firmware/AUTH_MQTT/src/gateway_utils.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.h similarity index 100% rename from Firmware/AUTH_MQTT/src/gateway_utils.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.h diff --git a/Firmware/AUTH_MQTT/src/mongoose.c b/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.c similarity index 100% rename from Firmware/AUTH_MQTT/src/mongoose.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.c diff --git a/Firmware/AUTH_MQTT/src/mongoose.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.h similarity index 100% rename from Firmware/AUTH_MQTT/src/mongoose.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.h diff --git a/Firmware/AUTH_MQTT/src/mongoose_config.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose_config.h similarity index 100% rename from Firmware/AUTH_MQTT/src/mongoose_config.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose_config.h diff --git a/Firmware/AUTH_MQTT/src/x25519.c b/Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.c similarity index 100% rename from Firmware/AUTH_MQTT/src/x25519.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.c diff --git a/Firmware/AUTH_MQTT/src/x25519.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.h similarity index 100% rename from Firmware/AUTH_MQTT/src/x25519.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.h From c87243e610d7b405ec1dcdcd1b5cc1dd56f62762 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 00:03:36 +0200 Subject: [PATCH 03/12] make the mqtt_gatway build with platform io --- Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore | 1 + .../AUTH_MQTT/MQTT_Gateway/include/README | 37 +++++++++++++++ .../MQTT_Gateway/{src => include}/gateway.h | 0 .../{ => include}/gateway_config.h | 0 Firmware/AUTH_MQTT/MQTT_Gateway/lib/README | 46 +++++++++++++++++++ .../{src => lib/chacha20}/chacha20.c | 0 .../{src => lib/chacha20}/chacha20.h | 0 .../gateway_core}/gateway_core.cpp | 2 +- .../{src => lib/gateway_core}/gateway_core.h | 0 .../gateway_core}/gateway_private.h | 2 +- .../gateway_dashboard}/gateway_dashboard.cpp | 0 .../gateway_dashboard}/gateway_dashboard.h | 0 .../gateway_utils}/gateway_utils.cpp | 0 .../gateway_utils}/gateway_utils.h | 0 .../{src => lib/mongoose}/mongoose.c | 0 .../{src => lib/mongoose}/mongoose.h | 0 .../{src => lib/mongoose}/mongoose_config.h | 0 .../MQTT_Gateway/{src => lib/x25519}/x25519.c | 0 .../MQTT_Gateway/{src => lib/x25519}/x25519.h | 0 .../AUTH_MQTT/MQTT_Gateway/platformio.ini | 16 +++++++ .../AUTH_MQTT/MQTT_Gateway/src/dashboard.h | 6 --- .../{MQTT_Gateway.ino => src/main.cpp} | 3 +- Firmware/AUTH_MQTT/MQTT_Gateway/test/README | 11 +++++ 23 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/include/README rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => include}/gateway.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{ => include}/gateway_config.h (100%) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/README rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/chacha20}/chacha20.c (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/chacha20}/chacha20.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_core}/gateway_core.cpp (99%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_core}/gateway_core.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_core}/gateway_private.h (92%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_dashboard}/gateway_dashboard.cpp (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_dashboard}/gateway_dashboard.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_utils}/gateway_utils.cpp (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/gateway_utils}/gateway_utils.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/mongoose}/mongoose.c (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/mongoose}/mongoose.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/mongoose}/mongoose_config.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/x25519}/x25519.c (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/{src => lib/x25519}/x25519.h (100%) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini delete mode 100755 Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h rename Firmware/AUTH_MQTT/MQTT_Gateway/{MQTT_Gateway.ino => src/main.cpp} (55%) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/README diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore b/Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore new file mode 100644 index 00000000..03f4a3c1 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/include/README b/Firmware/AUTH_MQTT/MQTT_Gateway/include/README new file mode 100644 index 00000000..49819c0d --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.h b/Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/gateway_config.h b/Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway_config.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/gateway_config.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway_config.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/README b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/README new file mode 100644 index 00000000..93793971 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.c b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/chacha20/chacha20.c similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/chacha20/chacha20.c diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/chacha20/chacha20.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/chacha20.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/chacha20/chacha20.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp similarity index 99% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp index 0e9a109f..e7093069 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp @@ -4,7 +4,7 @@ #include "chacha20.h" #include #include - +#include // ------------------------------------------------------------------- // Utility // ------------------------------------------------------------------- diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_core.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_private.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_private.h similarity index 92% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_private.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_private.h index 50989ed5..5189e187 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_private.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_private.h @@ -3,7 +3,7 @@ #include #include "mongoose.h" -#include "../gateway_config.h" +// #include "gateway_config.h" #include "gateway_utils.h" #include diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_dashboard.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/gateway_utils.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.c b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose.c similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose.c diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose_config.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose_config.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/mongoose_config.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/mongoose/mongoose_config.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.c b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/x25519/x25519.c similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.c rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/x25519/x25519.c diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/x25519/x25519.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/src/x25519.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/x25519/x25519.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini new file mode 100644 index 00000000..5657015b --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini @@ -0,0 +1,16 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +build_flags = -I include +monitor_speed = 115200 diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h b/Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h deleted file mode 100755 index 5739b834..00000000 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/src/dashboard.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __DASHBOARD__H -#define __DASHBOARD__H - - - -#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/MQTT_Gateway.ino b/Firmware/AUTH_MQTT/MQTT_Gateway/src/main.cpp similarity index 55% rename from Firmware/AUTH_MQTT/MQTT_Gateway/MQTT_Gateway.ino rename to Firmware/AUTH_MQTT/MQTT_Gateway/src/main.cpp index cbe65f79..354fecf7 100755 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/MQTT_Gateway.ino +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/src/main.cpp @@ -1,4 +1,5 @@ -#include "src/gateway.h" +#include +#include "gateway.h" void setup() { diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/README b/Firmware/AUTH_MQTT/MQTT_Gateway/test/README new file mode 100644 index 00000000..9b1e87bc --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From c5e2d66dcd85606db979fd3d07e56132069bb428 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 02:21:43 +0200 Subject: [PATCH 04/12] lib/gateway_core/gateway_device.cpp, create device class for handling devices in the ram and flash --- .../lib/gateway_core/gateway_core.cpp | 177 +++--------------- .../lib/gateway_core/gateway_core.h | 13 +- .../lib/gateway_core/gateway_device.cpp | 120 ++++++++++++ .../lib/gateway_core/gateway_device.h | 21 +++ .../lib/gateway_utils/gateway_utils.cpp | 7 + .../lib/gateway_utils/gateway_utils.h | 1 + 6 files changed, 185 insertions(+), 154 deletions(-) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp index e7093069..6cbb400d 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp @@ -8,12 +8,6 @@ // ------------------------------------------------------------------- // Utility // ------------------------------------------------------------------- -static void bytes_to_hex(const uint8_t* bytes, size_t len, char* hex) { - for (size_t i = 0; i < len; i++) { - sprintf(&hex[i*2], "%02x", bytes[i]); - } - hex[len*2] = '\0'; -} static String safeString(const char* str) { if (str == nullptr) return String(); @@ -50,7 +44,7 @@ void GatewayCore::begin() { Serial.println("LittleFS mounted"); } - loadDevices(); + m_devices.loadDevices(); setupRpc(); mg_timer_add(&m_mgr, GW_MQTT_RECONNECT_MS, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, @@ -174,8 +168,8 @@ void GatewayCore::sendError(const String& deviceId, const char* msg) { // ------------------------------------------------------------------- void GatewayCore::sendEncrypted(const String& deviceId, const uint8_t* plaintext, size_t len) { if (plaintext == nullptr || len == 0) return; - auto it = m_devices.find(deviceId); - if (it == m_devices.end()) { + auto it = m_devices.devices.find(deviceId); + if (it == m_devices.devices.end()) { Serial.printf("sendEncrypted: device %s not found\n", deviceId.c_str()); return; } @@ -218,7 +212,7 @@ void GatewayCore::sendEncrypted(const String& deviceId, const uint8_t* plaintext // FIX: avoid VLA on the stack (encLen is runtime-determined). // Allocate hex buffers on the heap instead. char nonceHex[25]; // nonce is always 12 bytes → 24 hex chars + NUL, safe as fixed array - bytes_to_hex(nonce, 12, nonceHex); + gw_bytes_to_hex(nonce, 12, nonceHex); char* cipherHex = (char*)malloc(encLen * 2 + 1); if (!cipherHex) { @@ -226,7 +220,7 @@ void GatewayCore::sendEncrypted(const String& deviceId, const uint8_t* plaintext free(cipher); return; } - bytes_to_hex(cipher, encLen, cipherHex); + gw_bytes_to_hex(cipher, encLen, cipherHex); char out[512]; int outLen = mg_snprintf(out, sizeof(out), @@ -239,117 +233,6 @@ void GatewayCore::sendEncrypted(const String& deviceId, const uint8_t* plaintext dev.lastNonce = counter; } -// ------------------------------------------------------------------- -// Persistent storage helpers (LittleFS) -// ------------------------------------------------------------------- -String GatewayCore::safeFilename(const String& id) { - String safe = id; - safe.replace("/", "_"); - safe.replace("\\", "_"); - safe.replace(":", "_"); - safe.replace("*", "_"); - safe.replace("?", "_"); - safe.replace("\"", "_"); - safe.replace("<", "_"); - safe.replace(">", "_"); - safe.replace("|", "_"); - return safe; -} - -void GatewayCore::loadDevices() { - Serial.println("Loading devices from LittleFS..."); - File root = LittleFS.open("/devices"); - if (!root || !root.isDirectory()) { - LittleFS.mkdir("/devices"); - Serial.println("Created /devices directory"); - return; - } - - File file; - while ((file = root.openNextFile())) { - if (!file.isDirectory()) { - String filename = file.name(); - if (filename.startsWith("dev_")) { - String id = filename.substring(4); - Serial.printf("Reading device file: %s\n", filename.c_str()); - String jsonStr = file.readString(); - file.close(); - - struct mg_str s = mg_str(jsonStr.c_str()); - Device dev; - char* idStr = mg_json_get_str(s, "$.id"); - if (idStr) dev.id = idStr; else dev.id = id; - free(idStr); - char* nameStr = mg_json_get_str(s, "$.name"); - if (nameStr) dev.name = nameStr; else dev.name = id; - free(nameStr); - char* typeStr = mg_json_get_str(s, "$.type"); - if (typeStr) dev.type = typeStr; else dev.type = "unknown"; - free(typeStr); - dev.status = (DeviceStatus)mg_json_get_long(s, "$.status", DEV_PENDING); - dev.lastNonce = mg_json_get_long(s, "$.lastNonce", 0); - dev.firstSeen = mg_json_get_long(s, "$.firstSeen", 0); - dev.lastSeen = mg_json_get_long(s, "$.lastSeen", 0); - dev.messageCount = mg_json_get_long(s, "$.messageCount", 0); - bool permPing = false; - mg_json_get_bool(s, "$.permPing", &permPing); - dev.permPing = permPing; - - char* keyHex = mg_json_get_str(s, "$.key"); - if (keyHex && strlen(keyHex) == 64) { - if (gw_hex_to_bytes(keyHex, dev.enc_key, 64) == 32) { - dev.keySet = true; - } else { - Serial.println("Failed to decode key hex"); - } - } - free(keyHex); - - dev.has_pending = false; - m_devices[dev.id] = dev; - Serial.printf("Loaded device %s from flash\n", dev.id.c_str()); - } - } - } - root.close(); - Serial.printf("Loaded %d devices from LittleFS\n", m_devices.size()); -} - -void GatewayCore::saveDevice(const Device& dev) { - char keyHex[65] = ""; - if (dev.keySet) { - bytes_to_hex(dev.enc_key, 32, keyHex); - } - - char buf[512]; - int n = mg_snprintf(buf, sizeof(buf), - "{\"id\":\"%s\",\"name\":\"%s\",\"type\":\"%s\",\"status\":%d,\"lastNonce\":%lu," - "\"firstSeen\":%lu,\"lastSeen\":%lu,\"messageCount\":%d,\"permPing\":%s,\"key\":\"%s\"}", - dev.id.c_str(), dev.name.c_str(), dev.type.c_str(), (int)dev.status, - (unsigned long)dev.lastNonce, dev.firstSeen, dev.lastSeen, dev.messageCount, - dev.permPing ? "true" : "false", keyHex); - - String safeId = safeFilename(dev.id); - String path = "/devices/dev_" + safeId; - File f = LittleFS.open(path, "w"); - if (f) { - f.print(buf); - f.close(); - Serial.printf("Saved device %s to flash\n", dev.id.c_str()); - } else { - Serial.printf("Failed to save device %s\n", dev.id.c_str()); - } -} - -void GatewayCore::removeDevice(const String& id) { - String safeId = safeFilename(id); - String path = "/devices/dev_" + safeId; - if (LittleFS.remove(path)) { - Serial.printf("Removed device %s from flash\n", id.c_str()); - } else { - Serial.printf("Failed to remove device %s\n", id.c_str()); - } -} // ------------------------------------------------------------------- // Handle connect request (encrypted) @@ -381,8 +264,8 @@ void GatewayCore::handleGatewayConnect(struct mg_str payload) { Serial.printf("nonce len: %d, cipher len: %d\n", strlen(nonceHex), strlen(cipherHex)); String devId(deviceId); - auto it = m_devices.find(devId); - if (it == m_devices.end()) { + auto it = m_devices.devices.find(devId); + if (it == m_devices.devices.end()) { // New device – create pending record Device dev; dev.id = devId; @@ -394,7 +277,7 @@ void GatewayCore::handleGatewayConnect(struct mg_str payload) { dev.has_pending = true; dev.pending_nonce = safeString(nonceHex); dev.pending_cipher = safeString(cipherHex); - m_devices[devId] = dev; + m_devices.devices[devId] = dev; if (m_eventCb) m_eventCb(devId, DEVICE_ADDED); Serial.printf("New device %s added as PENDING\n", deviceId); } else { @@ -425,8 +308,8 @@ bool GatewayCore::authorizeDevice(const String& id, const char* psk) { Serial.println("ERROR: psk is null"); return false; } - auto it = m_devices.find(id); - if (it == m_devices.end()) { + auto it = m_devices.devices.find(id); + if (it == m_devices.devices.end()) { Serial.println("ERROR: device not found"); return false; } @@ -540,7 +423,7 @@ bool GatewayCore::authorizeDevice(const String& id, const char* psk) { dev.has_pending = false; dev.pending_nonce = ""; dev.pending_cipher = ""; - saveDevice(dev); + m_devices.saveDevice(dev); free(cipher); free(plain); free(deviceName); free(deviceType); @@ -589,8 +472,8 @@ void GatewayCore::handleGatewayRx(struct mg_str payload) { } String devId(deviceId); - auto it = m_devices.find(devId); - if (it == m_devices.end()) { + auto it = m_devices.devices.find(devId); + if (it == m_devices.devices.end()) { Serial.printf("ERROR: device %s not found\n", deviceId); sendError(devId, "Device not found"); free(deviceId); free(nonceHex); free(cipherHex); @@ -743,13 +626,13 @@ void GatewayCore::rpcRequestConnect(struct mg_rpc_req *r) { // Device management // ------------------------------------------------------------------- Device* GatewayCore::getDevice(const String& id) { - auto it = m_devices.find(id); - return (it != m_devices.end()) ? &it->second : nullptr; + auto it = m_devices.devices.find(id); + return (it != m_devices.devices.end()) ? &it->second : nullptr; } void GatewayCore::approveDevice(const String& id, const DevicePerms& perms, const char* psk) { - auto it = m_devices.find(id); - if (it == m_devices.end()) return; + auto it = m_devices.devices.find(id); + if (it == m_devices.devices.end()) return; Device &dev = it->second; dev.status = DEV_APPROVED; dev.permPing = perms.ping; @@ -757,15 +640,15 @@ void GatewayCore::approveDevice(const String& id, const DevicePerms& perms, cons gw_psk_to_key(psk, strlen(psk), dev.enc_key); dev.keySet = true; } - saveDevice(dev); + m_devices.saveDevice(dev); if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); } void GatewayCore::denyDevice(const String& id) { - auto it = m_devices.find(id); - if (it == m_devices.end()) return; + auto it = m_devices.devices.find(id); + if (it == m_devices.devices.end()) return; it->second.status = DEV_DENIED; - saveDevice(it->second); + m_devices.saveDevice(it->second); if (m_eventCb) m_eventCb(id, DEVICE_UPDATED); } @@ -777,7 +660,7 @@ void GatewayCore::addDevice(const String& id, const String& name, const String& dev.firstSeen = millis(); dev.lastSeen = millis(); dev.status = DEV_PENDING; - m_devices[id] = dev; + m_devices.devices[id] = dev; if (m_eventCb) m_eventCb(id, DEVICE_ADDED); } @@ -785,13 +668,13 @@ void GatewayCore::addDevice(const String& id, const String& name, const String& // Delete helpers (called from dashboard) // ------------------------------------------------------------------- bool GatewayCore::deleteDevice(const String& id) { - auto it = m_devices.find(id); - if (it == m_devices.end()) { + auto it = m_devices.devices.find(id); + if (it == m_devices.devices.end()) { Serial.printf("deleteDevice: device %s not found\n", id.c_str()); return false; } - m_devices.erase(it); - removeDevice(id); // delete LittleFS file + m_devices.devices.erase(it); + m_devices.removeDevice(id); // delete LittleFS file if (m_eventCb) m_eventCb(id, DEVICE_REMOVED); Serial.printf("Deleted device %s\n", id.c_str()); return true; @@ -800,12 +683,12 @@ bool GatewayCore::deleteDevice(const String& id) { void GatewayCore::deleteAllDevices() { // Collect IDs first to avoid invalidating the iterator inside the loop std::vector ids; - ids.reserve(m_devices.size()); - for (auto& pair : m_devices) ids.push_back(pair.first); + ids.reserve(m_devices.devices.size()); + for (auto& pair : m_devices.devices) ids.push_back(pair.first); for (auto& id : ids) { - m_devices.erase(id); - removeDevice(id); + m_devices.devices.erase(id); + m_devices.removeDevice(id); if (m_eventCb) m_eventCb(id, DEVICE_REMOVED); } Serial.printf("Deleted all %d devices\n", (int)ids.size()); diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h index af75856a..b8bcc3c4 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h @@ -4,9 +4,10 @@ #include #include #include -#include // new #include "mongoose.h" #include "gateway_private.h" +#include "gateway_device.h" +#include "gateway_utils.h" class GatewayCore { public: @@ -17,7 +18,7 @@ class GatewayCore { void poll(); Device* getDevice(const String& id); - const std::map& getAllDevices() const { return m_devices; } + const std::map& getAllDevices() const { return m_devices.devices; } void approveDevice(const String& id, const DevicePerms& perms, const char* psk = nullptr); void denyDevice(const String& id); void addDevice(const String& id, const String& name, const String& type); @@ -41,7 +42,9 @@ class GatewayCore { struct mg_connection *m_mqttConn; struct mg_rpc *m_rpcHead; - std::map m_devices; + // std::map m_devices; + GatewayDevice m_devices; + EventCallback m_eventCb; // Preferences prefs; // removed // LittleFS is used directly @@ -60,10 +63,6 @@ class GatewayCore { void sendError(const String& deviceId, const char* msg); void sendEncrypted(const String& deviceId, const uint8_t* plaintext, size_t len); - void loadDevices(); // scan /devices directory - void saveDevice(const Device& dev); // write to /devices/ - void removeDevice(const String& id); // delete file - String safeFilename(const String& id); // sanitize for filename }; #endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp new file mode 100644 index 00000000..6f3cf6b9 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp @@ -0,0 +1,120 @@ +#include "gateway_device.h" + +// ------------------------------------------------------------------- +// Persistent storage helpers (LittleFS) +// ------------------------------------------------------------------- + +GatewayDevice::GatewayDevice(){ + +} + +GatewayDevice::~GatewayDevice() { +} +String GatewayDevice::safeFilename(const String& id) { + String safe = id; + safe.replace("/", "_"); + safe.replace("\\", "_"); + safe.replace(":", "_"); + safe.replace("*", "_"); + safe.replace("?", "_"); + safe.replace("\"", "_"); + safe.replace("<", "_"); + safe.replace(">", "_"); + safe.replace("|", "_"); + return safe; +} + +void GatewayDevice::loadDevices() { + Serial.println("Loading devices from LittleFS..."); + File root = LittleFS.open("/devices"); + if (!root || !root.isDirectory()) { + LittleFS.mkdir("/devices"); + Serial.println("Created /devices directory"); + return; + } + + File file; + while ((file = root.openNextFile())) { + if (!file.isDirectory()) { + String filename = file.name(); + if (filename.startsWith("dev_")) { + String id = filename.substring(4); + Serial.printf("Reading device file: %s\n", filename.c_str()); + String jsonStr = file.readString(); + file.close(); + + struct mg_str s = mg_str(jsonStr.c_str()); + Device dev; + char* idStr = mg_json_get_str(s, "$.id"); + if (idStr) dev.id = idStr; else dev.id = id; + free(idStr); + char* nameStr = mg_json_get_str(s, "$.name"); + if (nameStr) dev.name = nameStr; else dev.name = id; + free(nameStr); + char* typeStr = mg_json_get_str(s, "$.type"); + if (typeStr) dev.type = typeStr; else dev.type = "unknown"; + free(typeStr); + dev.status = (DeviceStatus)mg_json_get_long(s, "$.status", DEV_PENDING); + dev.lastNonce = mg_json_get_long(s, "$.lastNonce", 0); + dev.firstSeen = mg_json_get_long(s, "$.firstSeen", 0); + dev.lastSeen = mg_json_get_long(s, "$.lastSeen", 0); + dev.messageCount = mg_json_get_long(s, "$.messageCount", 0); + bool permPing = false; + mg_json_get_bool(s, "$.permPing", &permPing); + dev.permPing = permPing; + + char* keyHex = mg_json_get_str(s, "$.key"); + if (keyHex && strlen(keyHex) == 64) { + if (gw_hex_to_bytes(keyHex, dev.enc_key, 64) == 32) { + dev.keySet = true; + } else { + Serial.println("Failed to decode key hex"); + } + } + free(keyHex); + + dev.has_pending = false; + devices[dev.id] = dev; + Serial.printf("Loaded device %s from flash\n", dev.id.c_str()); + } + } + } + root.close(); + Serial.printf("Loaded %d devices from LittleFS\n", devices.size()); +} + +void GatewayDevice::saveDevice(const Device& dev) { + char keyHex[65] = ""; + if (dev.keySet) { + gw_bytes_to_hex(dev.enc_key, 32, keyHex); + } + + char buf[512]; + int n = mg_snprintf(buf, sizeof(buf), + "{\"id\":\"%s\",\"name\":\"%s\",\"type\":\"%s\",\"status\":%d,\"lastNonce\":%lu," + "\"firstSeen\":%lu,\"lastSeen\":%lu,\"messageCount\":%d,\"permPing\":%s,\"key\":\"%s\"}", + dev.id.c_str(), dev.name.c_str(), dev.type.c_str(), (int)dev.status, + (unsigned long)dev.lastNonce, dev.firstSeen, dev.lastSeen, dev.messageCount, + dev.permPing ? "true" : "false", keyHex); + + String safeId = safeFilename(dev.id); + String path = "/devices/dev_" + safeId; + File f = LittleFS.open(path, "w"); + if (f) { + f.print(buf); + f.close(); + Serial.printf("Saved device %s to flash\n", dev.id.c_str()); + } else { + Serial.printf("Failed to save device %s\n", dev.id.c_str()); + } +} + +void GatewayDevice::removeDevice(const String& id) { + String safeId = safeFilename(id); + String path = "/devices/dev_" + safeId; + if (LittleFS.remove(path)) { + Serial.printf("Removed device %s from flash\n", id.c_str()); + } else { + Serial.printf("Failed to remove device %s\n", id.c_str()); + } +} diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h new file mode 100644 index 00000000..34610a70 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h @@ -0,0 +1,21 @@ +#ifndef __GATEWAY_DEVICE_H +#define __GATEWAY_DEVICE_H +#include +#include // new +#include "gateway_private.h" +#include +#include"gateway_utils.h" + +class GatewayDevice { +public: + GatewayDevice(); + ~GatewayDevice(); + + void loadDevices(); // scan /devices directory + void saveDevice(const Device& dev); // write to /devices/ + void removeDevice(const String& id); // delete file + String safeFilename(const String& id); // sanitize for filename + std::map devices; + +}; +#endif \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp index 3ac34b03..48018a68 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp @@ -22,6 +22,13 @@ int gw_psk_to_key(const char *psk, size_t psk_len, uint8_t *key) { return 0; } +void gw_bytes_to_hex(const uint8_t* bytes, size_t len, char* hex) { + for (size_t i = 0; i < len; i++) { + sprintf(&hex[i*2], "%02x", bytes[i]); + } + hex[len*2] = '\0'; +} + // --------------------------------------------------------------------------- // gw_verify_auth // diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h index 863058c9..7b02ee1c 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h @@ -7,6 +7,7 @@ // Convert hex string to bytes. Returns byte count on success, -1 on error. int gw_hex_to_bytes(const char *hex, uint8_t *dst, size_t hex_len); +void gw_bytes_to_hex(const uint8_t* bytes, size_t len, char* hex); // Derive a 32-byte encryption key from a PSK string via SHA-256. int gw_psk_to_key(const char *psk, size_t psk_len, uint8_t *key); From 7a33c138ead9423575308506b22fefe46f1f801a Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 02:43:20 +0200 Subject: [PATCH 05/12] lib/gateway_dashboard/gateway_dashboard.cpp, add a comment to line 182 to make it more clear about the callback function --- .../lib/gateway_dashboard/gateway_dashboard.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp index f99c9cba..8a67f2f3 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_dashboard/gateway_dashboard.cpp @@ -179,7 +179,7 @@ void DashboardServer::begin(int port) { MG_INFO(("Dashboard started on port %d", port)); } - // Subscribe to core events + // Subscribe to core events using a lambda and pointer capture m_core.onEvent([this](const String& id, int event) { const char* status = nullptr; auto dev = m_core.getDevice(id); @@ -305,6 +305,14 @@ void DashboardServer::sendDeviceList(struct mg_connection *c) { mg_ws_send(c, json.c_str(), json.length(), WEBSOCKET_OP_TEXT); } +/** + * Send a WebSocket message to all connected clients to update + * the status of a device. The message is in the following format: + * {"type":"device_update","device":{"id":"","status":""}} + * + * @param deviceId The ID of the device to update. + * @paramstatus The new status of the device (e.g. "APPROVED", "PENDING", etc.) + */ void DashboardServer::broadcastDeviceUpdate(const String& deviceId, const char* status) { for (auto client : m_wsClients) { mg_ws_printf(client, WEBSOCKET_OP_TEXT, From 6b2dbb6c0e6002d9f3b6277fd1a120b8e2c41d0c Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 05:13:34 +0200 Subject: [PATCH 06/12] add unit test for gateway_device.cpp with mocking for littlfs and arduino libraries --- .../lib/gateway_core/gateway_core.cpp | 8 +- .../lib/gateway_core/gateway_core.h | 5 + .../lib/gateway_core/library.json | 9 + .../MQTT_Gateway/lib/littlefs_native/lfs.c | 6549 +++++++++++++++++ .../MQTT_Gateway/lib/littlefs_native/lfs.h | 801 ++ .../lib/littlefs_native/lfs_util.c | 37 + .../lib/littlefs_native/lfs_util.h | 273 + .../AUTH_MQTT/MQTT_Gateway/platformio.ini | 11 + .../test/test_gateway_device/mocks/Arduino.h | 54 + .../test/test_gateway_device/mocks/LittleFS.h | 170 + .../test/test_gateway_device/mocks/WiFi.h | 3 + .../test/test_gateway_device/test_main.cpp | 176 + 12 files changed, 8095 insertions(+), 1 deletion(-) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.c create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.h create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.c create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.h create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp index 6cbb400d..f9fac3e2 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp @@ -621,7 +621,13 @@ void GatewayCore::rpcPing(struct mg_rpc_req *r) { void GatewayCore::rpcRequestConnect(struct mg_rpc_req *r) { mg_rpc_err(r, -32601, "\"Method not found\""); } - +static void rpcCommand(struct mg_rpc_req *r) +{ + command_callback(mg_json_get_str(r->frame, "$.params.message")); + mg_rpc_ok(r, "{%m:true,%m:%lu}", + MG_ESC("recived"), MG_ESC("uptime_ms"), (unsigned long)millis()); +} + // ------------------------------------------------------------------- // Device management // ------------------------------------------------------------------- diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h index b8bcc3c4..86d26ebb 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h @@ -35,6 +35,9 @@ class GatewayCore { enum Event { DEVICE_ADDED, DEVICE_UPDATED, DEVICE_REMOVED }; void onEvent(EventCallback cb) { m_eventCb = cb; } + using MqttCallback = std::function; + void setMqttCallback(MqttCallback cb) { m_mqttCallback = cb; } + struct mg_mgr* getMgr() { return &m_mgr; } private: @@ -46,6 +49,7 @@ class GatewayCore { GatewayDevice m_devices; EventCallback m_eventCb; + MqttCallback m_mqttCallback; // Preferences prefs; // removed // LittleFS is used directly @@ -59,6 +63,7 @@ class GatewayCore { static void rpcPing(struct mg_rpc_req *r); static void rpcRequestConnect(struct mg_rpc_req *r); + static void rpcCommand(struct mg_rpc_req *r); void sendError(const String& deviceId, const char* msg); void sendEncrypted(const String& deviceId, const uint8_t* plaintext, size_t len); diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json new file mode 100644 index 00000000..d42aee69 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json @@ -0,0 +1,9 @@ +{ + "name": "gateway_core", + "version": "1.0.0", + "build": { + "srcFilter": [ + "+" + ] + } +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.c b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.c new file mode 100644 index 00000000..da4bfca4 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.c @@ -0,0 +1,6549 @@ +/* + * The little filesystem + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + + +// some constants used throughout the code +#define LFS_BLOCK_NULL ((lfs_block_t)-1) +#define LFS_BLOCK_INLINE ((lfs_block_t)-2) + +enum { + LFS_OK_RELOCATED = 1, + LFS_OK_DROPPED = 2, + LFS_OK_ORPHANED = 3, +}; + +enum { + LFS_CMP_EQ = 0, + LFS_CMP_LT = 1, + LFS_CMP_GT = 2, +}; + + +/// Caching block device operations /// + +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = LFS_BLOCK_NULL; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->cache_size); + pcache->block = LFS_BLOCK_NULL; +} + +static int lfs_bd_read(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size + || (lfs->block_count && block >= lfs->block_count)) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && block == pcache->block && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (block == rcache->block && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + if (size >= hint && off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + diff = lfs_aligndown(diff, lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + LFS_ASSERT(!lfs->block_count || block < lfs->block_count); + rcache->block = block; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min( + lfs_min( + lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->block_size) + - rcache->off, + lfs->cfg->cache_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, rcache->size); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_bd_cmp(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t diff = 0; + + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + + diff = lfs_min(size-i, sizeof(dat)); + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + int res = memcmp(dat, data + i, diff); + if (res) { + return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; + } + } + + return LFS_CMP_EQ; +} + +static int lfs_bd_crc(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) { + lfs_size_t diff = 0; + + for (lfs_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + diff = lfs_min(size-i, sizeof(dat)); + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, diff); + if (err) { + return err; + } + + *crc = lfs_crc(*crc, &dat, diff); + } + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_bd_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { + LFS_ASSERT(pcache->block < lfs->block_count); + lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + if (validate) { + // check data on disk + lfs_cache_drop(lfs, rcache); + int res = lfs_bd_cmp(lfs, + NULL, rcache, diff, + pcache->block, pcache->off, pcache->buffer, diff); + if (res < 0) { + return res; + } + + if (res != LFS_CMP_EQ) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_sync(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + lfs_cache_drop(lfs, rcache); + + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + + err = lfs->cfg->sync(lfs->cfg); + LFS_ASSERT(err <= 0); + return err; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_prog(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count); + LFS_ASSERT(off + size <= lfs->cfg->block_size); + + while (size > 0) { + if (block == pcache->block && + off >= pcache->off && + off < pcache->off + lfs->cfg->cache_size) { + // already fits in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->cache_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + pcache->size = lfs_max(pcache->size, off - pcache->off); + if (pcache->size == lfs->cfg->cache_size) { + // eagerly flush out pcache if we fill up + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == LFS_BLOCK_NULL); + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = lfs_aligndown(off, lfs->cfg->prog_size); + pcache->size = 0; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->block_count); + int err = lfs->cfg->erase(lfs->cfg, block); + LFS_ASSERT(err <= 0); + return err; +} +#endif + + +/// Small type-level utilities /// + +// some operations on paths +static inline lfs_size_t lfs_path_namelen(const char *path) { + return strcspn(path, "/"); +} + +static inline bool lfs_path_islast(const char *path) { + lfs_size_t namelen = lfs_path_namelen(path); + return path[namelen + strspn(path + namelen, "/")] == '\0'; +} + +static inline bool lfs_path_isdir(const char *path) { + return path[lfs_path_namelen(path)] != '\0'; +} + +// operations on block pairs +static inline void lfs_pair_swap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) { + return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL; +} + +static inline int lfs_pair_cmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pair_issync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { + pair[0] = lfs_fromle32(pair[0]); + pair[1] = lfs_fromle32(pair[1]); +} + +#ifndef LFS_READONLY +static inline void lfs_pair_tole32(lfs_block_t pair[2]) { + pair[0] = lfs_tole32(pair[0]); + pair[1] = lfs_tole32(pair[1]); +} +#endif + +// operations on 32-bit entry tags +typedef uint32_t lfs_tag_t; +typedef int32_t lfs_stag_t; + +#define LFS_MKTAG(type, id, size) \ + (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) + +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + +static inline bool lfs_tag_isvalid(lfs_tag_t tag) { + return !(tag & 0x80000000); +} + +static inline bool lfs_tag_isdelete(lfs_tag_t tag) { + return ((int32_t)(tag << 22) >> 22) == -1; +} + +static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { + return (tag & 0x70000000) >> 20; +} + +static inline uint16_t lfs_tag_type2(lfs_tag_t tag) { + return (tag & 0x78000000) >> 20; +} + +static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { + return (tag & 0x7ff00000) >> 20; +} + +static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) { + return (tag & 0x0ff00000) >> 20; +} + +static inline int8_t lfs_tag_splice(lfs_tag_t tag) { + return (int8_t)lfs_tag_chunk(tag); +} + +static inline uint16_t lfs_tag_id(lfs_tag_t tag) { + return (tag & 0x000ffc00) >> 10; +} + +static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) { + return tag & 0x000003ff; +} + +static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) { + return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag)); +} + +// operations on attributes in attribute lists +struct lfs_mattr { + lfs_tag_t tag; + const void *buffer; +}; + +struct lfs_diskoff { + lfs_block_t block; + lfs_off_t off; +}; + +#define LFS_MKATTRS(...) \ + (struct lfs_mattr[]){__VA_ARGS__}, \ + sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) + +// operations on global state +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { + a->tag ^= b->tag; + a->pair[0] ^= b->pair[0]; + a->pair[1] ^= b->pair[1]; +} + +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { + return a->tag == 0 + && a->pair[0] == 0 + && a->pair[1] == 0; +} + +#ifndef LFS_READONLY +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) & 0x1ff; +} + +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { + return lfs_tag_type1(a->tag); +} +#endif + +static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag) >> 9; +} + +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, + const lfs_block_t *pair) { + return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; +} + +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { + a->tag = lfs_fromle32(a->tag); + a->pair[0] = lfs_fromle32(a->pair[0]); + a->pair[1] = lfs_fromle32(a->pair[1]); +} + +#ifndef LFS_READONLY +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { + a->tag = lfs_tole32(a->tag); + a->pair[0] = lfs_tole32(a->pair[0]); + a->pair[1] = lfs_tole32(a->pair[1]); +} +#endif + +// operations on forward-CRCs used to track erased state +struct lfs_fcrc { + lfs_size_t size; + uint32_t crc; +}; + +static void lfs_fcrc_fromle32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_fromle32(fcrc->size); + fcrc->crc = lfs_fromle32(fcrc->crc); +} + +#ifndef LFS_READONLY +static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) { + fcrc->size = lfs_tole32(fcrc->size); + fcrc->crc = lfs_tole32(fcrc->crc); +} +#endif + +// other endianness operations +static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { + ctz->head = lfs_fromle32(ctz->head); + ctz->size = lfs_fromle32(ctz->size); +} + +#ifndef LFS_READONLY +static void lfs_ctz_tole32(struct lfs_ctz *ctz) { + ctz->head = lfs_tole32(ctz->head); + ctz->size = lfs_tole32(ctz->size); +} +#endif + +static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { + superblock->version = lfs_fromle32(superblock->version); + superblock->block_size = lfs_fromle32(superblock->block_size); + superblock->block_count = lfs_fromle32(superblock->block_count); + superblock->name_max = lfs_fromle32(superblock->name_max); + superblock->file_max = lfs_fromle32(superblock->file_max); + superblock->attr_max = lfs_fromle32(superblock->attr_max); +} + +#ifndef LFS_READONLY +static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { + superblock->version = lfs_tole32(superblock->version); + superblock->block_size = lfs_tole32(superblock->block_size); + superblock->block_count = lfs_tole32(superblock->block_count); + superblock->name_max = lfs_tole32(superblock->name_max); + superblock->file_max = lfs_tole32(superblock->file_max); + superblock->attr_max = lfs_tole32(superblock->attr_max); +} +#endif + +#ifndef LFS_NO_ASSERT +static bool lfs_mlist_isopen(struct lfs_mlist *head, + struct lfs_mlist *node) { + for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)node) { + return true; + } + } + + return false; +} +#endif + +static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) { + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == mlist) { + *p = (*p)->next; + break; + } + } +} + +static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { + mlist->next = lfs->mlist; + lfs->mlist = mlist; +} + +// some other filesystem operations +static uint32_t lfs_fs_disk_version(lfs_t *lfs) { + (void)lfs; +#ifdef LFS_MULTIVERSION + if (lfs->cfg->disk_version) { + return lfs->cfg->disk_version; + } else +#endif + { + return LFS_DISK_VERSION; + } +} + +static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 16); + +} + +static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) { + return 0xffff & (lfs_fs_disk_version(lfs) >> 0); +} + + +/// Internal operations predeclared here /// +#ifndef LFS_READONLY +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount); +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end); +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); + +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss); +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]); +static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *pdir); +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *parent); +static int lfs_fs_forceconsistency(lfs_t *lfs); +#endif + +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock); + +#ifdef LFS_MIGRATE +static int lfs1_traverse(lfs_t *lfs, + int (*cb)(void*, lfs_block_t), void *data); +#endif + +static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir); + +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); +static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file); +static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file); + +static lfs_ssize_t lfs_fs_size_(lfs_t *lfs); +static int lfs_fs_traverse_(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); + +static int lfs_deinit(lfs_t *lfs); +static int lfs_unmount_(lfs_t *lfs); + + +/// Block allocator /// + +// allocations should call this when all allocated blocks are committed to +// the filesystem +// +// after a checkpoint, the block allocator may realloc any untracked blocks +static void lfs_alloc_ckpoint(lfs_t *lfs) { + lfs->lookahead.ckpoint = lfs->block_count; +} + +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs_alloc_drop(lfs_t *lfs) { + lfs->lookahead.size = 0; + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); +} + +#ifndef LFS_READONLY +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = (lfs_t*)p; + lfs_block_t off = ((block - lfs->lookahead.start) + + lfs->block_count) % lfs->block_count; + + if (off < lfs->lookahead.size) { + lfs->lookahead.buffer[off / 8] |= 1U << (off % 8); + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_alloc_scan(lfs_t *lfs) { + // move lookahead buffer to the first unused block + // + // note we limit the lookahead buffer to at most the amount of blocks + // checkpointed, this prevents the math in lfs_alloc from underflowing + lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + lfs->lookahead.next = 0; + lfs->lookahead.size = lfs_min( + 8*lfs->cfg->lookahead_size, + lfs->lookahead.ckpoint); + + // find mask of free blocks from tree + memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + int err = lfs_fs_traverse_(lfs, lfs_alloc_lookahead, lfs, true); + if (err) { + lfs_alloc_drop(lfs); + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + // scan our lookahead buffer for free blocks + while (lfs->lookahead.next < lfs->lookahead.size) { + if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] + & (1U << (lfs->lookahead.next % 8)))) { + // found a free block + *block = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + + // eagerly find next free block to maximize how many blocks + // lfs_alloc_ckpoint makes available for scanning + while (true) { + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + + if (lfs->lookahead.next >= lfs->lookahead.size + || !(lfs->lookahead.buffer[lfs->lookahead.next / 8] + & (1U << (lfs->lookahead.next % 8)))) { + return 0; + } + } + } + + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + } + + // In order to keep our block allocator from spinning forever when our + // filesystem is full, we mark points where there are no in-flight + // allocations with a checkpoint before starting a set of allocations. + // + // If we've looked at all blocks since the last checkpoint, we report + // the filesystem as out of storage. + // + if (lfs->lookahead.ckpoint <= 0) { + LFS_ERROR("No more free space 0x%"PRIx32, + (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count); + return LFS_ERR_NOSPC; + } + + // No blocks in our lookahead buffer, we need to scan the filesystem for + // unused blocks in the next lookahead window. + int err = lfs_alloc_scan(lfs); + if(err) { + return err; + } + } +} +#endif + +/// Metadata pair and directory operations /// +static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t goff, void *gbuffer, lfs_size_t gsize) { + lfs_off_t off = dir->off; + lfs_tag_t ntag = dir->etag; + lfs_stag_t gdiff = 0; + + // synthetic moves + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(gtag)) { + return LFS_ERR_NOENT; + } else if (lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(gtag)) { + gdiff -= LFS_MKTAG(0, 1, 0); + } + } + + // iterate over dir block backwards (for faster lookups) + while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) { + off -= lfs_tag_dsize(ntag); + lfs_tag_t tag = ntag; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(ntag), + dir->pair[0], off, &ntag, sizeof(ntag)); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff; + + if (lfs_tag_id(gmask) != 0 && + lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) { + if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { + // found where we were created + return LFS_ERR_NOENT; + } + + // move around splices + gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + if ((gmask & tag) == (gmask & (gtag - gdiff))) { + if (lfs_tag_isdelete(tag)) { + return LFS_ERR_NOENT; + } + + lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize); + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, diff, + dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + memset((uint8_t*)gbuffer + diff, 0, gsize - diff); + + return tag + gdiff; + } + } + + return LFS_ERR_NOENT; +} + +static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) { + return lfs_dir_getslice(lfs, dir, + gmask, gtag, + 0, buffer, lfs_tag_size(gtag)); +} + +static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && pcache->block == LFS_BLOCK_INLINE && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (rcache->block == LFS_BLOCK_INLINE && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + } + + // load to cache, first condition can no longer fail + rcache->block = LFS_BLOCK_INLINE; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->cache_size); + int err = lfs_dir_getslice(lfs, dir, gmask, gtag, + rcache->off, rcache->buffer, rcache->size); + if (err < 0) { + return err; + } + } + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_dir_traverse_filter(void *p, + lfs_tag_t tag, const void *buffer) { + lfs_tag_t *filtertag = p; + (void)buffer; + + // which mask depends on unique bit in tag structure + uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0)) + ? LFS_MKTAG(0x7ff, 0x3ff, 0) + : LFS_MKTAG(0x700, 0x3ff, 0); + + // check for redundancy + if ((mask & tag) == (mask & *filtertag) || + lfs_tag_isdelete(*filtertag) || + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( + LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { + *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0); + return true; + } + + // check if we need to adjust for created/deleted tags + if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) { + *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + return false; +} +#endif + +#ifndef LFS_READONLY +// maximum recursive depth of lfs_dir_traverse, the deepest call: +// +// traverse with commit +// '-> traverse with move +// '-> traverse with filter +// +#define LFS_DIR_TRAVERSE_DEPTH 3 + +struct lfs_dir_traverse { + const lfs_mdir_t *dir; + lfs_off_t off; + lfs_tag_t ptag; + const struct lfs_mattr *attrs; + int attrcount; + + lfs_tag_t tmask; + lfs_tag_t ttag; + uint16_t begin; + uint16_t end; + int16_t diff; + + int (*cb)(void *data, lfs_tag_t tag, const void *buffer); + void *data; + + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk; +}; + +static int lfs_dir_traverse(lfs_t *lfs, + const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, + const struct lfs_mattr *attrs, int attrcount, + lfs_tag_t tmask, lfs_tag_t ttag, + uint16_t begin, uint16_t end, int16_t diff, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // This function in inherently recursive, but bounded. To allow tool-based + // analysis without unnecessary code-cost we use an explicit stack + struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1]; + unsigned sp = 0; + int res; + + // iterate over directory and attrs + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk = {0}; + while (true) { + { + if (off+lfs_tag_dsize(ptag) < dir->off) { + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + return err; + } + + tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; + disk.block = dir->pair[0]; + disk.off = off+sizeof(lfs_tag_t); + buffer = &disk; + ptag = tag; + } else if (attrcount > 0) { + tag = attrs[0].tag; + buffer = attrs[0].buffer; + attrs += 1; + attrcount -= 1; + } else { + // finished traversal, pop from stack? + res = 0; + break; + } + + // do we need to filter? + lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); + if ((mask & tmask & tag) != (mask & tmask & ttag)) { + continue; + } + + if (lfs_tag_id(tmask) != 0) { + LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH); + // recurse, scan for duplicates, and update tag based on + // creates/deletes + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = tag, + .buffer = buffer, + .disk = disk, + }; + sp += 1; + + tmask = 0; + ttag = 0; + begin = 0; + end = 0; + diff = 0; + cb = lfs_dir_traverse_filter; + data = &stack[sp-1].tag; + continue; + } + } + +popped: + // in filter range? + if (lfs_tag_id(tmask) != 0 && + !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + continue; + } + + // handle special cases for mcu-side operations + if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { + // do nothing + } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { + // Without this condition, lfs_dir_traverse can exhibit an + // extremely expensive O(n^3) of nested loops when renaming. + // This happens because lfs_dir_traverse tries to filter tags by + // the tags in the source directory, triggering a second + // lfs_dir_traverse with its own filter operation. + // + // traverse with commit + // '-> traverse with filter + // '-> traverse with move + // '-> traverse with filter + // + // However we don't actually care about filtering the second set of + // tags, since duplicate tags have no effect when filtering. + // + // This check skips this unnecessary recursive filtering explicitly, + // reducing this runtime from O(n^3) to O(n^2). + if (cb == lfs_dir_traverse_filter) { + continue; + } + + // recurse into move + stack[sp] = (struct lfs_dir_traverse){ + .dir = dir, + .off = off, + .ptag = ptag, + .attrs = attrs, + .attrcount = attrcount, + .tmask = tmask, + .ttag = ttag, + .begin = begin, + .end = end, + .diff = diff, + .cb = cb, + .data = data, + .tag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0), + }; + sp += 1; + + uint16_t fromid = lfs_tag_size(tag); + uint16_t toid = lfs_tag_id(tag); + dir = buffer; + off = 0; + ptag = 0xffffffff; + attrs = NULL; + attrcount = 0; + tmask = LFS_MKTAG(0x600, 0x3ff, 0); + ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0); + begin = fromid; + end = fromid+1; + diff = toid-fromid+diff; + } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { + for (unsigned i = 0; i < lfs_tag_size(tag); i++) { + const struct lfs_attr *a = buffer; + res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, + lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); + if (res < 0) { + return res; + } + + if (res) { + break; + } + } + } else { + res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); + if (res < 0) { + return res; + } + + if (res) { + break; + } + } + } + + if (sp > 0) { + // pop from the stack and return, fortunately all pops share + // a destination + dir = stack[sp-1].dir; + off = stack[sp-1].off; + ptag = stack[sp-1].ptag; + attrs = stack[sp-1].attrs; + attrcount = stack[sp-1].attrcount; + tmask = stack[sp-1].tmask; + ttag = stack[sp-1].ttag; + begin = stack[sp-1].begin; + end = stack[sp-1].end; + diff = stack[sp-1].diff; + cb = stack[sp-1].cb; + data = stack[sp-1].data; + tag = stack[sp-1].tag; + buffer = stack[sp-1].buffer; + disk = stack[sp-1].disk; + sp -= 1; + goto popped; + } else { + return res; + } +} +#endif + +static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2], + lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // we can find tag very efficiently during a fetch, since we're already + // scanning the entire directory + lfs_stag_t besttag = -1; + + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (lfs->block_count + && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) { + return LFS_ERR_CORRUPT; + } + + // find the block with the most recent revision + uint32_t revs[2] = {0, 0}; + int r = 0; + for (int i = 0; i < 2; i++) { + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(revs[i]), + pair[i], 0, &revs[i], sizeof(revs[i])); + revs[i] = lfs_fromle32(revs[i]); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT && + lfs_scmp(revs[i], revs[(i+1)%2]) > 0) { + r = i; + } + } + + dir->pair[0] = pair[(r+0)%2]; + dir->pair[1] = pair[(r+1)%2]; + dir->rev = revs[(r+0)%2]; + dir->off = 0; // nonzero = found some commits + + // now scan tags to fetch the actual dir and find possible match + for (int i = 0; i < 2; i++) { + lfs_off_t off = 0; + lfs_tag_t ptag = 0xffffffff; + + uint16_t tempcount = 0; + lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + bool tempsplit = false; + lfs_stag_t tempbesttag = besttag; + + // assume not erased until proven otherwise + bool maybeerased = false; + bool hasfcrc = false; + struct lfs_fcrc fcrc; + + dir->rev = lfs_tole32(dir->rev); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + + while (true) { + // extract next tag + lfs_tag_t tag; + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + // can't continue? + break; + } + return err; + } + + crc = lfs_crc(crc, &tag, sizeof(tag)); + tag = lfs_frombe32(tag) ^ ptag; + + // next commit not yet programmed? + if (!lfs_tag_isvalid(tag)) { + // we only might be erased if the last tag was a crc + maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC); + break; + // out of range? + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + break; + } + + ptag = tag; + + if (lfs_tag_type2(tag) == LFS_TYPE_CCRC) { + // check the crc attr + uint32_t dcrc; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + dcrc = lfs_fromle32(dcrc); + + if (crc != dcrc) { + break; + } + + // reset the next bit if we need to + ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; + + // toss our crc into the filesystem seed for + // pseudorandom numbers, note we use another crc here + // as a collection function because it is sufficiently + // random and convenient + lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc)); + + // update with what's found so far + besttag = tempbesttag; + dir->off = off + lfs_tag_dsize(tag); + dir->etag = ptag; + dir->count = tempcount; + dir->tail[0] = temptail[0]; + dir->tail[1] = temptail[1]; + dir->split = tempsplit; + + // reset crc, hasfcrc + crc = 0xffffffff; + continue; + } + + // crc the entry first, hopefully leaving it in the cache + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + lfs_tag_dsize(tag)-sizeof(tag), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + + // directory modification tags? + if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { + // increase count of files if necessary + if (lfs_tag_id(tag) >= tempcount) { + tempcount = lfs_tag_id(tag) + 1; + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) { + tempcount += lfs_tag_splice(tag); + + if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) { + tempbesttag |= 0x80000000; + } else if (tempbesttag != -1 && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) { + tempsplit = (lfs_tag_chunk(tag) & 1); + + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &temptail, 8); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + lfs_pair_fromle32(temptail); + } else if (lfs_tag_type3(tag) == LFS_TYPE_FCRC) { + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), + &fcrc, sizeof(fcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + break; + } + return err; + } + + lfs_fcrc_fromle32(&fcrc); + hasfcrc = true; + } + + // found a match for our fetcher? + if ((fmask & tag) == (fmask & ftag)) { + int res = cb(data, tag, &(struct lfs_diskoff){ + dir->pair[0], off+sizeof(tag)}); + if (res < 0) { + if (res == LFS_ERR_CORRUPT) { + break; + } + return res; + } + + if (res == LFS_CMP_EQ) { + // found a match + tempbesttag = tag; + } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { + // found an identical tag, but contents didn't match + // this must mean that our besttag has been overwritten + tempbesttag = -1; + } else if (res == LFS_CMP_GT && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + // found a greater match, keep track to keep things sorted + tempbesttag = tag | 0x80000000; + } + } + } + + // found no valid commits? + if (dir->off == 0) { + // try the other block? + lfs_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + continue; + } + + // did we end on a valid commit? we may have an erased block + dir->erased = false; + if (maybeerased && dir->off % lfs->cfg->prog_size == 0) { + #ifdef LFS_MULTIVERSION + // note versions < lfs2.1 did not have fcrc tags, if + // we're < lfs2.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs2.0 disks becomes very inefficient + if (lfs_fs_disk_version(lfs) < 0x00020001) { + dir->erased = true; + + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); + } + } + + // synthetic move + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { + besttag -= LFS_MKTAG(0, 1, 0); + } + } + + // found tag? or found best id? + if (id) { + *id = lfs_min(lfs_tag_id(besttag), dir->count); + } + + if (lfs_tag_isvalid(besttag)) { + return besttag; + } else if (lfs_tag_id(besttag) < dir->count) { + return LFS_ERR_NOENT; + } else { + return 0; + } + } + + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + return LFS_ERR_CORRUPT; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2]) { + // note, mask=-1, tag=-1 can never match a tag since this + // pattern has the invalid bit set + return (int)lfs_dir_fetchmatch(lfs, dir, pair, + (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL); +} + +static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_gstate_t *gstate) { + lfs_gstate_t temp; + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); + if (res < 0 && res != LFS_ERR_NOENT) { + return res; + } + + if (res != LFS_ERR_NOENT) { + // xor together to find resulting gstate + lfs_gstate_fromle32(&temp); + lfs_gstate_xor(gstate, &temp); + } + + return 0; +} + +static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, + uint16_t id, struct lfs_info *info) { + if (id == 0x3ff) { + // special case for root + strcpy(info->name, "/"); + info->type = LFS_TYPE_DIR; + return 0; + } + + lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); + if (tag < 0) { + return (int)tag; + } + + info->type = lfs_tag_type3(tag); + + struct lfs_ctz ctz; + tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + return (int)tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + info->size = ctz.size; + } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + info->size = lfs_tag_size(tag); + } + + return 0; +} + +struct lfs_dir_find_match { + lfs_t *lfs; + const void *name; + lfs_size_t size; +}; + +static int lfs_dir_find_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_dir_find_match *name = data; + lfs_t *lfs = name->lfs; + const struct lfs_diskoff *disk = buffer; + + // compare with disk + lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag)); + int res = lfs_bd_cmp(lfs, + NULL, &lfs->rcache, diff, + disk->block, disk->off, name->name, diff); + if (res != LFS_CMP_EQ) { + return res; + } + + // only equal if our size is still the same + if (name->size != lfs_tag_size(tag)) { + return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT; + } + + // found a match! + return LFS_CMP_EQ; +} + +// lfs_dir_find tries to set path and id even if file is not found +// +// returns: +// - 0 if file is found +// - LFS_ERR_NOENT if file or parent is not found +// - LFS_ERR_NOTDIR if parent is not a dir +static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, + const char **path, uint16_t *id) { + // we reduce path to a single name if we can find it + const char *name = *path; + + // default to root dir + lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); + dir->tail[0] = lfs->root[0]; + dir->tail[1] = lfs->root[1]; + + // empty paths are not allowed + if (*name == '\0') { + return LFS_ERR_INVAL; + } + + while (true) { +nextname: + // skip slashes if we're a directory + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + name += strspn(name, "/"); + } + lfs_size_t namelen = strcspn(name, "/"); + + // skip '.' + if (namelen == 1 && memcmp(name, ".", 1) == 0) { + name += namelen; + goto nextname; + } + + // error on unmatched '..', trying to go above root? + if (namelen == 2 && memcmp(name, "..", 2) == 0) { + return LFS_ERR_INVAL; + } + + // skip if matched by '..' in name + const char *suffix = name + namelen; + lfs_size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) { + // noop + } else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + name = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (*name == '\0') { + return tag; + } + + // update what we've found so far + *path = name; + + // only continue if we're a directory + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + // grab the entry data + if (lfs_tag_id(tag) != 0x3ff) { + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail); + if (res < 0) { + return res; + } + lfs_pair_fromle32(dir->tail); + } + + // find entry matching name + while (true) { + tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, + LFS_MKTAG(0x780, 0, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), + id, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, name, namelen}); + if (tag < 0) { + return tag; + } + + if (tag) { + break; + } + + if (!dir->split) { + return LFS_ERR_NOENT; + } + } + + // to next name + name += namelen; + } +} + +// commit logic +struct lfs_commit { + lfs_block_t block; + lfs_off_t off; + lfs_tag_t ptag; + uint32_t crc; + + lfs_off_t begin; + lfs_off_t end; +}; + +#ifndef LFS_READONLY +static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, + const void *buffer, lfs_size_t size) { + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off , + (const uint8_t*)buffer, size); + if (err) { + return err; + } + + commit->crc = lfs_crc(commit->crc, buffer, size); + commit->off += size; + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, + lfs_tag_t tag, const void *buffer) { + // check if we fit + lfs_size_t dsize = lfs_tag_dsize(tag); + if (commit->off + dsize > commit->end) { + return LFS_ERR_NOSPC; + } + + // write out tag + lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag); + int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + if (!(tag & 0x80000000)) { + // from memory + err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag)); + if (err) { + return err; + } + } else { + // from disk + const struct lfs_diskoff *disk = buffer; + for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) { + // rely on caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dsize-sizeof(tag)-i, + disk->block, disk->off+i, &dat, 1); + if (err) { + return err; + } + + err = lfs_dir_commitprog(lfs, commit, &dat, 1); + if (err) { + return err; + } + } + } + + commit->ptag = tag & 0x7fffffff; + return 0; +} +#endif + +#ifndef LFS_READONLY + +static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { + // align to program units + // + // this gets a bit complex as we have two types of crcs: + // - 5-word crc with fcrc to check following prog (middle of block) + // - 2-word crc with no following prog (end of block) + const lfs_off_t end = lfs_alignup( + lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size), + lfs->cfg->prog_size); + + lfs_off_t off1 = 0; + uint32_t crc1 = 0; + + // create crc tags to fill up remainder of commit, note that + // padding is not crced, which lets fetches skip padding but + // makes committing a bit more complicated + while (commit->off < end) { + lfs_off_t noff = ( + lfs_min(end - (commit->off+sizeof(lfs_tag_t)), 0x3fe) + + (commit->off+sizeof(lfs_tag_t))); + // too large for crc tag? need padding commits + if (noff < end) { + noff = lfs_min(noff, end - 5*sizeof(uint32_t)); + } + + // space for fcrc? + uint8_t eperturb = (uint8_t)-1; + if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) { + // first read the leading byte, this always contains a bit + // we can perturb to avoid writes that don't change the fcrc + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, &eperturb, 1); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + #ifdef LFS_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs2.1, so only write + // these if we're a >= lfs2.1 filesystem + if (lfs_fs_disk_version(lfs) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs_fcrc fcrc = { + .size = lfs->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, lfs->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + lfs_fcrc_tole32(&fcrc); + err = lfs_dir_commitattr(lfs, commit, + LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)), + &fcrc); + if (err) { + return err; + } + } + } + + // build commit crc + struct { + lfs_tag_t tag; + uint32_t crc; + } ccrc; + lfs_tag_t ntag = LFS_MKTAG( + LFS_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff, + noff - (commit->off+sizeof(lfs_tag_t))); + ccrc.tag = lfs_tobe32(ntag ^ commit->ptag); + commit->crc = lfs_crc(commit->crc, &ccrc.tag, sizeof(lfs_tag_t)); + ccrc.crc = lfs_tole32(commit->crc); + + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off, &ccrc, sizeof(ccrc)); + if (err) { + return err; + } + + // keep track of non-padding checksum to verify + if (off1 == 0) { + off1 = commit->off + sizeof(lfs_tag_t); + crc1 = commit->crc; + } + + commit->off = noff; + // perturb valid bit? + commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + // reset crc for next commit + commit->crc = 0xffffffff; + + // manually flush here since we don't prog the padding, this confuses + // the caching layer + if (noff >= end || noff >= lfs->pcache.off + lfs->cfg->cache_size) { + // flush buffers + int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + } + } + + // successful commit, check checksums to make sure + // + // note that we don't need to check padding commits, worst + // case if they are corrupted we would have had to compact anyways + lfs_off_t off = commit->begin; + uint32_t crc = 0xffffffff; + int err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, off1+sizeof(uint32_t), + commit->block, off, off1-off, &crc); + if (err) { + return err; + } + + // check non-padding commits against known crc + if (crc != crc1) { + return LFS_ERR_CORRUPT; + } + + // make sure to check crc in case we happen to pick + // up an unrelated crc (frozen block?) + err = lfs_bd_crc(lfs, + NULL, &lfs->rcache, sizeof(uint32_t), + commit->block, off1, sizeof(uint32_t), &crc); + if (err) { + return err; + } + + if (crc != 0) { + return LFS_ERR_CORRUPT; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { + // allocate pair of dir blocks (backwards, so we write block 1 first) + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); + if (err) { + return err; + } + } + + // zero for reproducibility in case initial block is unreadable + dir->rev = 0; + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(dir->rev), + dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // to make sure we don't immediately evict, align the new revision count + // to our block_cycles modulus, see lfs_dir_compact for why our modulus + // is tweaked this way + if (lfs->cfg->block_cycles > 0) { + dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|1)); + } + + // set defaults + dir->off = sizeof(dir->rev); + dir->etag = 0xffffffff; + dir->count = 0; + dir->tail[0] = LFS_BLOCK_NULL; + dir->tail[1] = LFS_BLOCK_NULL; + dir->erased = false; + dir->split = false; + + // don't write out yet, let caller take care of that + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { + // steal state + int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(tail->tail); + err = lfs_dir_commit(lfs, dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); + lfs_pair_fromle32(tail->tail); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_split(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t split, uint16_t end) { + // create tail metadata pair + lfs_mdir_t tail; + int err = lfs_dir_alloc(lfs, &tail); + if (err) { + return err; + } + + tail.split = dir->split; + tail.tail[0] = dir->tail[0]; + tail.tail[1] = dir->tail[1]; + + // note we don't care about LFS_OK_RELOCATED + int res = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); + if (res < 0) { + return res; + } + + dir->tail[0] = tail.pair[0]; + dir->tail[1] = tail.pair[1]; + dir->split = true; + + // update root if needed + if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) { + lfs->root[0] = tail.pair[0]; + lfs->root[1] = tail.pair[1]; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { + lfs_size_t *size = p; + (void)buffer; + + *size += lfs_tag_dsize(tag); + return 0; +} +#endif + +#ifndef LFS_READONLY +struct lfs_dir_commit_commit { + lfs_t *lfs; + struct lfs_commit *commit; +}; +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { + struct lfs_dir_commit_commit *commit = p; + return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); +} +#endif + +#ifndef LFS_READONLY +static bool lfs_dir_needsrelocation(lfs_t *lfs, lfs_mdir_t *dir) { + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless + return (lfs->cfg->block_cycles > 0 + && ((dir->rev + 1) % ((lfs->cfg->block_cycles+1)|1) == 0)); +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + // save some state in case block is bad + bool relocated = false; + bool tired = lfs_dir_needsrelocation(lfs, dir); + + // increment revision count + dir->rev += 1; + + // do not proactively relocate blocks during migrations, this + // can cause a number of failure states such: clobbering the + // v1 superblock if we relocate root, and invalidating directory + // pointers if we relocate the head of a directory. On top of + // this, relocations increase the overall complexity of + // lfs_migration, which is already a delicate operation. +#ifdef LFS_MIGRATE + if (lfs->lfs1) { + tired = false; + } +#endif + + if (tired && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) != 0) { + // we're writing too much, time to relocate + goto relocate; + } + + // begin loop to commit compaction to blocks until a compact sticks + while (true) { + { + // setup commit state + struct lfs_commit commit = { + .block = dir->pair[1], + .off = 0, + .ptag = 0xffffffff, + .crc = 0xffffffff, + + .begin = 0, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + }; + + // erase block to write to + int err = lfs_bd_erase(lfs, dir->pair[1]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // write out header + dir->rev = lfs_tole32(dir->rev); + err = lfs_dir_commitprog(lfs, &commit, + &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // traverse the directory, this time writing out all unique tags + err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // commit tail, which may be new after last size check + if (!lfs_pair_isnull(dir->tail)) { + lfs_pair_tole32(dir->tail); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // complete commit with crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful compaction, swap dir pair to indicate most recent + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + lfs_pair_swap(dir->pair); + dir->count = end - begin; + dir->off = commit.off; + dir->etag = commit.ptag; + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; + } + } + break; + +relocate: + // commit was corrupted, drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); + } + + // can't relocate superblock, filesystem is now frozen + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); + return LFS_ERR_NOSPC; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[1]); + if (err && (err != LFS_ERR_NOSPC || !tired)) { + return err; + } + + tired = false; + continue; + } + + return relocated ? LFS_OK_RELOCATED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + while (true) { + // find size of first split, we do this by halving the split until + // the metadata is guaranteed to fit + // + // Note that this isn't a true binary search, we never increase the + // split size. This may result in poorly distributed metadata but isn't + // worth the extra code size or performance hit to fix. + lfs_size_t split = begin; + while (end - split > 1) { + lfs_size_t size = 0; + int err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + split, end, -split, + lfs_dir_commit_size, &size); + if (err) { + return err; + } + + // space is complicated, we need room for: + // + // - tail: 4+2*4 = 12 bytes + // - gstate: 4+3*4 = 16 bytes + // - move delete: 4 = 4 bytes + // - crc: 4+4 = 8 bytes + // total = 40 bytes + // + // And we cap at half a block to avoid degenerate cases with + // nearly-full metadata blocks. + // + lfs_size_t metadata_max = (lfs->cfg->metadata_max) + ? lfs->cfg->metadata_max + : lfs->cfg->block_size; + if (end - split < 0xff + && size <= lfs_min( + metadata_max - 40, + lfs_alignup( + metadata_max/2, + lfs->cfg->prog_size))) { + break; + } + + split = split + ((end - split) / 2); + } + + if (split == begin) { + // no split needed + break; + } + + // split into two metadata pairs and continue + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, split, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + if (err) { + // we can't allocate a new block, try to compact with degraded + // performance + LFS_WARN("Unable to split {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + break; + } else { + end = split; + } + } + + if (lfs_dir_needsrelocation(lfs, dir) + && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + // oh no! we're writing too much to the superblock, + // should we expand? + lfs_ssize_t size = lfs_fs_size_(lfs); + if (size < 0) { + return size; + } + + // littlefs cannot reclaim expanded superblocks, so expand cautiously + // + // if our filesystem is more than ~88% full, don't expand, this is + // somewhat arbitrary + if (lfs->block_count - size > lfs->block_count/8) { + LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + if (err) { + // welp, we tried, if we ran out of space there's not much + // we can do, we'll error later if we've become frozen + LFS_WARN("Unable to expand superblock"); + } else { + // duplicate the superblock entry into the new superblock + end = 1; + } + } + } + + return lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end); +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir, + const lfs_block_t pair[2], + const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *pdir) { + int state = 0; + + // calculate changes to the directory + bool hasdelete = false; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { + dir->count += 1; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { + LFS_ASSERT(dir->count > 0); + dir->count -= 1; + hasdelete = true; + } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { + dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; + dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; + dir->split = (lfs_tag_chunk(attrs[i].tag) & 1); + lfs_pair_fromle32(dir->tail); + } + } + + // should we actually drop the directory block? + if (hasdelete && dir->count == 0) { + LFS_ASSERT(pdir); + int err = lfs_fs_pred(lfs, dir->pair, pdir); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err != LFS_ERR_NOENT && pdir->split) { + state = LFS_OK_DROPPED; + goto fixmlist; + } + } + + if (dir->erased && dir->count < 0xff) { + // try to commit + struct lfs_commit commit = { + .block = dir->pair[0], + .off = dir->off, + .ptag = dir->etag, + .crc = 0xffffffff, + + .begin = dir->off, + .end = (lfs->cfg->metadata_max ? + lfs->cfg->metadata_max : lfs->cfg->block_size) - 8, + }; + + // traverse attrs that need to be written out + lfs_pair_tole32(dir->tail); + int err = lfs_dir_traverse(lfs, + dir, dir->off, dir->etag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + + // commit any global diffs if we have any + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + } + + // finalize commit with the crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + return err; + } + + // successful commit, update dir + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + dir->off = commit.off; + dir->etag = commit.ptag; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; + + goto fixmlist; + } + +compact: + // fall back to compaction + lfs_cache_drop(lfs, &lfs->pcache); + + state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount, + dir, 0, dir->count); + if (state < 0) { + return state; + } + + goto fixmlist; + +fixmlist:; + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. + lfs_block_t oldpair[2] = {pair[0], pair[1]}; + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(d->m.pair, oldpair) == 0) { + d->m = *dir; + if (d->m.pair != pair) { + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag) && + d->type != LFS_TYPE_DIR) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } + } + } + } + + while (d->id >= d->m.count && d->m.split) { + // we split and id is on tail now + if (lfs_pair_cmp(d->m.tail, lfs->root) != 0) { + d->id -= d->m.count; + } + int err = lfs_dir_fetch(lfs, &d->m, d->m.tail); + if (err) { + return err; + } + } + } + } + + return state; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + // check for any inline files that aren't RAM backed and + // forcefully evict them, needed for filesystem consistency + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && + f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && + f->ctz.size > lfs->cfg->cache_size) { + int err = lfs_file_outline(lfs, f); + if (err) { + return err; + } + + err = lfs_file_flush(lfs, f); + if (err) { + return err; + } + } + } + + lfs_block_t lpair[2] = {dir->pair[0], dir->pair[1]}; + lfs_mdir_t ldir = *dir; + lfs_mdir_t pdir; + int state = lfs_dir_relocatingcommit(lfs, &ldir, dir->pair, + attrs, attrcount, &pdir); + if (state < 0) { + return state; + } + + // update if we're not in mlist, note we may have already been + // updated if we are in mlist + if (lfs_pair_cmp(dir->pair, lpair) == 0) { + *dir = ldir; + } + + // commit was successful, but may require other changes in the + // filesystem, these would normally be tail recursive, but we have + // flattened them here avoid unbounded stack usage + + // need to drop? + if (state == LFS_OK_DROPPED) { + // steal state + int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail, note that this can't create a recursive drop + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(dir->tail); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail}), + NULL); + lfs_pair_fromle32(dir->tail); + if (state < 0) { + return state; + } + + ldir = pdir; + } + + // need to relocate? + bool orphans = false; + while (state == LFS_OK_RELOCATED) { + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lpair[0], lpair[1], ldir.pair[0], ldir.pair[1]); + state = 0; + + // update internal root + if (lfs_pair_cmp(lpair, lfs->root) == 0) { + lfs->root[0] = ldir.pair[0]; + lfs->root[1] = ldir.pair[1]; + } + + // update internally tracked dirs + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(lpair, d->m.pair) == 0) { + d->m.pair[0] = ldir.pair[0]; + d->m.pair[1] = ldir.pair[1]; + } + + if (d->type == LFS_TYPE_DIR && + lfs_pair_cmp(lpair, ((lfs_dir_t*)d)->head) == 0) { + ((lfs_dir_t*)d)->head[0] = ldir.pair[0]; + ((lfs_dir_t*)d)->head[1] = ldir.pair[1]; + } + } + + // find parent + lfs_stag_t tag = lfs_fs_parent(lfs, lpair, &pdir); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + bool hasparent = (tag != LFS_ERR_NOENT); + if (tag != LFS_ERR_NOENT) { + // note that if we have a parent, we must have a pred, so this will + // always create an orphan + int err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + + lfs_block_t ppair[2] = {pdir.pair[0], pdir.pair[1]}; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, ppair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {tag, ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + if (state == LFS_OK_RELOCATED) { + lpair[0] = ppair[0]; + lpair[1] = ppair[1]; + ldir = pdir; + orphans = true; + continue; + } + } + + // find pred + int err = lfs_fs_pred(lfs, lpair, &pdir); + if (err && err != LFS_ERR_NOENT) { + return err; + } + LFS_ASSERT(!(hasparent && err == LFS_ERR_NOENT)); + + // if we can't find dir, it must be new + if (err != LFS_ERR_NOENT) { + if (lfs_gstate_hasorphans(&lfs->gstate)) { + // next step, clean up orphans + err = lfs_fs_preporphans(lfs, -(int8_t)hasparent); + if (err) { + return err; + } + } + + // fix pending move in this pair? this looks like an optimization + // but is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + // replace bad pair, either we clean up desync, or no desync occured + lpair[0] = pdir.pair[0]; + lpair[1] = pdir.pair[1]; + lfs_pair_tole32(ldir.pair); + state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_TAIL + pdir.split, 0x3ff, 8), + ldir.pair}), + NULL); + lfs_pair_fromle32(ldir.pair); + if (state < 0) { + return state; + } + + ldir = pdir; + } + } + + return orphans ? LFS_OK_ORPHANED : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + int orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount); + if (orphans < 0) { + return orphans; + } + + if (orphans) { + // make sure we've removed all orphans, this is a noop if there + // are none, but if we had nested blocks failures we may have + // created some + int err = lfs_fs_deorphan(lfs, false); + if (err) { + return err; + } + } + + return 0; +} +#endif + + +/// Top level directory operations /// +#ifndef LFS_READONLY +static int lfs_mkdir_(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + struct lfs_mlist cwd; + cwd.next = lfs->mlist; + uint16_t id; + err = lfs_dir_find(lfs, &cwd.m, &path, &id); + if (!(err == LFS_ERR_NOENT && lfs_path_islast(path))) { + return (err < 0) ? err : LFS_ERR_EXIST; + } + + // check that name fits + lfs_size_t nlen = lfs_path_namelen(path); + if (nlen > lfs->name_max) { + return LFS_ERR_NAMETOOLONG; + } + + // build up new directory + lfs_alloc_ckpoint(lfs); + lfs_mdir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + + // find end of list + lfs_mdir_t pred = cwd.m; + while (pred.split) { + err = lfs_dir_fetch(lfs, &pred, pred.tail); + if (err) { + return err; + } + } + + // setup dir + lfs_pair_tole32(pred.tail); + err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); + lfs_pair_fromle32(pred.tail); + if (err) { + return err; + } + + // current block not end of list? + if (cwd.m.split) { + // update tails, this creates a desync + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + lfs->mlist = cwd.next; + return err; + } + + lfs->mlist = cwd.next; + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + } + + // now insert into our parent block + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + return err; + } + + return 0; +} +#endif + +static int lfs_dir_open_(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); + if (tag < 0) { + return tag; + } + + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + lfs_block_t pair[2]; + if (lfs_tag_id(tag) == 0x3ff) { + // handle root dir separately + pair[0] = lfs->root[0]; + pair[1] = lfs->root[1]; + } else { + // get dir pair from parent + lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + return res; + } + lfs_pair_fromle32(pair); + } + + // fetch first pair + int err = lfs_dir_fetch(lfs, &dir->m, pair); + if (err) { + return err; + } + + // setup entry + dir->head[0] = dir->m.pair[0]; + dir->head[1] = dir->m.pair[1]; + dir->id = 0; + dir->pos = 0; + + // add to list of mdirs + dir->type = LFS_TYPE_DIR; + lfs_mlist_append(lfs, (struct lfs_mlist *)dir); + + return 0; +} + +static int lfs_dir_close_(lfs_t *lfs, lfs_dir_t *dir) { + // remove from list of mdirs + lfs_mlist_remove(lfs, (struct lfs_mlist *)dir); + + return 0; +} + +static int lfs_dir_read_(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == 0) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return true; + } else if (dir->pos == 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return true; + } + + while (true) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + return false; + } + + int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + return err; + } + + dir->id = 0; + } + + int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + dir->id += 1; + if (err != LFS_ERR_NOENT) { + break; + } + } + + dir->pos += 1; + return true; +} + +static int lfs_dir_seek_(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind_(lfs, dir); + if (err) { + return err; + } + + // first two for ./.. + dir->pos = lfs_min(2, off); + off -= dir->pos; + + // skip superblock entry + dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); + + while (off > 0) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + return err; + } + + dir->id = 0; + } + + int diff = lfs_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; + } + + return 0; +} + +static lfs_soff_t lfs_dir_tell_(lfs_t *lfs, lfs_dir_t *dir) { + (void)lfs; + return dir->pos; +} + +static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, &dir->m, dir->head); + if (err) { + return err; + } + + dir->id = 0; + dir->pos = 0; + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = LFS_BLOCK_NULL; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_bd_read(lfs, + pcache, rcache, sizeof(head), + head, 4*skip, &head, sizeof(head)); + head = lfs_fromle32(head); + if (err) { + return err; + } + + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +#ifndef LFS_READONLY +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; + + // just copy out the last block if it is incomplete + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { + uint8_t data; + err = lfs_bd_read(lfs, + NULL, rcache, noff-i, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_bd_prog(lfs, + pcache, rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = noff; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + lfs_block_t nhead = head; + for (lfs_off_t i = 0; i < skips; i++) { + nhead = lfs_tole32(nhead); + err = lfs_bd_prog(lfs, pcache, rcache, true, + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_bd_read(lfs, + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); + if (err) { + return err; + } + } + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, pcache); + } +} +#endif + +static int lfs_ctz_traverse(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_bd_read(lfs, + pcache, rcache, count*sizeof(head), + head, 0, &heads, count*sizeof(head)); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { +#ifndef LFS_READONLY + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + } +#else + LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); +#endif + + // setup simple file details + int err; + file->cfg = cfg; + file->flags = flags; + file->pos = 0; + file->off = 0; + file->cache.buffer = NULL; + + // allocate entry for file if it doesn't exist + lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); + if (tag < 0 && !(tag == LFS_ERR_NOENT && lfs_path_islast(path))) { + err = tag; + goto cleanup; + } + + // get id, add to list of mdirs to catch update changes + file->type = LFS_TYPE_REG; + lfs_mlist_append(lfs, (struct lfs_mlist *)file); + +#ifdef LFS_READONLY + if (tag == LFS_ERR_NOENT) { + err = LFS_ERR_NOENT; + goto cleanup; +#else + if (tag == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + err = LFS_ERR_NOENT; + goto cleanup; + } + + // don't allow trailing slashes + if (lfs_path_isdir(path)) { + err = LFS_ERR_NOTDIR; + goto cleanup; + } + + // check that name fits + lfs_size_t nlen = lfs_path_namelen(path); + if (nlen > lfs->name_max) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + // get next slot and create entry to remember name + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + + // it may happen that the file name doesn't fit in the metadata blocks, e.g., a 256 byte file name will + // not fit in a 128 byte block. + err = (err == LFS_ERR_NOSPC) ? LFS_ERR_NAMETOOLONG : err; + if (err) { + goto cleanup; + } + + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + } else if (flags & LFS_O_EXCL) { + err = LFS_ERR_EXIST; + goto cleanup; +#endif + } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { + err = LFS_ERR_ISDIR; + goto cleanup; +#ifndef LFS_READONLY + } else if (flags & LFS_O_TRUNC) { + // truncate if requested + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); + file->flags |= LFS_F_DIRTY; +#endif + } else { + // try to load what's on disk, if it's inlined we'll fix it later + tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_ctz_fromle32(&file->ctz); + } + + // fetch attrs + for (unsigned i = 0; i < file->cfg->attr_count; i++) { + // if opened for read / read-write operations + if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, + file->id, file->cfg->attrs[i].size), + file->cfg->attrs[i].buffer); + if (res < 0 && res != LFS_ERR_NOENT) { + err = res; + goto cleanup; + } + } + +#ifndef LFS_READONLY + // if opened for write / read-write operations + if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { + if (file->cfg->attrs[i].size > lfs->attr_max) { + err = LFS_ERR_NOSPC; + goto cleanup; + } + + file->flags |= LFS_F_DIRTY; + } +#endif + } + + // allocate buffer if needed + if (file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!file->cache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leak + lfs_cache_zero(lfs, &file->cache); + + if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + // load inline files + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = lfs_tag_size(tag); + file->flags |= LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + + // don't always read (may be new/trunc file) + if (file->ctz.size > 0) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, + lfs_min(file->cache.size, 0x3fe)), + file->cache.buffer); + if (res < 0) { + err = res; + goto cleanup; + } + } + } + + return 0; + +cleanup: + // clean up lingering resources +#ifndef LFS_READONLY + file->flags |= LFS_F_ERRED; +#endif + lfs_file_close_(lfs, file); + return err; +} + +#ifndef LFS_NO_MALLOC +static int lfs_file_open_(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + static const struct lfs_file_config defaults = {0}; + int err = lfs_file_opencfg_(lfs, file, path, flags, &defaults); + return err; +} +#endif + +static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file) { +#ifndef LFS_READONLY + int err = lfs_file_sync_(lfs, file); +#else + int err = 0; +#endif + + // remove from list of mdirs + lfs_mlist_remove(lfs, (struct lfs_mlist*)file); + + // clean up memory + if (!file->cfg->buffer) { + lfs_free(file->cache.buffer); + } + + return err; +} + + +#ifndef LFS_READONLY +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { + while (true) { + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + if (file->flags & LFS_F_INLINE) { + err = lfs_dir_getread(lfs, &file->m, + // note we evict inline files before they can be dirty + NULL, &file->cache, file->off-i, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + i, &data, 1); + if (err) { + return err; + } + } else { + err = lfs_bd_read(lfs, + &file->cache, &lfs->rcache, file->off-i, + file->block, i, &data, 1); + if (err) { + return err; + } + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + file->cache.size = lfs->pcache.size; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + file->flags |= LFS_F_WRITING; + return 0; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} +#endif + +#ifndef LFS_READONLY +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { + file->off = file->pos; + lfs_alloc_ckpoint(lfs); + int err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + + file->flags &= ~LFS_F_INLINE; + return 0; +} +#endif + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_READING) { + if (!(file->flags & LFS_F_INLINE)) { + lfs_cache_drop(lfs, &file->cache); + } + file->flags &= ~LFS_F_READING; + } + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + if (!(file->flags & LFS_F_INLINE)) { + // copy over anything after current branch + lfs_file_t orig = { + .ctz.head = file->ctz.head, + .ctz.size = file->ctz.size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->ctz.size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_flushedread(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_flushedwrite(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != LFS_BLOCK_NULL) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + } else { + file->pos = lfs_max(file->pos, file->ctz.size); + } + + // actual file updates + file->ctz.head = file->block; + file->ctz.size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } +#endif + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + return 0; + } + + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // before we commit metadata, we need sync the disk to make sure + // data writes don't complete after metadata writes + if (!(file->flags & LFS_F_INLINE)) { + err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + } + + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); + } + + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} +#endif + +static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->pos >= file->ctz.size) { + // eof if past end + return 0; + } + + size = lfs_min(size, file->ctz.size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + if (file->flags & LFS_F_INLINE) { + int err = lfs_dir_getread(lfs, &file->m, + NULL, &file->cache, lfs->cfg->block_size, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + file->off, data, diff); + if (err) { + return err; + } + } else { + int err = lfs_bd_read(lfs, + NULL, &file->cache, lfs->cfg->block_size, + file->block, file->off, data, diff); + if (err) { + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } +#endif + + return lfs_file_flushedread(lfs, file, buffer, size); +} + + +#ifndef LFS_READONLY +static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & LFS_F_INLINE) && + lfs_max(file->pos+nsize, file->ctz.size) > lfs->inline_max) { + // inline file doesn't fit anymore + int err = lfs_file_outline(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos-1, &file->block, &(lfs_off_t){0}); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ckpoint(lfs); + int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ckpoint(lfs); + } + + return size; +} + +static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { + file->pos = file->ctz.size; + } + + if (file->pos + size > lfs->file_max) { + // Larger than file limit? + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->ctz.size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_flushedwrite(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + } + + lfs_ssize_t nsize = lfs_file_flushedwrite(lfs, file, buffer, size); + if (nsize < 0) { + return nsize; + } + + file->flags &= ~LFS_F_ERRED; + return nsize; +} +#endif + +static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // find new pos + // + // fortunately for us, littlefs is limited to 31-bit file sizes, so we + // don't have to worry too much about integer overflow + lfs_off_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + npos = file->pos + (lfs_off_t)off; + } else if (whence == LFS_SEEK_END) { + npos = (lfs_off_t)lfs_file_size_(lfs, file) + (lfs_off_t)off; + } + + if (npos > lfs->file_max) { + // file position out of range + return LFS_ERR_INVAL; + } + + if (file->pos == npos) { + // noop - position has not changed + return npos; + } + + // if we're only reading and our new offset is still in the file's cache + // we can avoid flushing and needing to reread the data + if ((file->flags & LFS_F_READING) + && file->off != lfs->cfg->block_size) { + int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos}); + lfs_off_t noff = npos; + int nindex = lfs_ctz_index(lfs, &noff); + if (oindex == nindex + && noff >= file->cache.off + && noff < file->cache.off + file->cache.size) { + file->pos = npos; + file->off = noff; + return npos; + } + } + + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // update pos + file->pos = npos; + return npos; +} + +#ifndef LFS_READONLY +static int lfs_file_truncate_(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + + if (size > LFS_FILE_MAX) { + return LFS_ERR_INVAL; + } + + lfs_off_t pos = file->pos; + lfs_off_t oldsize = lfs_file_size_(lfs, file); + if (size < oldsize) { + // revert to inline file? + if (size <= lfs->inline_max) { + // flush+seek to head + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + // read our data into rcache temporarily + lfs_cache_drop(lfs, &lfs->rcache); + res = lfs_file_flushedread(lfs, file, + lfs->rcache.buffer, size); + if (res < 0) { + return (int)res; + } + + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + memcpy(file->cache.buffer, lfs->rcache.buffer, size); + + } else { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size-1, &file->block, &(lfs_off_t){0}); + if (err) { + return err; + } + + // need to set pos/block/off consistently so seeking back to + // the old position does not get confused + file->pos = size; + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING; + } + } else if (size > oldsize) { + // flush+seek if not already at end + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_END); + if (res < 0) { + return (int)res; + } + + // fill with zeros + while (file->pos < size) { + res = lfs_file_write_(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return (int)res; + } + } + } + + // restore pos + lfs_soff_t res = lfs_file_seek_(lfs, file, pos, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + return 0; +} +#endif + +static lfs_soff_t lfs_file_tell_(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + return file->pos; +} + +static int lfs_file_rewind_(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return (int)res; + } + + return 0; +} + +static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + +#ifndef LFS_READONLY + if (file->flags & LFS_F_WRITING) { + return lfs_max(file->pos, file->ctz.size); + } +#endif + + return file->ctz.size; +} + + +/// General fs operations /// +static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return (int)tag; + } + + // only allow trailing slashes on dirs + if (strchr(path, '/') != NULL + && lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); +} + +#ifndef LFS_READONLY +static int lfs_remove_(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { + return (tag < 0) ? (int)tag : LFS_ERR_INVAL; + } + + struct lfs_mlist dir; + dir.next = lfs->mlist; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + return (int)res; + } + lfs_pair_fromle32(pair); + + err = lfs_dir_fetch(lfs, &dir.m, pair); + if (err) { + return err; + } + + if (dir.m.count > 0 || dir.m.split) { + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; + } + + // delete the entry + err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); + if (err) { + lfs->mlist = dir.next; + return err; + } + + lfs->mlist = dir.next; + if (lfs_gstate_hasorphans(&lfs->gstate)) { + LFS_ASSERT(lfs_tag_type3(tag) == LFS_TYPE_DIR); + + // fix orphan + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); + if (err) { + return err; + } + + err = lfs_dir_drop(lfs, &cwd, &dir.m); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) { + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // find old entry + lfs_mdir_t oldcwd; + lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); + if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { + return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; + } + + // find new entry + lfs_mdir_t newcwd; + uint16_t newid; + lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); + if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && + !(prevtag == LFS_ERR_NOENT && lfs_path_islast(newpath))) { + return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; + } + + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; + if (prevtag == LFS_ERR_NOENT) { + // if we're a file, don't allow trailing slashes + if (lfs_path_isdir(newpath) + && lfs_tag_type3(oldtag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + // check that name fits + lfs_size_t nlen = lfs_path_namelen(newpath); + if (nlen > lfs->name_max) { + return LFS_ERR_NAMETOOLONG; + } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } + } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { + return (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) + ? LFS_ERR_ISDIR + : LFS_ERR_NOTDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + return 0; + } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t prevpair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); + if (res < 0) { + return (int)res; + } + lfs_pair_fromle32(prevpair); + + // must be empty before removal + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); + if (err) { + return err; + } + + if (prevdir.m.count > 0 || prevdir.m.split) { + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + err = lfs_fs_preporphans(lfs, +1); + if (err) { + return err; + } + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; + } + + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } + + // move over all attributes + err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, + {LFS_MKTAG(lfs_tag_type3(oldtag), + newid, lfs_path_namelen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + return err; + } + + // let commit clean up after move (if we're different! otherwise move + // logic already fixed it for us) + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + return err; + } + } + + lfs->mlist = prevdir.next; + if (lfs_gstate_hasorphans(&lfs->gstate)) { + LFS_ASSERT(prevtag != LFS_ERR_NOENT + && lfs_tag_type3(prevtag) == LFS_TYPE_DIR); + + // fix orphan + err = lfs_fs_preporphans(lfs, -1); + if (err) { + return err; + } + + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); + if (err) { + return err; + } + + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); + if (err) { + return err; + } + } + + return 0; +} +#endif + +static lfs_ssize_t lfs_getattr_(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + type, + id, lfs_min(size, lfs->attr_max)), + buffer); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + return LFS_ERR_NOATTR; + } + + return tag; + } + + return lfs_tag_size(tag); +} + +#ifndef LFS_READONLY +static int lfs_commitattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); +} +#endif + +#ifndef LFS_READONLY +static int lfs_setattr_(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + if (size > lfs->attr_max) { + return LFS_ERR_NOSPC; + } + + return lfs_commitattr(lfs, path, type, buffer, size); +} +#endif + +#ifndef LFS_READONLY +static int lfs_removeattr_(lfs_t *lfs, const char *path, uint8_t type) { + return lfs_commitattr(lfs, path, type, NULL, 0x3ff); +} +#endif + + +/// Filesystem operations /// + +// compile time checks, see lfs.h for why these limits exist +#if LFS_NAME_MAX > 1022 +#error "Invalid LFS_NAME_MAX, must be <= 1022" +#endif + +#if LFS_FILE_MAX > 2147483647 +#error "Invalid LFS_FILE_MAX, must be <= 2147483647" +#endif + +#if LFS_ATTR_MAX > 1022 +#error "Invalid LFS_ATTR_MAX, must be <= 1022" +#endif + +// common filesystem initialization +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + lfs->block_count = cfg->block_count; // May be 0 + int err = 0; + +#ifdef LFS_MULTIVERSION + // this driver only supports minor version < current minor version + LFS_ASSERT(!lfs->cfg->disk_version || ( + (0xffff & (lfs->cfg->disk_version >> 16)) + == LFS_DISK_VERSION_MAJOR + && (0xffff & (lfs->cfg->disk_version >> 0)) + <= LFS_DISK_VERSION_MINOR)); +#endif + + // check that bool is a truthy-preserving type + // + // note the most common reason for this failure is a before-c99 compiler, + // which littlefs currently does not support + LFS_ASSERT((bool)0x80000000); + + // check that the required io functions are provided + LFS_ASSERT(lfs->cfg->read != NULL); +#ifndef LFS_READONLY + LFS_ASSERT(lfs->cfg->prog != NULL); + LFS_ASSERT(lfs->cfg->erase != NULL); + LFS_ASSERT(lfs->cfg->sync != NULL); +#endif + + // validate that the lfs-cfg sizes were initiated properly before + // performing any arithmetic logics with them + LFS_ASSERT(lfs->cfg->read_size != 0); + LFS_ASSERT(lfs->cfg->prog_size != 0); + LFS_ASSERT(lfs->cfg->cache_size != 0); + + // check that block size is a multiple of cache size is a multiple + // of prog and read sizes + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); + + // check that the block size is large enough to fit all ctz pointers + LFS_ASSERT(lfs->cfg->block_size >= 128); + // this is the exact calculation for all ctz pointers, if this fails + // and the simpler assert above does not, math must be broken + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // block_cycles = 0 is no longer supported. + // + // block_cycles is the number of erase cycles before littlefs evicts + // metadata logs as a part of wear leveling. Suggested values are in the + // range of 100-1000, or set block_cycles to -1 to disable block-level + // wear-leveling. + LFS_ASSERT(lfs->cfg->block_cycles != 0); + + // check that compact_thresh makes sense + // + // metadata can't be compacted below block_size/2, and metadata can't + // exceed a block_size + LFS_ASSERT(lfs->cfg->compact_thresh == 0 + || lfs->cfg->compact_thresh >= lfs->cfg->block_size/2); + LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1 + || lfs->cfg->compact_thresh <= lfs->cfg->block_size); + + // check that metadata_max is a multiple of read_size and prog_size, + // and a factor of the block_size + LFS_ASSERT(!lfs->cfg->metadata_max + || lfs->cfg->metadata_max % lfs->cfg->read_size == 0); + LFS_ASSERT(!lfs->cfg->metadata_max + || lfs->cfg->metadata_max % lfs->cfg->prog_size == 0); + LFS_ASSERT(!lfs->cfg->metadata_max + || lfs->cfg->block_size % lfs->cfg->metadata_max == 0); + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->rcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->pcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->rcache); + lfs_cache_zero(lfs, &lfs->pcache); + + // setup lookahead buffer, note mount finishes initializing this after + // we establish a decent pseudo-random seed + LFS_ASSERT(lfs->cfg->lookahead_size > 0); + if (lfs->cfg->lookahead_buffer) { + lfs->lookahead.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->lookahead.buffer = lfs_malloc(lfs->cfg->lookahead_size); + if (!lfs->lookahead.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); + lfs->name_max = lfs->cfg->name_max; + if (!lfs->name_max) { + lfs->name_max = LFS_NAME_MAX; + } + + LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); + lfs->file_max = lfs->cfg->file_max; + if (!lfs->file_max) { + lfs->file_max = LFS_FILE_MAX; + } + + LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); + lfs->attr_max = lfs->cfg->attr_max; + if (!lfs->attr_max) { + lfs->attr_max = LFS_ATTR_MAX; + } + + LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); + + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= lfs->cfg->cache_size); + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= lfs->attr_max); + LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1 + || lfs->cfg->inline_max <= ((lfs->cfg->metadata_max) + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/8); + lfs->inline_max = lfs->cfg->inline_max; + if (lfs->inline_max == (lfs_size_t)-1) { + lfs->inline_max = 0; + } else if (lfs->inline_max == 0) { + lfs->inline_max = lfs_min( + lfs->cfg->cache_size, + lfs_min( + lfs->attr_max, + ((lfs->cfg->metadata_max) + ? lfs->cfg->metadata_max + : lfs->cfg->block_size)/8)); + } + + // setup default state + lfs->root[0] = LFS_BLOCK_NULL; + lfs->root[1] = LFS_BLOCK_NULL; + lfs->mlist = NULL; + lfs->seed = 0; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; +#ifdef LFS_MIGRATE + lfs->lfs1 = NULL; +#endif + + return 0; + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->lookahead.buffer); + } + + return 0; +} + + + +#ifndef LFS_READONLY +static int lfs_format_(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + LFS_ASSERT(cfg->block_count != 0); + + // create free lookahead + memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + lfs->lookahead.start = 0; + lfs->lookahead.size = lfs_min(8*lfs->cfg->lookahead_size, + lfs->block_count); + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); + + // create root dir + lfs_mdir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + // write one superblock + lfs_superblock_t superblock = { + .version = lfs_fs_disk_version(lfs), + .block_size = lfs->cfg->block_size, + .block_count = lfs->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting any + // older version of littlefs that may live on disk + root.erased = false; + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs_deinit(lfs); + return err; + +} +#endif + +struct lfs_tortoise_t { + lfs_block_t pair[2]; + lfs_size_t i; + lfs_size_t period; +}; + +static int lfs_tortoise_detectcycles( + const lfs_mdir_t *dir, struct lfs_tortoise_t *tortoise) { + // detect cycles with Brent's algorithm + if (lfs_pair_issync(dir->tail, tortoise->pair)) { + LFS_WARN("Cycle detected in tail list"); + return LFS_ERR_CORRUPT; + } + if (tortoise->i == tortoise->period) { + tortoise->pair[0] = dir->tail[0]; + tortoise->pair[1] = dir->tail[1]; + tortoise->i = 0; + tortoise->period *= 2; + } + tortoise->i += 1; + + return LFS_ERR_OK; +} + +static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // scan directory blocks for superblock and any global updates + lfs_mdir_t dir = {.tail = {0, 1}}; + struct lfs_tortoise_t tortoise = { + .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + while (!lfs_pair_isnull(dir.tail)) { + err = lfs_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { + goto cleanup; + } + + // fetch next block in tail list + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), + NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, "littlefs", 8}); + if (tag < 0) { + err = tag; + goto cleanup; + } + + // has superblock? + if (tag && !lfs_tag_isdelete(tag)) { + // update root + lfs->root[0] = dir.pair[0]; + lfs->root[1] = dir.pair[1]; + + // grab superblock + lfs_superblock_t superblock; + tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_superblock_fromle32(&superblock); + + // check version + uint16_t major_version = (0xffff & (superblock.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.version >> 0)); + if (major_version != lfs_fs_disk_version_major(lfs) + || minor_version > lfs_fs_disk_version_minor(lfs)) { + LFS_ERROR("Invalid version " + "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // found older minor version? set an in-device only bit in the + // gstate so we know we need to rewrite the superblock before + // the first write + bool needssuperblock = false; + if (minor_version < lfs_fs_disk_version_minor(lfs)) { + LFS_DEBUG("Found older minor version " + "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, + major_version, + minor_version, + lfs_fs_disk_version_major(lfs), + lfs_fs_disk_version_minor(lfs)); + needssuperblock = true; + } + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs_fs_prepsuperblock(lfs, needssuperblock); + + // check superblock configuration + if (superblock.name_max) { + if (superblock.name_max > lfs->name_max) { + LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", + superblock.name_max, lfs->name_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->name_max = superblock.name_max; + } + + if (superblock.file_max) { + if (superblock.file_max > lfs->file_max) { + LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", + superblock.file_max, lfs->file_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->file_max = superblock.file_max; + } + + if (superblock.attr_max) { + if (superblock.attr_max > lfs->attr_max) { + LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", + superblock.attr_max, lfs->attr_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->attr_max = superblock.attr_max; + + // we also need to update inline_max in case attr_max changed + lfs->inline_max = lfs_min(lfs->inline_max, lfs->attr_max); + } + + // this is where we get the block_count from disk if block_count=0 + if (lfs->cfg->block_count + && superblock.block_count != lfs->cfg->block_count) { + LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")", + superblock.block_count, lfs->cfg->block_count); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->block_count = superblock.block_count; + + if (superblock.block_size != lfs->cfg->block_size) { + LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")", + superblock.block_size, lfs->cfg->block_size); + err = LFS_ERR_INVAL; + goto cleanup; + } + } + + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); + if (err) { + goto cleanup; + } + } + + // update littlefs with gstate + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, + lfs->gstate.pair[0], + lfs->gstate.pair[1]); + } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; + + // setup free lookahead, to distribute allocations uniformly across + // boots, we start the allocator at a random location + lfs->lookahead.start = lfs->seed % lfs->block_count; + lfs_alloc_drop(lfs); + + return 0; + +cleanup: + lfs_unmount_(lfs); + return err; +} + +static int lfs_unmount_(lfs_t *lfs) { + return lfs_deinit(lfs); +} + + +/// Filesystem filesystem operations /// +static int lfs_fs_stat_(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + fsinfo->disk_version = lfs_fs_disk_version(lfs); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs_mdir_t dir; + int err = lfs_dir_fetch(lfs, &dir, lfs->root); + if (err) { + return err; + } + + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // filesystem geometry + fsinfo->block_size = lfs->cfg->block_size; + fsinfo->block_count = lfs->block_count; + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs->name_max; + fsinfo->file_max = lfs->file_max; + fsinfo->attr_max = lfs->attr_max; + + return 0; +} + +int lfs_fs_traverse_(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { + // iterate over metadata pairs + lfs_mdir_t dir = {.tail = {0, 1}}; + +#ifdef LFS_MIGRATE + // also consider v1 blocks during migration + if (lfs->lfs1) { + int err = lfs1_traverse(lfs, cb, data); + if (err) { + return err; + } + + dir.tail[0] = lfs->root[0]; + dir.tail[1] = lfs->root[1]; + } +#endif + + struct lfs_tortoise_t tortoise = { + .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS_ERR_OK; + while (!lfs_pair_isnull(dir.tail)) { + err = lfs_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { + return LFS_ERR_CORRUPT; + } + + for (int i = 0; i < 2; i++) { + int err = cb(data, dir.tail[i]); + if (err) { + return err; + } + } + + // iterate through ids in directory + int err = lfs_dir_fetch(lfs, &dir, dir.tail); + if (err) { + return err; + } + + for (uint16_t id = 0; id < dir.count; id++) { + struct lfs_ctz ctz; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + continue; + } + return tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + ctz.head, ctz.size, cb, data); + if (err) { + return err; + } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } + } + } + } + +#ifndef LFS_READONLY + // iterate over any open files + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (f->type != LFS_TYPE_REG) { + continue; + } + + if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->ctz.head, f->ctz.size, cb, data); + if (err) { + return err; + } + } + + if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } +#endif + + return 0; +} + +#ifndef LFS_READONLY +static int lfs_fs_pred(lfs_t *lfs, + const lfs_block_t pair[2], lfs_mdir_t *pdir) { + // iterate over all directory directory entries + pdir->tail[0] = 0; + pdir->tail[1] = 1; + struct lfs_tortoise_t tortoise = { + .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS_ERR_OK; + while (!lfs_pair_isnull(pdir->tail)) { + err = lfs_tortoise_detectcycles(pdir, &tortoise); + if (err < 0) { + return LFS_ERR_CORRUPT; + } + + if (lfs_pair_cmp(pdir->tail, pair) == 0) { + return 0; + } + + int err = lfs_dir_fetch(lfs, pdir, pdir->tail); + if (err) { + return err; + } + } + + return LFS_ERR_NOENT; +} +#endif + +#ifndef LFS_READONLY +struct lfs_fs_parent_match { + lfs_t *lfs; + const lfs_block_t pair[2]; +}; +#endif + +#ifndef LFS_READONLY +static int lfs_fs_parent_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_fs_parent_match *find = data; + lfs_t *lfs = find->lfs; + const struct lfs_diskoff *disk = buffer; + (void)tag; + + lfs_block_t child[2]; + int err = lfs_bd_read(lfs, + &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + disk->block, disk->off, &child, sizeof(child)); + if (err) { + return err; + } + + lfs_pair_fromle32(child); + return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; +} +#endif + +#ifndef LFS_READONLY +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], + lfs_mdir_t *parent) { + // use fetchmatch with callback to find pairs + parent->tail[0] = 0; + parent->tail[1] = 1; + struct lfs_tortoise_t tortoise = { + .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS_ERR_OK; + while (!lfs_pair_isnull(parent->tail)) { + err = lfs_tortoise_detectcycles(parent, &tortoise); + if (err < 0) { + return err; + } + + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, + LFS_MKTAG(0x7ff, 0, 0x3ff), + LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), + NULL, + lfs_fs_parent_match, &(struct lfs_fs_parent_match){ + lfs, {pair[0], pair[1]}}); + if (tag && tag != LFS_ERR_NOENT) { + return tag; + } + } + + return LFS_ERR_NOENT; +} +#endif + +static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) { + lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200)) + | (uint32_t)needssuperblock << 9; +} + +#ifndef LFS_READONLY +static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0); + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); + + return 0; +} +#endif + +#ifndef LFS_READONLY +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]) { + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_desuperblock(lfs_t *lfs) { + if (!lfs_gstate_needssuperblock(&lfs->gstate)) { + return 0; + } + + LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], + lfs->root[1]); + + lfs_mdir_t root; + int err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + // write a new superblock + lfs_superblock_t superblock = { + .version = lfs_fs_disk_version(lfs), + .block_size = lfs->cfg->block_size, + .block_count = lfs->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + return err; + } + + lfs_fs_prepsuperblock(lfs, false); + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_demove(lfs_t *lfs) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { + return 0; + } + + // Fix bad moves + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); + + // no other gstate is supported at this time, so if we found something else + // something most likely went wrong in gstate calculation + LFS_ASSERT(lfs_tag_type3(lfs->gdisk.tag) == LFS_TYPE_DELETE); + + // fetch and delete the moved entry + lfs_mdir_t movedir; + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); + if (err) { + return err; + } + + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) { + if (!lfs_gstate_hasorphans(&lfs->gstate)) { + return 0; + } + + // Check for orphans in two separate passes: + // - 1 for half-orphans (relocations) + // - 2 for full-orphans (removes/renames) + // + // Two separate passes are needed as half-orphans can contain outdated + // references to full-orphans, effectively hiding them from the deorphan + // search. + // + int pass = 0; + while (pass < 2) { + // Fix any orphans + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; + bool moreorphans = false; + + // iterate over all directory directory entries + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!pdir.split) { + // check if we have a parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (pass == 0 && tag != LFS_ERR_NOENT) { + lfs_block_t pair[2]; + lfs_stag_t state = lfs_dir_get(lfs, &parent, + LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); + if (state < 0) { + return state; + } + lfs_pair_fromle32(pair); + + if (!lfs_pair_issync(pair, pdir.tail)) { + // we have desynced + LFS_DEBUG("Fixing half-orphan " + "{0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); + + // fix pending move in this pair? this looks like an + // optimization but is in fact _required_ since + // relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while fixing orphans " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + pdir.pair[0], pdir.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + lfs_pair_tole32(pair); + state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), + pair})); + lfs_pair_fromle32(pair); + if (state < 0) { + return state; + } + + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } + } + + // note we only check for full orphans if we may have had a + // power-loss, otherwise orphans are created intentionally + // during operations such as lfs_mkdir + if (pass == 1 && tag == LFS_ERR_NOENT && powerloss) { + // we are an orphan + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); + + // steal state + err = lfs_dir_getgstate(lfs, &dir, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(dir.tail); + int state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + dir.split, 0x3ff, 8), + dir.tail})); + lfs_pair_fromle32(dir.tail); + if (state < 0) { + return state; + } + + // did our commit create more orphans? + if (state == LFS_OK_ORPHANED) { + moreorphans = true; + } + + // refetch tail + continue; + } + } + + pdir = dir; + } + + pass = moreorphans ? 0 : pass+1; + } + + // mark orphans as fixed + return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_forceconsistency(lfs_t *lfs) { + int err = lfs_fs_desuperblock(lfs); + if (err) { + return err; + } + + err = lfs_fs_demove(lfs); + if (err) { + return err; + } + + err = lfs_fs_deorphan(lfs, true); + if (err) { + return err; + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_fs_mkconsistent_(lfs_t *lfs) { + // lfs_fs_forceconsistency does most of the work here + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // do we have any pending gstate? + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + if (!lfs_gstate_iszero(&delta)) { + // lfs_dir_commit will implicitly write out any pending gstate + lfs_mdir_t root; + err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} +#endif + +static int lfs_fs_size_count(void *p, lfs_block_t block) { + (void)block; + lfs_size_t *size = p; + *size += 1; + return 0; +} + +static lfs_ssize_t lfs_fs_size_(lfs_t *lfs) { + lfs_size_t size = 0; + int err = lfs_fs_traverse_(lfs, lfs_fs_size_count, &size, false); + if (err) { + return err; + } + + return size; +} + +// explicit garbage collection +#ifndef LFS_READONLY +static int lfs_fs_gc_(lfs_t *lfs) { + // force consistency, even if we're not necessarily going to write, + // because this function is supposed to take care of janitorial work + // isn't it? + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // try to compact metadata pairs, note we can't really accomplish + // anything if compact_thresh doesn't at least leave a prog_size + // available + if (lfs->cfg->compact_thresh + < lfs->cfg->block_size - lfs->cfg->prog_size) { + // iterate over all mdirs + lfs_mdir_t mdir = {.tail = {0, 1}}; + while (!lfs_pair_isnull(mdir.tail)) { + err = lfs_dir_fetch(lfs, &mdir, mdir.tail); + if (err) { + return err; + } + + // not erased? exceeds our compaction threshold? + if (!mdir.erased || ((lfs->cfg->compact_thresh == 0) + ? mdir.off > lfs->cfg->block_size - lfs->cfg->block_size/8 + : mdir.off > lfs->cfg->compact_thresh)) { + // the easiest way to trigger a compaction is to mark + // the mdir as unerased and add an empty commit + mdir.erased = false; + err = lfs_dir_commit(lfs, &mdir, NULL, 0); + if (err) { + return err; + } + } + } + } + + // try to populate the lookahead buffer, unless it's already full + if (lfs->lookahead.size < lfs_min( + 8 * lfs->cfg->lookahead_size, + lfs->block_count)) { + err = lfs_alloc_scan(lfs); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +#ifdef LFS_SHRINKNONRELOCATING +static int lfs_shrink_checkblock(void *data, lfs_block_t block) { + lfs_size_t threshold = *((lfs_size_t*)data); + if (block >= threshold) { + return LFS_ERR_NOTEMPTY; + } + return 0; +} +#endif + +static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) { + int err; + + if (block_count == lfs->block_count) { + return 0; + } + + +#ifndef LFS_SHRINKNONRELOCATING + // shrinking is not supported + LFS_ASSERT(block_count >= lfs->block_count); +#endif +#ifdef LFS_SHRINKNONRELOCATING + if (block_count < lfs->block_count) { + err = lfs_fs_traverse_(lfs, lfs_shrink_checkblock, &block_count, true); + if (err) { + return err; + } + } +#endif + + lfs->block_count = block_count; + + // fetch the root + lfs_mdir_t root; + err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + // update the superblock + lfs_superblock_t superblock; + lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs_superblock_fromle32(&superblock); + + superblock.block_count = lfs->block_count; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } + return 0; +} +#endif + +#ifdef LFS_MIGRATE +////// Migration from littelfs v1 below this ////// + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_VERSION 0x00010007 +#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16)) +#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_DISK_VERSION 0x00010001 +#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16)) +#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0)) + + +/// v1 Definitions /// + +// File types +enum lfs1_type { + LFS1_TYPE_REG = 0x11, + LFS1_TYPE_DIR = 0x22, + LFS1_TYPE_SUPERBLOCK = 0x2e, +}; + +typedef struct lfs1 { + lfs_block_t root[2]; +} lfs1_t; + +typedef struct lfs1_entry { + lfs_off_t off; + + struct lfs1_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs1_entry_t; + +typedef struct lfs1_dir { + struct lfs1_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs1_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs1_dir_t; + +typedef struct lfs1_superblock { + lfs_off_t off; + + struct lfs1_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs1_superblock_t; + + +/// Low-level wrappers v1->v2 /// +static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) { + *crc = lfs_crc(*crc, buffer, size); +} + +static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size, + block, off, buffer, size); +} + +static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs1_bd_read(lfs, block, off+i, &c, 1); + if (err) { + return err; + } + + lfs1_crc(crc, &c, 1); + } + + return 0; +} + + +/// Endian swapping functions /// +static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs1_dir_tole32(struct lfs1_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs1_entry_tole32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + + +///// Metadata pair and directory operations /// +static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs1_dir_fetch(lfs_t *lfs, + lfs1_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs1_disk_dir test; + int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs1_dir_tole32(&test); + lfs1_crc(&crc, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + err = lfs1_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs1_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs1_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs1_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs1_entry_size(entry); + dir->pos += lfs1_entry_size(entry); + return 0; +} + +/// littlefs v1 specific operations /// +int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // iterate over metadata pairs + lfs1_dir_t dir; + lfs1_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs1_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs1_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs1_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs1_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + // we also need to check if we contain a threaded v2 directory + lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + break; + } + + for (int i = 0; i < 2; i++) { + err = cb(data, dir2.pair[i]); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pair_isnull(cwd)) { + break; + } + } + + return 0; +} + +static int lfs1_moved(lfs_t *lfs, const void *e) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // skip superblock + lfs1_dir_t cwd; + int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs1_entry_t entry; + while (!lfs_pair_isnull(cwd.d.tail)) { + err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs1_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +/// Filesystem operations /// +static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, + const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + lfs->lfs1 = lfs1; + lfs->lfs1->root[0] = LFS_BLOCK_NULL; + lfs->lfs1->root[1] = LFS_BLOCK_NULL; + + // setup free lookahead + lfs->lookahead.start = 0; + lfs->lookahead.size = 0; + lfs->lookahead.next = 0; + lfs_alloc_ckpoint(lfs); + + // load superblock + lfs1_dir_t dir; + lfs1_superblock_t superblock; + err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs1_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->lfs1->root[0] = superblock.d.root[0]; + lfs->lfs1->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS1_DISK_VERSION_MAJOR || + minor_version > LFS1_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs1_unmount(lfs_t *lfs) { + return lfs_deinit(lfs); +} + +/// v1 migration /// +static int lfs_migrate_(lfs_t *lfs, const struct lfs_config *cfg) { + struct lfs1 lfs1; + + // Indeterminate filesystem size not allowed for migration. + LFS_ASSERT(cfg->block_count != 0); + + int err = lfs1_mount(lfs, &lfs1, cfg); + if (err) { + return err; + } + + { + // iterate through each directory, copying over entries + // into new directory + lfs1_dir_t dir1; + lfs_mdir_t dir2; + dir1.d.tail[0] = lfs->lfs1->root[0]; + dir1.d.tail[1] = lfs->lfs1->root[1]; + while (!lfs_pair_isnull(dir1.d.tail)) { + // iterate old dir + err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail); + if (err) { + goto cleanup; + } + + // create new dir and bind as temporary pretend root + err = lfs_dir_alloc(lfs, &dir2); + if (err) { + goto cleanup; + } + + dir2.rev = dir1.d.rev; + dir1.head[0] = dir1.pair[0]; + dir1.head[1] = dir1.pair[1]; + lfs->root[0] = dir2.pair[0]; + lfs->root[1] = dir2.pair[1]; + + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + + while (true) { + lfs1_entry_t entry1; + err = lfs1_dir_next(lfs, &dir1, &entry1); + if (err && err != LFS_ERR_NOENT) { + goto cleanup; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // check that entry has not been moved + if (entry1.d.type & 0x80) { + int moved = lfs1_moved(lfs, &entry1.d.u); + if (moved < 0) { + err = moved; + goto cleanup; + } + + if (moved) { + continue; + } + + entry1.d.type &= ~0x80; + } + + // also fetch name + char name[LFS_NAME_MAX+1]; + memset(name, 0, sizeof(name)); + err = lfs1_bd_read(lfs, dir1.pair[0], + entry1.off + 4+entry1.d.elen+entry1.d.alen, + name, entry1.d.nlen); + if (err) { + goto cleanup; + } + + bool isdir = (entry1.d.type == LFS1_TYPE_DIR); + + // create entry in new dir + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + uint16_t id; + err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + err = (err < 0) ? err : LFS_ERR_EXIST; + goto cleanup; + } + + lfs1_entry_tole32(&entry1.d); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); + lfs1_entry_fromle32(&entry1.d); + if (err) { + goto cleanup; + } + } + + if (!lfs_pair_isnull(dir1.d.tail)) { + // find last block and update tail to thread into fs + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + goto cleanup; + } + } + + lfs_pair_tole32(dir2.pair); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); + lfs_pair_fromle32(dir2.pair); + if (err) { + goto cleanup; + } + } + + // Copy over first block to thread into fs. Unfortunately + // if this fails there is not much we can do. + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); + + err = lfs_bd_erase(lfs, dir1.head[1]); + if (err) { + goto cleanup; + } + + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + for (lfs_off_t i = 0; i < dir2.off; i++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dir2.off, + dir2.pair[0], i, &dat, 1); + if (err) { + goto cleanup; + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + dir1.head[1], i, &dat, 1); + if (err) { + goto cleanup; + } + } + + err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true); + if (err) { + goto cleanup; + } + } + + // Create new superblock. This marks a successful migration! + err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + dir2.pair[0] = dir1.pair[0]; + dir2.pair[1] = dir1.pair[1]; + dir2.rev = dir1.d.rev; + dir2.off = sizeof(dir2.rev); + dir2.etag = 0xffffffff; + dir2.count = 0; + dir2.tail[0] = lfs->lfs1->root[0]; + dir2.tail[1] = lfs->lfs1->root[1]; + dir2.erased = false; + dir2.split = true; + + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting v1 + dir2.erased = false; + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs1_unmount(lfs); + return err; +} + +#endif + + +/// Public API wrappers /// + +// Here we can add tracing/thread safety easily + +// Thread-safe wrappers if enabled +#ifdef LFS_THREADSAFE +#define LFS_LOCK(cfg) cfg->lock(cfg) +#define LFS_UNLOCK(cfg) cfg->unlock(cfg) +#else +#define LFS_LOCK(cfg) ((void)cfg, 0) +#define LFS_UNLOCK(cfg) ((void)cfg) +#endif + +// Public API +#ifndef LFS_READONLY +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_format_(lfs, cfg); + + LFS_TRACE("lfs_format -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_mount_(lfs, cfg); + + LFS_TRACE("lfs_mount -> %d", err); + LFS_UNLOCK(cfg); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_unmount(%p)", (void*)lfs); + + err = lfs_unmount_(lfs); + + LFS_TRACE("lfs_unmount -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_remove(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); + + err = lfs_remove_(lfs, path); + + LFS_TRACE("lfs_remove -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); + + err = lfs_rename_(lfs, oldpath, newpath); + + LFS_TRACE("lfs_rename -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); + + err = lfs_stat_(lfs, path, info); + + LFS_TRACE("lfs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + lfs_ssize_t res = lfs_getattr_(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_getattr -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + + err = lfs_setattr_(lfs, path, type, buffer, size); + + LFS_TRACE("lfs_setattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); + + err = lfs_removeattr_(lfs, path, type); + + LFS_TRACE("lfs_removeattr -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_NO_MALLOC +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", + (void*)lfs, (void*)file, path, (unsigned)flags); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_open_(lfs, file, path, flags); + + LFS_TRACE("lfs_file_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs, (void*)file, path, (unsigned)flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_opencfg_(lfs, file, path, flags, cfg); + + LFS_TRACE("lfs_file_opencfg -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_close_(lfs, file); + + LFS_TRACE("lfs_file_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_sync_(lfs, file); + + LFS_TRACE("lfs_file_sync -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_read_(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_read -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_ssize_t res = lfs_file_write_(lfs, file, buffer, size); + + LFS_TRACE("lfs_file_write -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} +#endif + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs, (void*)file, off, whence); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_seek_(lfs, file, off, whence); + + LFS_TRACE("lfs_file_seek -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs, (void*)file, size); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + err = lfs_file_truncate_(lfs, file, size); + + LFS_TRACE("lfs_file_truncate -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_tell_(lfs, file); + + LFS_TRACE("lfs_file_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); + + err = lfs_file_rewind_(lfs, file); + + LFS_TRACE("lfs_file_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + + lfs_soff_t res = lfs_file_size_(lfs, file); + + LFS_TRACE("lfs_file_size -> %"PRIu32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +#ifndef LFS_READONLY +int lfs_mkdir(lfs_t *lfs, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); + + err = lfs_mkdir_(lfs, path); + + LFS_TRACE("lfs_mkdir -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); + LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir)); + + err = lfs_dir_open_(lfs, dir, path); + + LFS_TRACE("lfs_dir_open -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_close_(lfs, dir); + + LFS_TRACE("lfs_dir_close -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_read(%p, %p, %p)", + (void*)lfs, (void*)dir, (void*)info); + + err = lfs_dir_read_(lfs, dir, info); + + LFS_TRACE("lfs_dir_read -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs, (void*)dir, off); + + err = lfs_dir_seek_(lfs, dir, off); + + LFS_TRACE("lfs_dir_seek -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); + + lfs_soff_t res = lfs_dir_tell_(lfs, dir); + + LFS_TRACE("lfs_dir_tell -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); + + err = lfs_dir_rewind_(lfs, dir); + + LFS_TRACE("lfs_dir_rewind -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo); + + err = lfs_fs_stat_(lfs, fsinfo); + + LFS_TRACE("lfs_fs_stat -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); + + lfs_ssize_t res = lfs_fs_size_(lfs); + + LFS_TRACE("lfs_fs_size -> %"PRId32, res); + LFS_UNLOCK(lfs->cfg); + return res; +} + +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + + err = lfs_fs_traverse_(lfs, cb, data, true); + + LFS_TRACE("lfs_fs_traverse -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} + +#ifndef LFS_READONLY +int lfs_fs_mkconsistent(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); + + err = lfs_fs_mkconsistent_(lfs); + + LFS_TRACE("lfs_fs_mkconsistent -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_gc(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); + + err = lfs_fs_gc_(lfs); + + LFS_TRACE("lfs_fs_gc -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifndef LFS_READONLY +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count); + + err = lfs_fs_grow_(lfs, block_count); + + LFS_TRACE("lfs_fs_grow -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + +#ifdef LFS_MIGRATE +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { + int err = LFS_LOCK(cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs_migrate_(lfs, cfg); + + LFS_TRACE("lfs_migrate -> %d", err); + LFS_UNLOCK(cfg); + return err; +} +#endif + diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.h new file mode 100644 index 00000000..215309c5 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs.h @@ -0,0 +1,801 @@ +/* + * The little filesystem + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include "lfs_util.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x0002000b +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00020001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Maximum name size in bytes, may be redefined to reduce the size of the +// info struct. Limited to <= 1022. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Maximum size of a file in bytes, may be redefined to limit to support other +// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#endif + +// Maximum size of custom attributes in bytes, may be redefined, but there is +// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. Stored +// in superblock and must be respected by other littlefs drivers. +#ifndef LFS_ATTR_MAX +#define LFS_ATTR_MAX 1022 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -84, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NOATTR = -61, // No data/attr available + LFS_ERR_NAMETOOLONG = -36, // File name too long +}; + +// File types +enum lfs_type { + // file types + LFS_TYPE_REG = 0x001, + LFS_TYPE_DIR = 0x002, + + // internally used types + LFS_TYPE_SPLICE = 0x400, + LFS_TYPE_NAME = 0x000, + LFS_TYPE_STRUCT = 0x200, + LFS_TYPE_USERATTR = 0x300, + LFS_TYPE_FROM = 0x100, + LFS_TYPE_TAIL = 0x600, + LFS_TYPE_GLOBALS = 0x700, + LFS_TYPE_CRC = 0x500, + + // internally used type specializations + LFS_TYPE_CREATE = 0x401, + LFS_TYPE_DELETE = 0x4ff, + LFS_TYPE_SUPERBLOCK = 0x0ff, + LFS_TYPE_DIRSTRUCT = 0x200, + LFS_TYPE_CTZSTRUCT = 0x202, + LFS_TYPE_INLINESTRUCT = 0x201, + LFS_TYPE_SOFTTAIL = 0x600, + LFS_TYPE_HARDTAIL = 0x601, + LFS_TYPE_MOVESTATE = 0x7ff, + LFS_TYPE_CCRC = 0x500, + LFS_TYPE_FCRC = 0x5ff, + + // internal chip sources + LFS_FROM_NOOP = 0x000, + LFS_FROM_MOVE = 0x101, + LFS_FROM_USERATTRS = 0x102, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only +#ifndef LFS_READONLY + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write +#endif + + // internally used flags +#ifndef LFS_READONLY + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush +#endif + LFS_F_READING = 0x040000, // File has been read since last flush +#ifndef LFS_READONLY + LFS_F_ERRED = 0x080000, // An error occurred during write +#endif + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propagated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propagated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propagated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propagated to the user. + int (*sync)(const struct lfs_config *c); + +#ifdef LFS_THREADSAFE + // Lock the underlying block device. Negative error codes + // are propagated to the user. + int (*lock)(const struct lfs_config *c); + + // Unlock the underlying block device. Negative error codes + // are propagated to the user. + int (*unlock)(const struct lfs_config *c); +#endif + + // Minimum size of a block read in bytes. All read operations will be a + // multiple of this value. + lfs_size_t read_size; + + // Minimum size of a block program in bytes. All program operations will be + // a multiple of this value. + lfs_size_t prog_size; + + // Size of an erasable block in bytes. This does not impact ram consumption + // and may be larger than the physical erase size. However, non-inlined + // files take up at minimum one block. Must be a multiple of the read and + // program sizes. + lfs_size_t block_size; + + // Number of erasable blocks on the device. Defaults to block_count stored + // on disk when zero. + lfs_size_t block_count; + + // Number of erase cycles before littlefs evicts metadata logs and moves + // the metadata to another block. Suggested values are in the + // range 100-1000, with large values having better performance at the cost + // of less consistent wear distribution. + // + // Set to -1 to disable block-level wear-leveling. + int32_t block_cycles; + + // Size of block caches in bytes. Each cache buffers a portion of a block in + // RAM. The littlefs needs a read cache, a program cache, and one additional + // cache per file. Larger caches can improve performance by storing more + // data and reducing the number of disk accesses. Must be a multiple of the + // read and program sizes, and a factor of the block size. + lfs_size_t cache_size; + + // Size of the lookahead buffer in bytes. A larger lookahead buffer + // increases the number of blocks found during an allocation pass. The + // lookahead buffer is stored as a compact bitmap, so each byte of RAM + // can track 8 blocks. + lfs_size_t lookahead_size; + + // Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata + // pairs that exceed this threshold will be compacted during lfs_fs_gc. + // Defaults to ~88% block_size when zero, though the default may change + // in the future. + // + // Note this only affects lfs_fs_gc. Normal compactions still only occur + // when full. + // + // Set to -1 to disable metadata compaction during lfs_fs_gc. + lfs_size_t compact_thresh; + + // Optional statically allocated read buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *read_buffer; + + // Optional statically allocated program buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *prog_buffer; + + // Optional statically allocated lookahead buffer. Must be lookahead_size. + // By default lfs_malloc is used to allocate this buffer. + void *lookahead_buffer; + + // Optional upper limit on length of file names in bytes. No downside for + // larger names except the size of the info struct which is controlled by + // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX or name_max stored on + // disk when zero. + lfs_size_t name_max; + + // Optional upper limit on files in bytes. No downside for larger files + // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX or file_max stored + // on disk when zero. + lfs_size_t file_max; + + // Optional upper limit on custom attributes in bytes. No downside for + // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to + // LFS_ATTR_MAX or attr_max stored on disk when zero. + lfs_size_t attr_max; + + // Optional upper limit on total space given to metadata pairs in bytes. On + // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) + // can help bound the metadata compaction time. Must be <= block_size. + // Defaults to block_size when zero. + lfs_size_t metadata_max; + + // Optional upper limit on inlined files in bytes. Inlined files live in + // metadata and decrease storage requirements, but may be limited to + // improve metadata-related performance. Must be <= cache_size, <= + // attr_max, and <= block_size/8. Defaults to the largest possible + // inline_max when zero. + // + // Set to -1 to disable inlined files. + lfs_size_t inline_max; + +#ifdef LFS_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files. Limited to 32-bits. + lfs_size_t size; + + // Name of the file stored as a null-terminated string. Limited to + // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to + // reduce RAM. LFS_NAME_MAX is stored in superblock and must be + // respected by other littlefs drivers. + char name[LFS_NAME_MAX+1]; +}; + +// Filesystem info structure +struct lfs_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Size of a logical block in bytes. + lfs_size_t block_size; + + // Number of logical blocks in filesystem. + lfs_size_t block_count; + + // Upper limit on the length of file names in bytes. + lfs_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs_size_t attr_max; +}; + +// Custom attribute structure, used to describe custom attributes +// committed atomically during file writes. +struct lfs_attr { + // 8-bit type of attribute, provided by user and used to + // identify the attribute + uint8_t type; + + // Pointer to buffer containing the attribute + void *buffer; + + // Size of attribute in bytes, limited to LFS_ATTR_MAX + lfs_size_t size; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional statically allocated file buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *buffer; + + // Optional list of custom attributes related to the file. If the file + // is opened with read access, these attributes will be read from disk + // during the open call. If the file is opened with write access, the + // attributes will be written to disk every file sync or close. This + // write occurs atomically with update to the file's contents. + // + // Custom attributes are uniquely identified by an 8-bit type and limited + // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller + // than the buffer, it will be padded with zeros. If the stored attribute + // is larger, then it will be silently truncated. If the attribute is not + // found, it will be created implicitly. + struct lfs_attr *attrs; + + // Number of custom attributes in the list + lfs_size_t attr_count; +}; + + +/// internal littlefs data structures /// +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + lfs_size_t size; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_mdir { + lfs_block_t pair[2]; + uint32_t rev; + lfs_off_t off; + uint32_t etag; + uint16_t count; + bool erased; + bool split; + lfs_block_t tail[2]; +} lfs_mdir_t; + +// littlefs directory type +typedef struct lfs_dir { + struct lfs_dir *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + lfs_off_t pos; + lfs_block_t head[2]; +} lfs_dir_t; + +// littlefs file type +typedef struct lfs_file { + struct lfs_file *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + struct lfs_ctz { + lfs_block_t head; + lfs_size_t size; + } ctz; + + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; + + const struct lfs_file_config *cfg; +} lfs_file_t; + +typedef struct lfs_superblock { + uint32_t version; + lfs_size_t block_size; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; +} lfs_superblock_t; + +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + +// The littlefs filesystem type +typedef struct lfs { + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_block_t root[2]; + struct lfs_mlist { + struct lfs_mlist *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + } *mlist; + uint32_t seed; + + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; + + struct lfs_lookahead { + lfs_block_t start; + lfs_block_t size; + lfs_block_t next; + lfs_block_t ckpoint; + uint8_t *buffer; + } lookahead; + + const struct lfs_config *cfg; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; + lfs_size_t inline_max; + +#ifdef LFS_MIGRATE + struct lfs1 *lfs1; +#endif +} lfs_t; + + +/// Filesystem functions /// + +#ifndef LFS_READONLY +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); +#endif + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +#ifndef LFS_READONLY +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); +#endif + +#ifndef LFS_READONLY +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); +#endif + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + +// Get a custom attribute +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than +// the buffer, it will be padded with zeros. If the stored attribute is larger, +// then it will be silently truncated. If no attribute is found, the error +// LFS_ERR_NOATTR is returned and the buffer is filled with zeros. +// +// Returns the size of the attribute, or a negative error code on failure. +// Note, the returned size is the size of the attribute on disk, irrespective +// of the size of the buffer. This can be used to dynamically allocate a buffer +// or check for existence. +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size); + +#ifndef LFS_READONLY +// Set custom attributes +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be +// implicitly created. +// +// Returns a negative error code on failure. +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size); +#endif + +#ifndef LFS_READONLY +// Removes a custom attribute +// +// If an attribute is not found, nothing happens. +// +// Returns a negative error code on failure. +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); +#endif + + +/// File operations /// + +#ifndef LFS_NO_MALLOC +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM +// thus use lfs_file_opencfg() with config.buffer set. +#endif + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must remain allocated while the file is open, and +// the config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +#ifndef LFS_READONLY +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); +#endif + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the new position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +#ifndef LFS_READONLY +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); +#endif + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +#ifndef LFS_READONLY +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); +#endif + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a positive value on success, 0 at the end of directory, +// or a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Filesystem-level filesystem operations + +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); + +// Finds the current size of the filesystem +// +// Note: Result is best effort. If files share COW structures, the returned +// size may be larger than the filesystem actually is. +// +// Returns the number of allocated blocks, or a negative error code on failure. +lfs_ssize_t lfs_fs_size(lfs_t *lfs); + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +#ifndef LFS_READONLY +// Attempt to make the filesystem consistent and ready for writing +// +// Calling this function is not required, consistency will be implicitly +// enforced on the first operation that writes to the filesystem, but this +// function allows the work to be performed earlier and without other +// filesystem changes. +// +// Returns a negative error code on failure. +int lfs_fs_mkconsistent(lfs_t *lfs); +#endif + +#ifndef LFS_READONLY +// Attempt any janitorial work +// +// This currently: +// 1. Calls mkconsistent if not already consistent +// 2. Compacts metadata > compact_thresh +// 3. Populates the block allocator +// +// Though additional janitorial work may be added in the future. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. Accomplishing nothing is not +// an error. +int lfs_fs_gc(lfs_t *lfs); +#endif + +#ifndef LFS_READONLY +// Grows the filesystem to a new size, updating the superblock with the new +// block count. +// +// If LFS_SHRINKNONRELOCATING is defined, this function will also accept +// block_counts smaller than the current configuration, after checking +// that none of the blocks that are being removed are in use. +// Note that littlefs's pseudorandom block allocation means that +// this is very unlikely to work in the general case. +// +// Returns a negative error code on failure. +int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); +#endif + +#ifndef LFS_READONLY +#ifdef LFS_MIGRATE +// Attempts to migrate a previous version of littlefs +// +// Behaves similarly to the lfs_format function. Attempts to mount +// the previous version of littlefs and update the filesystem so it can be +// mounted with the current version of littlefs. +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); +#endif +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.c b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.c new file mode 100644 index 00000000..dac72abc --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.c @@ -0,0 +1,37 @@ +/* + * lfs util functions + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// If user provides their own CRC impl we don't need this +#ifndef LFS_CRC +// Software CRC implementation with small lookup table +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; + } + + return crc; +} +#endif + + +#endif diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.h new file mode 100644 index 00000000..c1999fa9 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/lfs_util.h @@ -0,0 +1,273 @@ +/* + * lfs utility functions + * + * Copyright (c) 2022, The littlefs authors. + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start, I would suggest copying lfs_util.h +// and modifying as needed. +#ifdef LFS_CONFIG +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// Alternatively, users can provide a header file which defines +// macros and other things consumed by littlefs. +// +// For example, provide my_defines.h, which contains +// something like: +// +// #include +// extern void *my_malloc(size_t sz); +// #define LFS_MALLOC(sz) my_malloc(sz) +// +// And build littlefs with the header by defining LFS_DEFINES. +// (-DLFS_DEFINES=my_defines.h) + +#ifdef LFS_DEFINES +#include LFS_STRINGIZE(LFS_DEFINES) +#endif + +// System includes +#include +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifndef LFS_TRACE +#ifdef LFS_YES_TRACE +#define LFS_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") +#else +#define LFS_TRACE(...) +#endif +#endif + +#ifndef LFS_DEBUG +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") +#else +#define LFS_DEBUG(...) +#endif +#endif + +#ifndef LFS_WARN +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") +#else +#define LFS_WARN(...) +#endif +#endif + +#ifndef LFS_ERROR +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") +#else +#define LFS_ERROR(...) +#endif +#endif + +// Runtime assertions +#ifndef LFS_ASSERT +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Align to nearest multiple of a size +static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { + return lfs_aligndown(a + alignment-1, alignment); +} + +// Find the smallest power of 2 greater than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert between 32-bit little-endian and native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return ((uint32_t)((uint8_t*)&a)[0] << 0) | + ((uint32_t)((uint8_t*)&a)[1] << 8) | + ((uint32_t)((uint8_t*)&a)[2] << 16) | + ((uint32_t)((uint8_t*)&a)[3] << 24); +#endif +} + +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Convert between 32-bit big-endian and native order +static inline uint32_t lfs_frombe32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return __builtin_bswap32(a); +#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return a; +#else + return ((uint32_t)((uint8_t*)&a)[0] << 24) | + ((uint32_t)((uint8_t*)&a)[1] << 16) | + ((uint32_t)((uint8_t*)&a)[2] << 8) | + ((uint32_t)((uint8_t*)&a)[3] << 0); +#endif +} + +static inline uint32_t lfs_tobe32(uint32_t a) { + return lfs_frombe32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +#ifdef LFS_CRC +static inline uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS_CRC(crc, buffer, size); +} +#else +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); +#endif + +// Allocate memory, only used if buffers are not provided to littlefs +// +// littlefs current has no alignment requirements, as it only allocates +// byte-level buffers. +static inline void *lfs_malloc(size_t size) { +#if defined(LFS_MALLOC) + return LFS_MALLOC(size); +#elif !defined(LFS_NO_MALLOC) + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#if defined(LFS_FREE) + LFS_FREE(p); +#elif !defined(LFS_NO_MALLOC) + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini index 5657015b..ced0eff6 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini @@ -8,6 +8,17 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html +[env:native] +platform = native +test_framework = unity +build_flags = + -std=c++17 + -I test/test_gateway_device/mocks ; mocks found BEFORE system headers + -I lib/gateway_core ; your source lib + -I lib/littlefs_native + -DMG_ENABLE_LOG=0 ; silence mongoose in tests +build_src_filter = + -<*> ; ignore all source files [env:esp32dev] platform = espressif32 board = esp32dev diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h new file mode 100644 index 00000000..af96b984 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include + +// ── String ──────────────────────────────────────────────────────────── +class String { +public: + std::string _s; + + String() = default; + String(const char* s) : _s(s ? s : "") {} + String(std::string s) : _s(std::move(s)) {} + String(int v) : _s(std::to_string(v)) {} + String(unsigned long v) : _s(std::to_string(v)) {} + + const char* c_str() const { return _s.c_str(); } + size_t length() const { return _s.size(); } + bool isEmpty()const { return _s.empty(); } + + bool startsWith(const String& p) const { + return _s.rfind(p._s, 0) == 0; + } + String substring(size_t from) const { + return String(_s.substr(from)); + } + void replace(const char* from, const char* to) { + size_t pos = 0; + while ((pos = _s.find(from, pos)) != std::string::npos) { + _s.replace(pos, strlen(from), to); + pos += strlen(to); + } + } + + String operator+ (const String& o) const { return String(_s + o._s); } + String& operator+=(const String& o) { _s += o._s; return *this; } + bool operator==(const String& o) const { return _s == o._s; } + bool operator!=(const String& o) const { return _s != o._s; } + bool operator< (const String& o) const { return _s < o._s; } + bool operator> (const String& o) const { return _s > o._s; } + operator const char*() const { return _s.c_str(); } +}; +inline String operator+(const char* lhs, const String& rhs) { + return String(std::string(lhs) + rhs._s); +} +// ── Serial stub ──────────────────────────────────────────────────────── +struct SerialClass { + void println(const char*) {} + void println(const String&) {} + template + void printf(const char*, A...) {} +}; +static SerialClass Serial; \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h new file mode 100644 index 00000000..c672f6e9 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h @@ -0,0 +1,170 @@ +#pragma once +#include "Arduino.h" +#include "lfs.h" +#include +#include +#include +#include + +// ── RAM flash config ────────────────────────────────────────────────── +#define LFS_FLASH_SIZE (256 * 1024) +#define LFS_BLOCK_SIZE 4096 +#define LFS_BLOCK_COUNT (LFS_FLASH_SIZE / LFS_BLOCK_SIZE) + +inline uint8_t g_flash_buf[LFS_FLASH_SIZE]; +inline lfs_t g_lfs; +inline bool g_lfs_mounted = false; + +// ── Block device callbacks ──────────────────────────────────────────── +inline int lfs_ram_read(const struct lfs_config* c, lfs_block_t block, + lfs_off_t off, void* buf, lfs_size_t size) { + memcpy(buf, g_flash_buf + block * LFS_BLOCK_SIZE + off, size); + return LFS_ERR_OK; +} +inline int lfs_ram_prog(const struct lfs_config* c, lfs_block_t block, + lfs_off_t off, const void* buf, lfs_size_t size) { + memcpy(g_flash_buf + block * LFS_BLOCK_SIZE + off, buf, size); + return LFS_ERR_OK; +} +inline int lfs_ram_erase(const struct lfs_config* c, lfs_block_t block) { + memset(g_flash_buf + block * LFS_BLOCK_SIZE, 0xFF, LFS_BLOCK_SIZE); + return LFS_ERR_OK; +} +inline int lfs_ram_sync(const struct lfs_config*) { return LFS_ERR_OK; } + +inline const struct lfs_config g_lfs_cfg = { + .context = NULL, + .read = lfs_ram_read, + .prog = lfs_ram_prog, + .erase = lfs_ram_erase, + .sync = lfs_ram_sync, + .read_size = 16, + .prog_size = 16, + .block_size = LFS_BLOCK_SIZE, + .block_count = LFS_BLOCK_COUNT, + .block_cycles = 500, + .cache_size = 16, + .lookahead_size = 16, +}; + +inline void lfs_ram_mount_fresh() { + memset(g_flash_buf, 0xFF, LFS_FLASH_SIZE); + lfs_format(&g_lfs, &g_lfs_cfg); + lfs_mount(&g_lfs, &g_lfs_cfg); + g_lfs_mounted = true; +} +inline void lfs_ram_unmount() { + if (g_lfs_mounted) { + lfs_unmount(&g_lfs); + g_lfs_mounted = false; + } +} + +// ── File ────────────────────────────────────────────────────────────── +class File { +public: + bool _valid = false; + bool _isDir = false; + std::string _path; + std::string _name; + + // heap-allocated — address stays stable when File is copied + std::shared_ptr _file; + std::shared_ptr _dir; + + File() = default; + + explicit operator bool() const { return _valid; } + bool isDirectory() const { return _isDir; } + const char* name() const { return _name.c_str(); } + + String readString() { + if (!_valid || _isDir || !_file) return String(""); + lfs_ssize_t sz = lfs_file_size(&g_lfs, _file.get()); + lfs_file_seek(&g_lfs, _file.get(), 0, LFS_SEEK_SET); + std::string buf(sz, '\0'); + lfs_file_read(&g_lfs, _file.get(), &buf[0], sz); + return String(buf.c_str()); + } + + void print(const char* s) { + if (_valid && !_isDir && _file) + lfs_file_write(&g_lfs, _file.get(), s, strlen(s)); + } + + void close() { + if (!_valid) return; + if (_isDir && _dir) lfs_dir_close (&g_lfs, _dir.get()); + if (!_isDir && _file) lfs_file_close(&g_lfs, _file.get()); + _valid = false; + } + + File openNextFile() { + if (!_valid || !_isDir || !_dir) return File{}; + lfs_info info; + while (true) { + int res = lfs_dir_read(&g_lfs, _dir.get(), &info); + if (res <= 0) return File{}; + if (strcmp(info.name, ".") == 0 || + strcmp(info.name, "..") == 0) continue; + + std::string childPath = _path + "/" + info.name; + File f; + f._name = info.name; + f._path = childPath; + + if (info.type == LFS_TYPE_DIR) { + f._isDir = true; + f._dir = std::make_shared(); + f._valid = (lfs_dir_open(&g_lfs, f._dir.get(), + childPath.c_str()) == 0); + } else { + f._isDir = false; + f._file = std::make_shared(); + f._valid = (lfs_file_open(&g_lfs, f._file.get(), + childPath.c_str(), + LFS_O_RDONLY) == 0); + } + return f; + } + } +}; + +// ── LittleFS ────────────────────────────────────────────────────────── +struct LittleFSClass { + + File open(const char* path, const char* mode = "r") { + File f; + f._path = path; + std::string p(path); + size_t slash = p.rfind('/'); + f._name = (slash == std::string::npos) ? p : p.substr(slash + 1); + + lfs_info info; + if (lfs_stat(&g_lfs, path, &info) == 0 && + info.type == LFS_TYPE_DIR) { + f._isDir = true; + f._dir = std::make_shared(); + f._valid = (lfs_dir_open(&g_lfs, f._dir.get(), path) == 0); + return f; + } + + int flags = (mode[0] == 'w') + ? LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC + : LFS_O_RDONLY; + f._isDir = false; + f._file = std::make_shared(); + f._valid = (lfs_file_open(&g_lfs, f._file.get(), path, flags) == 0); + return f; + } + + bool mkdir(const char* path) { + return lfs_mkdir(&g_lfs, path) == 0; + } + + bool remove(const char* path) { + return lfs_remove(&g_lfs, path) == 0; + } +}; + +inline LittleFSClass LittleFS; \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h new file mode 100644 index 00000000..feb77a98 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h @@ -0,0 +1,3 @@ +#pragma once +// WiFi stub — gateway_device does not use WiFi directly. +// This file exists only to satisfy the #include in gateway_private.h. \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp new file mode 100644 index 00000000..d8652303 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp @@ -0,0 +1,176 @@ +m#include +#include "gateway_device.h" // found via -I lib/gateway_core + +// ── Helpers ─────────────────────────────────────────────────────────── + +// Builds a valid JSON device file the way saveDevice() would write it +static void write_device_file(const char* id, const char* name, + const char* type, int status, + unsigned long nonce) { + char path[64]; + snprintf(path, sizeof(path), "/devices/dev_%s", id); + + char buf[256]; + snprintf(buf, sizeof(buf), + "{\"id\":\"%s\",\"name\":\"%s\",\"type\":\"%s\"," + "\"status\":%d,\"lastNonce\":%lu," + "\"firstSeen\":1000,\"lastSeen\":2000," + "\"messageCount\":3,\"permPing\":false,\"key\":\"\"}", + id, name, type, status, nonce); + + lfs_file_t f; + lfs_file_open(&g_lfs, &f, path, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + lfs_file_write(&g_lfs, &f, buf, strlen(buf)); + lfs_file_close(&g_lfs, &f); +} + +// ── setUp / tearDown ────────────────────────────────────────────────── + +void setUp() { + lfs_ram_mount_fresh(); // blank formatted flash per test + lfs_mkdir(&g_lfs, "/devices"); // pre-create the devices directory +} + +void tearDown() { + lfs_ram_unmount(); +} + +// ── Tests ───────────────────────────────────────────────────────────── + +// loadDevices() on empty directory → no devices loaded, no crash +void test_load_empty_directory() { + GatewayDevice gd; + gd.loadDevices(); + TEST_ASSERT_EQUAL(0, (int)gd.devices.size()); +} + +// loadDevices() creates /devices dir when it doesn't exist yet +void test_load_creates_devices_dir_if_missing() { + lfs_remove(&g_lfs, "/devices"); // remove it so loadDevices must create it + + GatewayDevice gd; + gd.loadDevices(); + + lfs_info info; + int res = lfs_stat(&g_lfs, "/devices", &info); + TEST_ASSERT_EQUAL(0, res); + TEST_ASSERT_EQUAL(LFS_TYPE_DIR, (int)info.type); +} + +// loadDevices() correctly parses a JSON device file +void test_load_reads_one_device() { + write_device_file("sensor_01", "Temp Sensor", "sensor", DEV_APPROVED, 42); + + GatewayDevice gd; + gd.loadDevices(); + + TEST_ASSERT_EQUAL(1, (int)gd.devices.size()); + TEST_ASSERT_TRUE(gd.devices.count("sensor_01") > 0); + + const Device& d = gd.devices["sensor_01"]; + TEST_ASSERT_EQUAL_STRING("sensor_01", d.id.c_str()); + TEST_ASSERT_EQUAL_STRING("Temp Sensor", d.name.c_str()); + TEST_ASSERT_EQUAL_STRING("sensor", d.type.c_str()); + TEST_ASSERT_EQUAL(DEV_APPROVED, (int)d.status); + TEST_ASSERT_EQUAL(42, (int)d.lastNonce); + TEST_ASSERT_FALSE(d.has_pending); +} + +// loadDevices() loads multiple devices in one pass +void test_load_reads_multiple_devices() { + write_device_file("node_A", "Node A", "relay", DEV_PENDING, 1); + write_device_file("node_B", "Node B", "sensor", DEV_APPROVED, 9); + + GatewayDevice gd; + gd.loadDevices(); + + TEST_ASSERT_EQUAL(2, (int)gd.devices.size()); + TEST_ASSERT_TRUE(gd.devices.count("node_A") > 0); + TEST_ASSERT_TRUE(gd.devices.count("node_B") > 0); +} + +// saveDevice() writes a file that actually exists on the FS +void test_save_creates_file() { + Device dev; + dev.id = "relay_01"; + dev.name = "Main Relay"; + dev.type = "relay"; + dev.status = DEV_APPROVED; + dev.lastNonce = 7; + dev.firstSeen = 100; + dev.lastSeen = 200; + dev.messageCount = 0; + dev.permPing = false; + dev.keySet = false; + + GatewayDevice gd; + gd.saveDevice(dev); + + lfs_info info; + int res = lfs_stat(&g_lfs, "/devices/dev_relay_01", &info); + TEST_ASSERT_EQUAL_MESSAGE(0, res, "File should exist after saveDevice()"); + TEST_ASSERT_TRUE(info.size > 0); +} + +// save then load round-trip preserves all fields +void test_roundtrip_save_load() { + Device dev; + dev.id = "node_A"; + dev.name = "Node Alpha"; + dev.type = "relay"; + dev.status = DEV_PENDING; + dev.lastNonce = 99; + dev.firstSeen = 10; + dev.lastSeen = 20; + dev.messageCount = 5; + dev.permPing = true; + dev.keySet = false; + + GatewayDevice gd; + gd.saveDevice(dev); + gd.devices.clear(); + gd.loadDevices(); + + TEST_ASSERT_EQUAL(1, (int)gd.devices.size()); + const Device& d = gd.devices["node_A"]; + TEST_ASSERT_EQUAL_STRING("Node Alpha", d.name.c_str()); + TEST_ASSERT_EQUAL_STRING("relay", d.type.c_str()); + TEST_ASSERT_EQUAL(DEV_PENDING, (int)d.status); + TEST_ASSERT_EQUAL(99, (int)d.lastNonce); + TEST_ASSERT_EQUAL(5, d.messageCount); + TEST_ASSERT_TRUE(d.permPing); +} + +// removeDevice() deletes the file from flash +void test_remove_deletes_file() { + write_device_file("sensor_01", "Temp Sensor", "sensor", DEV_PENDING, 0); + + GatewayDevice gd; + gd.removeDevice("sensor_01"); + + lfs_info info; + int res = lfs_stat(&g_lfs, "/devices/dev_sensor_01", &info); + TEST_ASSERT_NOT_EQUAL(0, res); // file should no longer exist +} + +// safeFilename() replaces all illegal characters +void test_safe_filename_special_chars() { + GatewayDevice gd; + String result = gd.safeFilename("home/topic:name*val"); + TEST_ASSERT_EQUAL_STRING("home_topic_name_val", result.c_str()); +} + +// ── Runner ──────────────────────────────────────────────────────────── +int main(int argc, char** argv) { + UNITY_BEGIN(); + RUN_TEST(test_load_empty_directory); + RUN_TEST(test_load_creates_devices_dir_if_missing); + RUN_TEST(test_load_reads_one_device); + RUN_TEST(test_load_reads_multiple_devices); + RUN_TEST(test_save_creates_file); + RUN_TEST(test_roundtrip_save_load); + RUN_TEST(test_remove_deletes_file); + RUN_TEST(test_safe_filename_special_chars); + UNITY_END(); + return 0; +} \ No newline at end of file From 81788071a8244f3a7113f535b3bb5d7c92e37c4c Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 16:50:47 +0200 Subject: [PATCH 07/12] test/test_gateway_device/test_main.cpp, fix error in syntaxx at the line 1 of the file --- .../MQTT_Gateway/test/test_gateway_device/test_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp index d8652303..66734dd9 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp @@ -1,4 +1,4 @@ -m#include +#include #include "gateway_device.h" // found via -I lib/gateway_core // ── Helpers ─────────────────────────────────────────────────────────── From 2566183a323c0fc41297b8bfb1a7eddbb943e1bd Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 17:02:13 +0200 Subject: [PATCH 08/12] lib/gateway_core/build_filter.py, make a diffrent build filter for firmware build and for unit test --- .../MQTT_Gateway/lib/gateway_core/build_filter.py | 13 +++++++++++++ .../MQTT_Gateway/lib/gateway_core/gateway_core.cpp | 2 +- .../MQTT_Gateway/lib/gateway_core/library.json | 4 +--- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/build_filter.py diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/build_filter.py b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/build_filter.py new file mode 100644 index 00000000..d3f65012 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/build_filter.py @@ -0,0 +1,13 @@ +Import("env") + +# Check which platform is currently building +platform = env.get("PIOPLATFORM") + +if platform == "native": + # Build ONLY gateway_device.cpp + # "-<*>" removes everything, "+" adds just that one + env.Replace(SRC_FILTER=["-<*>", "+"]) + +else: + # Build everything for ESP32 or any other platform + env.Replace(SRC_FILTER=["+<*>"]) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp index f9fac3e2..10e7d3c7 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.cpp @@ -623,7 +623,7 @@ void GatewayCore::rpcRequestConnect(struct mg_rpc_req *r) { } static void rpcCommand(struct mg_rpc_req *r) { - command_callback(mg_json_get_str(r->frame, "$.params.message")); + // command_callback(mg_json_get_str(r->frame, "$.params.message")); mg_rpc_ok(r, "{%m:true,%m:%lu}", MG_ESC("recived"), MG_ESC("uptime_ms"), (unsigned long)millis()); } diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json index d42aee69..b46c65f0 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json @@ -2,8 +2,6 @@ "name": "gateway_core", "version": "1.0.0", "build": { - "srcFilter": [ - "+" - ] + "extraScript": "build_filter.py" } } \ No newline at end of file From f99165fac36ab3a4964af737137ffdc2a472ca47 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 17:07:24 +0200 Subject: [PATCH 09/12] lib/gateway_device/gateway_device.cpp, make the gateway_device as a library component --- .../MQTT_Gateway/lib/gateway_core/gateway_core.h | 1 - .../MQTT_Gateway/lib/gateway_core/library.json | 14 +++++++------- .../gateway_device.cpp | 0 .../gateway_device.h | 0 .../gateway_private.h | 0 5 files changed, 7 insertions(+), 8 deletions(-) rename Firmware/AUTH_MQTT/MQTT_Gateway/lib/{gateway_core => gateway_device}/gateway_device.cpp (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/lib/{gateway_core => gateway_device}/gateway_device.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/lib/{gateway_core => gateway_device}/gateway_private.h (100%) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h index 86d26ebb..a8292325 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_core.h @@ -5,7 +5,6 @@ #include #include #include "mongoose.h" -#include "gateway_private.h" #include "gateway_device.h" #include "gateway_utils.h" diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json index b46c65f0..5a1e9b54 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/library.json @@ -1,7 +1,7 @@ -{ - "name": "gateway_core", - "version": "1.0.0", - "build": { - "extraScript": "build_filter.py" - } -} \ No newline at end of file +// { +// "name": "gateway_core", +// "version": "1.0.0", +// "build": { +// "extraScript": "build_filter.py" +// } +// } \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_device.cpp similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_device.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_device.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_device.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_device.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_private.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_private.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_core/gateway_private.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_device/gateway_private.h From c7a0d930e5147fe4da43dd2d723d7d6a0fbee1e4 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 17:15:19 +0200 Subject: [PATCH 10/12] platformio.ini, make only the files needed build for native uint test --- Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini | 1 - .../MQTT_Gateway/test/test_gateway_device/test_main.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini index ced0eff6..64736d52 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini @@ -14,7 +14,6 @@ test_framework = unity build_flags = -std=c++17 -I test/test_gateway_device/mocks ; mocks found BEFORE system headers - -I lib/gateway_core ; your source lib -I lib/littlefs_native -DMG_ENABLE_LOG=0 ; silence mongoose in tests build_src_filter = diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp index 66734dd9..8b09c6fc 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp @@ -1,8 +1,7 @@ #include -#include "gateway_device.h" // found via -I lib/gateway_core +#include "gateway_device.h" // ── Helpers ─────────────────────────────────────────────────────────── - // Builds a valid JSON device file the way saveDevice() would write it static void write_device_file(const char* id, const char* name, const char* type, int status, From fb7267a716ccbffe796f87682df7cff572a9f504 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Fri, 20 Mar 2026 17:19:31 +0200 Subject: [PATCH 11/12] change structure for unit test to make all the test files use the same mocks --- Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini | 2 +- .../test/{test_gateway_device => test_gateway}/mocks/Arduino.h | 0 .../test/{test_gateway_device => test_gateway}/mocks/LittleFS.h | 0 .../test/{test_gateway_device => test_gateway}/mocks/WiFi.h | 0 .../test_main.cpp => test_gateway/test_gateway_device_main.cpp} | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway_device => test_gateway}/mocks/Arduino.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway_device => test_gateway}/mocks/LittleFS.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway_device => test_gateway}/mocks/WiFi.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway_device/test_main.cpp => test_gateway/test_gateway_device_main.cpp} (100%) diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini index 64736d52..6438f5aa 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini @@ -13,7 +13,7 @@ platform = native test_framework = unity build_flags = -std=c++17 - -I test/test_gateway_device/mocks ; mocks found BEFORE system headers + -I test/test_gateway/mocks ; mocks found BEFORE system headers -I lib/littlefs_native -DMG_ENABLE_LOG=0 ; silence mongoose in tests build_src_filter = diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/Arduino.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/Arduino.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/Arduino.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/LittleFS.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/LittleFS.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/LittleFS.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/WiFi.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/mocks/WiFi.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/WiFi.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/test_gateway_device_main.cpp similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway_device/test_main.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/test_gateway_device_main.cpp From 3941692efdb9c8880402d3c69204396eef594b20 Mon Sep 17 00:00:00 2001 From: Yehia Shalaby Date: Sat, 21 Mar 2026 12:07:33 +0200 Subject: [PATCH 12/12] Makefile, make file for build and test platformio.ini, change the build setting for each test --- Firmware/AUTH_MQTT/MQTT_Gateway/Makefile | 105 ++++++++++++++++++ .../lib/gateway_utils/gateway_utils.cpp | 5 +- .../lib/gateway_utils/gateway_utils.h | 4 + .../lib/littlefs_native/library.json | 7 ++ .../AUTH_MQTT/MQTT_Gateway/platformio.ini | 28 ++++- .../test/{test_gateway => }/mocks/Arduino.h | 0 .../test/{test_gateway => }/mocks/LittleFS.h | 0 .../test/{test_gateway => }/mocks/WiFi.h | 0 .../test_gateway_device_main.cpp | 0 .../test/test_utils/test_gateway_utils.cpp | 30 +++++ 10 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/Makefile create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/library.json rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway => }/mocks/Arduino.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway => }/mocks/LittleFS.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway => }/mocks/WiFi.h (100%) rename Firmware/AUTH_MQTT/MQTT_Gateway/test/{test_gateway => test_device}/test_gateway_device_main.cpp (100%) create mode 100644 Firmware/AUTH_MQTT/MQTT_Gateway/test/test_utils/test_gateway_utils.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/Makefile b/Firmware/AUTH_MQTT/MQTT_Gateway/Makefile new file mode 100644 index 00000000..1daea01a --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/Makefile @@ -0,0 +1,105 @@ +# ── Configuration ───────────────────────────────────────────────────────────── +SHELL := /bin/bash +PORT ?= /dev/ttyUSB0 +LOG_DIR := .pio/test_logs + +# ── Colors ──────────────────────────────────────────────────────────────────── +RED := \033[0;31m +GREEN := \033[0;32m +YELLOW := \033[0;33m +CYAN := \033[0;36m +BOLD := \033[1m +RESET := \033[0m + +# ── Help (default target) ───────────────────────────────────────────────────── +.PHONY: help +help: + @echo -e "$(BOLD)Usage: make $(RESET)" + @echo "" + @echo -e " $(CYAN)Build:$(RESET)" + @echo " build Build firmware for esp32dev" + @echo " clean Clean all build artifacts" + @echo "" + @echo -e " $(CYAN)Flash & Monitor:$(RESET)" + @echo " flash Flash firmware to esp32dev" + @echo " monitor Open serial monitor" + @echo " flash-monitor Flash then open serial monitor" + @echo "" + @echo -e " $(CYAN)Test:$(RESET)" + @echo " test Run all native tests" + @echo " test-device Run only test_device suite" + @echo " test-utils Run only test_utils suite" + @echo " test-verbose Run all native tests with verbose output" + @echo "" + @echo -e " $(CYAN)Options:$(RESET)" + @echo " PORT=/dev/ttyUSB0 Override serial port (default: /dev/ttyUSB0)" + +# ── Helpers ─────────────────────────────────────────────────────────────────── +define run_test + @mkdir -p $(LOG_DIR) + @pio test $(1) 2>&1 | \ + tee $(LOG_DIR)/last.log | \ + sed 's/\[PASSED\]/\x1b[0;32m[PASSED]\x1b[0m/g; s/\[FAILED\]/\x1b[0;31m[FAILED]\x1b[0m/g; s/\bFAIL\b/\x1b[0;31mFAIL\x1b[0m/g; s/\bPASSED\b/\x1b[0;32mPASSED\x1b[0m/g'; \ + EXIT=$${PIPESTATUS[0]}; \ + echo ""; \ + if grep -q ":FAIL" $(LOG_DIR)/last.log; then \ + echo -e "$(RED)$(BOLD)============================================$(RESET)"; \ + echo -e "$(RED)$(BOLD) FAILED TESTS $(RESET)"; \ + echo -e "$(RED)$(BOLD)============================================$(RESET)"; \ + echo ""; \ + grep ":FAIL" $(LOG_DIR)/last.log | while IFS=: read -r file line test msg; do \ + echo -e " $(YELLOW)File :$(RESET) $$file"; \ + echo -e " $(YELLOW)Line :$(RESET) $$line"; \ + echo -e " $(YELLOW)Test :$(RESET) $$test"; \ + echo -e " $(RED)Reason:$(RESET) $$msg"; \ + echo -e " $(RED)--------------------------------------------$(RESET)"; \ + done; \ + else \ + echo -e "$(GREEN)$(BOLD)============================================$(RESET)"; \ + echo -e "$(GREEN)$(BOLD) All tests passed! $(RESET)"; \ + echo -e "$(GREEN)$(BOLD)============================================$(RESET)"; \ + fi; \ + exit $$EXIT +endef +# ── Build ───────────────────────────────────────────────────────────────────── +.PHONY: build +build: + @echo -e "$(CYAN)$(BOLD)Building firmware...$(RESET)" + pio run -e esp32dev + +.PHONY: clean +clean: + @echo -e "$(YELLOW)$(BOLD)Cleaning build artifacts...$(RESET)" + pio run --target clean + @rm -rf $(LOG_DIR) + +# ── Flash & Monitor ─────────────────────────────────────────────────────────── +.PHONY: flash +flash: + @echo -e "$(CYAN)$(BOLD)Flashing firmware to $(PORT)...$(RESET)" + pio run -e esp32dev --target upload --upload-port $(PORT) + +.PHONY: monitor +monitor: + @echo -e "$(CYAN)$(BOLD)Opening serial monitor on $(PORT)...$(RESET)" + pio device monitor --port $(PORT) + +.PHONY: flash-monitor +flash-monitor: flash monitor + +# ── Test ────────────────────────────────────────────────────────────────────── +.PHONY: test +test: + $(call run_test, -e native_device -e native_utils) + +.PHONY: test-device +test-device: + $(call run_test, -e native_device) + +.PHONY: test-utils +test-utils: + $(call run_test, -e native_utils) + +.PHONY: test-verbose +test-verbose: + $(call run_test, -e native_device -e native_utils -vvv) \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp index 48018a68..63dcc4d4 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.cpp @@ -1,8 +1,5 @@ #include "gateway_utils.h" -#include "mongoose.h" -#include -#include -#include + // --------------------------------------------------------------------------- int gw_hex_to_bytes(const char *hex, uint8_t *dst, size_t hex_len) { diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h index 7b02ee1c..7eefe86a 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/gateway_utils/gateway_utils.h @@ -3,6 +3,10 @@ #include #include +#include +#include +#include +#include "mongoose.h" // Convert hex string to bytes. Returns byte count on success, -1 on error. int gw_hex_to_bytes(const char *hex, uint8_t *dst, size_t hex_len); diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/library.json b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/library.json new file mode 100644 index 00000000..69afe2f8 --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/lib/littlefs_native/library.json @@ -0,0 +1,7 @@ +{ + "name": "littlefs_native", + "version": "0.1.0", + "build": { + "srcFilter": ["+<*.c>"] + } +} \ No newline at end of file diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini index 6438f5aa..8e9cac1b 100644 --- a/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/platformio.ini @@ -8,16 +8,34 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:native] +[native_base] platform = native test_framework = unity +build_src_filter = + -<*> + +[env:native_device] +extends = native_base +test_filter = test_device build_flags = -std=c++17 - -I test/test_gateway/mocks ; mocks found BEFORE system headers + -I test/test_device/mocks ; suite-specific FIRST (takes priority) + -I test/mocks ; shared mocks SECOND (fallback) -I lib/littlefs_native - -DMG_ENABLE_LOG=0 ; silence mongoose in tests -build_src_filter = - -<*> ; ignore all source files + -DMG_ENABLE_LOG=0 +lib_deps = + throwtheswitch/Unity@^2.6.1 + lib/littlefs_native + +[env:native_utils] +extends = native_base +test_filter = test_utils +build_flags = + -std=c++17 + -I test/mocks ; only shared mocks needed + -I lib/littlefs_native + -DMG_ENABLE_LOG=0 ; ignore all source files + [env:esp32dev] platform = espressif32 board = esp32dev diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/Arduino.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/Arduino.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/Arduino.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/Arduino.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/LittleFS.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/LittleFS.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/LittleFS.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/LittleFS.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/WiFi.h b/Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/WiFi.h similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/mocks/WiFi.h rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/mocks/WiFi.h diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/test_gateway_device_main.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_device/test_gateway_device_main.cpp similarity index 100% rename from Firmware/AUTH_MQTT/MQTT_Gateway/test/test_gateway/test_gateway_device_main.cpp rename to Firmware/AUTH_MQTT/MQTT_Gateway/test/test_device/test_gateway_device_main.cpp diff --git a/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_utils/test_gateway_utils.cpp b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_utils/test_gateway_utils.cpp new file mode 100644 index 00000000..3b49546d --- /dev/null +++ b/Firmware/AUTH_MQTT/MQTT_Gateway/test/test_utils/test_gateway_utils.cpp @@ -0,0 +1,30 @@ +#include +#include "gateway_utils.h" + +void setUp(void) { +} + +void tearDown(void) { +} + +void test_hex_to_bytes() { + const char* hex = "25"; + uint8_t result = 0; + gw_hex_to_bytes(hex, &result,sizeof(hex)); + TEST_ASSERT_EQUAL_INT(0x25, result); +} +void test_hex_to_bytes_30() { + const char* hex = "30"; + uint8_t result = 0; + gw_hex_to_bytes(hex, &result,sizeof(hex)); + TEST_ASSERT_EQUAL_INT(0x30, result); +} + +int main(void) { + UNITY_BEGIN(); + //run test + RUN_TEST(test_hex_to_bytes); + RUN_TEST(test_hex_to_bytes_30); + UNITY_END(); + return 0; +} \ No newline at end of file