From e910304f6fceec42805b9c841e1ad020d62459b6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 2 Mar 2026 14:49:44 -0400 Subject: [PATCH] v0.0.27 - Add admin API docs/frontend brief and improve context readability with titled sections and pretty context.log formatting --- .gitignore | 5 + .tmp_api_8585.log | 0 .tmp_api_8586.log | 14 + .tmp_api_8590.log | 1 + .tmp_api_8590.pid | 1 + .tmp_api_8591.log | 14 + .tmp_api_8592.log | 19 + .tmp_api_check.log | 0 .tmp_api_config.json | 251 + .tmp_api_config_8585.json | 251 + .tmp_api_config_8586.json | 251 + .tmp_api_config_8590.json | 251 + .tmp_api_config_8591.json | 251 + .tmp_api_config_8592.json | 251 + .tmp_api_run.log | 0 Makefile | 4 +- README.md | 4 +- config.json.example | 5 + docs/API.md | 438 + docs/TOOLS_AND_SKILLS.md | 4 +- plans/admin_api.md | 469 + plans/admin_web_frontend.md | 301 + plans/context_architecture.md | 139 + src/agent.c | 619 +- src/agent.h | 3 + src/config.c | 43 + src/config.h | 7 + src/http_api.c | 710 + src/http_api.h | 19 + src/main.c | 32 + src/main.h | 4 +- src/mongoose.c | 28193 ++++++++++++++++++++++++++++++++ src/mongoose.h | 4038 +++++ vendor/mongoose.c | 28193 ++++++++++++++++++++++++++++++++ vendor/mongoose.h | 4038 +++++ 35 files changed, 68794 insertions(+), 29 deletions(-) create mode 100644 .tmp_api_8585.log create mode 100644 .tmp_api_8586.log create mode 100644 .tmp_api_8590.log create mode 100644 .tmp_api_8590.pid create mode 100644 .tmp_api_8591.log create mode 100644 .tmp_api_8592.log create mode 100644 .tmp_api_check.log create mode 100644 .tmp_api_config.json create mode 100644 .tmp_api_config_8585.json create mode 100644 .tmp_api_config_8586.json create mode 100644 .tmp_api_config_8590.json create mode 100644 .tmp_api_config_8591.json create mode 100644 .tmp_api_config_8592.json create mode 100644 .tmp_api_run.log create mode 100644 docs/API.md create mode 100644 plans/admin_api.md create mode 100644 plans/admin_web_frontend.md create mode 100644 plans/context_architecture.md create mode 100644 src/http_api.c create mode 100644 src/http_api.h create mode 100644 src/mongoose.c create mode 100644 src/mongoose.h create mode 100644 vendor/mongoose.c create mode 100644 vendor/mongoose.h diff --git a/.gitignore b/.gitignore index 6ce7084..2c32e04 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ /nips/ /config.json test_keys.txt + +/mongoose/ + + + # Build artifacts /build/ /didactyl diff --git a/.tmp_api_8585.log b/.tmp_api_8585.log new file mode 100644 index 0000000..e69de29 diff --git a/.tmp_api_8586.log b/.tmp_api_8586.log new file mode 100644 index 0000000..afb016c --- /dev/null +++ b/.tmp_api_8586.log @@ -0,0 +1,14 @@ +11469571 3 mongoose.c:4824:mg_mgr_init MG_IO_SIZE: 16384, TLS: none +11469571 3 mongoose.c:4741:mg_listen 1 9 http://127.0.0.1:8586 +114695f9 3 mongoose.c:10782:accept_conn 2 10 accepted 127.0.0.1:51708 -> 127.0.0.1:8586 +11469636 3 mongoose.c:10632:read_conn 2 10 0:0:0 88 err 0 +11469674 3 mongoose.c:10643:write_conn 2 10 snd 406/16384 rcv 0/16384 n=406 err=0 +114696b1 3 mongoose.c:10632:read_conn 2 10 0:0:0 -1 err 0 +114696b2 3 mongoose.c:4677:mg_close_con 2 10 closed +114696b2 3 mongoose.c:10782:accept_conn 3 10 accepted 127.0.0.1:51718 -> 127.0.0.1:8586 +114696dd 3 mongoose.c:10632:read_conn 3 10 0:0:0 257 err 0 +11469e00 3 mongoose.c:10643:write_conn 3 10 snd 424/16384 rcv 0/16384 n=424 err=0 +11469e3d 3 mongoose.c:10632:read_conn 3 10 0:0:0 -1 err 0 +11469e3d 3 mongoose.c:4677:mg_close_con 3 10 closed +1146a21a 3 mongoose.c:4677:mg_close_con 1 9 closed +1146a21a 3 mongoose.c:4788:mg_mgr_free All connections closed diff --git a/.tmp_api_8590.log b/.tmp_api_8590.log new file mode 100644 index 0000000..8c74c58 --- /dev/null +++ b/.tmp_api_8590.log @@ -0,0 +1 @@ +[2026-03-02 14:46:38] [WARN ] [didactyl] retrying kind 4 event once after auth handshake window diff --git a/.tmp_api_8590.pid b/.tmp_api_8590.pid new file mode 100644 index 0000000..07bc0b7 --- /dev/null +++ b/.tmp_api_8590.pid @@ -0,0 +1 @@ +1217023 diff --git a/.tmp_api_8591.log b/.tmp_api_8591.log new file mode 100644 index 0000000..1c19cbc --- /dev/null +++ b/.tmp_api_8591.log @@ -0,0 +1,14 @@ +1148b030 3 mongoose.c:4824:mg_mgr_init MG_IO_SIZE: 16384, TLS: none +1148b030 3 mongoose.c:4741:mg_listen 1 9 http://127.0.0.1:8591 +1148b087 3 mongoose.c:10782:accept_conn 2 10 accepted 127.0.0.1:34824 -> 127.0.0.1:8591 +1148b0c7 3 mongoose.c:10632:read_conn 2 10 0:0:0 88 err 0 +1148b0eb 3 mongoose.c:10643:write_conn 2 10 snd 406/16384 rcv 0/16384 n=406 err=0 +1148b129 3 mongoose.c:10632:read_conn 2 10 0:0:0 -1 err 0 +1148b129 3 mongoose.c:4677:mg_close_con 2 10 closed +1148b129 3 mongoose.c:10782:accept_conn 3 10 accepted 127.0.0.1:34828 -> 127.0.0.1:8591 +1148b166 3 mongoose.c:10632:read_conn 3 10 0:0:0 257 err 0 +1148b6c4 3 mongoose.c:10643:write_conn 3 10 snd 424/16384 rcv 0/16384 n=424 err=0 +1148b701 3 mongoose.c:10632:read_conn 3 10 0:0:0 -1 err 0 +1148b701 3 mongoose.c:4677:mg_close_con 3 10 closed +1148bac5 3 mongoose.c:4677:mg_close_con 1 9 closed +1148bac5 3 mongoose.c:4788:mg_mgr_free All connections closed diff --git a/.tmp_api_8592.log b/.tmp_api_8592.log new file mode 100644 index 0000000..f3771a1 --- /dev/null +++ b/.tmp_api_8592.log @@ -0,0 +1,19 @@ +11494a06 3 mongoose.c:4824:mg_mgr_init MG_IO_SIZE: 16384, TLS: none +11494a06 3 mongoose.c:4741:mg_listen 1 9 http://127.0.0.1:8592 +11494c7b 3 mongoose.c:10782:accept_conn 2 10 accepted 127.0.0.1:46110 -> 127.0.0.1:8592 +11494cb8 3 mongoose.c:10632:read_conn 2 10 0:0:0 88 err 0 +11494cf5 3 mongoose.c:10643:write_conn 2 10 snd 406/16384 rcv 0/16384 n=406 err=0 +11494d32 3 mongoose.c:10632:read_conn 2 10 0:0:0 -1 err 0 +11494d32 3 mongoose.c:4677:mg_close_con 2 10 closed +11494d32 3 mongoose.c:10782:accept_conn 3 10 accepted 127.0.0.1:46120 -> 127.0.0.1:8592 +11494d6f 3 mongoose.c:10632:read_conn 3 10 0:0:0 97 err 0 +114973d4 3 mongoose.c:10643:write_conn 3 10 snd 13914/16384 rcv 0/16384 n=13914 err=0 +11497449 3 mongoose.c:10632:read_conn 3 10 0:0:0 -1 err 0 +11497449 3 mongoose.c:4677:mg_close_con 3 10 closed +11497449 3 mongoose.c:10782:accept_conn 4 10 accepted 127.0.0.1:59822 -> 127.0.0.1:8592 +114974b8 3 mongoose.c:10632:read_conn 4 10 0:0:0 257 err 0 +11497ed5 3 mongoose.c:10643:write_conn 4 10 snd 424/16384 rcv 0/16384 n=424 err=0 +11497f43 3 mongoose.c:10632:read_conn 4 10 0:0:0 -1 err 11 +11497f43 3 mongoose.c:4677:mg_close_con 4 10 closed +1149831f 3 mongoose.c:4677:mg_close_con 1 9 closed +1149831f 3 mongoose.c:4788:mg_mgr_free All connections closed diff --git a/.tmp_api_check.log b/.tmp_api_check.log new file mode 100644 index 0000000..e69de29 diff --git a/.tmp_api_config.json b/.tmp_api_config.json new file mode 100644 index 0000000..eaa91ae --- /dev/null +++ b/.tmp_api_config.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8484, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_config_8585.json b/.tmp_api_config_8585.json new file mode 100644 index 0000000..ced4ed1 --- /dev/null +++ b/.tmp_api_config_8585.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8585, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_config_8586.json b/.tmp_api_config_8586.json new file mode 100644 index 0000000..c492c22 --- /dev/null +++ b/.tmp_api_config_8586.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8586, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_config_8590.json b/.tmp_api_config_8590.json new file mode 100644 index 0000000..a4fb80c --- /dev/null +++ b/.tmp_api_config_8590.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8590, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_config_8591.json b/.tmp_api_config_8591.json new file mode 100644 index 0000000..f8281b1 --- /dev/null +++ b/.tmp_api_config_8591.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8591, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_config_8592.json b/.tmp_api_config_8592.json new file mode 100644 index 0000000..67d18b7 --- /dev/null +++ b/.tmp_api_config_8592.json @@ -0,0 +1,251 @@ +{ + "keys": { + "nsec": "nsec17pcmmqxh99rtu5ctpxrdyywdd8f7zfqgf3y2f4fvcm3tk3vpuyqqvlxl4e", + "npub": "npub12237stmmxapc2ta7spx0e06dkdzgz9vg0z2jglqqru44peus4juqg598qn", + "npubHex": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "nsecHex": "f071bd80d72946be530b0986d211cd69d3e124084c48a4d52cc6e2bb4581e100" + }, + "admin": { + "pubkey": "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks" + }, + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-L9kd0LkZaoeaua0qqosnHy", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 512, + "temperature": 0.7 + }, + "security": { + "verify_signatures": true, + "stranger_response": "I only respond to people in my web of trust. You can always identify me by my public key (npub).", + "tiers": { + "admin": { + "tools_enabled": true + }, + "wot": { + "enabled": true, + "tools_enabled": false + }, + "stranger": { + "enabled": true + } + } + }, + "admin_context": { + "enabled": true, + "subscribe_kinds": [ + 0, + 3, + 10002, + 1 + ], + "kind_1_limit": 10 + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Agent", + "display_name": "Didactyl", + "about": "A sovereign AI agent on Nostr", + "picture": "https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png", + "banner": "https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + [ + "r", + "wss://relay.damus.io" + ], + [ + "r", + "wss://nos.lol" + ], + [ + "r", + "wss://relay.primal.net" + ], + [ + "r", + "ws://127.0.0.1:7777" + ] + ] + }, + { + "kind": 1, + "content": "Hello world from Didactyl startup", + "tags": [ + [ + "t", + "didactyl" + ], + [ + "t", + "startup" + ] + ] + }, + { + "kind": 3, + "content": "", + "tags": [] + }, + { + "kind": 31120, + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.", + "tags": [ + [ + "d", + "soul" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "long_form_note", + "description": "How to publish a NIP-23 long-form article (kind 30023)", + "nip": "NIP-23", + "event_kind": 30023, + "format": "The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.", + "required_tags": { + "d": "Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.", + "title": "Human-readable article title.", + "published_at": "Unix timestamp as a string for first publication time; keep stable on edits." + }, + "optional_tags": { + "summary": "Short 1-2 sentence summary.", + "image": "URL for article preview image.", + "t": "Topic hashtags using repeated t tags, usually 3-6 lowercase terms." + }, + "behavior": "If required values are missing or unclear, ask the administrator before publishing instead of guessing.", + "procedure": [ + "Determine title and d tag from admin input or source material.", + "Draft markdown body content.", + "Set published_at as unix seconds string.", + "Add optional summary/image/t tags when known.", + "Publish with nostr_post kind 30023 including tags array." + ] + }, + "tags": [ + [ + "d", + "long_form_note" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "long_form_note" + ] + ] + }, + { + "kind": 31123, + "content_fields": { + "name": "post_readme_to_nostr", + "description": "Publish README.md using the dedicated nostr_post_readme tool", + "uses_skill": "long_form_note", + "event_kind": 30023, + "procedure": [ + "Call nostr_post_readme with empty arguments {}.", + "Return the tool response including event_id, d_tag, and naddr_uri.", + "If the tool returns success=false, report the tool error verbatim." + ], + "required_values": { + "d": "readme.md", + "tool": "nostr_post_readme" + } + }, + "tags": [ + [ + "d", + "post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ], + [ + "slug", + "post_readme_to_nostr" + ] + ] + }, + { + "kind": 31124, + "content_fields": { + "name": "admin_ops", + "description": "Private operational procedures" + }, + "tags": [ + [ + "d", + "admin_ops" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "private" + ], + [ + "slug", + "admin_ops" + ] + ] + }, + { + "kind": 10123, + "content": "", + "tags": [ + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note" + ], + [ + "a", + "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr" + ], + [ + "app", + "didactyl" + ], + [ + "scope", + "public" + ] + ] + } + ], + "api": { + "enabled": true, + "port": 8592, + "bind_address": "127.0.0.1" + } +} \ No newline at end of file diff --git a/.tmp_api_run.log b/.tmp_api_run.log new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index 9fc43f2..2da3dfc 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_POSIX_C_SOURCE=200809L +CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L SRC_DIR = src TARGET = didactyl @@ -13,6 +13,8 @@ SRCS = \ $(SRC_DIR)/agent.c \ $(SRC_DIR)/tools.c \ $(SRC_DIR)/trigger_manager.c \ + $(SRC_DIR)/http_api.c \ + $(SRC_DIR)/mongoose.c \ $(SRC_DIR)/debug.c INCLUDES = \ diff --git a/README.md b/README.md index c17ff04..545ec33 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.0.26 +## Current Status — v0.0.27 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.26 — Add nostr_pubkey/nostr_npub tools with my_pubkey/my_npub aliases for agent key output +> Last release update: v0.0.27 — Add admin API docs/frontend brief and improve context readability with titled sections and pretty context.log formatting - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/config.json.example b/config.json.example index 9a6af69..40e6221 100644 --- a/config.json.example +++ b/config.json.example @@ -42,6 +42,11 @@ ], "kind_1_limit": 10 }, + "api": { + "enabled": false, + "port": 8484, + "bind_address": "127.0.0.1" + }, "startup_events": [ { "kind": 0, diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..91ff0f7 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,438 @@ +# Didactyl Admin HTTP API + +## Overview + +Didactyl exposes a localhost-only HTTP API for external tools and dashboards to inspect agent state, explore LLM context, craft prompts, and compare prompt variants. The API runs inside the same process as the agent — no separate server, no authentication required. + +All responses are JSON. CORS headers are included on every response for browser access from any local origin. + +--- + +## Configuration + +Enable the API in `config.json`: + +```json +{ + "api": { + "enabled": true, + "port": 8484, + "bind_address": "127.0.0.1" + } +} +``` + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | bool | `false` | Must be explicitly set to `true` to start the HTTP server | +| `port` | int | `8484` | TCP port to listen on | +| `bind_address` | string | `"127.0.0.1"` | Bind address — use `127.0.0.1` for localhost-only access | + +The API is disabled by default. When disabled, no listener is created and no resources are consumed. + +--- + +## CORS + +Every response includes: + +``` +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +Access-Control-Allow-Headers: Content-Type +``` + +`OPTIONS` requests return `204 No Content` with these headers for browser preflight support. + +--- + +## Endpoints + +### GET /api/status + +Returns agent runtime status. + +**Response:** + +```json +{ + "success": true, + "name": "Didactyl", + "version": "v0.0.26", + "pubkey": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8", + "relay_count": 4, + "connected_relays": 4, + "active_triggers": 0 +} +``` + +| Field | Description | +|---|---| +| `name` | Agent display name constant | +| `version` | Build version string | +| `pubkey` | Agent public key in hex | +| `relay_count` | Number of configured relays | +| `connected_relays` | Number of currently connected relays | +| `active_triggers` | Number of active triggered-skill subscriptions | + +--- + +### GET /api/context/current + +Returns the full LLM context message array that would be sent to the model right now. This is the same context the agent builds for an admin DM conversation. + +**Response:** + +```json +{ + "success": true, + "total_chars": 13131, + "total_estimated_tokens": 3283, + "messages": [ + {"role": "system", "content": "# Didactyl Agent\n\nYou are Didactyl..."}, + {"role": "system", "content": "This is your administrator! Admin pubkey..."}, + {"role": "system", "content": "Administrator kind 0 profile content..."}, + {"role": "system", "content": "Administrator kind 10002 relay-list content..."}, + {"role": "system", "content": "Startup events memory..."}, + {"role": "system", "content": "Adopted skills memory..."}, + {"role": "system", "content": "Administrator recent public notes..."} + ] +} +``` + +| Field | Description | +|---|---| +| `total_chars` | Total character count across all messages | +| `total_estimated_tokens` | Rough token estimate using `chars / 4` heuristic | +| `messages` | OpenAI-format messages array with role and content | + +--- + +### GET /api/context/parts + +Returns the context broken into labeled, individually-sized parts. Useful for understanding what consumes context budget. + +**Response:** + +```json +{ + "success": true, + "total_chars": 13131, + "total_estimated_tokens": 3283, + "parts": [ + { + "name": "system_prompt", + "role": "system", + "chars": 1200, + "estimated_tokens": 300, + "content": "# Didactyl Agent..." + }, + { + "name": "admin_identity", + "role": "system", + "chars": 120, + "estimated_tokens": 30, + "content": "This is your administrator!..." + }, + { + "name": "admin_kind0", + "role": "system", + "chars": 450, + "estimated_tokens": 113, + "content": "Administrator kind 0 profile content..." + }, + { + "name": "admin_relay_list", + "role": "system", + "chars": 50, + "estimated_tokens": 13, + "content": "Administrator kind 10002 relay-list content..." + }, + { + "name": "startup_events", + "role": "system", + "chars": 4800, + "estimated_tokens": 1200, + "content": "Startup events memory..." + }, + { + "name": "adopted_skills", + "role": "system", + "chars": 2100, + "estimated_tokens": 525, + "content": "Adopted skills memory..." + }, + { + "name": "admin_notes", + "role": "system", + "chars": 680, + "estimated_tokens": 170, + "content": "Administrator recent public notes..." + } + ], + "messages": [...] +} +``` + +**Part names:** + +| Name | Description | +|---|---| +| `system_prompt` | The agent soul / system prompt (first system message) | +| `admin_identity` | Admin pubkey identification message | +| `admin_kind0` | Admin kind 0 profile metadata | +| `admin_relay_list` | Admin kind 10002 relay list | +| `startup_events` | Startup events memory block | +| `adopted_skills` | Adopted skills behavioral instructions | +| `admin_notes` | Admin recent kind 1 public notes | +| `dm_history` | Recent DM conversation history | +| `context_part` | Any other context message | + +--- + +### POST /api/prompt/run-simple + +Submit a system prompt and user message for a simple LLM call with no tools. Useful for quick prompt iteration. + +**Request:** + +```json +{ + "system": "You are a helpful assistant that writes tweets.", + "user": "Write a tweet about AI agents on Nostr", + "model": "claude-haiku-4.5" +} +``` + +| Field | Required | Description | +|---|---|---| +| `system` | yes | System prompt string | +| `user` | yes | User message string | +| `model` | no | Override the current model for this request only | + +**Response:** + +```json +{ + "success": true, + "response": "AI agents are finding their home on Nostr...", + "model_used": "claude-haiku-4.5", + "input_tokens_estimate": 85, + "output_tokens_estimate": 42 +} +``` + +--- + +### POST /api/prompt/run + +Submit a full messages array with the agent tool set enabled. The agent executes tool calls in a loop and returns the complete conversation trace. + +**Request:** + +```json +{ + "messages": [ + {"role": "system", "content": "You are Didactyl..."}, + {"role": "user", "content": "Tweet about the weather"} + ], + "model": "claude-haiku-4.5", + "max_turns": 5 +} +``` + +| Field | Required | Description | +|---|---|---| +| `messages` | yes | OpenAI-format messages array | +| `model` | no | Override the current model for this request only | +| `max_turns` | no | Maximum tool-call loop iterations (default: 4, max: 16) | + +**Response:** + +```json +{ + "success": true, + "final_response": "Done! I posted a tweet about the weather.", + "turns": [ + { + "turn": 1, + "tool_calls": [ + { + "name": "nostr_post", + "arguments": "{\"kind\":1,\"content\":\"Beautiful day!\"}", + "result": "{\"success\":true,\"event_id\":\"abc123\"}" + } + ] + } + ], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3200, + "total_output_tokens_estimate": 180 +} +``` + +| Field | Description | +|---|---| +| `final_response` | The LLM final text response after all tool calls complete | +| `turns` | Array of turn objects, each containing tool calls made in that turn | +| `turns[].tool_calls[]` | Each tool call with name, arguments JSON, and result JSON | +| `model_used` | The model that was actually used | +| `total_input_tokens_estimate` | Estimated input tokens for the full conversation | +| `total_output_tokens_estimate` | Estimated output tokens for the final response | + +--- + +### POST /api/prompt/compare + +A/B testing: submit two prompt variants, both are executed sequentially, responses returned side-by-side. Each variant can optionally use a different model for cross-model comparison. + +**Request:** + +```json +{ + "variant_a": { + "messages": [ + {"role": "system", "content": "You are concise. No emoji."}, + {"role": "user", "content": "Say hi."} + ], + "model": "claude-haiku-4.5", + "max_turns": 1 + }, + "variant_b": { + "messages": [ + {"role": "system", "content": "You are verbose and friendly."}, + {"role": "user", "content": "Say hi."} + ], + "model": "claude-haiku-4.5", + "max_turns": 1 + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `variant_a` | yes | First prompt variant — same shape as `/api/prompt/run` request | +| `variant_b` | yes | Second prompt variant — same shape as `/api/prompt/run` request | + +**Response:** + +```json +{ + "success": true, + "variant_a": { + "success": true, + "final_response": "Hi. How can I help?", + "turns": [{"turn": 1, "tool_calls": []}], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 21, + "total_output_tokens_estimate": 6 + }, + "variant_b": { + "success": true, + "final_response": "Hey there! Nice to meet you! I am here and ready to help...", + "turns": [{"turn": 1, "tool_calls": []}], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 21, + "total_output_tokens_estimate": 72 + } +} +``` + +Variant A runs first, then variant B. The original model config is restored after each variant completes. + +--- + +## Error Responses + +All error responses follow this shape: + +```json +{ + "success": false, + "error": "description of what went wrong" +} +``` + +Common HTTP status codes: + +| Code | Meaning | +|---|---| +| `200` | Success | +| `204` | OPTIONS preflight success | +| `400` | Bad request — missing or invalid parameters | +| `404` | Endpoint not found | +| `500` | Internal server error | + +--- + +## Token Estimation + +All token estimates use a simple `chars / 4` heuristic. This is a rough approximation that works well enough for English text across major model families. No real tokenizer is used. + +--- + +## Security + +- Binds to `127.0.0.1` only — not accessible from the network +- No authentication — this is a local development and administration tool +- The `api.enabled` config flag defaults to `false` and must be explicitly opted in +- Prompt execution endpoints have full tool access equivalent to admin-tier DM conversations +- The agent process must be running for the API to be available + +--- + +## Architecture + +The HTTP server is embedded in the didactyl process using the Mongoose library. It runs in the same thread as the main poll loop — each iteration calls `http_api_poll()` which does non-blocking accept/read/write. This avoids threading complexity and gives the API direct access to all agent state. + +```mermaid +flowchart LR + subgraph didactyl process + MAIN[main loop] --> POLL[nostr_handler_poll] + MAIN --> TPOLL[trigger_manager_poll] + MAIN --> HPOLL[http_api_poll] + HPOLL --> ROUTER[request router] + ROUTER --> AGENT[agent internals] + ROUTER --> LLM[LLM client] + ROUTER --> TOOLS[tools context] + ROUTER --> CONFIG[config] + ROUTER --> TRIGGERS[trigger_manager] + end + BROWSER[Web Dashboard] -- HTTP localhost:8484 --> HPOLL +``` + +### Source Files + +| File | Purpose | +|---|---| +| `src/http_api.c` | HTTP server, request router, all endpoint handlers | +| `src/http_api.h` | Public API: `http_api_init`, `http_api_poll`, `http_api_cleanup` | +| `src/mongoose.c` | Mongoose embedded HTTP library | +| `src/mongoose.h` | Mongoose header | + +--- + +## Future Endpoints + +The following endpoints are planned but not yet implemented: + +| Method | Path | Description | +|---|---|---| +| GET | `/api/config` | Current runtime config with redacted secrets | +| GET | `/api/events/soul` | Fetch agent soul event | +| PUT | `/api/events/soul` | Update soul content | +| GET | `/api/events/skills` | List published skills | +| GET | `/api/events/skills/:slug` | Fetch skill by slug | +| PUT | `/api/events/skills/:slug` | Update skill | +| GET | `/api/events/adoption` | Fetch adoption list | +| GET | `/api/events/profile` | Fetch agent profile | +| PUT | `/api/events/profile` | Update agent profile | +| GET | `/api/triggers` | List active triggers | +| GET | `/api/model` | Current model config | +| PUT | `/api/model` | Update model config | +| GET | `/api/models` | List available models | +| GET | `/api/relays` | Relay connection status | +| GET | `/api/tools` | List tool schemas | +| POST | `/api/tools/:name/execute` | Execute a tool directly | +| GET | `/api/context/log` | Recent context.log entries | +| POST | `/api/context/preview` | Dry-run context preview | diff --git a/docs/TOOLS_AND_SKILLS.md b/docs/TOOLS_AND_SKILLS.md index ae1dbbc..2880a15 100644 --- a/docs/TOOLS_AND_SKILLS.md +++ b/docs/TOOLS_AND_SKILLS.md @@ -158,7 +158,9 @@ flowchart LR ### How Skills Are Used Today -Currently, skills are **passive knowledge**. They exist on Nostr and are loaded into the LLM context when relevant. The admin might say "use your summarize-thread skill" and the LLM retrieves and follows the skill's instructions. +Adopted skills are now **always-on contextual knowledge** for admin DM handling: the agent resolves the local adoption list (kind `10123`), caches referenced skills, and injects their instructions into the LLM system context each turn. + +To keep context stable and safe, skill injection is bounded by hard caps (per-skill truncation and total skill-context budget), and excess skills are omitted with an explicit budget notice. --- diff --git a/plans/admin_api.md b/plans/admin_api.md new file mode 100644 index 0000000..ba91305 --- /dev/null +++ b/plans/admin_api.md @@ -0,0 +1,469 @@ +# Didactyl Admin HTTP API — Architecture & Implementation Plan + +## Overview + +Add a localhost-only HTTP API to didactyl so an external web dashboard can inspect and manage the agent at runtime. No authentication required — binding to `127.0.0.1` only. All responses are JSON. CORS headers included for browser access from any local origin. + +The web frontend is a separate project; this plan covers only the C-side HTTP server and API endpoints. + +--- + +## Architecture + +```mermaid +flowchart LR + subgraph didactyl process + MAIN[main loop] --> POLL[nostr_handler_poll] + MAIN --> TPOLL[trigger_manager_poll] + MAIN --> HPOLL[http_api_poll] + HPOLL --> ROUTER[request router] + ROUTER --> AGENT[agent internals] + ROUTER --> NOSTR[nostr_handler] + ROUTER --> TOOLS[tools context] + ROUTER --> CONFIG[config] + ROUTER --> TRIGGERS[trigger_manager] + end + BROWSER[Web Dashboard] -- HTTP localhost:8484 --> HPOLL +``` + +### HTTP Library Choice + +Use a minimal embedded HTTP server. Two good options for C with no extra dependencies: + +1. **mongoose** (single `mongoose.c` + `mongoose.h`) — battle-tested, MIT license, supports polling model +2. **microhttpd** (libmicrohttpd) — GNU project, available as system package + +**Recommendation: mongoose** — it is a single-file drop-in, works with the existing poll-based main loop, and requires zero system dependencies. Just add `mongoose.c` and `mongoose.h` to the project. + +### Integration Pattern + +The HTTP server runs in the same thread as the main poll loop. Each iteration calls `http_api_poll()` which does non-blocking accept/read/write via mongoose's `mg_mgr_poll()`. This avoids threading complexity and gives the API direct access to all agent state. + +--- + +## Config Extension + +```json +{ + "api": { + "enabled": true, + "port": 8484, + "bind_address": "127.0.0.1" + } +} +``` + +Defaults: enabled=false, port=8484, bind=127.0.0.1. + +--- + +## API Endpoints + +All endpoints return JSON. All mutations use POST/PUT/DELETE. All reads use GET. + +### Agent Identity & Status + +| Method | Path | Description | +|---|---|---| +| GET | `/api/status` | Agent runtime status: pubkey, display name, version, uptime, connected relay count, trigger count | +| GET | `/api/config` | Current runtime config (redacted: nsec/api_key masked) | + +### Nostr Events — Read & Edit + +| Method | Path | Description | +|---|---|---| +| GET | `/api/events/soul` | Fetch the agent soul event (kind 31120, d=soul) | +| PUT | `/api/events/soul` | Update soul content, republish to relays | +| GET | `/api/events/skills` | List all published skills (kind 31123/31124 by own pubkey) | +| GET | `/api/events/skills/:slug` | Fetch a single skill by slug | +| PUT | `/api/events/skills/:slug` | Update skill content/tags, republish | +| DELETE | `/api/events/skills/:slug` | Remove skill from adoption list | +| GET | `/api/events/adoption` | Fetch kind 10123 adoption list | +| GET | `/api/events/startup` | List startup events from config | +| GET | `/api/events/profile` | Fetch agent kind 0 profile | +| PUT | `/api/events/profile` | Update agent kind 0 profile, republish | +| GET | `/api/events/query` | Generic Nostr query — pass filter as query params or JSON body | + +### Context Inspector + +| Method | Path | Description | +|---|---|---| +| GET | `/api/context/current` | Build and return the full context that would be sent to the LLM right now, broken into labeled parts | +| GET | `/api/context/parts` | Return context parts with individual sizes (bytes and estimated tokens) | +| GET | `/api/context/log` | Return recent context.log entries (last N blocks, configurable via ?limit=) | +| POST | `/api/context/preview` | Accept a modified context structure, return what the LLM payload would look like (dry run, no send) | + +### Context Parts Response Shape + +```json +{ + "total_chars": 12450, + "total_estimated_tokens": 3112, + "parts": [ + { + "name": "system_prompt", + "role": "system", + "chars": 1200, + "estimated_tokens": 300, + "content": "# Didactyl Agent..." + }, + { + "name": "admin_identity", + "role": "system", + "chars": 450, + "estimated_tokens": 112, + "content": "This is your administrator!..." + }, + { + "name": "admin_kind0", + "role": "system", + "chars": 320, + "estimated_tokens": 80, + "content": "Administrator kind 0 profile..." + }, + { + "name": "startup_events", + "role": "system", + "chars": 4800, + "estimated_tokens": 1200, + "content": "Startup events memory..." + }, + { + "name": "adopted_skills", + "role": "system", + "chars": 2100, + "estimated_tokens": 525, + "content": "Adopted skills memory..." + }, + { + "name": "dm_history", + "role": "mixed", + "chars": 2400, + "estimated_tokens": 600, + "turns": 8 + }, + { + "name": "admin_notes", + "role": "system", + "chars": 680, + "estimated_tokens": 170, + "content": "Administrator recent public notes..." + }, + { + "name": "tools_schema", + "chars": 500, + "estimated_tokens": 125, + "tool_count": 28 + } + ] +} +``` + +### Triggers + +| Method | Path | Description | +|---|---|---| +| GET | `/api/triggers` | List active triggers with status (wraps existing trigger_manager_status_json) | + +### Model / LLM + +| Method | Path | Description | +|---|---|---| +| GET | `/api/model` | Current model config (wraps existing model_get) | +| PUT | `/api/model` | Update model config (wraps existing model_set) | +| GET | `/api/models` | List available models from provider (wraps existing model_list) | + +### Relays + +| Method | Path | Description | +|---|---|---| +| GET | `/api/relays` | Relay connection status (wraps existing relay_status tool) | + +### Prompt Crafting & Execution + +| Method | Path | Description | +|---|---|---| +| POST | `/api/prompt/run` | Submit a custom messages array with tools enabled; returns full LLM response including tool calls and results | +| POST | `/api/prompt/run-simple` | Submit system prompt + user message; returns LLM text response (no tools) | +| POST | `/api/prompt/compare` | A/B test: submit two prompt variants, run both, return side-by-side responses | + +#### POST /api/prompt/run + +Send a fully crafted messages array to the LLM with the full tool set enabled. The agent executes tool calls and returns the complete conversation. + +```json +{ + "messages": [ + {"role": "system", "content": "You are Didactyl..."}, + {"role": "system", "content": "Adopted skills memory..."}, + {"role": "user", "content": "Tweet about the weather"} + ], + "model": "claude-haiku-4.5", + "max_turns": 5, + "tools_enabled": true +} +``` + +Response: + +```json +{ + "success": true, + "final_response": "Done! I posted a tweet about the weather.", + "turns": [ + { + "turn": 1, + "tool_calls": [ + {"name": "nostr_post", "arguments": "...", "result": "..."} + ] + } + ], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3200, + "total_output_tokens_estimate": 180 +} +``` + +#### POST /api/prompt/run-simple + +Quick iteration on prompt wording without tools. + +```json +{ + "system": "You are a helpful assistant that writes tweets...", + "user": "Write a tweet about AI agents on Nostr", + "model": "claude-haiku-4.5" +} +``` + +Response: + +```json +{ + "success": true, + "response": "AI agents are finding their home on Nostr...", + "model_used": "claude-haiku-4.5", + "input_tokens_estimate": 85, + "output_tokens_estimate": 42 +} +``` + +#### POST /api/prompt/compare + +A/B testing: submit two prompt variants, both are executed, responses returned side-by-side. + +```json +{ + "variant_a": { + "messages": [ + {"role": "system", "content": "You are Didactyl. Keep responses under 280 chars."}, + {"role": "user", "content": "Tweet about your new skill"} + ], + "model": "claude-haiku-4.5", + "tools_enabled": true + }, + "variant_b": { + "messages": [ + {"role": "system", "content": "You are Didactyl. Be concise. No markdown. No emoji."}, + {"role": "user", "content": "Tweet about your new skill"} + ], + "model": "claude-haiku-4.5", + "tools_enabled": true + } +} +``` + +Response: + +```json +{ + "success": true, + "variant_a": { + "final_response": "Just picked up the tweet-composer skill! ...", + "turns": [], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3200, + "total_output_tokens_estimate": 95 + }, + "variant_b": { + "final_response": "New skill acquired: tweet-composer. ...", + "turns": [], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3100, + "total_output_tokens_estimate": 78 + } +} +``` + +The compare endpoint runs variant_a first, then variant_b sequentially. Each variant can optionally use a different model for cross-model comparison. + +#### Prompt Crafting Workflow + +```mermaid +flowchart TD + LOAD[GET /api/context/parts] --> EDIT[Edit parts in UI] + EDIT --> PREVIEW[POST /api/context/preview] + PREVIEW --> FIRE[POST /api/prompt/run] + FIRE --> COMPARE{Want to compare?} + COMPARE -- Yes --> AB[POST /api/prompt/compare] + COMPARE -- No --> PERSIST{Like the result?} + AB --> PERSIST + PERSIST -- Yes --> SAVE[PUT /api/events/soul or skills] + PERSIST -- No --> EDIT +``` + +### Tools + +| Method | Path | Description | +|---|---|---| +| GET | `/api/tools` | List all registered tool schemas | +| POST | `/api/tools/:name/execute` | Execute a tool by name with JSON body as args (admin-only equivalent) | + +--- + +## Implementation Plan + +### New Files + +| File | Purpose | +|---|---| +| `src/http_api.c` | HTTP server, request router, endpoint handlers | +| `src/http_api.h` | Public API: init, poll, cleanup | +| `vendor/mongoose.c` | Mongoose HTTP library (single file) | +| `vendor/mongoose.h` | Mongoose header | + +### Modified Files + +| File | Change | +|---|---| +| `src/config.h` | Add `api_config_t` struct to `didactyl_config_t` | +| `src/config.c` | Parse `api` config section | +| `src/main.c` | Call `http_api_init()`, add `http_api_poll()` to main loop, call `http_api_cleanup()` on shutdown | +| `src/agent.h` | Expose `agent_build_context_parts_json()` for context inspector | +| `src/agent.c` | Implement `agent_build_context_parts_json()` that builds context and returns labeled parts with sizes | +| `Makefile` | Add `vendor/mongoose.c` and `src/http_api.c` to SRCS, add `-Ivendor` to INCLUDES | + +### http_api.h + +```c +#ifndef DIDACTYL_HTTP_API_H +#define DIDACTYL_HTTP_API_H + +#include "config.h" +#include "tools.h" + +struct trigger_manager; + +typedef struct { + didactyl_config_t* cfg; + tools_context_t* tools_ctx; + struct trigger_manager* trigger_manager; +} http_api_context_t; + +int http_api_init(http_api_context_t* ctx); +int http_api_poll(int timeout_ms); +void http_api_cleanup(void); + +#endif +``` + +### Main Loop Integration + +```c +// In main.c, after agent_init and trigger_manager_init: +http_api_context_t api_ctx = { + .cfg = &cfg, + .tools_ctx = &g_tools_ctx, // need to expose from agent + .trigger_manager = &trigger_manager +}; + +if (cfg.api.enabled) { + if (http_api_init(&api_ctx) != 0) { + DEBUG_WARN("HTTP API failed to start"); + } +} + +// In main loop: +while (g_running) { + nostr_handler_poll(100); + trigger_manager_poll(&trigger_manager); + if (cfg.api.enabled) { + http_api_poll(0); // non-blocking + } + nanosleep(...); +} + +// On shutdown: +if (cfg.api.enabled) { + http_api_cleanup(); +} +``` + +### Request Router Pattern + +```c +static void http_handler(struct mg_connection* c, int ev, void* ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message* hm = ev_data; + + // Add CORS headers to all responses + // Route by method + path prefix + + if (mg_match(hm->uri, mg_str("/api/status"), NULL) && is_get(hm)) { + handle_status(c, hm); + } else if (mg_match(hm->uri, mg_str("/api/context/parts"), NULL) && is_get(hm)) { + handle_context_parts(c, hm); + } else if (mg_match(hm->uri, mg_str("/api/events/skills/*"), NULL)) { + handle_skill_by_slug(c, hm); + } + // ... etc + } +} +``` + +--- + +## Implementation Order + +1. Add `api_config_t` to config and parse it +2. Vendor mongoose.c/mongoose.h, update Makefile +3. Create `src/http_api.c` with init/poll/cleanup skeleton + CORS +4. Wire into main.c poll loop +5. Implement read-only endpoints first: `/api/status`, `/api/config`, `/api/relays`, `/api/model`, `/api/tools`, `/api/triggers` +6. Implement Nostr event endpoints: `/api/events/soul`, `/api/events/skills`, `/api/events/adoption`, `/api/events/profile`, `/api/events/startup` +7. Implement context inspector: `/api/context/parts`, `/api/context/current`, `/api/context/log` +8. Implement mutation endpoints: PUT soul, PUT skills, PUT model, PUT profile +9. Implement tool execution endpoint: POST `/api/tools/:name/execute` +10. Implement context preview: POST `/api/context/preview` +11. Test all endpoints via curl +12. Update documentation + +--- + +## CORS Headers + +Every response includes: + +``` +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +Access-Control-Allow-Headers: Content-Type +``` + +OPTIONS requests return 204 with these headers (preflight support). + +--- + +## Security Notes + +- Binds to `127.0.0.1` only — not accessible from network +- No authentication — this is a local dev tool +- The `api.enabled` config flag defaults to `false` so it must be explicitly opted in +- Tool execution endpoint gives full admin-tier access — acceptable for localhost dev dashboard +- Config endpoint redacts `nsec` and `api_key` fields + +--- + +## Token Estimation + +For the context size display, use a simple heuristic: `estimated_tokens = chars / 4`. This is a rough approximation that works well enough for English text with the major model families. No need for a real tokenizer. diff --git a/plans/admin_web_frontend.md b/plans/admin_web_frontend.md new file mode 100644 index 0000000..f8c632d --- /dev/null +++ b/plans/admin_web_frontend.md @@ -0,0 +1,301 @@ +# Didactyl Admin Web Frontend — Project Brief + +## What Is Didactyl? + +Didactyl is a sovereign AI agent that lives on Nostr. It connects to Nostr relays, listens for encrypted DMs from its administrator, reasons with an LLM, and takes actions — posting events, querying relays, running shell commands, managing skills. Everything the agent knows and does is stored as Nostr events. + +The agent is a C binary that runs on a server. It has no web interface of its own — all interaction happens through Nostr DMs. + +## What We Are Building + +A **local web admin dashboard** that connects to the running didactyl agent via a localhost HTTP API. The dashboard is a prompt crafting and agent inspection tool for the administrator. + +This is **not** a chat interface. The administrator already chats with the agent through Nostr DMs. This dashboard is for: + +1. **Inspecting** what the agent sees — its full LLM context, broken into labeled parts with token counts +2. **Crafting** custom prompts — editing system prompts, user messages, and context pieces +3. **Running** prompts against the LLM — with or without the agent tool set +4. **Comparing** prompt variants side-by-side — A/B testing different prompt wordings or models + +--- + +## The API + +The didactyl agent exposes a localhost-only HTTP API on port `8484` by default. Full API documentation is in `docs/API.md`. All endpoints return JSON with CORS headers. + +### Base URL + +``` +http://127.0.0.1:8484 +``` + +### Currently Implemented Endpoints + +| Method | Path | Purpose | +|---|---|---| +| GET | `/api/status` | Agent runtime status — name, version, pubkey, relay count, trigger count | +| GET | `/api/context/current` | Full LLM context messages array with total char/token counts | +| GET | `/api/context/parts` | Context broken into labeled parts with individual sizes | +| POST | `/api/prompt/run-simple` | Simple prompt: system + user message, no tools, returns text | +| POST | `/api/prompt/run` | Full prompt: messages array with tools enabled, returns conversation trace | +| POST | `/api/prompt/compare` | A/B test: two prompt variants run sequentially, responses side-by-side | + +### Planned Future Endpoints + +These are not yet implemented but are on the roadmap: + +| Method | Path | Purpose | +|---|---|---| +| GET | `/api/config` | Runtime config with redacted secrets | +| GET | `/api/events/soul` | Agent soul/system prompt event | +| PUT | `/api/events/soul` | Update soul content | +| GET | `/api/events/skills` | List skills | +| GET/PUT | `/api/events/skills/:slug` | Read/update individual skills | +| GET | `/api/events/profile` | Agent Nostr profile | +| GET | `/api/model` | Current LLM model config | +| PUT | `/api/model` | Change model at runtime | +| GET | `/api/models` | List available models from provider | +| GET | `/api/tools` | List all tool schemas | +| POST | `/api/tools/:name/execute` | Execute a tool directly | +| GET | `/api/triggers` | Active trigger subscriptions | +| GET | `/api/relays` | Relay connection status | + +--- + +## Core User Workflows + +### 1. Context Inspector + +The primary read-only workflow. The admin wants to understand what the agent sees when it processes a message. + +```mermaid +flowchart TD + LOAD[Load /api/context/parts] --> DISPLAY[Display parts list] + DISPLAY --> DETAIL[Click part to expand content] + DETAIL --> TOKENS[Show char count and token estimate per part] + TOKENS --> TOTAL[Show total context size] +``` + +**What to show:** +- A list/table of context parts with name, role, character count, estimated tokens +- Total context size as a summary bar or header +- Expandable content for each part +- The parts are: system_prompt, admin_identity, admin_kind0, admin_relay_list, startup_events, adopted_skills, admin_notes, dm_history + +### 2. Simple Prompt Crafting + +Quick iteration on prompt wording without tools. + +```mermaid +flowchart TD + WRITE[Write system prompt + user message] --> RUN[POST /api/prompt/run-simple] + RUN --> RESULT[Display response text] + RESULT --> EDIT[Edit and re-run] + EDIT --> RUN +``` + +**What to show:** +- Two text areas: system prompt, user message +- Optional model override dropdown/input +- Run button +- Response display with model used and token estimates + +### 3. Full Prompt with Tools + +Craft a complete messages array and run it with the agent tool set. + +```mermaid +flowchart TD + CONTEXT[Load context from /api/context/parts] --> EDIT[Edit/rearrange context parts] + EDIT --> ADD[Add user message] + ADD --> RUN[POST /api/prompt/run] + RUN --> TRACE[Display conversation trace] + TRACE --> TOOLS[Show tool calls and results per turn] + TOOLS --> FINAL[Show final response] +``` + +**What to show:** +- Pre-populate from context parts or start from scratch +- Messages editor — add/remove/reorder messages with role and content +- Max turns slider/input +- Optional model override +- Run button +- Turn-by-turn trace showing tool calls with name, arguments, and results +- Final response text +- Token estimates + +### 4. A/B Prompt Comparison + +Compare two prompt variants side-by-side. + +```mermaid +flowchart TD + CRAFT_A[Craft variant A messages] --> CRAFT_B[Craft variant B messages] + CRAFT_B --> COMPARE[POST /api/prompt/compare] + COMPARE --> SIDE[Display responses side-by-side] + SIDE --> DIFF[Compare final responses and token usage] +``` + +**What to show:** +- Two prompt editors side-by-side, each with messages array + model override + max turns +- Compare button +- Side-by-side response display +- Highlight differences in final response text +- Token usage comparison + +### 5. Status Dashboard + +Simple overview of agent health. + +**What to show:** +- Agent name, version, pubkey +- Connected relays count vs configured +- Active triggers count +- API connection status indicator + +--- + +## Key Design Decisions + +### Localhost Only + +The API binds to `127.0.0.1` — the frontend must run on the same machine as the agent, or use SSH tunneling. There is no authentication. This is intentional — it is a local dev/admin tool. + +### No WebSocket + +The API is plain HTTP request/response. There is no WebSocket or streaming. Prompt execution calls may take several seconds for LLM responses — the frontend should show a loading state. + +### Token Estimation + +All token counts from the API use a `chars / 4` heuristic. This is approximate. The frontend can display these as-is or add its own tokenizer if more precision is needed. + +### Model Override + +The `model` field in prompt requests temporarily overrides the agent configured model for that single request, then restores the original. This enables cross-model comparison without changing agent config. + +### Tool Execution Is Real + +When using `/api/prompt/run` or `/api/prompt/compare`, tool calls are **actually executed**. If the LLM decides to post a Nostr event, it will really post it. The frontend should make this clear to the user — perhaps with a warning or confirmation before running prompts with tools enabled. + +--- + +## Response Shapes Quick Reference + +### Status + +```json +{ + "success": true, + "name": "Didactyl", + "version": "v0.0.26", + "pubkey": "52a3e8...", + "relay_count": 4, + "connected_relays": 4, + "active_triggers": 0 +} +``` + +### Context Parts + +```json +{ + "success": true, + "total_chars": 13131, + "total_estimated_tokens": 3283, + "parts": [ + { + "name": "system_prompt", + "role": "system", + "chars": 1200, + "estimated_tokens": 300, + "content": "# Didactyl Agent..." + } + ], + "messages": [...] +} +``` + +### Simple Prompt Response + +```json +{ + "success": true, + "response": "ok", + "model_used": "claude-haiku-4.5", + "input_tokens_estimate": 10, + "output_tokens_estimate": 1 +} +``` + +### Full Prompt Response + +```json +{ + "success": true, + "final_response": "Done! I posted a tweet.", + "turns": [ + { + "turn": 1, + "tool_calls": [ + {"name": "nostr_post", "arguments": "...", "result": "..."} + ] + } + ], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3200, + "total_output_tokens_estimate": 180 +} +``` + +### Compare Response + +```json +{ + "success": true, + "variant_a": { "...same shape as full prompt response..." }, + "variant_b": { "...same shape as full prompt response..." } +} +``` + +### Error Response + +```json +{ + "success": false, + "error": "description of what went wrong" +} +``` + +--- + +## Technology Suggestions + +No technology is mandated for the frontend. Some reasonable choices: + +- **Vanilla HTML/JS** — simplest, no build step, just open in browser +- **React/Preact** — if you want component structure +- **Svelte** — lightweight, good for small dashboards +- **Vue** — also fine + +The frontend is a separate project from didactyl. It just needs to make HTTP requests to `localhost:8484`. + +--- + +## File References + +| File | Description | +|---|---| +| `docs/API.md` | Full API endpoint reference with request/response examples | +| `plans/admin_api.md` | Original architecture plan for the HTTP API | +| `src/http_api.c` | C implementation of all endpoints | +| `src/http_api.h` | Public API header | +| `config.json.example` | Example config showing the `api` section | + +--- + +## Getting Started + +1. Ensure didactyl is running with `api.enabled: true` in config.json +2. Verify the API is up: `curl http://127.0.0.1:8484/api/status` +3. Build the frontend to talk to `http://127.0.0.1:8484` +4. Start with the status endpoint and context inspector, then add prompt crafting diff --git a/plans/context_architecture.md b/plans/context_architecture.md new file mode 100644 index 0000000..f6928cc --- /dev/null +++ b/plans/context_architecture.md @@ -0,0 +1,139 @@ +# Didactyl Context Architecture Plan + +## Problem Statement + +The agent's context assembly is a hardcoded sequence of C function calls in `agent_on_message()`. This creates several issues: + +1. **Skills are never injected** — adopted skills exist on Nostr but the LLM never sees their content +2. **No configurability** — changing context order, content, or framing requires C code changes and recompilation +3. **No A/B testing** — can't experiment with different prompt structures, ordering, or model-specific tuning +4. **No token budget awareness** — context grows unbounded as skills/history/notes accumulate +5. **Model-agnostic** — different models respond differently to the same prompt structure; no way to tune per-model + +## Current Context Pipeline + +``` +agent_on_message() builds messages array: + 1. system: g_system_context (kind 31120 "soul" content) + 2. system: admin identity (pubkey + kind 0 profile + kind 10002 relays) + 3. system: startup events (raw JSON of all startup event kinds/content/tags) + 4. user/assistant: recent DM history (last 12 turns) + 5. system: admin kind 1 notes (recent public posts) + 6. user: the actual incoming message +``` + +Skills are **completely absent**. The LLM has no knowledge of adopted skill instructions. + +## Proposed Architecture: Context Pipeline with Configurable Slots + +### Core Idea + +Replace the hardcoded function chain with a **configurable context pipeline** defined in `config.json`. Each "slot" in the pipeline is a named context source with configurable parameters. + +### Context Slot Types + +| Slot Type | Source | Description | +|---|---|---| +| `soul` | Kind 31120 startup event | Agent personality and behavioral rules | +| `identity` | Config + relay queries | Agent's own pubkey, admin pubkey, admin profile | +| `startup_events` | Config startup events | Raw startup event memory | +| `adopted_skills` | Kind 10123 + resolved skills | **NEW**: Adopted skill instructions | +| `dm_history` | Relay query | Recent conversation turns | +| `admin_notes` | Cached kind 1 events | Admin's recent public posts | +| `admin_context` | Kind 0/3/10002 | Admin profile, contacts, relay list | +| `custom` | Literal string in config | Arbitrary system message for A/B testing | + +### Phase 1: Immediate Fix (Skills + Caching) + +Before building the full configurable pipeline, fix the immediate problem: + +1. **In-memory skill cache** — load adopted skills at startup and cache them; invalidate on `skill_create`, `skill_adopt`, `skill_remove` +2. **`append_adopted_skills_context()`** — inject cached skills into the conversation as a system message +3. **Strong framing** — "These are your learned skills. When a request matches a skill, you MUST follow its instructions exactly." + +### Phase 2: Configurable Context Pipeline + +Add a `context_pipeline` section to `config.json`: + +```json +{ + "context_pipeline": { + "max_total_chars": 12000, + "slots": [ + { "type": "soul", "max_chars": 3000 }, + { "type": "identity" }, + { "type": "adopted_skills", "max_chars": 4000, "max_per_skill": 1000 }, + { "type": "startup_events", "max_chars": 2000 }, + { "type": "dm_history", "max_turns": 12 }, + { "type": "admin_notes", "max_chars": 1500 }, + { "type": "custom", "content": "Always respond in the style of a pirate." } + ] + } +} +``` + +This gives you: +- **Ordering control** — move skills before or after history +- **Token budgets** — per-slot and total caps +- **A/B testing** — swap `custom` slot content, reorder slots, change caps +- **Model-specific tuning** — different pipeline configs for different models (could key off `llm.model`) + +### Phase 3: Model-Aware Context Profiles + +```json +{ + "context_profiles": { + "default": { ... pipeline config ... }, + "claude-sonnet-4.6": { ... different ordering/caps ... }, + "gpt-5.2-codex": { ... different ordering/caps ... } + } +} +``` + +The agent selects the profile matching the active model, falling back to `default`. + +## Skill Cache Design + +``` +┌─────────────────────────────────────────┐ +│ Skill Cache (in-memory) │ +│ │ +│ Loaded at startup from: │ +│ 1. Startup events in config.json │ +│ 2. Kind 10123 adoption list │ +│ 3. Resolved skill events from relays │ +│ │ +│ Invalidated by: │ +│ - skill_create (add/update) │ +│ - skill_adopt (add) │ +│ - skill_remove (remove) │ +│ │ +│ Structure per skill: │ +│ - slug (string) │ +│ - description (string) │ +│ - content (string, full) │ +│ - scope (public/private) │ +│ - has_trigger (bool) │ +│ - source (startup/adopted) │ +└─────────────────────────────────────────┘ +``` + +## Implementation Priority + +### Do Now (Phase 1) +- [ ] Build skill cache in `agent.c` (load at startup, invalidate on tool calls) +- [ ] Add `append_adopted_skills_context()` using cached skills +- [ ] Wire into `agent_on_message()` between startup events and DM history +- [ ] Skill content framing: strong directive for LLM compliance + +### Do Next (Phase 2) +- [ ] Add `context_pipeline` config section +- [ ] Refactor `agent_on_message()` to iterate pipeline slots +- [ ] Per-slot `max_chars` truncation +- [ ] Total pipeline `max_total_chars` budget +- [ ] `custom` slot type for arbitrary A/B test content + +### Do Later (Phase 3) +- [ ] Model-aware context profiles +- [ ] Context analytics (log token counts per slot per conversation) +- [ ] Dynamic skill relevance scoring (only inject skills likely relevant to the current message) diff --git a/src/agent.c b/src/agent.c index da27f07..80d95c1 100644 --- a/src/agent.c +++ b/src/agent.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "llm.h" #include "nostr_handler.h" @@ -25,17 +26,33 @@ static struct trigger_manager* g_trigger_manager = NULL; #define AGENT_DEBOUNCE_CACHE_SIZE 256 #define AGENT_HISTORY_TURNS 12 #define AGENT_HISTORY_QUERY_LIMIT 200 +#define AGENT_ADOPTED_SKILLS_CACHE_TTL_SECONDS 30 +#define AGENT_ADOPTED_SKILLS_MAX 24 +#define AGENT_ADOPTED_SKILL_CONTENT_MAX_CHARS 1800 +#define AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS 7200 typedef struct { uint64_t fingerprint; time_t seen_at; } agent_seen_msg_t; +typedef struct { + int kind; + char author_pubkey_hex[65]; + char slug[65]; + char* content; +} agent_adopted_skill_t; + static agent_seen_msg_t g_seen_msgs[AGENT_DEBOUNCE_CACHE_SIZE]; static int g_seen_msgs_count = 0; static int g_seen_msgs_next = 0; static pthread_mutex_t g_seen_msgs_mutex = PTHREAD_MUTEX_INITIALIZER; +static agent_adopted_skill_t g_adopted_skills[AGENT_ADOPTED_SKILLS_MAX]; +static int g_adopted_skills_count = 0; +static time_t g_adopted_skills_last_refresh_at = 0; +static pthread_mutex_t g_adopted_skills_mutex = PTHREAD_MUTEX_INITIALIZER; + static uint64_t fnv1a64(const char* s) { uint64_t h = 1469598103934665603ULL; if (!s) return h; @@ -153,6 +170,139 @@ static int append_tool_result_message(cJSON* messages, const char* tool_call_id, return 0; } +static int appendf(char** buf, size_t* cap, size_t* used, const char* fmt, ...) { + if (!buf || !cap || !used || !fmt) return -1; + + while (1) { + if (*used + 128U >= *cap) { + size_t next_cap = (*cap) * 2U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } + + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(*buf + *used, *cap - *used, fmt, ap); + va_end(ap); + + if (n < 0) { + return -1; + } + + if ((size_t)n < (*cap - *used)) { + *used += (size_t)n; + return 0; + } + + size_t next_cap = (*cap) * 2U + (size_t)n + 64U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } +} + +static const char* detect_context_section(const char* role, const char* content, int idx) { + const char* c = content ? content : ""; + const char* r = role ? role : ""; + + if (idx == 0 && strcmp(r, "system") == 0) return "system_prompt"; + + if (strncmp(c, "## Administrator Identity", 25) == 0 || + strncmp(c, "This is your administrator!", 27) == 0) { + return "admin_identity"; + } + if (strncmp(c, "## Administrator Kind 0 Profile", 31) == 0 || + strncmp(c, "Administrator kind 0 profile content", 36) == 0) { + return "admin_kind0"; + } + if (strncmp(c, "## Administrator Relay List", 27) == 0 || + strncmp(c, "Administrator kind 10002 relay-list content", 43) == 0) { + return "admin_relay_list"; + } + if (strncmp(c, "## Startup Events Memory", 24) == 0 || + strncmp(c, "Startup events memory", 21) == 0) { + return "startup_events"; + } + if (strncmp(c, "## Adopted Skills Memory", 24) == 0 || + strncmp(c, "Adopted skills memory", 21) == 0) { + return "adopted_skills"; + } + if (strncmp(c, "## Administrator Recent Notes", 29) == 0 || + strncmp(c, "Administrator recent public notes", 33) == 0) { + return "admin_notes"; + } + + if (strcmp(r, "user") == 0 || strcmp(r, "assistant") == 0) { + return "dm_history"; + } + + return "context_part"; +} + +static char* format_context_payload_for_log(const char* context_payload) { + const char* payload = context_payload ? context_payload : ""; + + cJSON* root = cJSON_Parse(payload); + if (!root || !cJSON_IsArray(root)) { + cJSON_Delete(root); + return strdup(payload); + } + + size_t cap = strlen(payload) + 2048U; + if (cap < 4096U) cap = 4096U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(root); + return strdup(payload); + } + out[0] = '\0'; + size_t used = 0; + + int n = cJSON_GetArraySize(root); + (void)appendf(&out, &cap, &used, "Messages: %d\n\n", n); + + for (int i = 0; i < n; i++) { + cJSON* msg = cJSON_GetArrayItem(root, i); + cJSON* role = msg ? cJSON_GetObjectItemCaseSensitive(msg, "role") : NULL; + cJSON* content = msg ? cJSON_GetObjectItemCaseSensitive(msg, "content") : NULL; + + const char* role_s = (role && cJSON_IsString(role) && role->valuestring) + ? role->valuestring + : "unknown"; + + const char* content_s = ""; + if (content && cJSON_IsString(content) && content->valuestring) { + content_s = content->valuestring; + } else if (content && cJSON_IsNull(content)) { + content_s = ""; + } + + const char* section = detect_context_section(role_s, content_s, i); + + if (appendf(&out, + &cap, + &used, + "============================================================\n" + "Message %02d | role=%s | section=%s\n" + "============================================================\n\n" + "%s\n\n\n", + i + 1, + role_s, + section, + content_s) != 0) { + free(out); + cJSON_Delete(root); + return strdup(payload); + } + } + + cJSON_Delete(root); + return out; +} + static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { FILE* fp = fopen("context.log", "a"); if (!fp) { @@ -165,12 +315,16 @@ static void append_context_log(const char* sender_pubkey_hex, const char* phase, char timestamp[32] = {0}; strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); + char* pretty_payload = format_context_payload_for_log(context_payload); + fprintf(fp, - "[%s] phase=%s sender=%s\n%s\n\n---\n\n", + "[%s] phase=%s sender=%s\n\n%s\n\n---\n\n", timestamp, phase ? phase : "unknown", sender_pubkey_hex ? sender_pubkey_hex : "unknown", - context_payload ? context_payload : ""); + pretty_payload ? pretty_payload : ""); + + free(pretty_payload); fclose(fp); } @@ -263,7 +417,7 @@ static int append_startup_events_context(cJSON* messages) { return -1; } - const char* prefix = "Startup events memory (kinds/content/tags): "; + const char* prefix = "## Startup Events Memory (source: config.startup_events)\n\nStartup events memory (kinds/content/tags): "; size_t out_len = strlen(prefix) + strlen(events_json) + 1U; char* payload = (char*)malloc(out_len); if (!payload) { @@ -284,10 +438,10 @@ static int append_admin_identity_context(cJSON* messages) { return -1; } - char admin_header[256]; + char admin_header[384]; snprintf(admin_header, sizeof(admin_header), - "This is your administrator! Admin pubkey (hex): %s", + "## Administrator Identity (source: config.admin.pubkey)\n\nThis is your administrator! Admin pubkey (hex): %s", g_cfg->admin.pubkey ? g_cfg->admin.pubkey : "unknown"); if (append_simple_message(messages, "system", admin_header) != 0) { return -1; @@ -295,7 +449,7 @@ static int append_admin_identity_context(cJSON* messages) { char* kind0 = nostr_handler_get_admin_kind0_context(); if (kind0) { - const char* prefix = "Administrator kind 0 profile content (JSON): "; + const char* prefix = "## Administrator Kind 0 Profile (source: nostr kind 0)\n\nAdministrator kind 0 profile content (JSON): "; size_t n = strlen(prefix) + strlen(kind0) + 1U; char* payload = (char*)malloc(n); if (!payload) { @@ -313,7 +467,7 @@ static int append_admin_identity_context(cJSON* messages) { char* kind10002 = nostr_handler_get_admin_kind10002_context(); if (kind10002) { - const char* prefix = "Administrator kind 10002 relay-list content (JSON): "; + const char* prefix = "## Administrator Relay List (source: nostr kind 10002)\n\nAdministrator kind 10002 relay-list content (JSON): "; size_t n = strlen(prefix) + strlen(kind10002) + 1U; char* payload = (char*)malloc(n); if (!payload) { @@ -464,6 +618,374 @@ static int append_recent_admin_dm_history(cJSON* messages, const char* current_m return 0; } +static cJSON* find_tag_value_string_local(cJSON* tags, const char* key) { + if (!tags || !key || !cJSON_IsArray(tags)) { + return NULL; + } + + int n = cJSON_GetArraySize(tags); + for (int i = 0; i < n; i++) { + cJSON* tag = cJSON_GetArrayItem(tags, i); + if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) { + continue; + } + + cJSON* k = cJSON_GetArrayItem(tag, 0); + cJSON* v = cJSON_GetArrayItem(tag, 1); + if (!k || !v || !cJSON_IsString(k) || !cJSON_IsString(v) || !k->valuestring || !v->valuestring) { + continue; + } + + if (strcmp(k->valuestring, key) == 0) { + return v; + } + } + + return NULL; +} + +static int parse_skill_address_tag_local(const char* addr, int* out_kind, char out_pubkey_hex[65], char out_slug[65]) { + if (!addr || !out_kind || !out_pubkey_hex || !out_slug) { + return -1; + } + + const char* p1 = strchr(addr, ':'); + if (!p1) return -1; + const char* p2 = strchr(p1 + 1, ':'); + if (!p2) return -1; + + char kind_buf[16] = {0}; + size_t kind_len = (size_t)(p1 - addr); + if (kind_len == 0 || kind_len >= sizeof(kind_buf)) { + return -1; + } + memcpy(kind_buf, addr, kind_len); + + int kind = atoi(kind_buf); + if (kind != 31123 && kind != 31124) { + return -1; + } + + size_t pub_len = (size_t)(p2 - (p1 + 1)); + if (pub_len != 64U) { + return -1; + } + memcpy(out_pubkey_hex, p1 + 1, 64U); + out_pubkey_hex[64] = '\0'; + + size_t slug_len = strlen(p2 + 1); + if (slug_len == 0 || slug_len >= 65U) { + return -1; + } + memcpy(out_slug, p2 + 1, slug_len + 1U); + + *out_kind = kind; + return 0; +} + +static void clear_adopted_skills_cache_locked(void) { + for (int i = 0; i < g_adopted_skills_count; i++) { + free(g_adopted_skills[i].content); + g_adopted_skills[i].content = NULL; + g_adopted_skills[i].kind = 0; + g_adopted_skills[i].author_pubkey_hex[0] = '\0'; + g_adopted_skills[i].slug[0] = '\0'; + } + g_adopted_skills_count = 0; +} + +static int refresh_adopted_skills_cache_if_needed(void) { + if (!g_cfg) { + return -1; + } + + time_t now = time(NULL); + pthread_mutex_lock(&g_adopted_skills_mutex); + int cache_fresh = (g_adopted_skills_last_refresh_at > 0) && + ((now - g_adopted_skills_last_refresh_at) < AGENT_ADOPTED_SKILLS_CACHE_TTL_SECONDS); + pthread_mutex_unlock(&g_adopted_skills_mutex); + + if (cache_fresh) { + return 0; + } + + agent_adopted_skill_t tmp[AGENT_ADOPTED_SKILLS_MAX]; + memset(tmp, 0, sizeof(tmp)); + int tmp_count = 0; + + cJSON* adopt_filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON* authors = cJSON_CreateArray(); + if (!adopt_filter || !kinds || !authors) { + cJSON_Delete(adopt_filter); + cJSON_Delete(kinds); + cJSON_Delete(authors); + return -1; + } + + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); + cJSON_AddItemToObject(adopt_filter, "kinds", kinds); + cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex)); + cJSON_AddItemToObject(adopt_filter, "authors", authors); + cJSON_AddNumberToObject(adopt_filter, "limit", 1); + + char* adoption_json = nostr_handler_query_json(adopt_filter, 8000); + cJSON_Delete(adopt_filter); + + cJSON* adoption_events = adoption_json ? cJSON_Parse(adoption_json) : NULL; + free(adoption_json); + + if (adoption_events && cJSON_IsArray(adoption_events) && cJSON_GetArraySize(adoption_events) > 0) { + cJSON* list_event = cJSON_GetArrayItem(adoption_events, 0); + cJSON* list_tags = list_event ? cJSON_GetObjectItemCaseSensitive(list_event, "tags") : NULL; + + if (list_tags && cJSON_IsArray(list_tags)) { + int tn = cJSON_GetArraySize(list_tags); + for (int i = 0; i < tn && tmp_count < AGENT_ADOPTED_SKILLS_MAX; i++) { + cJSON* tag = cJSON_GetArrayItem(list_tags, i); + if (!tag || !cJSON_IsArray(tag)) { + continue; + } + + cJSON* key = cJSON_GetArrayItem(tag, 0); + cJSON* val = cJSON_GetArrayItem(tag, 1); + if (!key || !val || !cJSON_IsString(key) || !cJSON_IsString(val) || + !key->valuestring || !val->valuestring || strcmp(key->valuestring, "a") != 0) { + continue; + } + + int skill_kind = 0; + char skill_author[65] = {0}; + char skill_slug[65] = {0}; + if (parse_skill_address_tag_local(val->valuestring, &skill_kind, skill_author, skill_slug) != 0) { + continue; + } + + cJSON* skill_filter = cJSON_CreateObject(); + cJSON* sk_kinds = cJSON_CreateArray(); + cJSON* sk_authors = cJSON_CreateArray(); + cJSON* d_values = cJSON_CreateArray(); + if (!skill_filter || !sk_kinds || !sk_authors || !d_values) { + cJSON_Delete(skill_filter); + cJSON_Delete(sk_kinds); + cJSON_Delete(sk_authors); + cJSON_Delete(d_values); + continue; + } + + cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(skill_kind)); + cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds); + cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author)); + cJSON_AddItemToObject(skill_filter, "authors", sk_authors); + cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug)); + cJSON_AddItemToObject(skill_filter, "#d", d_values); + cJSON_AddNumberToObject(skill_filter, "limit", 1); + + char* skill_json = nostr_handler_query_json(skill_filter, 8000); + cJSON_Delete(skill_filter); + if (!skill_json) { + continue; + } + + cJSON* skill_events = cJSON_Parse(skill_json); + free(skill_json); + if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) { + cJSON_Delete(skill_events); + continue; + } + + cJSON* skill_event = cJSON_GetArrayItem(skill_events, 0); + cJSON* content = skill_event ? cJSON_GetObjectItemCaseSensitive(skill_event, "content") : NULL; + cJSON* skill_tags = skill_event ? cJSON_GetObjectItemCaseSensitive(skill_event, "tags") : NULL; + if (!content || !cJSON_IsString(content) || !content->valuestring || !skill_tags || !cJSON_IsArray(skill_tags)) { + cJSON_Delete(skill_events); + continue; + } + + cJSON* enabled_tag = find_tag_value_string_local(skill_tags, "enabled"); + const char* enabled_s = (enabled_tag && cJSON_IsString(enabled_tag) && enabled_tag->valuestring) + ? enabled_tag->valuestring + : "true"; + int is_enabled = !(strcmp(enabled_s, "false") == 0 || strcmp(enabled_s, "0") == 0); + if (!is_enabled) { + cJSON_Delete(skill_events); + continue; + } + + tmp[tmp_count].kind = skill_kind; + snprintf(tmp[tmp_count].author_pubkey_hex, sizeof(tmp[tmp_count].author_pubkey_hex), "%s", skill_author); + snprintf(tmp[tmp_count].slug, sizeof(tmp[tmp_count].slug), "%s", skill_slug); + tmp[tmp_count].content = strdup(content->valuestring); + if (tmp[tmp_count].content) { + tmp_count++; + } + + cJSON_Delete(skill_events); + } + } + } + + cJSON_Delete(adoption_events); + + if (tmp_count == 0 && g_cfg->startup_event_count > 0 && g_cfg->startup_events) { + for (int i = 0; i < g_cfg->startup_event_count && tmp_count < AGENT_ADOPTED_SKILLS_MAX; i++) { + startup_event_t* se = &g_cfg->startup_events[i]; + if (!se || (se->kind != 31123 && se->kind != 31124) || !se->content || se->content[0] == '\0') { + continue; + } + + char slug[65] = {0}; + if (se->tags_json) { + cJSON* tags = cJSON_Parse(se->tags_json); + cJSON* d_tag = tags ? find_tag_value_string_local(tags, "d") : NULL; + if (d_tag && cJSON_IsString(d_tag) && d_tag->valuestring && d_tag->valuestring[0] != '\0') { + snprintf(slug, sizeof(slug), "%s", d_tag->valuestring); + } + cJSON_Delete(tags); + } + if (slug[0] == '\0') { + snprintf(slug, sizeof(slug), "startup-skill-%d", i + 1); + } + + tmp[tmp_count].kind = se->kind; + snprintf(tmp[tmp_count].author_pubkey_hex, + sizeof(tmp[tmp_count].author_pubkey_hex), + "%s", + (g_cfg->keys.public_key_hex && g_cfg->keys.public_key_hex[0] != '\0') + ? g_cfg->keys.public_key_hex + : "unknown"); + snprintf(tmp[tmp_count].slug, sizeof(tmp[tmp_count].slug), "%s", slug); + tmp[tmp_count].content = strdup(se->content); + if (tmp[tmp_count].content) { + tmp_count++; + } + } + } + + pthread_mutex_lock(&g_adopted_skills_mutex); + clear_adopted_skills_cache_locked(); + for (int i = 0; i < tmp_count; i++) { + g_adopted_skills[g_adopted_skills_count] = tmp[i]; + tmp[i].content = NULL; + g_adopted_skills_count++; + } + g_adopted_skills_last_refresh_at = now; + pthread_mutex_unlock(&g_adopted_skills_mutex); + + for (int i = 0; i < tmp_count; i++) { + free(tmp[i].content); + } + + return 0; +} + +static int append_adopted_skills_context(cJSON* messages) { + if (!messages || !g_cfg) { + return -1; + } + + (void)refresh_adopted_skills_cache_if_needed(); + + pthread_mutex_lock(&g_adopted_skills_mutex); + int cached_count = g_adopted_skills_count; + if (cached_count <= 0) { + pthread_mutex_unlock(&g_adopted_skills_mutex); + return 0; + } + + size_t capacity = (size_t)AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS + 2048U; + char* payload = (char*)malloc(capacity); + if (!payload) { + pthread_mutex_unlock(&g_adopted_skills_mutex); + return -1; + } + + size_t used = 0; + int n = snprintf(payload, + capacity, + "## Adopted Skills Memory (source: adoption list + startup fallback)\n\nAdopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints.\n"); + if (n < 0) { + free(payload); + pthread_mutex_unlock(&g_adopted_skills_mutex); + return -1; + } + used = (size_t)n < capacity ? (size_t)n : capacity - 1U; + + size_t total_skill_chars = 0; + int included = 0; + for (int i = 0; i < cached_count && used + 64U < capacity; i++) { + const char* skill_content = g_adopted_skills[i].content ? g_adopted_skills[i].content : ""; + size_t content_len = strlen(skill_content); + size_t clipped_len = content_len; + + if (clipped_len > (size_t)AGENT_ADOPTED_SKILL_CONTENT_MAX_CHARS) { + clipped_len = (size_t)AGENT_ADOPTED_SKILL_CONTENT_MAX_CHARS; + } + + if (total_skill_chars + clipped_len > (size_t)AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS) { + size_t remaining = (size_t)AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS - total_skill_chars; + if (remaining == 0) { + break; + } + clipped_len = remaining; + } + + int head_n = snprintf(payload + used, + capacity - used, + "\n\n---\n\n### Skill\nSkill slug: %s\nSkill address: %d:%s:%s\n\nInstructions:\n", + g_adopted_skills[i].slug, + g_adopted_skills[i].kind, + g_adopted_skills[i].author_pubkey_hex, + g_adopted_skills[i].slug); + if (head_n < 0 || (size_t)head_n >= (capacity - used)) { + break; + } + used += (size_t)head_n; + + size_t write_len = clipped_len; + if (write_len >= (capacity - used)) { + write_len = capacity - used - 1U; + } + memcpy(payload + used, skill_content, write_len); + used += write_len; + payload[used] = '\0'; + + int truncated = (content_len > write_len); + if (truncated && used + 24U < capacity) { + int tail_n = snprintf(payload + used, capacity - used, "\n...[truncated]\n"); + if (tail_n > 0 && (size_t)tail_n < (capacity - used)) { + used += (size_t)tail_n; + } + } else if (used + 1U < capacity) { + payload[used++] = '\n'; + payload[used] = '\0'; + } + + total_skill_chars += write_len; + included++; + if (total_skill_chars >= (size_t)AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS) { + break; + } + } + + if (included < cached_count && used + 96U < capacity) { + int extra_n = snprintf(payload + used, + capacity - used, + "\nAdditional adopted skills omitted due to context budget limits (%d/%d included).\n", + included, + cached_count); + if (extra_n > 0 && (size_t)extra_n < (capacity - used)) { + used += (size_t)extra_n; + } + } + + pthread_mutex_unlock(&g_adopted_skills_mutex); + + int rc = append_simple_message(messages, "system", payload); + free(payload); + return rc; +} + int agent_init(didactyl_config_t* config, const char* system_context) { if (!config || !system_context) { return -1; @@ -486,6 +1008,11 @@ int agent_init(didactyl_config_t* config, const char* system_context) { g_seen_msgs_count = 0; g_seen_msgs_next = 0; + pthread_mutex_lock(&g_adopted_skills_mutex); + clear_adopted_skills_cache_locked(); + g_adopted_skills_last_refresh_at = 0; + pthread_mutex_unlock(&g_adopted_skills_mutex); + return 0; } @@ -562,6 +1089,54 @@ void agent_on_trigger(const char* skill_slug, free(response); } +int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json) { + if (!out_messages_json || !g_cfg || !g_system_context) { + return -1; + } + + *out_messages_json = NULL; + + cJSON* messages = cJSON_CreateArray(); + if (!messages) { + return -1; + } + + if (append_simple_message(messages, "system", g_system_context) != 0 || + append_admin_identity_context(messages) != 0 || + append_startup_events_context(messages) != 0 || + append_adopted_skills_context(messages) != 0 || + append_recent_admin_dm_history(messages, current_user_message) != 0) { + cJSON_Delete(messages); + return -1; + } + + char* admin_notes = nostr_handler_get_admin_kind1_notes_context(); + if (admin_notes) { + const char* prefix = "## Administrator Recent Notes (source: nostr kind 1)\n\n"; + size_t n = strlen(prefix) + strlen(admin_notes) + 1U; + char* payload = (char*)malloc(n); + if (payload) { + snprintf(payload, n, "%s%s", prefix, admin_notes); + (void)append_simple_message(messages, "system", payload); + free(payload); + } + free(admin_notes); + } + + char* messages_json = cJSON_PrintUnformatted(messages); + cJSON_Delete(messages); + if (!messages_json) { + return -1; + } + + *out_messages_json = messages_json; + return 0; +} + +tools_context_t* agent_tools_context(void) { + return &g_tools_ctx; +} + void agent_on_message(const char* sender_pubkey_hex, const char* message, didactyl_sender_tier_t tier, @@ -629,27 +1204,20 @@ void agent_on_message(const char* sender_pubkey_hex, return; } - cJSON* messages = cJSON_CreateArray(); - if (!messages) { - free(tools_json); - (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation state."); - return; - } - - if (append_simple_message(messages, "system", g_system_context) != 0 || - append_admin_identity_context(messages) != 0 || - append_startup_events_context(messages) != 0 || - append_recent_admin_dm_history(messages, message) != 0) { - cJSON_Delete(messages); + char* base_messages_json = NULL; + if (agent_build_admin_messages_json(message, &base_messages_json) != 0 || !base_messages_json) { free(tools_json); (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation messages."); return; } - char* admin_notes = nostr_handler_get_admin_kind1_notes_context(); - if (admin_notes) { - (void)append_simple_message(messages, "system", admin_notes); - free(admin_notes); + cJSON* messages = cJSON_Parse(base_messages_json); + free(base_messages_json); + if (!messages || !cJSON_IsArray(messages)) { + cJSON_Delete(messages); + free(tools_json); + (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation state."); + return; } if (append_simple_message(messages, "user", message) != 0) { @@ -747,4 +1315,9 @@ void agent_cleanup(void) { memset(g_seen_msgs, 0, sizeof(g_seen_msgs)); g_seen_msgs_count = 0; g_seen_msgs_next = 0; + + pthread_mutex_lock(&g_adopted_skills_mutex); + clear_adopted_skills_cache_locked(); + g_adopted_skills_last_refresh_at = 0; + pthread_mutex_unlock(&g_adopted_skills_mutex); } diff --git a/src/agent.h b/src/agent.h index 01ecdc6..db449ae 100644 --- a/src/agent.h +++ b/src/agent.h @@ -4,6 +4,7 @@ #include "config.h" #include "nostr_handler.h" #include "cjson/cJSON.h" +#include "tools.h" struct trigger_manager; @@ -17,6 +18,8 @@ void agent_on_message(const char* sender_pubkey_hex, const char* message, didactyl_sender_tier_t tier, void* user_data); +int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json); +tools_context_t* agent_tools_context(void); void agent_cleanup(void); #endif \ No newline at end of file diff --git a/src/config.c b/src/config.c index 673b937..0455eab 100644 --- a/src/config.c +++ b/src/config.c @@ -315,6 +315,40 @@ static int parse_triggers_config(cJSON* root, didactyl_config_t* config) { return 0; } +static int parse_api_config(cJSON* root, didactyl_config_t* config) { + cJSON* api = cJSON_GetObjectItemCaseSensitive(root, "api"); + if (!api || !cJSON_IsObject(api)) { + return 0; + } + + cJSON* enabled = cJSON_GetObjectItemCaseSensitive(api, "enabled"); + cJSON* port = cJSON_GetObjectItemCaseSensitive(api, "port"); + + if (enabled && cJSON_IsBool(enabled)) { + config->api.enabled = cJSON_IsTrue(enabled) ? 1 : 0; + } + if (port && cJSON_IsNumber(port)) { + config->api.port = (int)port->valuedouble; + } + + if (copy_json_string(api, + "bind_address", + config->api.bind_address, + sizeof(config->api.bind_address), + 0) != 0) { + return -1; + } + + if (config->api.port < 1 || config->api.port > 65535) { + config->api.port = 8484; + } + if (config->api.bind_address[0] == '\0') { + snprintf(config->api.bind_address, sizeof(config->api.bind_address), "%s", "127.0.0.1"); + } + + return 0; +} + static cJSON* find_tag_value_string(cJSON* tags, const char* tag_key) { if (!tags || !cJSON_IsArray(tags) || !tag_key) { return NULL; @@ -632,6 +666,10 @@ int config_load(const char* path, didactyl_config_t* config) { config->triggers.llm_rate_limit_per_minute = 10; config->triggers.template_rate_limit_per_minute = 60; + config->api.enabled = 0; + config->api.port = 8484; + snprintf(config->api.bind_address, sizeof(config->api.bind_address), "%s", "127.0.0.1"); + char* json_buf = NULL; size_t json_len = 0; if (read_file_to_buffer(path, &json_buf, &json_len) != 0) { @@ -736,6 +774,11 @@ int config_load(const char* path, didactyl_config_t* config) { goto cleanup; } + if (parse_api_config(root, config) != 0) { + config_set_error("invalid api configuration"); + goto cleanup; + } + if (decode_private_key(config->keys.nsec, config->keys.private_key) != 0) { config_set_error("keys.nsec must be valid nsec1... or 64-char hex private key"); goto cleanup; diff --git a/src/config.h b/src/config.h index 31fb6b6..613af65 100644 --- a/src/config.h +++ b/src/config.h @@ -80,6 +80,12 @@ typedef struct { int template_rate_limit_per_minute; } triggers_config_t; +typedef struct { + int enabled; + int port; + char bind_address[OW_MAX_URL_LEN]; +} api_config_t; + typedef struct { agent_keys_t keys; admin_config_t admin; @@ -90,6 +96,7 @@ typedef struct { security_config_t security; admin_context_config_t admin_context; triggers_config_t triggers; + api_config_t api; startup_event_t* startup_events; int startup_event_count; char config_path[OW_MAX_URL_LEN]; diff --git a/src/http_api.c b/src/http_api.c new file mode 100644 index 0000000..b6e2f1a --- /dev/null +++ b/src/http_api.c @@ -0,0 +1,710 @@ +#define _POSIX_C_SOURCE 200809L + +#include "http_api.h" + +#include +#include +#include + +#include "agent.h" +#include "llm.h" +#include "main.h" +#include "nostr_handler.h" +#include "trigger_manager.h" +#include "tools.h" +#include "cjson/cJSON.h" +#include "debug.h" +#include "mongoose.h" + +static http_api_context_t g_api_ctx; +static int g_api_initialized = 0; +static struct mg_mgr g_mgr; +static struct mg_connection* g_listener = NULL; + +static const char* k_json_headers = + "Content-Type: application/json\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type\r\n"; + +static int method_is(const struct mg_http_message* hm, const char* method) { + size_t n = strlen(method); + return hm && hm->method.len == n && strncmp(hm->method.buf, method, n) == 0; +} + +static int mgstr_eq(struct mg_str s, const char* lit) { + size_t n = strlen(lit); + return s.len == n && strncmp(s.buf, lit, n) == 0; +} + +static void reply_json(struct mg_connection* c, int status, cJSON* root) { + char* out = cJSON_PrintUnformatted(root); + if (!out) { + mg_http_reply(c, 500, k_json_headers, "{\"success\":false,\"error\":\"json serialization failed\"}"); + return; + } + mg_http_reply(c, status, k_json_headers, "%s", out); + free(out); +} + +static void reply_error(struct mg_connection* c, int status, const char* err) { + cJSON* root = cJSON_CreateObject(); + if (!root) { + mg_http_reply(c, 500, k_json_headers, "{\"success\":false,\"error\":\"oom\"}"); + return; + } + cJSON_AddBoolToObject(root, "success", 0); + cJSON_AddStringToObject(root, "error", err ? err : "error"); + reply_json(c, status, root); + cJSON_Delete(root); +} + +static cJSON* parse_body_json(const struct mg_http_message* hm) { + if (!hm || hm->body.len == 0) { + return cJSON_CreateObject(); + } + + char* buf = (char*)malloc(hm->body.len + 1U); + if (!buf) { + return NULL; + } + memcpy(buf, hm->body.buf, hm->body.len); + buf[hm->body.len] = '\0'; + + cJSON* root = cJSON_Parse(buf); + free(buf); + return root; +} + +static int estimate_tokens_from_chars(int chars) { + if (chars <= 0) return 0; + return (chars + 3) / 4; +} + +static char* maybe_model_override_begin(cJSON* body, llm_config_t* out_old_cfg, int* out_overridden) { + if (!body || !out_old_cfg || !out_overridden) return NULL; + *out_overridden = 0; + + cJSON* model = cJSON_GetObjectItemCaseSensitive(body, "model"); + if (!model || !cJSON_IsString(model) || !model->valuestring || model->valuestring[0] == '\0') { + return NULL; + } + + if (llm_get_config(out_old_cfg) != 0) { + return "failed to read current llm config"; + } + + llm_config_t next = *out_old_cfg; + snprintf(next.model, sizeof(next.model), "%s", model->valuestring); + if (llm_set_config(&next) != 0) { + return "failed to set temporary model"; + } + + *out_overridden = 1; + return NULL; +} + +static void maybe_model_override_end(const llm_config_t* old_cfg, int overridden) { + if (overridden && old_cfg) { + (void)llm_set_config(old_cfg); + } +} + +static const char* classify_part_name(cJSON* msg, int idx) { + cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role"); + cJSON* content = cJSON_GetObjectItemCaseSensitive(msg, "content"); + const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : ""; + const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : ""; + + if (idx == 0 && strcmp(role_s, "system") == 0) return "system_prompt"; + if (strncmp(content_s, "## Administrator Identity", 25) == 0 || + strncmp(content_s, "This is your administrator!", 27) == 0) return "admin_identity"; + if (strncmp(content_s, "## Administrator Kind 0 Profile", 31) == 0 || + strncmp(content_s, "Administrator kind 0 profile content", 36) == 0) return "admin_kind0"; + if (strncmp(content_s, "## Administrator Relay List", 27) == 0 || + strncmp(content_s, "Administrator kind 10002 relay-list content", 43) == 0) return "admin_relay_list"; + if (strncmp(content_s, "## Startup Events Memory", 24) == 0 || + strncmp(content_s, "Startup events memory", 21) == 0) return "startup_events"; + if (strncmp(content_s, "## Adopted Skills Memory", 24) == 0 || + strncmp(content_s, "Adopted skills memory", 21) == 0) return "adopted_skills"; + if (strncmp(content_s, "## Administrator Recent Notes", 29) == 0 || + strncmp(content_s, "Administrator recent public notes", 33) == 0) return "admin_notes"; + if (strcmp(role_s, "user") == 0 || strcmp(role_s, "assistant") == 0) return "dm_history"; + return "context_part"; +} + +static int build_context_parts_response(cJSON* out_root) { + if (!out_root) return -1; + + char* messages_json = NULL; + if (agent_build_admin_messages_json("", &messages_json) != 0 || !messages_json) { + return -1; + } + + cJSON* arr = cJSON_Parse(messages_json); + free(messages_json); + if (!arr || !cJSON_IsArray(arr)) { + cJSON_Delete(arr); + return -1; + } + + cJSON* parts = cJSON_CreateArray(); + if (!parts) { + cJSON_Delete(arr); + return -1; + } + + int total_chars = 0; + int n = cJSON_GetArraySize(arr); + for (int i = 0; i < n; i++) { + cJSON* msg = cJSON_GetArrayItem(arr, i); + if (!msg || !cJSON_IsObject(msg)) continue; + + cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role"); + cJSON* content = cJSON_GetObjectItemCaseSensitive(msg, "content"); + const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "unknown"; + const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : ""; + + int chars = (int)strlen(content_s); + total_chars += chars; + + cJSON* part = cJSON_CreateObject(); + if (!part) continue; + cJSON_AddStringToObject(part, "name", classify_part_name(msg, i)); + cJSON_AddStringToObject(part, "role", role_s); + cJSON_AddNumberToObject(part, "chars", chars); + cJSON_AddNumberToObject(part, "estimated_tokens", estimate_tokens_from_chars(chars)); + cJSON_AddStringToObject(part, "content", content_s); + cJSON_AddItemToArray(parts, part); + } + + cJSON_AddBoolToObject(out_root, "success", 1); + cJSON_AddNumberToObject(out_root, "total_chars", total_chars); + cJSON_AddNumberToObject(out_root, "total_estimated_tokens", estimate_tokens_from_chars(total_chars)); + cJSON_AddItemToObject(out_root, "parts", parts); + + cJSON_AddItemToObject(out_root, "messages", arr); + return 0; +} + +static int append_simple_message_local(cJSON* messages, const char* role, const char* content) { + if (!messages || !role) return -1; + cJSON* msg = cJSON_CreateObject(); + if (!msg) return -1; + cJSON_AddStringToObject(msg, "role", role); + cJSON_AddStringToObject(msg, "content", content ? content : ""); + cJSON_AddItemToArray(messages, msg); + return 0; +} + +static int append_assistant_tool_calls_message_local(cJSON* messages, const llm_response_t* resp) { + if (!messages || !resp || resp->tool_call_count <= 0) return -1; + + cJSON* assistant = cJSON_CreateObject(); + cJSON* tool_calls = cJSON_CreateArray(); + if (!assistant || !tool_calls) { + cJSON_Delete(assistant); + cJSON_Delete(tool_calls); + return -1; + } + + cJSON_AddStringToObject(assistant, "role", "assistant"); + if (resp->content) cJSON_AddStringToObject(assistant, "content", resp->content); + else cJSON_AddNullToObject(assistant, "content"); + + for (int i = 0; i < resp->tool_call_count; i++) { + const llm_tool_call_t* tc = &resp->tool_calls[i]; + cJSON* tc_obj = cJSON_CreateObject(); + cJSON* fn_obj = cJSON_CreateObject(); + if (!tc_obj || !fn_obj) { + cJSON_Delete(tc_obj); + cJSON_Delete(fn_obj); + cJSON_Delete(assistant); + cJSON_Delete(tool_calls); + return -1; + } + + cJSON_AddStringToObject(tc_obj, "id", tc->id ? tc->id : ""); + cJSON_AddStringToObject(tc_obj, "type", "function"); + cJSON_AddStringToObject(fn_obj, "name", tc->name ? tc->name : ""); + cJSON_AddStringToObject(fn_obj, "arguments", tc->arguments_json ? tc->arguments_json : "{}"); + cJSON_AddItemToObject(tc_obj, "function", fn_obj); + cJSON_AddItemToArray(tool_calls, tc_obj); + } + + cJSON_AddItemToObject(assistant, "tool_calls", tool_calls); + cJSON_AddItemToArray(messages, assistant); + return 0; +} + +static int append_tool_result_message_local(cJSON* messages, const char* tool_call_id, const char* tool_result_json) { + if (!messages || !tool_call_id) return -1; + + cJSON* msg = cJSON_CreateObject(); + if (!msg) return -1; + + cJSON_AddStringToObject(msg, "role", "tool"); + cJSON_AddStringToObject(msg, "tool_call_id", tool_call_id); + cJSON_AddStringToObject(msg, + "content", + tool_result_json ? tool_result_json : "{\"success\":false,\"error\":\"empty tool result\"}"); + cJSON_AddItemToArray(messages, msg); + return 0; +} + +static cJSON* run_prompt_with_tools(cJSON* body) { + if (!body || !cJSON_IsObject(body)) return NULL; + + cJSON* messages_node = cJSON_GetObjectItemCaseSensitive(body, "messages"); + if (!messages_node || !cJSON_IsArray(messages_node)) return NULL; + + cJSON* convo = cJSON_Duplicate(messages_node, 1); + if (!convo || !cJSON_IsArray(convo)) { + cJSON_Delete(convo); + return NULL; + } + + int max_turns = 4; + cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns"); + if (max_turns_node && cJSON_IsNumber(max_turns_node)) { + max_turns = (int)max_turns_node->valuedouble; + if (max_turns < 1) max_turns = 1; + if (max_turns > 16) max_turns = 16; + } + + char* tools_json = tools_build_openai_schema_json(g_api_ctx.tools_ctx); + if (!tools_json) { + cJSON_Delete(convo); + return NULL; + } + + cJSON* root = cJSON_CreateObject(); + cJSON* turns = cJSON_CreateArray(); + if (!root || !turns) { + cJSON_Delete(root); + cJSON_Delete(turns); + free(tools_json); + cJSON_Delete(convo); + return NULL; + } + + char* final_response = NULL; + + for (int turn = 0; turn < max_turns; turn++) { + char* messages_json = cJSON_PrintUnformatted(convo); + if (!messages_json) break; + + llm_response_t resp; + int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); + free(messages_json); + + if (rc != 0) { + final_response = strdup("LLM request failed."); + break; + } + + cJSON* turn_obj = cJSON_CreateObject(); + if (turn_obj) { + cJSON_AddNumberToObject(turn_obj, "turn", turn + 1); + cJSON* tool_calls = cJSON_CreateArray(); + if (tool_calls) cJSON_AddItemToObject(turn_obj, "tool_calls", tool_calls); + cJSON_AddItemToArray(turns, turn_obj); + } + + if (resp.tool_call_count <= 0) { + final_response = strdup(resp.content ? resp.content : "No response content."); + llm_response_free(&resp); + break; + } + + if (append_assistant_tool_calls_message_local(convo, &resp) != 0) { + llm_response_free(&resp); + final_response = strdup("Failed to append assistant tool calls."); + break; + } + + cJSON* current_turn = cJSON_GetArrayItem(turns, cJSON_GetArraySize(turns) - 1); + cJSON* turn_tool_calls = current_turn ? cJSON_GetObjectItemCaseSensitive(current_turn, "tool_calls") : NULL; + + for (int i = 0; i < resp.tool_call_count; i++) { + llm_tool_call_t* tc = &resp.tool_calls[i]; + char* tool_result = tools_execute(g_api_ctx.tools_ctx, tc->name, tc->arguments_json); + if (!tool_result) { + tool_result = strdup("{\"success\":false,\"error\":\"tool execution failed\"}"); + } + + if (turn_tool_calls && cJSON_IsArray(turn_tool_calls)) { + cJSON* tr = cJSON_CreateObject(); + if (tr) { + cJSON_AddStringToObject(tr, "name", tc->name ? tc->name : ""); + cJSON_AddStringToObject(tr, "arguments", tc->arguments_json ? tc->arguments_json : "{}"); + cJSON_AddStringToObject(tr, "result", tool_result ? tool_result : "{}"); + cJSON_AddItemToArray(turn_tool_calls, tr); + } + } + + if (append_tool_result_message_local(convo, + tc->id ? tc->id : "", + tool_result ? tool_result : "{\"success\":false,\"error\":\"tool execution failed\"}") != 0) { + free(tool_result); + llm_response_free(&resp); + final_response = strdup("Failed to append tool result."); + goto done; + } + free(tool_result); + } + + llm_response_free(&resp); + } + +done: + if (!final_response) { + final_response = strdup("I hit my tool-use limit for this prompt run."); + } + + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddStringToObject(root, "final_response", final_response ? final_response : ""); + cJSON_AddItemToObject(root, "turns", turns); + + llm_config_t cfg; + if (llm_get_config(&cfg) == 0) { + cJSON_AddStringToObject(root, "model_used", cfg.model); + } + + int input_chars = 0; + char* convo_json = cJSON_PrintUnformatted(convo); + if (convo_json) { + input_chars = (int)strlen(convo_json); + free(convo_json); + } + cJSON_AddNumberToObject(root, "total_input_tokens_estimate", estimate_tokens_from_chars(input_chars)); + cJSON_AddNumberToObject(root, + "total_output_tokens_estimate", + estimate_tokens_from_chars((int)strlen(final_response ? final_response : ""))); + + free(final_response); + free(tools_json); + cJSON_Delete(convo); + return root; +} + +static void handle_status(struct mg_connection* c) { + cJSON* root = cJSON_CreateObject(); + if (!root) { + reply_error(c, 500, "oom"); + return; + } + + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddStringToObject(root, "name", DIDACTYL_NAME); + cJSON_AddStringToObject(root, "version", DIDACTYL_VERSION); + + if (g_api_ctx.cfg) { + cJSON_AddStringToObject(root, "pubkey", g_api_ctx.cfg->keys.public_key_hex); + cJSON_AddNumberToObject(root, "relay_count", g_api_ctx.cfg->relay_count); + } + + cJSON_AddNumberToObject(root, "connected_relays", nostr_handler_connected_relay_count()); + if (g_api_ctx.trigger_manager) { + cJSON_AddNumberToObject(root, "active_triggers", trigger_manager_active_count(g_api_ctx.trigger_manager)); + } + + reply_json(c, 200, root); + cJSON_Delete(root); +} + +static void handle_context_current(struct mg_connection* c) { + char* messages_json = NULL; + if (agent_build_admin_messages_json("", &messages_json) != 0 || !messages_json) { + reply_error(c, 500, "failed to build context messages"); + return; + } + + cJSON* messages = cJSON_Parse(messages_json); + cJSON* root = cJSON_CreateObject(); + if (!root || !messages || !cJSON_IsArray(messages)) { + free(messages_json); + cJSON_Delete(messages); + cJSON_Delete(root); + reply_error(c, 500, "failed to serialize context"); + return; + } + + int chars = (int)strlen(messages_json); + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddNumberToObject(root, "total_chars", chars); + cJSON_AddNumberToObject(root, "total_estimated_tokens", estimate_tokens_from_chars(chars)); + cJSON_AddItemToObject(root, "messages", messages); + + reply_json(c, 200, root); + + free(messages_json); + cJSON_Delete(root); +} + +static void handle_context_parts(struct mg_connection* c) { + cJSON* root = cJSON_CreateObject(); + if (!root) { + reply_error(c, 500, "oom"); + return; + } + + if (build_context_parts_response(root) != 0) { + cJSON_Delete(root); + reply_error(c, 500, "failed to build context parts"); + return; + } + + reply_json(c, 200, root); + cJSON_Delete(root); +} + +static void handle_prompt_run_simple(struct mg_connection* c, const struct mg_http_message* hm) { + cJSON* body = parse_body_json(hm); + if (!body || !cJSON_IsObject(body)) { + cJSON_Delete(body); + reply_error(c, 400, "invalid JSON body"); + return; + } + + cJSON* system = cJSON_GetObjectItemCaseSensitive(body, "system"); + cJSON* user = cJSON_GetObjectItemCaseSensitive(body, "user"); + if (!system || !user || !cJSON_IsString(system) || !cJSON_IsString(user) || + !system->valuestring || !user->valuestring) { + cJSON_Delete(body); + reply_error(c, 400, "system and user strings are required"); + return; + } + + llm_config_t old_cfg; + int overridden = 0; + char* model_err = maybe_model_override_begin(body, &old_cfg, &overridden); + if (model_err) { + cJSON_Delete(body); + reply_error(c, 400, model_err); + return; + } + + char* response = llm_chat(system->valuestring, user->valuestring); + maybe_model_override_end(&old_cfg, overridden); + + if (!response) { + cJSON_Delete(body); + reply_error(c, 500, "llm request failed"); + return; + } + + cJSON* root = cJSON_CreateObject(); + if (!root) { + free(response); + cJSON_Delete(body); + reply_error(c, 500, "oom"); + return; + } + + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddStringToObject(root, "response", response); + + llm_config_t cfg; + if (llm_get_config(&cfg) == 0) { + cJSON_AddStringToObject(root, "model_used", cfg.model); + } + + int in_chars = (int)strlen(system->valuestring) + (int)strlen(user->valuestring); + cJSON_AddNumberToObject(root, "input_tokens_estimate", estimate_tokens_from_chars(in_chars)); + cJSON_AddNumberToObject(root, "output_tokens_estimate", estimate_tokens_from_chars((int)strlen(response))); + + free(response); + cJSON_Delete(body); + reply_json(c, 200, root); + cJSON_Delete(root); +} + +static void handle_prompt_run(struct mg_connection* c, const struct mg_http_message* hm) { + cJSON* body = parse_body_json(hm); + if (!body || !cJSON_IsObject(body)) { + cJSON_Delete(body); + reply_error(c, 400, "invalid JSON body"); + return; + } + + llm_config_t old_cfg; + int overridden = 0; + char* model_err = maybe_model_override_begin(body, &old_cfg, &overridden); + if (model_err) { + cJSON_Delete(body); + reply_error(c, 400, model_err); + return; + } + + cJSON* result = run_prompt_with_tools(body); + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body); + + if (!result) { + reply_error(c, 500, "failed to run prompt"); + return; + } + + reply_json(c, 200, result); + cJSON_Delete(result); +} + +static cJSON* run_variant(cJSON* variant) { + if (!variant || !cJSON_IsObject(variant)) return NULL; + cJSON* body_copy = cJSON_Duplicate(variant, 1); + if (!body_copy) return NULL; + + llm_config_t old_cfg; + int overridden = 0; + char* model_err = maybe_model_override_begin(body_copy, &old_cfg, &overridden); + if (model_err) { + cJSON* err = cJSON_CreateObject(); + if (err) { + cJSON_AddBoolToObject(err, "success", 0); + cJSON_AddStringToObject(err, "error", model_err); + } + cJSON_Delete(body_copy); + return err; + } + + cJSON* out = run_prompt_with_tools(body_copy); + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body_copy); + return out; +} + +static void handle_prompt_compare(struct mg_connection* c, const struct mg_http_message* hm) { + cJSON* body = parse_body_json(hm); + if (!body || !cJSON_IsObject(body)) { + cJSON_Delete(body); + reply_error(c, 400, "invalid JSON body"); + return; + } + + cJSON* a = cJSON_GetObjectItemCaseSensitive(body, "variant_a"); + cJSON* b = cJSON_GetObjectItemCaseSensitive(body, "variant_b"); + if (!a || !b || !cJSON_IsObject(a) || !cJSON_IsObject(b)) { + cJSON_Delete(body); + reply_error(c, 400, "variant_a and variant_b objects are required"); + return; + } + + cJSON* out_a = run_variant(a); + cJSON* out_b = run_variant(b); + + if (!out_a || !out_b) { + cJSON_Delete(out_a); + cJSON_Delete(out_b); + cJSON_Delete(body); + reply_error(c, 500, "failed to run one or more variants"); + return; + } + + cJSON* root = cJSON_CreateObject(); + if (!root) { + cJSON_Delete(out_a); + cJSON_Delete(out_b); + cJSON_Delete(body); + reply_error(c, 500, "oom"); + return; + } + + cJSON_AddBoolToObject(root, "success", 1); + cJSON_AddItemToObject(root, "variant_a", out_a); + cJSON_AddItemToObject(root, "variant_b", out_b); + + cJSON_Delete(body); + reply_json(c, 200, root); + cJSON_Delete(root); +} + +static void http_handler(struct mg_connection* c, int ev, void* ev_data) { + if (ev != MG_EV_HTTP_MSG) { + return; + } + + struct mg_http_message* hm = (struct mg_http_message*)ev_data; + + if (method_is(hm, "OPTIONS")) { + mg_http_reply(c, 204, k_json_headers, ""); + return; + } + + if (method_is(hm, "GET") && mg_match(hm->uri, mg_str("/api/status"), NULL)) { + handle_status(c); + return; + } + if (method_is(hm, "GET") && mg_match(hm->uri, mg_str("/api/context/current"), NULL)) { + handle_context_current(c); + return; + } + if (method_is(hm, "GET") && mg_match(hm->uri, mg_str("/api/context/parts"), NULL)) { + handle_context_parts(c); + return; + } + if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/run-simple"), NULL)) { + handle_prompt_run_simple(c, hm); + return; + } + if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/run"), NULL)) { + handle_prompt_run(c, hm); + return; + } + if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/compare"), NULL)) { + handle_prompt_compare(c, hm); + return; + } + + reply_error(c, 404, "not found"); +} + +int http_api_init(const http_api_context_t* ctx) { + if (!ctx || !ctx->cfg || !ctx->tools_ctx) { + return -1; + } + + g_api_ctx = *ctx; + + mg_mgr_init(&g_mgr); + + char listen_url[320]; + snprintf(listen_url, + sizeof(listen_url), + "http://%s:%d", + g_api_ctx.cfg->api.bind_address[0] ? g_api_ctx.cfg->api.bind_address : "127.0.0.1", + g_api_ctx.cfg->api.port > 0 ? g_api_ctx.cfg->api.port : 8484); + + g_listener = mg_http_listen(&g_mgr, listen_url, http_handler, NULL); + if (!g_listener) { + mg_mgr_free(&g_mgr); + memset(&g_mgr, 0, sizeof(g_mgr)); + return -1; + } + + g_api_initialized = 1; + DEBUG_INFO("[didactyl] http api listening on %s", listen_url); + return 0; +} + +int http_api_poll(int timeout_ms) { + if (!g_api_initialized) { + return 0; + } + + if (timeout_ms < 0) timeout_ms = 0; + mg_mgr_poll(&g_mgr, timeout_ms); + return 0; +} + +void http_api_cleanup(void) { + if (!g_api_initialized) { + return; + } + + mg_mgr_free(&g_mgr); + memset(&g_mgr, 0, sizeof(g_mgr)); + g_listener = NULL; + memset(&g_api_ctx, 0, sizeof(g_api_ctx)); + g_api_initialized = 0; +} \ No newline at end of file diff --git a/src/http_api.h b/src/http_api.h new file mode 100644 index 0000000..6485df9 --- /dev/null +++ b/src/http_api.h @@ -0,0 +1,19 @@ +#ifndef DIDACTYL_HTTP_API_H +#define DIDACTYL_HTTP_API_H + +#include "config.h" +#include "tools.h" + +struct trigger_manager; + +typedef struct { + didactyl_config_t* cfg; + tools_context_t* tools_ctx; + struct trigger_manager* trigger_manager; +} http_api_context_t; + +int http_api_init(const http_api_context_t* ctx); +int http_api_poll(int timeout_ms); +void http_api_cleanup(void); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 52efa13..638e677 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,7 @@ #include "trigger_manager.h" #include "tools.h" #include "cjson/cJSON.h" +#include "http_api.h" static volatile sig_atomic_t g_running = 1; @@ -274,18 +275,49 @@ int main(int argc, char** argv) { signal(SIGTERM, signal_handler); signal(SIGPIPE, SIG_IGN); + int http_api_started = 0; + if (cfg.api.enabled) { + http_api_context_t http_ctx; + memset(&http_ctx, 0, sizeof(http_ctx)); + http_ctx.cfg = &cfg; + http_ctx.tools_ctx = agent_tools_context(); + http_ctx.trigger_manager = &trigger_manager; + + if (http_api_init(&http_ctx) != 0) { + fprintf(stderr, + "Failed to initialize HTTP API on %s:%d\n", + cfg.api.bind_address, + cfg.api.port); + agent_cleanup(); + nostr_handler_cleanup(); + llm_cleanup(); + trigger_manager_cleanup(&trigger_manager); + config_free(&cfg); + nostr_cleanup(); + return 1; + } + http_api_started = 1; + } + DEBUG_INFO("[didactyl] entering main poll loop"); DEBUG_INFO("[didactyl] running with pubkey %s", cfg.keys.public_key_hex); while (g_running) { (void)nostr_handler_poll(100); (void)trigger_manager_poll(&trigger_manager); + if (http_api_started) { + (void)http_api_poll(0); + } struct timespec ts = {0, 10 * 1000 * 1000}; nanosleep(&ts, NULL); } DEBUG_INFO("[didactyl] shutting down"); + if (http_api_started) { + http_api_cleanup(); + } + agent_cleanup(); nostr_handler_cleanup(); llm_cleanup(); diff --git a/src/main.h b/src/main.h index 794cd91..ff1e9d6 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 0 -#define DIDACTYL_VERSION_PATCH 26 -#define DIDACTYL_VERSION "v0.0.26" +#define DIDACTYL_VERSION_PATCH 27 +#define DIDACTYL_VERSION "v0.0.27" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/mongoose.c b/src/mongoose.c new file mode 100644 index 0000000..8391089 --- /dev/null +++ b/src/mongoose.c @@ -0,0 +1,28193 @@ +// 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; + uint16_t id; + mg_random(&id, sizeof(uint16_t)); + // TODO(): traverse reqs and check id != reqs->txnid; repeat otherwise + if (reqs != NULL) id = (uint16_t) (reqs->txnid + 1); // no collision + d->txnid = id; + d->next = reqs; + 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/src/mongoose.h b/src/mongoose.h new file mode 100644 index 0000000..0fa8353 --- /dev/null +++ b/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/vendor/mongoose.c b/vendor/mongoose.c new file mode 100644 index 0000000..8391089 --- /dev/null +++ b/vendor/mongoose.c @@ -0,0 +1,28193 @@ +// 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; + uint16_t id; + mg_random(&id, sizeof(uint16_t)); + // TODO(): traverse reqs and check id != reqs->txnid; repeat otherwise + if (reqs != NULL) id = (uint16_t) (reqs->txnid + 1); // no collision + d->txnid = id; + d->next = reqs; + 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/vendor/mongoose.h b/vendor/mongoose.h new file mode 100644 index 0000000..0fa8353 --- /dev/null +++ b/vendor/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