mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b766c65615 | ||
|
|
8296b88fb6 | ||
|
|
a5efbf0592 | ||
|
|
37c98dd893 | ||
|
|
770a4ff27e | ||
|
|
b6560306ba | ||
|
|
5b39c93478 | ||
|
|
086008b716 | ||
|
|
35eb50a1e6 | ||
|
|
431e35ae8c | ||
|
|
25e2a99d8e | ||
|
|
88b01ef6f0 | ||
|
|
bd8ee86f9e | ||
|
|
ef96f7a28c | ||
|
|
03f240a244 | ||
|
|
c775f31ca7 | ||
|
|
1ca4fd1fef | ||
|
|
a6b6a0d779 | ||
|
|
38c326644f | ||
|
|
e6e7a690a7 | ||
|
|
c76f092ba1 | ||
|
|
60840d0d89 | ||
|
|
298ee549da | ||
|
|
5e4ae64e1b | ||
|
|
c5778aa608 | ||
|
|
6d32a7c238 | ||
|
|
497a3f5e73 | ||
|
|
10e67fdf2b | ||
|
|
2f2a5f2f02 | ||
|
|
aa21ed443a | ||
|
|
10adde595c | ||
|
|
c957f29f23 | ||
|
|
37f3ed5ba4 | ||
|
|
b68da1548b | ||
|
|
a9a94a6904 | ||
|
|
3a4660cd40 | ||
|
|
fc478a70f7 | ||
|
|
a6b5565e9d | ||
|
|
e7113408a9 | ||
|
|
fdf12844e3 | ||
|
|
bc5afa4519 | ||
|
|
a4607933a4 | ||
|
|
fd7db76324 | ||
|
|
b1e6cb320f | ||
|
|
2b9d805d84 | ||
|
|
a9a843d6e2 | ||
|
|
aae179177c | ||
|
|
300011a882 | ||
|
|
eda9a5726c | ||
|
|
16a5a7d9fb | ||
|
|
69c3bb8d12 | ||
|
|
8fc5651044 | ||
|
|
338756d0b0 | ||
|
|
c30afffcee | ||
|
|
76c0953210 | ||
|
|
381e31198b | ||
|
|
28ba381bd9 | ||
|
|
567963b034 | ||
|
|
7d3fe4f9cb | ||
|
|
c8bf39dcd8 | ||
|
|
393c811f31 | ||
|
|
be40827e17 | ||
|
|
ad45248327 | ||
|
|
c2d95cc19b | ||
|
|
4aa0a243f9 | ||
|
|
65a7996ef9 | ||
|
|
5879c3cbf2 | ||
|
|
cf9d56be79 | ||
|
|
da82df2041 | ||
|
|
954eb07b6d | ||
|
|
d7e708b9bc | ||
|
|
ce35cbf586 | ||
|
|
3aa5f2a1fd | ||
|
|
1b0906510b | ||
|
|
8a116d9289 | ||
|
|
71e0176944 | ||
|
|
bba5a0ca3a | ||
|
|
9c388b5f19 | ||
|
|
8afa940acf | ||
|
|
df1a409b21 | ||
|
|
7ec403341c | ||
|
|
1f2b14bb66 | ||
|
|
8e0c765c2c | ||
|
|
883e0a981f | ||
|
|
ff1fb5c745 | ||
|
|
795f16d5fb | ||
|
|
51773ecea3 | ||
|
|
2834aa57ee | ||
|
|
e2b5d7175f | ||
|
|
a419e16f26 | ||
|
|
789372c5b3 | ||
|
|
81f6acbce7 | ||
|
|
7fa96f3a38 | ||
|
|
876cf5605f | ||
|
|
8df146b1b8 | ||
|
|
f62f3545b4 | ||
|
|
4d6fa4b91c | ||
|
|
1be11a66e3 | ||
|
|
d1060d247b | ||
|
|
002c9df3de | ||
|
|
fa34b7439a | ||
|
|
60c75a22b8 | ||
|
|
bbee8ae61f | ||
|
|
9173c8dfda | ||
|
|
b93cbbeb13 | ||
|
|
aa1dfe8000 | ||
|
|
e8a82d1821 | ||
|
|
201a81a315 | ||
|
|
aae31aba9c | ||
|
|
29aa53cebd | ||
|
|
5d4fffbb14 | ||
|
|
ed5ef09d1c | ||
|
|
1e99974b83 | ||
|
|
6814b44b03 | ||
|
|
7e9ddc2d09 | ||
|
|
c13d5d7a56 | ||
|
|
555c5a76be | ||
|
|
863aea34c4 | ||
|
|
830f6c589b | ||
|
|
cc64c41737 | ||
|
|
4660f2ca0f | ||
|
|
ffcad7ee47 | ||
|
|
09a97b915a | ||
|
|
35a9242b67 | ||
|
|
5c34018899 | ||
|
|
c857ddae57 | ||
|
|
830acc19ee | ||
|
|
f87d439710 | ||
|
|
86adf12a77 | ||
|
|
a561f3e096 | ||
|
|
2b0a5e9701 | ||
|
|
8346a60971 | ||
|
|
140fa6cebf | ||
|
|
cd05b162a8 | ||
|
|
3fde04137c | ||
|
|
5591b7d131 | ||
|
|
9ddac8faa7 | ||
|
|
9123330f72 | ||
|
|
b938728c2a | ||
|
|
fab922230c | ||
|
|
90418ffb1b | ||
|
|
5a317e81d9 | ||
|
|
7e8c3a59e2 | ||
|
|
6093ac12a0 | ||
|
|
e7cfaa70c7 | ||
|
|
897d26edfb | ||
|
|
6b5d358ede | ||
|
|
0d97280af4 | ||
|
|
afc84b7c92 | ||
|
|
66bf5952a4 | ||
|
|
f002c30cbb | ||
|
|
53d2356472 | ||
|
|
e312219e80 | ||
|
|
2aff813795 | ||
|
|
9c4506763b | ||
|
|
ec3b9e7c4f | ||
|
|
ddd9651f91 | ||
|
|
304ea2a649 | ||
|
|
83bac34094 | ||
|
|
9c6b4edd37 |
No files matched your search
@@ -1,16 +1,42 @@
|
||||
name: Claude Full Codebase Security Scan
|
||||
name: Claude Codebase Security Scan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
prompt_path:
|
||||
description: "Path to the repo prompt file Claude should read"
|
||||
required: true
|
||||
default: "resources/specs/claude-security-scan.md"
|
||||
target_ref:
|
||||
description: "Branch, tag, or SHA to scan"
|
||||
required: false
|
||||
default: ""
|
||||
common_misc:
|
||||
description: "Run common/misc security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
mongoose_core:
|
||||
description: "Run Mongoose core security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
web_http_auth:
|
||||
description: "Run web/HTTP/auth security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
tls:
|
||||
description: "Run TLS security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
net_builtin:
|
||||
description: "Run built-in network security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
protocols:
|
||||
description: "Run protocol security scan"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
#schedule:
|
||||
# Weekly scan: Sunday 02:00 UTC
|
||||
# - cron: "0 2 * * 0"
|
||||
@@ -20,53 +46,140 @@ permissions:
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: claude-full-codebase-security-scan-${{ github.ref }}
|
||||
group: claude-security-scans
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Full codebase security scan
|
||||
prepare:
|
||||
name: Prepare selected scan matrix
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DEFAULT_PROMPT_PATH: "resources/specs/claude-security-scan.md"
|
||||
outputs:
|
||||
target_ref: ${{ steps.plan.outputs.target_ref }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
has_scans: ${{ steps.plan.outputs.has_scans }}
|
||||
|
||||
steps:
|
||||
- name: Resolve scan inputs
|
||||
id: vars
|
||||
- name: Resolve scan inputs and build matrix
|
||||
id: plan
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
PROMPT_PATH="${{ inputs.prompt_path }}"
|
||||
TARGET_REF="${{ inputs.target_ref }}"
|
||||
RUN_COMMON_MISC="${{ inputs.common_misc }}"
|
||||
RUN_MONGOOSE_CORE="${{ inputs.mongoose_core }}"
|
||||
RUN_WEB_HTTP_AUTH="${{ inputs.web_http_auth }}"
|
||||
RUN_TLS="${{ inputs.tls }}"
|
||||
RUN_NET_BUILTIN="${{ inputs.net_builtin }}"
|
||||
RUN_PROTOCOLS="${{ inputs.protocols }}"
|
||||
else
|
||||
PROMPT_PATH="${DEFAULT_PROMPT_PATH}"
|
||||
TARGET_REF=""
|
||||
RUN_COMMON_MISC="true"
|
||||
RUN_MONGOOSE_CORE="true"
|
||||
RUN_WEB_HTTP_AUTH="true"
|
||||
RUN_TLS="true"
|
||||
RUN_NET_BUILTIN="true"
|
||||
RUN_PROTOCOLS="true"
|
||||
fi
|
||||
|
||||
if [ -z "$TARGET_REF" ]; then
|
||||
TARGET_REF="${{ github.ref }}"
|
||||
fi
|
||||
|
||||
echo "prompt_path=$PROMPT_PATH" >> "$GITHUB_OUTPUT"
|
||||
echo "target_ref=$TARGET_REF" >> "$GITHUB_OUTPUT"
|
||||
MATRIX="$(jq -cn \
|
||||
--arg common_misc "$RUN_COMMON_MISC" \
|
||||
--arg mongoose_core "$RUN_MONGOOSE_CORE" \
|
||||
--arg web_http_auth "$RUN_WEB_HTTP_AUTH" \
|
||||
--arg tls "$RUN_TLS" \
|
||||
--arg net_builtin "$RUN_NET_BUILTIN" \
|
||||
--arg protocols "$RUN_PROTOCOLS" \
|
||||
'{
|
||||
include: ([
|
||||
{
|
||||
enabled: ($common_misc == "true"),
|
||||
area: "common_misc",
|
||||
display_name: "Common / Misc",
|
||||
area_prompt: "resources/specs/areas/common_misc.md"
|
||||
},
|
||||
{
|
||||
enabled: ($mongoose_core == "true"),
|
||||
area: "mongoose_core",
|
||||
display_name: "Mongoose Core",
|
||||
area_prompt: "resources/specs/areas/mongoose_core.md"
|
||||
},
|
||||
{
|
||||
enabled: ($web_http_auth == "true"),
|
||||
area: "web_http_auth",
|
||||
display_name: "Web / HTTP / Auth",
|
||||
area_prompt: "resources/specs/areas/web_http_auth.md"
|
||||
},
|
||||
{
|
||||
enabled: ($tls == "true"),
|
||||
area: "tls",
|
||||
display_name: "TLS",
|
||||
area_prompt: "resources/specs/areas/tls.md"
|
||||
},
|
||||
{
|
||||
enabled: ($net_builtin == "true"),
|
||||
area: "net_builtin",
|
||||
display_name: "Built-In Network",
|
||||
area_prompt: "resources/specs/areas/net_builtin.md"
|
||||
},
|
||||
{
|
||||
enabled: ($protocols == "true"),
|
||||
area: "protocols",
|
||||
display_name: "Protocols",
|
||||
area_prompt: "resources/specs/areas/protocols.md"
|
||||
}
|
||||
] | map(select(.enabled)) | map(del(.enabled)))
|
||||
}'
|
||||
)"
|
||||
|
||||
HAS_SCANS="$(jq -r '.include | length > 0' <<< "$MATRIX")"
|
||||
|
||||
echo "target_ref=$TARGET_REF" >> "$GITHUB_OUTPUT"
|
||||
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
|
||||
echo "has_scans=$HAS_SCANS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
scan:
|
||||
name: ${{ matrix.display_name }} security scan
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.has_scans == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
|
||||
|
||||
env:
|
||||
MASTER_PROMPT_PATH: "resources/specs/claude-security-scan.md"
|
||||
FILTERING_PROMPT_PATH: "resources/specs/claude-false-positive-filtering.md"
|
||||
SCAN_AREA: ${{ matrix.area }}
|
||||
AREA_PROMPT_PATH: ${{ matrix.area_prompt }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.vars.outputs.target_ref }}
|
||||
ref: ${{ needs.prepare.outputs.target_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify prompt file exists
|
||||
- name: Verify required prompt files exist
|
||||
shell: bash
|
||||
run: |
|
||||
test -f "${{ steps.vars.outputs.prompt_path }}" || {
|
||||
echo "Prompt file not found: ${{ steps.vars.outputs.prompt_path }}"
|
||||
exit 1
|
||||
}
|
||||
for prompt_file in "$MASTER_PROMPT_PATH" "$AREA_PROMPT_PATH" "$FILTERING_PROMPT_PATH"; do
|
||||
test -f "$prompt_file" || {
|
||||
echo "Required prompt file not found: $prompt_file"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
- name: Run Claude full codebase security scan
|
||||
- name: Run Claude ${{ matrix.display_name }} security scan
|
||||
id: claude_scan
|
||||
uses: anthropics/claude-code-action@v1
|
||||
timeout-minutes: 30
|
||||
@@ -75,19 +188,21 @@ jobs:
|
||||
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
REF: ${{ steps.vars.outputs.target_ref }}
|
||||
PROMPT_FILE: ${{ steps.vars.outputs.prompt_path }}
|
||||
REF: ${{ needs.prepare.outputs.target_ref }}
|
||||
MASTER_PROMPT_FILE: ${{ env.MASTER_PROMPT_PATH }}
|
||||
SCAN_AREA: ${{ matrix.area }}
|
||||
AREA_PROMPT_FILE: ${{ matrix.area_prompt }}
|
||||
FILTERING_PROMPT_FILE: ${{ env.FILTERING_PROMPT_PATH }}
|
||||
|
||||
You are running a full-codebase security scan in GitHub Actions.
|
||||
You are running a focused security scan for the assigned area in GitHub Actions.
|
||||
|
||||
First, read the main prompt file at:
|
||||
${{ steps.vars.outputs.prompt_path }}
|
||||
First, read the master prompt at ${{ env.MASTER_PROMPT_PATH }}.
|
||||
Then read the assigned area prompt at ${{ matrix.area_prompt }}.
|
||||
Read the false-positive filtering prompt at ${{ env.FILTERING_PROMPT_PATH }} as required by the master prompt.
|
||||
|
||||
Treat that file as the authoritative security-review instruction set. You will also
|
||||
read and analyze additional prompt files mentioned in the main prompt file by their absolute paths in the repo,
|
||||
as per the instructions found in it.
|
||||
Perform the security review for the ${{ matrix.area }} area. Follow relevant supporting code when necessary, but do not independently scan unrelated areas.
|
||||
Return only the JSON required by the master prompt.
|
||||
|
||||
Scan the checked-out repository as a whole codebase.
|
||||
Do not modify files.
|
||||
Do not create commits.
|
||||
Do not open pull requests.
|
||||
@@ -106,8 +221,8 @@ jobs:
|
||||
set -u
|
||||
mkdir -p private-scan-results
|
||||
TODAY="$(date -u +%Y%m%d)"
|
||||
EXECUTION_OUTPUT_FILE="claude-execution-output_${TODAY}.json"
|
||||
FINDINGS_FILE="security-findings_${TODAY}.json"
|
||||
EXECUTION_OUTPUT_FILE="claude-execution-output_${SCAN_AREA}_${TODAY}.json"
|
||||
FINDINGS_FILE="security-findings_${SCAN_AREA}_${TODAY}.json"
|
||||
|
||||
HAS_EXECUTION_OUTPUT="false"
|
||||
HAS_JSON_FINDINGS="false"
|
||||
@@ -177,5 +292,5 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "Add security scan results ${{ steps.prepare_outputs.outputs.date_suffix }}"
|
||||
git commit -m "Add ${SCAN_AREA} security scan results ${{ steps.prepare_outputs.outputs.date_suffix }}"
|
||||
git push
|
||||
@@ -1,9 +1,6 @@
|
||||
name: Claude PR Security Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
@@ -22,7 +19,7 @@ permissions:
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: claude-pr-security-review-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
group: claude-pr-security-review-${{ inputs.pr_number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
@@ -33,7 +30,7 @@ jobs:
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
|
||||
steps:
|
||||
- name: Resolve workflow variables
|
||||
|
||||
@@ -116,6 +116,26 @@ jobs:
|
||||
cat log
|
||||
test/health.awk < log > json
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json"
|
||||
|
||||
miprtx:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ssl: ["", BUILTIN]
|
||||
name: MIP RTX TAP SSL=${{ matrix.ssl }}
|
||||
env:
|
||||
IPV6: 0
|
||||
RTX: 1
|
||||
SSL: ${{ matrix.ssl }}
|
||||
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: |
|
||||
source ./test/setup_ga_network.sh ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh
|
||||
make -C test mip_tap_test
|
||||
|
||||
mip89:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -145,7 +165,22 @@ jobs:
|
||||
- run: |
|
||||
sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
if [[ "${{ matrix.target }}" == *port* ]]; then source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2 ; fi
|
||||
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }} MULTIREC=NO
|
||||
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }}
|
||||
|
||||
miprtxbe:
|
||||
runs-on: ubuntu-latest
|
||||
name: MIP RTX be s390 port
|
||||
env:
|
||||
IPV6: 0
|
||||
RTX: 1
|
||||
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: |
|
||||
sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC -e RTX" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2
|
||||
make -C test mip_port_s390
|
||||
|
||||
l2:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -268,6 +303,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [bsd, bsd_nortos]
|
||||
cc: [gcc, clang, g++, clang++]
|
||||
name: bsd CC=${{ matrix.cc }}
|
||||
steps:
|
||||
@@ -281,7 +317,7 @@ jobs:
|
||||
make -C test/bsd shm_tap_bridge
|
||||
test/bsd/shm_tap_bridge &
|
||||
sleep 2
|
||||
make -C test/bsd CC=${{ matrix.cc }} > log
|
||||
make -C test/bsd ${{ matrix.target }} CC=${{ matrix.cc }} > log
|
||||
- if: success() || failure()
|
||||
run: |
|
||||
cat log
|
||||
@@ -318,7 +354,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390 SSL=${{ matrix.ssl }} MULTIREC=NO
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390 SSL=${{ matrix.ssl }}
|
||||
armhf:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -331,7 +367,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test armhf SSL=${{ matrix.ssl }} MULTIREC=NO
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test armhf SSL=${{ matrix.ssl }}
|
||||
unamalgamated-mg_prefix:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -534,11 +570,12 @@ jobs:
|
||||
infineon/xmc47_relax-make-baremetal-builtin
|
||||
infineon/xmc_plt2go_4400-make-baremetal-builtin
|
||||
infineon/xmc72_evk-make-baremetal-builtin
|
||||
nxp/frdm-mcxn947-make-baremetal-builtin
|
||||
nxp/frdm-mcxn947-make-freertos-builtin
|
||||
nxp/rt1020-evk-make-baremetal-builtin
|
||||
nxp/rt1060-evk-make-baremetal-builtin
|
||||
nxp/rt1170-evk-make-baremetal-builtin
|
||||
nxp/frdm-imxrt1186/minimal
|
||||
nxp/frdm-mcxe247/minimal
|
||||
nxp/frdm-mcxn947/minimal
|
||||
nxp/frdm-rw612/minimal
|
||||
nxp/rt1060-evkb/minimal
|
||||
nxp/rt1170-evkb/minimal
|
||||
- group: rp-ti
|
||||
examples: |
|
||||
rp/pico-w
|
||||
@@ -553,10 +590,12 @@ jobs:
|
||||
stm32/nucleo-f746zg/minimal
|
||||
stm32/nucleo-f746zg/minimal-pppoe
|
||||
stm32/nucleo-f756zg/minimal
|
||||
stm32/nucleo-h563zi/minimal
|
||||
stm32/nucleo-h723zg/minimal
|
||||
stm32/nucleo-h723zg/minimal-bsd-baremetal
|
||||
stm32/nucleo-h723zg/minimal-bsd-freertos
|
||||
- group: stm32-2
|
||||
examples: |
|
||||
stm32/nucleo-h563zi/minimal
|
||||
stm32/nucleo-h743zi/minimal
|
||||
stm32/nucleo-n657x0-q/minimal
|
||||
stm32/nucleo-u5a5zj-q/minimal
|
||||
@@ -597,8 +636,7 @@ jobs:
|
||||
matrix:
|
||||
example:
|
||||
- path: micropython/esp32
|
||||
- path: http/uart-bridge/esp32
|
||||
- path: http/http-client/esp8266/http-client-server
|
||||
- path: esp32/uart-bridge
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
@@ -614,31 +652,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- group: nxp-1
|
||||
examples: |
|
||||
nxp/nxp-frdmk66f-freertos
|
||||
nxp/nxp-lpcxpresso54s018m-freertos
|
||||
nxp/nxp-mimxrt1020-freertos
|
||||
nxp/nxp-evkbimxrt1050-lwip-freertos
|
||||
nxp/nxp-evkmimxrt1020-lwip-freertos
|
||||
nxp/nxp-evkmimxrt1024-lwip-freertos
|
||||
nxp/nxp-evkmimxrt1060-lwip-freertos
|
||||
nxp/nxp-evkmimxrt1064-lwip-freertos
|
||||
nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
|
||||
- group: nxp-2
|
||||
examples: |
|
||||
nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
|
||||
nxp/nxp-frdmk64f-lwip-freertos
|
||||
nxp/nxp-frdmk66f-lwip-freertos
|
||||
nxp/nxp-lpcxpresso54018-lwip-freertos
|
||||
nxp/nxp-lpcxpresso54608-lwip-freertos
|
||||
nxp/nxp-lpcxpresso54618-lwip-freertos
|
||||
nxp/nxp-lpcxpresso54628-lwip-freertos
|
||||
nxp/nxp-twrk65f180m-lwip-freertos
|
||||
nxp/nxp-twrkv58f220m-lwip-freertos
|
||||
- group: other
|
||||
examples: |
|
||||
mqtt/mqtt-client/microchip/same54-xpro
|
||||
microchip/same54-xpro/mqtt-client
|
||||
rp/pico-rmii
|
||||
stm32/nucleo-f746zg/minimal-rm2
|
||||
stm32/nucleo-f746zg/minimal-rndis
|
||||
@@ -677,13 +693,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: esp32
|
||||
- path: esp32/device-dashboard
|
||||
name: ${{ matrix.example.path }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: echo # nothing specific to install or do
|
||||
- run: make -C tutorials/http/device-dashboard/${{ matrix.example.path }} build
|
||||
- run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
|
||||
device_dashboard_examples_arm:
|
||||
@@ -695,18 +711,12 @@ jobs:
|
||||
- group: none
|
||||
cflags: ""
|
||||
examples: |
|
||||
http/device-dashboard/microchip/same54-xpro
|
||||
nxp/rt1020-evk-make-freertos-builtin
|
||||
nxp/rt1060-evk-make-freertos-builtin
|
||||
nxp/rt1170-evk-make-freertos-builtin
|
||||
microchip/same54-xpro/device-dashboard
|
||||
renesas/ek-ra6m4-make-baremetal-builtin
|
||||
- group: builtin
|
||||
cflags: -DMG_TLS=MG_TLS_BUILTIN
|
||||
examples: |
|
||||
http/device-dashboard/microchip/same54-xpro
|
||||
nxp/rt1020-evk-make-freertos-builtin
|
||||
nxp/rt1060-evk-make-freertos-builtin
|
||||
nxp/rt1170-evk-make-freertos-builtin
|
||||
microchip/same54-xpro/device-dashboard
|
||||
renesas/ek-ra6m4-make-baremetal-builtin
|
||||
name: device dashboard ARM ${{ matrix.group }}
|
||||
steps:
|
||||
@@ -732,19 +742,19 @@ jobs:
|
||||
done
|
||||
exit $failed
|
||||
|
||||
# mqtt_dashboard_examples:
|
||||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# example:
|
||||
# - path:
|
||||
# name: ${{ matrix.example.path }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v5
|
||||
# with: { fetch-depth: 2 }
|
||||
# - run: echo # nothing specific to install or do
|
||||
# - run: make -C tutorials/${{ matrix.example.path }} build
|
||||
mqtt_dashboard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: rp/pico-rndis-device
|
||||
name: ${{ matrix.example.path }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
|
||||
# dual_examples:
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
run: |
|
||||
min=$(date -u -d '5 hours ago' +%s)
|
||||
max=$(date -u -d '2 hours ago' +%s)
|
||||
gh api --paginate "/repos/$GITHUB_REPOSITORY/actions/workflows/nightly.yml/runs?status=failure&per_page=100" --jq '.workflow_runs[] | select(.run_attempt == 1) | [.id, .updated_at] | @tsv' |
|
||||
gh api --paginate "/repos/$GITHUB_REPOSITORY/actions/workflows/nightly.yml/runs?status=failure&per_page=100" --jq '.workflow_runs[] | select(.run_attempt == 1) | [.id, .created_at] | @tsv' |
|
||||
while IFS=$'\t' read -r id updated; do
|
||||
when=$(date -u -d "$updated" +%s)
|
||||
if [ "$when" -ge "$min" ] && [ "$when" -le "$max" ]; then
|
||||
|
||||
@@ -53,16 +53,6 @@ jobs:
|
||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/stm32/nucleo-h563zi/minimal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_rt1020:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 3 }
|
||||
- name: baremetal
|
||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/nxp/rt1020-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/nxp/rt1020-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_rt1060:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -70,8 +60,7 @@ jobs:
|
||||
with: { fetch-depth: 3 }
|
||||
- name: baremetal
|
||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/nxp/rt1060-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/nxp/rt1060-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/nxp/rt1060-evkb/minimal build
|
||||
|
||||
test_rt1170:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -80,8 +69,7 @@ jobs:
|
||||
with: { fetch-depth: 3 }
|
||||
- name: baremetal
|
||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/nxp/rt1170-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/nxp/rt1170-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/nxp/rt1170-evkb/minimal build
|
||||
|
||||
test_ra6m4:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -110,8 +98,8 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- name: baremetal
|
||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
- run: make -C tutorials/http/device-dashboard/microchip/same54-xpro test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/mqtt/mqtt-client/microchip/same54-xpro test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/microchip/same54-xpro/device-dashboard test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
- run: make -C tutorials/microchip/same54-xpro/mqtt-client test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||
|
||||
test_pico_w5500:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -148,16 +148,18 @@ jobs:
|
||||
- path: infineon/xmc47_relax-make-baremetal-builtin
|
||||
- path: infineon/xmc_plt2go_4400-make-baremetal-builtin
|
||||
- path: infineon/xmc72_evk-make-baremetal-builtin
|
||||
- path: nxp/frdm-mcxn947-make-baremetal-builtin
|
||||
- path: nxp/frdm-mcxn947-make-freertos-builtin
|
||||
- path: nxp/rt1020-evk-make-baremetal-builtin
|
||||
- path: nxp/rt1060-evk-make-baremetal-builtin
|
||||
- path: nxp/rt1170-evk-make-baremetal-builtin
|
||||
- path: nxp/frdm-imxrt1186/minimal
|
||||
- path: nxp/frdm-mcxe247/minimal
|
||||
- path: nxp/frdm-mcxn947/minimal
|
||||
- path: nxp/frdm-rw612/minimal
|
||||
- path: nxp/rt1060-evkb/minimal
|
||||
- path: nxp/rt1170-evkb/minimal
|
||||
- path: stm32/nucleo-f429zi/minimal
|
||||
- path: stm32/nucleo-f746zg/minimal
|
||||
- path: stm32/nucleo-h563zi/minimal
|
||||
- path: stm32/nucleo-h723zg/minimal
|
||||
- path: stm32/nucleo-h723zg/minimal-bsd
|
||||
- path: stm32/nucleo-h723zg/minimal-bsd-baremetal
|
||||
- path: stm32/nucleo-h723zg/minimal-bsd-freertos
|
||||
- path: stm32/nucleo-h743zi/minimal
|
||||
- path: stm32/stm32h573i-dk/minimal
|
||||
- path: ti/ek-tm4c1294xl-make-baremetal-builtin
|
||||
@@ -169,7 +171,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: |
|
||||
if ./test/match_changed_files.sh '^src'; then
|
||||
if ./test/match_changed_files.sh '^src|^tutorials/${{ matrix.example.path }}'; then
|
||||
echo GO=1 >> $GITHUB_ENV
|
||||
sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
fi
|
||||
@@ -183,8 +185,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: http/uart-bridge/esp32
|
||||
- path: http/http-client/esp8266/http-client-server
|
||||
- path: esp32/uart-bridge
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
@@ -226,7 +227,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: esp32
|
||||
- path: esp32/device-dashboard
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
@@ -234,12 +235,12 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: |
|
||||
if ./test/match_changed_files.sh '^src|tutorials/http/device-dashboard'; then
|
||||
if ./test/match_changed_files.sh '^src|^tutorials/http/device-dashboard|^tutorials/${{ matrix.example.path }}'; then
|
||||
echo GO=1 >> $GITHUB_ENV
|
||||
# nothing specific to install or do
|
||||
fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C tutorials/http/device-dashboard/${{ matrix.example.path }} build
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
device_dashboard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -247,10 +248,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: http/device-dashboard/microchip/same54-xpro
|
||||
- path: nxp/rt1020-evk-make-freertos-builtin
|
||||
- path: nxp/rt1060-evk-make-freertos-builtin
|
||||
- path: nxp/rt1170-evk-make-freertos-builtin
|
||||
- path: microchip/same54-xpro/device-dashboard
|
||||
- path: renesas/ek-ra6m4-make-baremetal-builtin
|
||||
- path: rp/pico-rndis
|
||||
- path: rp/pico-w
|
||||
@@ -269,3 +267,24 @@ jobs:
|
||||
fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
mqtt_dashboard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- path: rp/pico-rndis-device
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with: { fetch-depth: 2 }
|
||||
- run: |
|
||||
if ./test/match_changed_files.sh '^src|^tutorials/mqtt/mqtt-dashboard/device|^tutorials/${{ matrix.example.path }}'; then
|
||||
echo GO=1 >> $GITHUB_ENV
|
||||
sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||
fi
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
@@ -179,7 +179,6 @@ jobs:
|
||||
example:
|
||||
- path: esp32/uart-bridge
|
||||
- path: esp32/micropython
|
||||
- path: esp8266/http-client-server
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# Mongoose - Embedded Network Library
|
||||
# Mongoose: a full-stack embedded networking library
|
||||
|
||||
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
|
||||
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
|
||||
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
|
||||
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
|
||||
external dependencies. Use it for cloud connectivity, device web dashboards,
|
||||
secure firmware updates and remote control.
|
||||
|
||||
Mongoose is an open source, two-file C networking library and embedded web server for
|
||||
microcontrollers that combines TCP/IP stack, HTTP, WebSocket, MQTT, TLS 1.3
|
||||
stack, built-in firmware OTA updates, and device-dashboard infrastructure.
|
||||
License: GPLv2 or commercial.
|
||||
Website: https://mongoose.ws/
|
||||
GitHub repo: https://github.com/cesanta/mongoose
|
||||
@@ -486,7 +490,7 @@ embedded devices and needs only a few lines added to `mongoose_config.h`:
|
||||
|
||||
```c
|
||||
#define MG_OTA_URL "https://mongoose.ws/ota/u/PREFIX/ota.json" // Copy from instructions panel
|
||||
#define MG_OTA_FIRMWARE_VERSION "MY_PRODUCT_1.4.2" // Change this
|
||||
#define MG_FIRMWARE_VERSION "MY_PRODUCT_1.4.2" // Change this
|
||||
```
|
||||
|
||||
No backend programming required. The device registers itself and checks in at
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Mongoose - Embedded Web Server / Embedded Network Library
|
||||
# Mongoose: a full-stack embedded networking library
|
||||
|
||||
[](https://opensource.org/licenses/gpl-2.0.php)
|
||||
[](https://github.com/cesanta/mongoose/actions)
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
<img src="https://mongoose.ws/images/logo.svg" width="48" height="48"
|
||||
align="left" style="float:left;" />
|
||||
Two files. Drop `mongoose.c` and `mongoose.h` into any C/C++ project and get a
|
||||
full network stack — HTTP, WebSocket, MQTT, TLS, firmware OTA
|
||||
updates, device dashboard — on any microcontroller or desktop OS.
|
||||
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
|
||||
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
|
||||
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
|
||||
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
|
||||
external dependencies. Use it for cloud connectivity, device web dashboards,
|
||||
secure firmware updates and remote control.
|
||||
|
||||
On the market since 2004, used by vast number of open source and commercial
|
||||
Use it instead of lwIP + mbedTLS + httpd + an MQTT client + OTA glue code, or
|
||||
run it on top of lwIP or Zephyr.
|
||||
|
||||
On the market since 2004, used by a vast number of open source and commercial
|
||||
products - it even runs on the International Space Station! Features include:
|
||||
|
||||
- Cross-platform:
|
||||
@@ -24,7 +30,7 @@ products - it even runs on the International Space Station! Features include:
|
||||
- Source code is both ISO C and ISO C++ compliant
|
||||
- Very easy to integrate
|
||||
- Own TCP/IP stack for bare metal / RTOS, or run on top of any existing one (lwIP, Zephyr, ...)
|
||||
- Built-in TLS 1.3 ECC stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
|
||||
- Built-in TLS 1.3 stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
|
||||
- Does not depend on any other software to implement networking
|
||||
- Built-in firmware updates for STM32 H5, STM32 H7, NXP IMXRT, RP2040/2350, ESP32 and more
|
||||
- Commercial support with proactive security updates; [CRA-compliant](https://mongoose.ws/security/cra-compliance/)
|
||||
@@ -35,8 +41,32 @@ See https://mongoose.ws/ for complete documentation, videos, case studies, etc.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
Mongoose can work on top of any TCP/IP stack that supports BSD sockets API.
|
||||
Platforms supported by the 3rd party TCP/IP stacks:
|
||||
Mongoose has its own built-in TCP/IP stack with drivers, so it needs no
|
||||
additional software to implement networking. The built-in stack runs on bare
|
||||
metal and under any RTOS. Platforms supported by the Mongoose built-in TCP/IP
|
||||
stack:
|
||||
|
||||
| MCU | Connectivity | Notes |
|
||||
| :-- | :----------- | :---- |
|
||||
| **STM32** | Built-in Ethernet | Ethernet-capable STM32F, STM32H5, STM32H7, STM32N6, STM32C5 variants |
|
||||
| **NXP i.MX RT** | Built-in Ethernet | RT102x, RT104x, RT105x, RT106x, RT117x, RT118x (NETC) |
|
||||
| **NXP MCX** | Built-in Ethernet | MCXN94x, MCXE247 |
|
||||
| **NXP RW612** | Eth, WiFi, 802.15.4 | Separate Ethernet and radio drivers |
|
||||
| **Microchip ATSAME54** | Built-in Ethernet | GMAC driver |
|
||||
| **Renesas RA** | Built-in Ethernet | RA6M, RA8M |
|
||||
| **Infineon XMC** | Built-in Ethernet | XMC4, XMC7 |
|
||||
| **Texas Instruments** | Built-in Ethernet | TM4C, TMS570 |
|
||||
| **RP2040, RP2350, STM32, others** | CYW43xx Wi-Fi chips | SPI or SDIO; Pico W, Pico 2 W, Pico RM2, Arduino Portenta H7 |
|
||||
| **STM32, others** | ST67W6 Wi-Fi modules | SPI; for example, STM32U5 with the ST67W611M1 module |
|
||||
| **MCUs with an NXP Wi-Fi SDK port** | NXP Wi-Fi | Requires the SDK's Wi-Fi port implementing the driver's network hooks |
|
||||
| **NXP MCXW71** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4 |
|
||||
| **NXP RW612** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4; requires NXP radio firmware |
|
||||
| **Any MCU with SPI** | W5500 or W5100 Ethernet chips | External Wiznet MAC+PHY |
|
||||
| **MCUs connected to a cellular modem** | Cellular | AT commands and PPP; for example, SIM800 |
|
||||
| **MCUs with CMSIS Ethernet drivers** | Ethernet via CMSIS-Driver | Requires CMSIS Ethernet MAC and PHY drivers |
|
||||
|
||||
Mongoose can also run on top of any TCP/IP stack that supports the BSD
|
||||
sockets API. Platforms supported via 3rd party TCP/IP stacks:
|
||||
|
||||
| TCP/IP stack | Notes |
|
||||
| :-------------- | :------- |
|
||||
@@ -45,23 +75,6 @@ Platforms supported by the 3rd party TCP/IP stacks:
|
||||
| **Other** | Any other TCP/IP stack that supports BSD socket API, for example Amazon FreeRTOS-TCP |
|
||||
| **Linux, Mac, Windows** | Workstations, server, single board computers, embedded Linux devices running on MPUs or FPGAs |
|
||||
|
||||
Optionally, Mongoose provides its own built-in TCP/IP stack, eliminating the
|
||||
need for additional software to implement networking functionality. The
|
||||
built-in stack supports operation in both bare-metal and RTOS environments.
|
||||
Platforms supported by the Mongoose built-in TCP/IP stack:
|
||||
|
||||
| Hardware | Notes |
|
||||
| :------------- | :------- |
|
||||
| **STM32** | All STM32 MCUs with built-in Ethernet: STM32Fxx, STM32H5xx, STM32H7xx |
|
||||
| **NXP** | All NXP MCUs with built-in Ethernet: IMXRT102x, IMXRT104x, IMXRT105x, IMXRT106x, IMXRT117x, RW612, MCXN94x |
|
||||
| **Microchip** | ATSAME54 MCUs with built-int Ethernet |
|
||||
| **Renesas** | RA5M, RA6M, RA8M MCUs with built-in Ethernet |
|
||||
| **Infineon** | XMC4, XMC7 MCUs with built-in Ethernet |
|
||||
| **Texas Instruments** | TM4C, TMS570 MCUs with built-in Ethernet |
|
||||
| **Cypress WiFi** | Any MCU with CY43xx WiFi chips, like RP2040 Pico-W, RP2350 Pico2-W, Arduino Portenta |
|
||||
| **Wiznet Ethernet** | Any MCU that use Wiznet W5500 or Wiznet 5100 MAC+PHY chips |
|
||||
| **Cellular** | NRF9160, SIM800 |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
Below are quick snippets that should give an idea how simple the API is and
|
||||
@@ -158,6 +171,17 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Licensing
|
||||
|
||||
Mongoose is dual-licensed:
|
||||
- GPLv2: free for open source projects. Many popular open source projects use
|
||||
Mongoose, for example
|
||||
[SWUpdate](https://github.com/sbabic/swupdate/tree/master/mongoose), the
|
||||
embedded Linux update framework
|
||||
- Commercial: for closed-source products. Includes support directly from the
|
||||
engineers who wrote the whole stack, and early security fixes.
|
||||
See [licensing](https://mongoose.ws/licensing/)
|
||||
|
||||
## Commercial use
|
||||
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
|
||||
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
|
||||
@@ -220,6 +244,15 @@ Technical guides and deep dives into embedded web servers, WebUI integration and
|
||||
- [STM32 Ethernet and caches](https://mongoose.ws/articles/stm32-ethernet-and-cache/)
|
||||
- [NXP RW612 OTA Firmware Update](https://mongoose.ws/articles/rw612-ota-firmware-update/)
|
||||
- [lwIP vs Mongoose - TCP/IP Stack Integration Benchmark](https://mongoose.ws/articles/lwip-vs-mongoose-tcpip-stack-integration/)
|
||||
- [Embedded TCP/IP Stacks Comparison](https://mongoose.ws/embedded-tcpip-stack-comparison/)
|
||||
- [STM32H5 File System on Internal Flash: LittleFS + Mongoose](https://mongoose.ws/articles/stm32h5-file-system-on-internal-flash/)
|
||||
- [Firmware OTA and remote control on NXP FRDM-RW612](https://mongoose.ws/articles/rw612-mdash/)
|
||||
- [25000 WebSocket messages per second on STM32 Nucleo-H723ZG](https://mongoose.ws/articles/stm32-websocket-benchmark/)
|
||||
- [How to connect STM32 to AWS IoT Core](https://mongoose.ws/articles/stm32-aws-iot/)
|
||||
- [How to connect STM32 to Azure IoT Hub](https://mongoose.ws/articles/stm32-azure-iot-hub/)
|
||||
- [How to connect STM32 to EMQX](https://mongoose.ws/articles/stm32-emqx/)
|
||||
- [STM32 Device Management deep dive: Secure OTA, Remote Control, health monitor](https://mongoose.ws/articles/stm32-mdash-deep-dive/)
|
||||
- [STM32 TCP/IP footprint: how much flash and RAM does Mongoose take?](https://mongoose.ws/articles/tcp-ip-footprint/)
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Security Policy
|
||||
|
||||
Mongoose is a full-stack embedded networking library by Cesanta Software Ltd,
|
||||
<https://mongoose.ws>. This policy covers how to report vulnerabilities
|
||||
and how Cesanta handles them.
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes go to the `master` branch and the latest release.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Do not report security issues in public GitHub issues. Report them
|
||||
privately using GitHub Security Advisories:
|
||||
<https://github.com/cesanta/mongoose/security/advisories/new>
|
||||
|
||||
Include the Mongoose version or commit, steps or a proof of concept to reproduce, and
|
||||
the impact. Cesanta usually responds the same business day.
|
||||
|
||||
## Disclosure process
|
||||
|
||||
1. Cesanta reviews the report and coordinates a fix with the reporter.
|
||||
2. Eligible commercial customers receive a notification and a private fix
|
||||
one month before the CVE becomes public.
|
||||
3. The fix and the CVE are then published. The reporter is credited in the
|
||||
GitHub Security Advisory.
|
||||
|
||||
## Security testing
|
||||
|
||||
- CI on every push, unit tests built with ASAN and UBSan
|
||||
- CodeQL static analysis on every commit
|
||||
- Continuous fuzzing on OSS-Fuzz, plus nightly libFuzzer runs
|
||||
- AI-assisted security review on every pull request
|
||||
- Regular review by independent security research groups worldwide
|
||||
|
||||
## EU Cyber Resilience Act (CRA)
|
||||
|
||||
Mongoose supports CRA compliance for connected products: a documented
|
||||
vulnerability handling process (this file), TLS 1.3, signed OTA firmware
|
||||
updates, and an SBOM (`sbom.cdx.json`). Details:
|
||||
<https://mongoose.ws/security/cra-compliance/>
|
||||
+9901
-5728
File diff suppressed because it is too large.
Load diff
+809
-982
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"version": "0.0.1",
|
||||
"componentid": "::Mongoose:Networking Core",
|
||||
"type": "object",
|
||||
"grammarVersion": "3.8.0",
|
||||
"license": "Copyright (c) Cesanta Software Limited",
|
||||
"properties": {
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"title": "General information",
|
||||
"properties": {
|
||||
"description": {
|
||||
"title": "Middleware description",
|
||||
"type": "string",
|
||||
"actions": [
|
||||
{
|
||||
"type": "disable",
|
||||
"condition": "true"
|
||||
}
|
||||
],
|
||||
"default": "Mongoose networking library: TCP/IP, TLS, HTTP, WebSocket, MQTT, OTA. Edit mongoose_config.h to configure it."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<package schemaVersion="1.7.40" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="PACK.xsd">
|
||||
<package schemaVersion="1.7.56" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:stm32="https://developer.st.com/schemas/stm32cube/1.0.0" xsi:noNamespaceSchemaLocation="PACK.xsd">
|
||||
<vendor>Cesanta</vendor>
|
||||
<name>I-CUBE-Mongoose</name>
|
||||
<description>Mongoose - a lightweight networking library providing TCP/IP stack, TLS, HTTP (Embedded Web Server), WebSocket, MQTT, and Firmware OTA updates. Build production-ready web device dashboards and MQTT cloud connectivity without stitching multiple components together.
|
||||
</description>
|
||||
<description>Mongoose - a full-stack networking library for microcontrollers: Ethernet and Wi-Fi drivers, TCP/IP stack, TLS 1.3, HTTP, WebSocket, MQTT, Modbus, OTA updates and device management, in a two-file C/C++ library, on any RTOS or bare metal.</description>
|
||||
<url>https://mongoose.ws/downloads/stm32pack/</url>
|
||||
<license>License.txt</license>
|
||||
<supportContact>https://mongoose.ws/contact/</supportContact>
|
||||
<environments>
|
||||
<environment name="STM32Cube">
|
||||
<stm32:pack type="MW" friendlyName="Mongoose" shortDesc="Mongoose full-stack networking library">
|
||||
<relocation-info parent="middleware"/>
|
||||
<no-compatibility-constraint/>
|
||||
</stm32:pack>
|
||||
</environment>
|
||||
</environments>
|
||||
<releases>
|
||||
<release date="2026-10-21" version="7.23.3">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-10-19" version="7.23.2">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-09-28" version="7.23.1">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-05-11" version="7.21.7">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
<release date="2026-05-05" version="7.21.5">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
<release date="2026-04-24" version="7.21.4">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
@@ -19,7 +29,7 @@
|
||||
<keyword>IMPLEMENTED_WITH_STM32CUBEMX</keyword>
|
||||
</keywords>
|
||||
<components>
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.21.7" maxInstances="1">
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.3" maxInstances="1">
|
||||
<description>Networking Core</description>
|
||||
<files>
|
||||
<file category="doc" name="README.md"/>
|
||||
@@ -27,7 +37,15 @@
|
||||
<file category="sourceC" name="mongoose.c" />
|
||||
<file category="header" name="mongoose_config.h"/>
|
||||
<file category="utility" name="README.md" public="false"/>
|
||||
<file category="genAsset" name=".config/mongoose_parameters.json"/>
|
||||
</files>
|
||||
<environments>
|
||||
<environment name="STM32Cube">
|
||||
<stm32:component>
|
||||
<middleware-entry-point id="Mongoose" name="Mongoose" category="TCP/IP"/>
|
||||
</stm32:component>
|
||||
</environment>
|
||||
</environments>
|
||||
</component>
|
||||
</components>
|
||||
</package>
|
||||
@@ -1,10 +1,16 @@
|
||||
VERSION = 7.21.7
|
||||
VERSION = 7.23.3
|
||||
REPO_ROOT = ../..
|
||||
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
||||
FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md
|
||||
FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md .config/mongoose_parameters.json
|
||||
GENERATED = License.txt mongoose.h mongoose.c
|
||||
MX2 ?= $(HOME)/STMicroelectronics/STM32CubeMX2_1.1.2/.bin/cube
|
||||
PACKSDIR ?= $(HOME)/Library/Application Support/stm32cube/packs
|
||||
MX2CPN ?= STM32C5A3ZGT6
|
||||
MX2PROJ = $(CURDIR)/mx2test/test.ioc2
|
||||
|
||||
NAME = Cesanta.I-CUBE-Mongoose.$(VERSION)
|
||||
PACK = $(NAME).pack
|
||||
|
||||
PACK = Cesanta.I-CUBE-Mongoose.$(VERSION).pack
|
||||
all: $(PACK)
|
||||
|
||||
Cesanta.I-CUBE-Mongoose.$(VERSION).pack: $(FILES) $(GENERATED)
|
||||
@@ -44,23 +50,19 @@ mongoose.h: Mongoose
|
||||
mongoose.c: Mongoose
|
||||
cp $(REPO_ROOT)/mongoose.c $@
|
||||
|
||||
pk: $(PACK)
|
||||
rm -f gen.mx
|
||||
echo swmgr remove Cesanta.I-CUBE-Mongoose.$(VERSION) >> gen.mx
|
||||
echo swmgr install `pwd`/Cesanta.I-CUBE-Mongoose.$(VERSION).pack ask >> gen.mx
|
||||
echo exit >> gen.mx
|
||||
$(MX) -q `pwd`/gen.mx
|
||||
|
||||
|
||||
mx: $(PACK)
|
||||
mkdir -p $@
|
||||
cp ../downloads/nucleo_h563zi.ioc $@
|
||||
V=`grep 'Mcu.ThirdParty0' $@/nucleo_h563zi.ioc | sed 's,^.*se.,,'`; sed -i '' "s,$$V,$(VERSION),g" $@/nucleo_h563zi.ioc
|
||||
rm -f gen.mx
|
||||
echo config load `pwd`/$@/nucleo_h563zi.ioc >> gen.mx
|
||||
echo project generate >> gen.mx
|
||||
echo exit >> gen.mx
|
||||
$(MX) -q `pwd`/gen.mx
|
||||
test_mx2: $(PACK)
|
||||
-$(MX2) pack remove --cmsis-pack-root "$(PACKSDIR)" $(NAME)
|
||||
rm -f "$(PACKSDIR)/.Download/$(PACK)"
|
||||
$(MX2) pack install --cmsis-pack-root "$(PACKSDIR)" "$(CURDIR)/$(PACK)"
|
||||
export PATH="$(dir $(MX2)):$$PATH"; \
|
||||
PORT=$$(cube lifecycle-mx start-only | sed -n 's/^BACKEND_PORT *//p'); \
|
||||
cube mx project close --port $$PORT -p "$(MX2PROJ)" --discard > /dev/null 2>&1; \
|
||||
rm -rf mx2test && mkdir mx2test && \
|
||||
cube mx project create-from-mcu --port $$PORT --cpn $(MX2CPN) -l "$(CURDIR)/mx2test" -n test --empty-project && \
|
||||
cube mx middleware enable-middleware --port $$PORT -p "$(MX2PROJ)" -n Mongoose | tee mx2test/enable.json && \
|
||||
grep -q '"status": "success"' mx2test/enable.json && \
|
||||
cube mx project save --port $$PORT -p "$(MX2PROJ)" && \
|
||||
cube mx project close --port $$PORT -p "$(MX2PROJ)" --discard
|
||||
|
||||
clean:
|
||||
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) gen.mx mx
|
||||
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) mx2test
|
||||
@@ -39,8 +39,7 @@ patch_linker_script() {
|
||||
HUART=`perl -nle 'print \$1 if /^UART_HandleTypeDef (.+);/' $MAIN_C`
|
||||
grep -q mongoose.h $MAIN_C || perl -i -ne 'print; print "#include \"mongoose.h\"\n" if /BEGIN Includes/' $MAIN_C
|
||||
grep -q '^static void log_fn' $MAIN_C || perl -i -ne "print; print \"static void log_fn(char ch, void *param) {\n HAL_UART_Transmit(param, (unsigned char *) &ch, 1, HAL_MAX_DELAY);\n}\n\" if /USER CODE BEGIN 0/" $MAIN_C
|
||||
grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C
|
||||
grep -q 'mg_mgr_init' $MAIN_C && grep -q 'mg_log_set_fn' $MAIN_C || perl -i -ne 'print; print " mg_log_set_fn(log_fn, &'"${HUART}"');\n" if /mg_mgr_init/' $MAIN_C
|
||||
grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_log_set_fn(log_fn, &'"${HUART}"');\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C
|
||||
grep -q 'mg_mgr_poll' $MAIN_C || perl -i -ne 'print " mg_mgr_poll(&mgr, 0);\n" if /USER CODE END WHILE/; print;' $MAIN_C
|
||||
patch_linker_script $DIR/STM32H723XG_FLASH.ld
|
||||
patch_linker_script $DIR/STM32H743XX_FLASH.ld
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Security Scan Area: common_misc
|
||||
|
||||
## Target
|
||||
|
||||
Area: common_misc
|
||||
|
||||
This area covers common utility code, parsing/string/formatting helpers, URL handling, hash/base64 helpers, filesystem backends, little filesystem integration, and flash support.
|
||||
|
||||
Primary files:
|
||||
- `src/base64.c`
|
||||
- `src/fmt.c`
|
||||
- `src/json.c`
|
||||
- `src/log.c`
|
||||
- `src/md5.c`
|
||||
- `src/printf.c`
|
||||
- `src/sha1.c`
|
||||
- `src/sha256.c`
|
||||
- `src/str.c`
|
||||
- `src/url.c`
|
||||
- `src/util.c`
|
||||
- `src/fs*.c`
|
||||
- `src/lfs.c`
|
||||
- `src/flash.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
**Filesystem Backends:**
|
||||
- Review packed filesystem code and filesystem backends for traversal, unauthorized file read/write, overwrite, or truncation.
|
||||
- Check path canonicalization across POSIX, Windows, embedded filesystems, packed filesystems, and custom `mg_fs` implementations.
|
||||
- Look for trailing slash confusion, NUL byte truncation, and case sensitivity differences in backend path handling.
|
||||
|
||||
**URL Parsing:**
|
||||
- Review URL parsing for scheme, host, port, IPv6 literal, userinfo, percent-encoding, embedded NUL, empty host, default port, and path/query boundary confusion.
|
||||
- Check for SSRF-relevant behavior only when attacker input can control the destination host, protocol, or network boundary. Path-only control is not sufficient.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Security Scan Area: mongoose_core
|
||||
|
||||
## Target
|
||||
|
||||
Area: mongoose_core
|
||||
|
||||
This area covers the core event/network/connection machinery, iobuf handling, queues, socket transport, timers, and BSD abstraction.
|
||||
|
||||
Primary files:
|
||||
- `src/event.c`
|
||||
- `src/iobuf.c`
|
||||
- `src/net.c`
|
||||
- `src/queue.c`
|
||||
- `src/sock.c`
|
||||
- `src/timer.c`
|
||||
- `src/bsd.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
**Protocol State Machine and Event-Driven Lifecycle Bugs:**
|
||||
- Review transitions between listening, accepted, connecting, resolving, TLS handshaking, HTTP, WebSocket, MQTT, draining, closing, and closed states.
|
||||
- Check whether malformed input can cause protocol handlers to process data in the wrong state, process stale data after close, or invoke callbacks with invalid event data.
|
||||
- Look for use-after-free or stale pointer use when callbacks call `mg_close_conn`, mark a connection closing, mutate iobufs, change protocol handlers, or initiate TLS/WebSocket/MQTT transitions.
|
||||
- Verify that partial reads/writes, backpressure, `is_full`, draining, TLS pending states, and send-buffer mutations cannot cause duplicated sends, skipped validation, or parser confusion.
|
||||
- Review timer and wakeup handling for stale user data pointers, cross-thread queue misuse, and callbacks firing after associated connections/state have been freed.
|
||||
- Only report concurrency or race issues when a concrete event ordering can trigger memory corruption, unauthorized action, or reliable device/process crash.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Security Scan Area: net_builtin
|
||||
|
||||
## Target
|
||||
|
||||
Area: net_builtin
|
||||
|
||||
This area covers the built-in TCP/IP stack and its selected L2 implementations.
|
||||
|
||||
Primary files:
|
||||
- `src/l2_*.c`
|
||||
- `src/net_builtin.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
**Built-In TCP/IP Stack, Packet Parsing, and Embedded Network Drivers:**
|
||||
- Review Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, and driver receive/transmit paths for packet-length validation before reading protocol headers.
|
||||
- Check IP/TCP/UDP header length fields, options length, fragmentation/reassembly behavior, checksum handling, and payload offset calculations for underflow, overflow, or out-of-bounds access.
|
||||
- Look for malformed packets that can corrupt descriptor rings, RX/TX buffers, queue state, ARP tables, connection state, or timers.
|
||||
- Review interrupt handlers and bare-metal driver code for bounded processing loops. Flag IRQ paths where attacker-controlled traffic can cause livelock, starvation, descriptor ownership loss, or repeated processing of the same malformed frame.
|
||||
- Check DMA descriptor ownership transitions, memory barriers, cache coherency handling, and buffer length fields for race-like bugs that can cause memory corruption or stale packet reuse.
|
||||
- Review platform-specific drivers such as STM32, NXP, TI, Microchip, Renesas, Infineon, Wiznet, Cypress WiFi, cellular, and other built-in stack integrations for inconsistent bounds checks or endian conversions.
|
||||
- Treat a remotely reachable malformed Ethernet/IP/TCP/UDP packet that crashes, wedges, or corrupts an embedded target as reportable.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Security Scan Area: protocols
|
||||
|
||||
## Target
|
||||
|
||||
Area: protocols
|
||||
|
||||
This area covers the smaller externally controlled protocol parsers and state machines other than HTTP, WebSocket, and TLS.
|
||||
|
||||
Primary files:
|
||||
- `src/modbus.c`
|
||||
- `src/rpc.c`
|
||||
- `src/sntp.c`
|
||||
- `src/mqtt.c`
|
||||
- `src/dns.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
**MQTT Parser and Broker/Client State Machine Vulnerabilities:**
|
||||
- Review MQTT fixed header parsing, remaining-length decoding, variable-length integer loops, packet identifier handling, QoS transitions, topic extraction, property parsing, and MQTT 3.x vs MQTT 5 differences.
|
||||
- Look for malformed MQTT packets that can trigger out-of-bounds reads/writes, stack buffer overflow, heap corruption, assertion/crash, or parser desynchronization.
|
||||
- Specifically check SUBSCRIBE and UNSUBSCRIBE handling for unbounded numbers of topics, malformed topic length fields, missing QoS bytes, invalid QoS values, and writes to fixed-size response arrays.
|
||||
- Verify that PUBLISH handling enforces topic length, packet length, QoS rules, retained flag handling, packet identifier requirements, and message bounds before accessing fields.
|
||||
- Check protocol handlers for authorization bypass, cross-topic data exposure, wildcard topic mishandling, `$SYS` or reserved topic confusion, and unintended publish/subscribe access.
|
||||
- Review MQTT reconnect/resubscribe logic for stale connection state, packet identifier reuse bugs, and memory lifetime problems after close/reconnect.
|
||||
- Treat unauthenticated single-packet broker crash, client crash from malicious broker response, or memory corruption during MQTT parsing as reportable.
|
||||
|
||||
**DNS and mDNS Parsing and Name Resolution:**
|
||||
- Review DNS and mDNS message parsing for label length validation, compression pointer loops, pointer bounds, recursive expansion limits, integer overflow, malformed resource records, and out-of-bounds reads.
|
||||
- Check that DNS answers are matched to outstanding requests by transaction ID, question name/type/class, and source expectations where applicable.
|
||||
- Look for cache poisoning, response spoofing, or name confusion if DNS response validation is weak in client or resolver code.
|
||||
- Verify that DNS failures, timeouts, and retries cannot leave dangling active request state or corrupt connection state.
|
||||
|
||||
**Modbus, RPC, and SNTP:**
|
||||
- Review externally controlled lengths, framing, message parsing, and protocol state for malformed peer input that can cause parser desynchronization, state corruption, or a remotely triggerable crash or wedge.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Security Scan Area: tls
|
||||
|
||||
## Target
|
||||
|
||||
Area: tls
|
||||
|
||||
This area covers the complete Mongoose TLS implementation represented by the primary files.
|
||||
|
||||
Primary files:
|
||||
- `src/tls_aes128.c`
|
||||
- `src/tls_builtin.c`
|
||||
- `src/tls_chacha20.c`
|
||||
- `src/tls_dummy.c`
|
||||
- `src/tls_mbed.c`
|
||||
- `src/tls_openssl.c`
|
||||
- `src/tls_p256_cortex.c`
|
||||
- `src/tls_p256.c`
|
||||
- `src/tls_p384.c`
|
||||
- `src/tls_rsa.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
Connection, core, network, and helper code under `src/` may be inspected when needed to understand the complete TLS execution path.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
### Mandatory High-Risk Coverage: Built-In TLS
|
||||
|
||||
The built-in TLS implementation (`MG_TLS_BUILTIN`) is a mandatory high-risk review target. Perform a substantial security analysis of all security-relevant built-in TLS code, including parsing, cryptographic processing, authentication and certificate validation, length and buffer handling, protocol state, cross-call behavior, and failure paths.
|
||||
|
||||
Trace attacker-controlled TLS peer input through normal client and server execution, including traffic received before peer authentication is complete. Report any credible memory-safety, authentication, confidentiality, integrity, state-corruption, or remotely triggerable availability vulnerability.
|
||||
|
||||
**TLS, Certificate, Hostname Verification, and Cryptographic Validation:**
|
||||
- Review all TLS backends, including built-in TLS, mbedTLS, OpenSSL, WolfSSL, and custom TLS integration, for equivalent security behavior where possible.
|
||||
- Flag certificate verification bypasses, accidental `skip_verification` use in security-sensitive defaults, missing hostname verification when `name`/SNI is available, or inconsistent validation across TLS backends.
|
||||
- For hostname verification, check wildcard handling carefully. A wildcard certificate identity must not match multiple DNS labels. For example, `*.example.com` must not match `a.b.example.com`.
|
||||
- Verify that wildcard matching is applied only to certificate presented identifiers, not to arbitrary reference identifiers or general DNS glob matching.
|
||||
- Review `mg_match` or other glob/matching helpers if used for certificate identity checks, authorization checks, filesystem paths, routing, or topic matching. Flag cases where glob semantics are broader than the security policy requires.
|
||||
- Check certificate parsing for DER/PEM boundary errors, length overflows, malformed ASN.1, extension parsing mistakes, name constraints, SAN vs CN precedence, embedded NUL, and invalid string type handling.
|
||||
- Review TLS record parsing, handshake transcript handling, key schedule, random generation, ECC (P-256, P-384) operations, AES-GCM nonce/tag handling, and error handling for memory safety and authentication failures.
|
||||
- Flag any behavior that silently falls back from verified TLS to unverified TLS or plaintext.
|
||||
- Check that TLS receive/send error paths cannot corrupt connection state, reuse freed buffers, or continue processing unauthenticated plaintext as encrypted data.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Security Scan Area: web_http_auth
|
||||
|
||||
## Target
|
||||
|
||||
Area: web_http_auth
|
||||
|
||||
This area covers HTTP parsing and framing, HTTP-facing filesystem behavior, SSI, WebSocket, authentication/token/dashboard code, and HTTP-based OTA behavior.
|
||||
|
||||
Primary files:
|
||||
- `src/http.c`
|
||||
- `src/ssi.c`
|
||||
- `src/ws.c`
|
||||
- `src/jwt.c`
|
||||
- `src/dash.c`
|
||||
- `src/ota.c`
|
||||
|
||||
Perform a focused security review centered on these files and this security surface.
|
||||
|
||||
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
|
||||
|
||||
For OTA behavior, follow operations under `src/` into TLS, flash, filesystem, JSON, URL, or other support code when necessary, while keeping the review centered on the `web_http_auth` surface.
|
||||
|
||||
## Area-Specific Security Guidance
|
||||
|
||||
**HTTP Parser, Request Smuggling, and Message Framing:**
|
||||
- Verify that HTTP requests or responses containing both `Content-Length` and `Transfer-Encoding` are rejected or handled in a way that cannot cause front-end/back-end desynchronization. Treat CL+TE coexistence as security-relevant request smuggling risk.
|
||||
- Verify that duplicate `Content-Length`, duplicate `Transfer-Encoding`, conflicting transfer codings, malformed chunk sizes, chunk extensions, premature EOF, and body length mismatches cannot cause inconsistent body parsing.
|
||||
- Check whether HTTP/1.0, HTTP/1.1, and malformed protocol versions alter CL/TE handling unsafely. A message with both `Content-Length` and `Transfer-Encoding` should not be accepted merely because of version differences.
|
||||
- Look for parsing paths where headers are normalized differently from later lookup paths, including case folding, whitespace around colon, obs-fold/line folding, embedded NUL, duplicate headers, or partial header names.
|
||||
- Review URL decoding, query parsing, multipart parsing, upload handling, range handling, and chunked responses for bounds mistakes, truncation, or inconsistent decoded-vs-raw path decisions.
|
||||
- Check static file serving and SSI handling for path traversal through percent-encoding, double decoding, backslashes on Windows, mixed separators, dot-dot segments, absolute paths, drive letters, symlinks, embedded NUL, or alias/root_dir confusion.
|
||||
- Review HTTP authentication helper paths for parsing ambiguities in Basic auth, header extraction, credential truncation, empty username/password acceptance, or incorrect comparison behavior.
|
||||
- Check that HTTP upgrade to WebSocket validates method, headers, key, version, and connection semantics consistently before switching protocol state.
|
||||
|
||||
**WebSocket Framing and Upgrade Vulnerabilities:**
|
||||
- Review WebSocket frame parsing for extended payload length handling, 16-bit/64-bit length conversion, masking rules, fragmentation, continuation frames, control-frame length limits, close/ping/pong handling, and compression-related assumptions if present.
|
||||
- Check that client-to-server frames require masking and that malformed mask/payload boundaries cannot read or write out of bounds.
|
||||
- Look for state-machine confusion after HTTP upgrade, especially where partial frames, fragmented messages, or mixed HTTP/WebSocket bytes can cause parser desynchronization.
|
||||
- Check whether large or malformed frame lengths can cause integer overflow, memory corruption, uncontrolled allocation, connection lockup, or single-connection/device crash.
|
||||
- Verify that WebSocket message APIs preserve attacker-controlled binary data length correctly and do not pass non-null-terminated payloads to C-string functions.
|
||||
|
||||
**HTTP-Facing Filesystem Serving, Uploads, and SSI:**
|
||||
- Review `mg_http_serve_dir`, `mg_http_serve_file`, upload APIs, and SSI expansion for traversal, unauthorized file read/write, overwrite, truncation, or unexpected executable/include behavior.
|
||||
- Look for inconsistent handling of decoded and undecoded paths, double decoding, query string leakage into path, trailing slash confusion, NUL byte truncation, and case sensitivity differences.
|
||||
- Review multipart upload parsing for boundary confusion, filename traversal, field length overflow, partial-write behavior, and max-size enforcement.
|
||||
- Check SSI include handling for directory escape, recursive include loops, unintended disclosure of local files, and parsing of attacker-controlled SSI directives.
|
||||
- Verify that range requests and static response headers cannot cause out-of-bounds reads or disclose memory.
|
||||
|
||||
**OTA/Firmware Update and Device Dashboard Security:**
|
||||
- Review firmware update endpoints and code paths for missing authentication, authorization bypass, path confusion, arbitrary write, unsafe flash offset/length handling, and failure to verify firmware authenticity where the code claims secure update behavior.
|
||||
- Check update chunk parsing for integer overflow, flash boundary crossing, rollback/downgrade exposure, incomplete image bootability, and crash/power-loss unsafe state transitions.
|
||||
- Flag any code path where unauthenticated network input can write firmware, configuration, filesystem contents, flash pages, bootloader state, or device identity material.
|
||||
- Review device dashboard code for security-sensitive default routes, credentials, upload handlers, debug endpoints, config mutation endpoints, and CORS/header behavior.
|
||||
|
||||
**Authentication, Authorization, and Access-Control Patterns in Mongoose Integrations:**
|
||||
- Review helper APIs and code paths that implement HTTP auth, cookies, sessions, tokens, device dashboards, REST APIs, upload endpoints, and firmware update endpoints.
|
||||
- Flag authentication bypasses caused by prefix route matching, glob matching, decoded-vs-raw URI confusion, case normalization mistakes, missing boundary checks, or default-open sensitive endpoints.
|
||||
- Check for authorization decisions made in client-side JavaScript only. Client-side checks are not security boundaries.
|
||||
- Review CORS and origin checks only when they protect credentialed sensitive operations and the exploit path is concrete.
|
||||
- Look for credential comparison bugs, truncation of usernames/passwords, accepting empty credentials, parsing only part of an Authorization header, or treating malformed credentials as anonymous-but-authorized.
|
||||
- Flag sensitive data exposure when secrets, credentials, tokens, private keys, firmware signing material, PII, or device identity values can be read by unauthorized remote clients or logged from attacker-triggered paths.
|
||||
@@ -5,7 +5,7 @@
|
||||
3. The exploit depends on NULL, dangling, fake, or corrupted pointers/objects supplied by a local caller, including invalid `mg_connection`, `mg_mgr`, `mg_str`, iobufs, or callback data.
|
||||
4. The exploit requires manually mutating internal structs, flags, parser offsets, descriptor indexes, connection state, TLS state, MQTT/WebSocket state, or filesystem state into impossible combinations.
|
||||
5. The bug exists only in tests, fuzz harnesses, benchmarks, synthetic unit tests, or debug-only code, with no shared production logic.
|
||||
6. The issue is only in a tutorial/demo/example with no realistic production relevance or sensitive operation.
|
||||
6. The affected file is outside `src/`.
|
||||
7. The attacker must edit source code, build flags, compile-time macros, TLS backend choices, filesystem roots, or local device configuration.
|
||||
8. The attacker must directly write hardware registers, MMIO, DMA descriptors, descriptor ownership bits, IRQ state, cache registers, or peripheral configuration.
|
||||
9. Generic DoS from high traffic volume, many valid connections, slow clients, missing quotas, missing rate limits, or ordinary resource saturation.
|
||||
@@ -14,6 +14,8 @@
|
||||
12. Generic crypto advice with no concrete impact on authentication, confidentiality, integrity, downgrade resistance, hostname verification, certificate validation, key handling, nonces/tags, or plaintext fallback.
|
||||
13. Speculative protocol claims without a specific malformed input shape, reachable parser path, and concrete security impact.
|
||||
14. Pure application policy gaps: no default auth, no CSRF, no user access model, no account lockout, no audit logs, or no password policy.
|
||||
15. OTA findings based only on allowing plain HTTP, lacking firmware hash/signature verification, or permitting update URLs to change scheme/host when this is documented/configurable behavior and no existing security check is bypassed.
|
||||
16. Filter TCP findings that rely primarily on predictability or insufficient entropy in protocol sequence-state generation, including attacks based on guessing or reconstructing connection state without observing the traffic.
|
||||
|
||||
# SIGNAL QUALITY CRITERIA
|
||||
**For remaining findings, assess:**
|
||||
@@ -25,7 +27,7 @@
|
||||
6. Does the report provide exact files, functions, line numbers, data flow, bounds reasoning, state reasoning, or a minimal malformed input?
|
||||
7. Is the malformed input shape clear, such as CL+TE HTTP, oversized MQTT SUBSCRIBE, malformed WebSocket length, DNS compression loop, wildcard TLS certificate, invalid packet header length, or encoded path traversal?
|
||||
8. Is the severity justified by reachability and impact?
|
||||
9. Is this a library/helper/default/example-pattern vulnerability rather than only arbitrary application misuse?
|
||||
9. Is this a library/helper/default vulnerability rather than only arbitrary application misuse?
|
||||
10. Is the fix specific enough to act on: reject CL+TE, bound arrays, validate DNS pointers, enforce one-label wildcard matching, check packet lengths, canonicalize paths, or bound RX loops?
|
||||
11. Is a DoS finding caused by a concrete implementation flaw rather than volume, slow clients, missing quotas, or capacity limits?
|
||||
12. In embedded context, can a small malformed input reliably crash, wedge, corrupt, or permanently disrupt a device/process?
|
||||
@@ -42,26 +44,26 @@
|
||||
8. Keep unsafe `mg_match` or glob use at security boundaries: certificate identity, route authorization, filesystem policy, MQTT topic authorization, or access control.
|
||||
9. Keep DNS/mDNS label, compression pointer, pointer loop, RR length, transaction matching, spoofing, OOB, or infinite-loop issues with external reachability.
|
||||
10. Keep filesystem traversal through exposed serving/upload/SSI paths, including encoding, double decoding, backslashes, dot-dot, absolute paths, drive letters, symlinks, NULs, alias/root confusion, and upload filename traversal.
|
||||
11. Keep OTA/update issues allowing unauthenticated or improperly validated writes to firmware, flash, config, filesystem, boot state, device identity, or update metadata.
|
||||
12. Keep embedded packet parser bugs in Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, or drivers when malformed traffic causes OOB, overflow, descriptor corruption, queue corruption, state corruption, or crash.
|
||||
13. Keep IRQ, polling, RX descriptor, and packet-loop livelock/starvation/wedge bugs caused by received traffic.
|
||||
14. Keep event-lifecycle bugs reachable from external input: UAF, stale pointer, double close, callback lifetime error, iobuf mutation after close, protocol transition confusion, timer/wakeup after free.
|
||||
15. Keep externally reachable `mg_str` null-termination mistakes causing OOB read, data leak, auth bypass, parser confusion, or crash.
|
||||
16. Keep integer conversion bugs where external lengths affect allocation, copying, parsing, or bounds checks.
|
||||
17. Filter parser/helper reports based only on a local caller passing invalid pointers, fake lengths, fake structs, or corrupted objects.
|
||||
18. Filter reports requiring direct mutation of connection internals such as protocol flags, closing flags, handler pointers, iobuf fields, callback pointers, or user data.
|
||||
19. Filter reports requiring attacker control of `MG_ENABLE_*`, TLS backend macros, filesystem backend macros, compiler flags, debug flags, sanitizer settings, or platform defines.
|
||||
20. Filter direct hardware-access reports requiring writes to registers, DMA descriptors, MMIO, IRQ state, cache controls, or peripheral configuration.
|
||||
21. Filter generic “Mongoose lacks authentication” reports unless a helper, example, route pattern, auth parser, MQTT broker behavior, dashboard, upload handler, or OTA endpoint creates a concrete issue.
|
||||
22. Filter generic rate-limit/quota/connection-cap/upload-cap findings unless a specific bug lets a small malformed input trigger crash, corruption, infinite loop, or disproportionate exhaustion.
|
||||
23. Filter purely theoretical UB, portability, unaligned-access, or signed-overflow reports with no supported-platform impact and no realistic external trigger.
|
||||
24. Filter RFC non-compliance without concrete smuggling, bypass, memory corruption, data exposure, state corruption, or crash.
|
||||
25. Keep security-relevant production-like examples: dashboards, upload servers, MQTT brokers, firmware update flows, auth examples, TLS examples, and filesystem-serving examples.
|
||||
26. Keep client-side findings caused by malicious servers, brokers, DNS responders, TLS peers, WebSocket peers, or HTTP peers.
|
||||
27. Keep callback-related findings only when Mongoose invokes the callback normally and then unsafely continues using freed or mutated state.
|
||||
28. Keep availability findings when one malformed packet, request, certificate, DNS response, MQTT message, WebSocket frame, or small sequence can reliably crash, wedge, corrupt, or disrupt a Mongoose target.
|
||||
29. Keep custom backend findings only when the issue is in Mongoose’s backend interface, default backend, documented backend contract, or common backend implementation.
|
||||
30. When uncertain, keep findings with clear external input, reachable code path, and concrete impact in Mongoose library/protocol/driver code; filter internal-access, impossible-state, local-misuse, generic-hardening, and speculative reports.
|
||||
31. For dashboard reports, do not infer unauthenticated access from a route-specific check alone; account for the broader authentication flow. Keep concrete reachable bugs such as path traversal, unsafe decoded paths, or file read/write/delete issues.
|
||||
32. Filter reports regarding DNS transaction ID predictability or lack of strict question-name matching in DNS response processing, as these are acknowledged design limitations of the current implementation.
|
||||
33. Filter reports that unauthenticated SNTP time affects TLS certificate validity when Mongoose only performs the SNTP sync if mg_boot_timestamp_ms has not already been initialized.
|
||||
11. Keep embedded packet parser bugs in Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, or drivers when malformed traffic causes OOB, overflow, descriptor corruption, queue corruption, state corruption, or crash.
|
||||
12. Keep IRQ, polling, RX descriptor, and packet-loop livelock/starvation/wedge bugs caused by received traffic.
|
||||
13. Keep event-lifecycle bugs reachable from external input: UAF, stale pointer, double close, callback lifetime error, iobuf mutation after close, protocol transition confusion, timer/wakeup after free.
|
||||
14. Keep externally reachable `mg_str` null-termination mistakes causing OOB read, data leak, auth bypass, parser confusion, or crash.
|
||||
15. Keep integer conversion bugs where external lengths affect allocation, copying, parsing, or bounds checks.
|
||||
16. Filter parser/helper reports based only on a local caller passing invalid pointers, fake lengths, fake structs, or corrupted objects.
|
||||
17. Filter reports requiring direct mutation of connection internals such as protocol flags, closing flags, handler pointers, iobuf fields, callback pointers, or user data.
|
||||
18. Filter reports requiring attacker control of `MG_ENABLE_*`, TLS backend macros, filesystem backend macros, compiler flags, debug flags, sanitizer settings, or platform defines.
|
||||
19. Filter direct hardware-access reports requiring writes to registers, DMA descriptors, MMIO, IRQ state, cache controls, or peripheral configuration.
|
||||
20. Filter generic “Mongoose lacks authentication” reports unless a helper, route pattern, auth parser, MQTT broker behavior, dashboard, upload handler, or OTA endpoint creates a concrete issue.
|
||||
21. Filter generic rate-limit/quota/connection-cap/upload-cap findings unless a specific bug lets a small malformed input trigger crash, corruption, infinite loop, or disproportionate exhaustion.
|
||||
22. Filter purely theoretical UB, portability, unaligned-access, or signed-overflow reports with no supported-platform impact and no realistic external trigger.
|
||||
23. Filter RFC non-compliance without concrete smuggling, bypass, memory corruption, data exposure, state corruption, or crash.
|
||||
24. Keep security-relevant production library code under `src/` for dashboards, uploads, MQTT, firmware updates, authentication, TLS, and filesystem serving.
|
||||
25. Keep client-side findings caused by malicious servers, brokers, DNS responders, TLS peers, WebSocket peers, or HTTP peers.
|
||||
26. Keep callback-related findings only when Mongoose invokes the callback normally and then unsafely continues using freed or mutated state.
|
||||
27. Keep availability findings when one malformed packet, request, certificate, DNS response, MQTT message, WebSocket frame, or small sequence can reliably crash, wedge, corrupt, or disrupt a Mongoose target.
|
||||
28. Keep custom backend findings only when the issue is in Mongoose’s backend interface, default backend, documented backend contract, or common backend implementation.
|
||||
29. When uncertain, keep findings with clear external input, reachable code path, and concrete impact in Mongoose library/protocol/driver code; filter internal-access, impossible-state, local-misuse, generic-hardening, and speculative reports.
|
||||
30. For dashboard reports, do not infer unauthenticated access from a route-specific check alone; account for the broader authentication flow. Keep concrete reachable bugs such as path traversal, unsafe decoded paths, or file read/write/delete issues.
|
||||
31. Filter reports regarding DNS transaction ID predictability or lack of strict question-name matching in DNS response processing, as these are acknowledged design limitations of the current implementation.
|
||||
32. Filter reports that unauthenticated SNTP time affects TLS certificate validity when Mongoose only performs the SNTP sync if mg_boot_timestamp_ms has not already been initialized.
|
||||
33. Filter TCP ISN predictability, weak sequence-number generation, RFC 6528 non-compliance, blind/off-path handshake spoofing, forged RST, and sequence-number-guessing reports in the built-in TCP/IP stack.
|
||||
@@ -1,24 +1,35 @@
|
||||
Review the complete PR diff. This contains all code changes in the PR.
|
||||
Review only changes under `src/` in the complete PR diff. Ignore all other changes in the PR.
|
||||
|
||||
# OBJECTIVE
|
||||
|
||||
Perform a security-focused code review to identify HIGH-CONFIDENCE security vulnerabilities that could have real exploitation potential.
|
||||
|
||||
This is not a general code review - focus ONLY on security implications newly added by this PR. Do not comment on existing security concerns.
|
||||
This is not a general code review - focus ONLY on security implications newly added by this PR under `src/`. Do not analyze, count, or report changes outside `src/`. Do not comment on existing security concerns.
|
||||
|
||||
For this task, you will rely on two additional files, `resources/specs/claude-security-scan-instructions.md` detailing security scan instructions specific to the repository and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those two files are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account.
|
||||
For this task, you will rely on the specified shared security-review sections in `resources/specs/claude-security-scan.md`, the specified security-guidance sections in the six `resources/specs/areas/*.md` prompts listed below, and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those referenced sections and the filtering file are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account.
|
||||
|
||||
YOU MUST READ AND ANALYZE BOTH OF THESE FILES BEFORE COMMENCING THE SECURITY AUDIT. THESE FILES, ALONG WITH SECTIONS FROM THEM, WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK.
|
||||
YOU MUST READ AND ANALYZE THE SPECIFIED SECTIONS AND THE FILTERING FILE BEFORE COMMENCING THE SECURITY AUDIT. THESE SOURCES WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK.
|
||||
|
||||
# CRITICAL INSTRUCTIONS
|
||||
1. MINIMIZE FALSE POSITIVES: Only flag issues where you're >80% confident of actual exploitability
|
||||
2. AVOID NOISE: Skip theoretical issues, style concerns, or low-impact findings
|
||||
3. UNDERSTANDING CONTEXT AND SCOPE OF REVIEW: In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model, code review context and the perspective from which the scanning is going to take place.
|
||||
3. UNDERSTANDING CONTEXT AND SCOPE OF REVIEW: In `resources/specs/claude-security-scan.md`, read only the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model, code review context and the perspective from which the scanning is going to take place. Do not apply unrelated full-scan instructions from that file. In particular, do not inherit assigned-area scan scope, full-scan JSON output requirements, full-scan `review_completed` semantics, instructions to scan long-standing code independently of the PR, or area-selection workflow behavior. This PR prompt's scope, methodology, validation, and output rules remain authoritative.
|
||||
4. TREAT EXCLUSIONS AS MANDATORY. The exclusions section below is mandatory. If a potential finding falls under an exclusion, do not report it. If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact.
|
||||
|
||||
# SECURITY CATEGORIES TO EXAMINE
|
||||
|
||||
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read all the security categories found in the `SECURITY CATEGORIES TO EXAMINE` main section. THESE ARE THE SECURITY CATEGORIES WHICH NEED TO BE EXAMINED.
|
||||
Read the general/common security categories in the `GENERAL SECURITY CATEGORIES TO EXAMINE` section of `resources/specs/claude-security-scan.md`.
|
||||
|
||||
Also read the `Area-Specific Security Guidance` section from every area prompt:
|
||||
|
||||
* `resources/specs/areas/common_misc.md`
|
||||
* `resources/specs/areas/mongoose_core.md`
|
||||
* `resources/specs/areas/web_http_auth.md`
|
||||
* `resources/specs/areas/tls.md`
|
||||
* `resources/specs/areas/net_builtin.md`
|
||||
* `resources/specs/areas/protocols.md`
|
||||
|
||||
Use only the security guidance from the area prompts and ignore their full-scan-specific `Target` sections. Consider the complete set of common and area-specific security categories for every file the PR modifies under `src/`. Do not choose one area or make this PR review area-scoped. Ignore all other changed paths and report only security vulnerabilities introduced by the PR under `src/`.
|
||||
|
||||
Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item.
|
||||
|
||||
@@ -37,7 +48,7 @@ Phase 2 - Comparative Analysis:
|
||||
- Flag code that introduces new attack surfaces
|
||||
|
||||
Phase 3 - Vulnerability Assessment:
|
||||
- Examine each modified file for security implications
|
||||
- Examine each modified file under `src/` for security implications
|
||||
- Trace data flow from user inputs to sensitive operations
|
||||
- Look for privilege boundaries being crossed unsafely
|
||||
- Identify injection points and unsafe deserialization
|
||||
@@ -110,6 +121,6 @@ Better to miss some theoretical issues than flood the report with false positive
|
||||
|
||||
Read the `HARD EXCLUSIONS` section found in the `resources/specs/claude-false-positive-filtering.md` filtering file, for each possbile vulnerability found, check if it matches any items from the patterns listed in that section and if that is the case, exclude that vulnerability from the report. DO NOT REPORT IT if a match is found. Also, take into account the project-specific filtering and reporting precedents found in the `PRECEDENTS` section of the filtering file.
|
||||
|
||||
Begin your analysis now. Use the repository exploration tools to understand the codebase context, then analyze the PR changes for security implications.
|
||||
Begin your analysis now. Use the repository exploration tools to understand the `src/` codebase context, then analyze only the PR changes under `src/` for security implications.
|
||||
|
||||
Your final action must be posting the PR comment. You should not reply again after posting the PR comment.
|
||||
Your final action must be posting the PR comment. You should not reply again after posting the PR comment.
|
||||
@@ -1,130 +0,0 @@
|
||||
# SECURITY SCAN CONTEXT
|
||||
|
||||
**External Attacker Threat Model:**
|
||||
- Analyze vulnerabilities from the perspective of an external attacker, not from the perspective of a developer, local user, debugger, test harness, or code running inside the process.
|
||||
- The attacker can provide bytes through real external interfaces exposed by Mongoose or a Mongoose-based application: TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS handshakes/certificates, Ethernet/IP/TCP/UDP packets, uploaded files, request paths, query strings, headers, message bodies, malicious server responses, and other protocol inputs.
|
||||
- The attacker cannot directly call internal C functions, pass arbitrary invalid pointers, mutate internal structs, invoke callbacks manually, edit compile-time macros, alter build flags, write to hardware registers, control DMA descriptors directly, attach a debugger, or run code inside the target process.
|
||||
- For a finding to be valid, show how attacker-controlled external input reaches the vulnerable code path through normal library/application execution.
|
||||
- Do not report findings that require impossible internal states unless the finding also explains how an external input can cause that state.
|
||||
- Do not report “API misuse” as a library vulnerability unless the API contract makes the insecure use likely, undocumented, or unavoidable.
|
||||
- For embedded networking and driver code, assume the attacker can send malformed network traffic to the device, but cannot directly manipulate hardware registers, memory-mapped I/O, DMA rings, or interrupt state except as a consequence of packets processed by the driver.
|
||||
|
||||
**Mongoose-Specific Security Review Scope:**
|
||||
- Treat this repository as a security-sensitive embedded C/C++ networking stack. Mongoose processes attacker-controlled bytes from TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS, filesystem upload/download paths, and built-in TCP/IP drivers.
|
||||
- In this repository, remotely triggerable denial of service is security-relevant when it is caused by a concrete implementation flaw such as memory corruption, parser abort, assertion failure, stack exhaustion, infinite loop, unbounded recursion, IRQ livelock, connection state corruption, descriptor-ring corruption, or a single unauthenticated packet causing process/device crash.
|
||||
- Prioritize HIGH and MEDIUM severity findings. Include LOW only when unusually concrete, externally relevant, and useful. Do not report vague robustness concerns. Every finding must identify the attacker-controlled input, affected parser/state machine/buffer, concrete impact, and the exact code path.
|
||||
- Prefer findings with a plausible packet/request/message shape. For network protocol issues, describe the relevant malformed HTTP request, MQTT packet, WebSocket frame, DNS message, TLS handshake/certificate input, Ethernet/IP/TCP/UDP packet, or filesystem request.
|
||||
- Because this is C/C++, memory safety vulnerabilities are in scope. Buffer overflows, stack overflows, heap overflows, out-of-bounds reads/writes, use-after-free, double free, integer overflow leading to memory corruption, invalid pointer lifetime, and unsafe length conversions must be reviewed carefully.
|
||||
- Do not dismiss issues merely because they are “only DoS” if the issue is remotely triggerable against an embedded server, broker, device dashboard, firmware-update endpoint, or network-facing parser.
|
||||
|
||||
# SECURITY CATEGORIES TO EXAMINE
|
||||
|
||||
## Mandatory High-Risk Coverage: Built-In TLS
|
||||
|
||||
The built-in TLS implementation (`MG_TLS_BUILTIN`) is a mandatory high-risk review target. Perform a substantial security analysis of all security-relevant built-in TLS code, including parsing, cryptographic processing, authentication and certificate validation, length and buffer handling, protocol state, cross-call behavior, and failure paths.
|
||||
|
||||
Trace attacker-controlled TLS peer input through normal client and server execution, including traffic received before peer authentication is complete. Report any credible memory-safety, authentication, confidentiality, integrity, state-corruption, or remotely triggerable availability vulnerability.
|
||||
|
||||
Do not set `review_completed` to `true` unless the built-in TLS implementation has been meaningfully inspected as a whole.
|
||||
|
||||
|
||||
**C Memory Safety and Length-Handling Vulnerabilities:**
|
||||
- Look for writes to fixed-size stack or heap buffers where the loop bound is derived from attacker-controlled protocol fields, including topic counts, header counts, chunk counts, multipart parts, DNS labels, WebSocket fragments, TCP/IP options, or filesystem path components.
|
||||
- Check all conversions between `size_t`, `int`, `long`, `uint16_t`, `uint32_t`, and signed protocol lengths. Flag integer truncation, wraparound, negative-to-large conversion, or off-by-one behavior that can affect allocation, parsing, copying, or bounds checks.
|
||||
- Review any `memcpy`, `memmove`, `memcmp`, `snprintf`, `mg_snprintf`, `mg_xprintf`, iobuf append/delete/resize operation, and manual pointer increment where source length can originate from network input.
|
||||
- Treat `struct mg_str` values as non-null-terminated unless the code proves otherwise. Flag use of `strlen`, `%s`, `strcmp`, `strstr`, or C-string APIs on attacker-controlled `mg_str.buf` unless length and termination are guaranteed.
|
||||
- Check for parser code that advances pointers without proving that enough bytes remain. Flag any read of fixed-size fields, variable-length integers, DNS labels, TLS vectors, MQTT remaining length, WebSocket extended lengths, or HTTP chunks before bounds are validated.
|
||||
- Look for inconsistent ownership and lifetime of buffers referenced by `mg_str`, connection receive/send iobufs, TLS buffers, filesystem buffers, and event callback data.
|
||||
- Check for reentrancy hazards in event callbacks where user handlers may close a connection, mutate iobufs, start TLS, change protocol handlers, or free state while protocol code continues using stale pointers.
|
||||
|
||||
**HTTP Parser, Request Smuggling, and Message Framing:**
|
||||
- Verify that HTTP requests or responses containing both `Content-Length` and `Transfer-Encoding` are rejected or handled in a way that cannot cause front-end/back-end desynchronization. Treat CL+TE coexistence as security-relevant request smuggling risk.
|
||||
- Verify that duplicate `Content-Length`, duplicate `Transfer-Encoding`, conflicting transfer codings, malformed chunk sizes, chunk extensions, premature EOF, and body length mismatches cannot cause inconsistent body parsing.
|
||||
- Check whether HTTP/1.0, HTTP/1.1, and malformed protocol versions alter CL/TE handling unsafely. A message with both `Content-Length` and `Transfer-Encoding` should not be accepted merely because of version differences.
|
||||
- Look for parsing paths where headers are normalized differently from later lookup paths, including case folding, whitespace around colon, obs-fold/line folding, embedded NUL, duplicate headers, or partial header names.
|
||||
- Review URL decoding, query parsing, multipart parsing, upload handling, range handling, and chunked responses for bounds mistakes, truncation, or inconsistent decoded-vs-raw path decisions.
|
||||
- Check static file serving and SSI handling for path traversal through percent-encoding, double decoding, backslashes on Windows, mixed separators, dot-dot segments, absolute paths, drive letters, symlinks, embedded NUL, or alias/root_dir confusion.
|
||||
- Review HTTP authentication helper paths for parsing ambiguities in Basic auth, header extraction, credential truncation, empty username/password acceptance, or incorrect comparison behavior.
|
||||
- Check that HTTP upgrade to WebSocket validates method, headers, key, version, and connection semantics consistently before switching protocol state.
|
||||
|
||||
**MQTT Parser and Broker/Client State Machine Vulnerabilities:**
|
||||
- Review MQTT fixed header parsing, remaining-length decoding, variable-length integer loops, packet identifier handling, QoS transitions, topic extraction, property parsing, and MQTT 3.x vs MQTT 5 differences.
|
||||
- Look for malformed MQTT packets that can trigger out-of-bounds reads/writes, stack buffer overflow, heap corruption, assertion/crash, or parser desynchronization.
|
||||
- Specifically check SUBSCRIBE and UNSUBSCRIBE handling for unbounded numbers of topics, malformed topic length fields, missing QoS bytes, invalid QoS values, and writes to fixed-size response arrays.
|
||||
- Verify that PUBLISH handling enforces topic length, packet length, QoS rules, retained flag handling, packet identifier requirements, and message bounds before accessing fields.
|
||||
- Check broker examples and protocol handlers for authorization bypass, cross-topic data exposure, wildcard topic mishandling, `$SYS` or reserved topic confusion, and unintended publish/subscribe access.
|
||||
- Review MQTT reconnect/resubscribe logic for stale connection state, packet identifier reuse bugs, and memory lifetime problems after close/reconnect.
|
||||
- Treat unauthenticated single-packet broker crash, client crash from malicious broker response, or memory corruption during MQTT parsing as reportable.
|
||||
|
||||
**WebSocket Framing and Upgrade Vulnerabilities:**
|
||||
- Review WebSocket frame parsing for extended payload length handling, 16-bit/64-bit length conversion, masking rules, fragmentation, continuation frames, control-frame length limits, close/ping/pong handling, and compression-related assumptions if present.
|
||||
- Check that client-to-server frames require masking and that malformed mask/payload boundaries cannot read or write out of bounds.
|
||||
- Look for state-machine confusion after HTTP upgrade, especially where partial frames, fragmented messages, or mixed HTTP/WebSocket bytes can cause parser desynchronization.
|
||||
- Check whether large or malformed frame lengths can cause integer overflow, memory corruption, uncontrolled allocation, connection lockup, or single-connection/device crash.
|
||||
- Verify that WebSocket message APIs preserve attacker-controlled binary data length correctly and do not pass non-null-terminated payloads to C-string functions.
|
||||
|
||||
**TLS, Certificate, Hostname Verification, and Cryptographic Validation:**
|
||||
- Review all TLS backends, including built-in TLS, mbedTLS, OpenSSL, WolfSSL, and custom TLS integration, for equivalent security behavior where possible.
|
||||
- Flag certificate verification bypasses, accidental `skip_verification` use in security-sensitive examples or defaults, missing hostname verification when `name`/SNI is available, or inconsistent validation across TLS backends.
|
||||
- For hostname verification, check wildcard handling carefully. A wildcard certificate identity must not match multiple DNS labels. For example, `*.example.com` must not match `a.b.example.com`.
|
||||
- Verify that wildcard matching is applied only to certificate presented identifiers, not to arbitrary reference identifiers or general DNS glob matching.
|
||||
- Review `mg_match` or other glob/matching helpers if used for certificate identity checks, authorization checks, filesystem paths, routing, or topic matching. Flag cases where glob semantics are broader than the security policy requires.
|
||||
- Check certificate parsing for DER/PEM boundary errors, length overflows, malformed ASN.1, extension parsing mistakes, name constraints, SAN vs CN precedence, embedded NUL, and invalid string type handling.
|
||||
- Review TLS record parsing, handshake transcript handling, key schedule, random generation, ECC/X25519 operations, AES-GCM nonce/tag handling, and error handling for memory safety and authentication failures.
|
||||
- Flag any behavior that silently falls back from verified TLS to unverified TLS or plaintext.
|
||||
- Check that TLS receive/send error paths cannot corrupt connection state, reuse freed buffers, or continue processing unauthenticated plaintext as encrypted data.
|
||||
|
||||
**DNS, mDNS, URL Parsing, and Name Resolution:**
|
||||
- Review DNS and mDNS message parsing for label length validation, compression pointer loops, pointer bounds, recursive expansion limits, integer overflow, malformed resource records, and out-of-bounds reads.
|
||||
- Check that DNS answers are matched to outstanding requests by transaction ID, question name/type/class, and source expectations where applicable.
|
||||
- Look for cache poisoning, response spoofing, or name confusion if DNS response validation is weak in client or resolver code.
|
||||
- Review URL parsing for scheme, host, port, IPv6 literal, userinfo, percent-encoding, embedded NUL, empty host, default port, and path/query boundary confusion.
|
||||
- Check for SSRF-relevant behavior only when attacker input can control the destination host, protocol, or network boundary. Path-only control is not sufficient.
|
||||
- Verify that DNS failures, timeouts, and retries cannot leave dangling active request state or corrupt connection state.
|
||||
|
||||
**Built-In TCP/IP Stack, Packet Parsing, and Embedded Network Drivers:**
|
||||
- Review Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, and driver receive/transmit paths for packet-length validation before reading protocol headers.
|
||||
- Check IP/TCP/UDP header length fields, options length, fragmentation/reassembly behavior, checksum handling, and payload offset calculations for underflow, overflow, or out-of-bounds access.
|
||||
- Look for malformed packets that can corrupt descriptor rings, RX/TX buffers, queue state, ARP tables, connection state, or timers.
|
||||
- Review interrupt handlers and bare-metal driver code for bounded processing loops. Flag IRQ paths where attacker-controlled traffic can cause livelock, starvation, descriptor ownership loss, or repeated processing of the same malformed frame.
|
||||
- Check DMA descriptor ownership transitions, memory barriers, cache coherency handling, and buffer length fields for race-like bugs that can cause memory corruption or stale packet reuse.
|
||||
- Review platform-specific drivers such as STM32, NXP, TI, Microchip, Renesas, Infineon, Wiznet, Cypress WiFi, cellular, and other built-in stack integrations for inconsistent bounds checks or endian conversions.
|
||||
- Treat a remotely reachable malformed Ethernet/IP/TCP/UDP packet that crashes, wedges, or corrupts an embedded target as reportable.
|
||||
|
||||
**Filesystem Serving, Uploads, Packed Filesystems, and SSI:**
|
||||
- Review `mg_http_serve_dir`, `mg_http_serve_file`, upload APIs, packed filesystem code, and SSI expansion for traversal, unauthorized file read/write, overwrite, truncation, or unexpected executable/include behavior.
|
||||
- Check path canonicalization across POSIX, Windows, embedded filesystems, packed filesystems, and custom `mg_fs` implementations.
|
||||
- Look for inconsistent handling of decoded and undecoded paths, double decoding, query string leakage into path, trailing slash confusion, NUL byte truncation, and case sensitivity differences.
|
||||
- Review multipart upload parsing for boundary confusion, filename traversal, field length overflow, partial-write behavior, and max-size enforcement.
|
||||
- Check SSI include handling for directory escape, recursive include loops, unintended disclosure of local files, and parsing of attacker-controlled SSI directives.
|
||||
- Verify that range requests and static response headers cannot cause out-of-bounds reads or disclose memory.
|
||||
|
||||
**OTA/Firmware Update and Device Dashboard Security:**
|
||||
- Review firmware update endpoints and examples for missing authentication, authorization bypass, path confusion, arbitrary write, unsafe flash offset/length handling, and failure to verify firmware authenticity where the code claims secure update behavior.
|
||||
- Check update chunk parsing for integer overflow, flash boundary crossing, rollback/downgrade exposure, incomplete image bootability, and crash/power-loss unsafe state transitions.
|
||||
- Flag any code path where unauthenticated network input can write firmware, configuration, filesystem contents, flash pages, bootloader state, or device identity material.
|
||||
- Review device dashboard examples for security-sensitive default routes, credentials, upload handlers, debug endpoints, config mutation endpoints, and CORS/header behavior when they are likely to be copied into production.
|
||||
- Do not report ordinary insecure demo behavior unless it creates a realistic copy-paste production vulnerability or affects library code.
|
||||
|
||||
**Protocol State Machine and Event-Driven Lifecycle Bugs:**
|
||||
- Review transitions between listening, accepted, connecting, resolving, TLS handshaking, HTTP, WebSocket, MQTT, draining, closing, and closed states.
|
||||
- Check whether malformed input can cause protocol handlers to process data in the wrong state, process stale data after close, or invoke callbacks with invalid event data.
|
||||
- Look for use-after-free or stale pointer use when callbacks call `mg_close_conn`, mark a connection closing, mutate iobufs, change protocol handlers, or initiate TLS/WebSocket/MQTT transitions.
|
||||
- Verify that partial reads/writes, backpressure, `is_full`, draining, TLS pending states, and send-buffer mutations cannot cause duplicated sends, skipped validation, or parser confusion.
|
||||
- Review timer and wakeup handling for stale user data pointers, cross-thread queue misuse, and callbacks firing after associated connections/state have been freed.
|
||||
- Only report concurrency or race issues when a concrete event ordering can trigger memory corruption, unauthorized action, or reliable device/process crash.
|
||||
|
||||
**Cross-Platform C Portability With Security Impact:**
|
||||
- Review code paths whose security depends on struct packing, alignment, endianness, pointer size, signedness of `char`, integer width, or platform-specific filesystem/network behavior.
|
||||
- Check for unaligned memory access on embedded targets when parsing attacker-controlled network packets.
|
||||
- Verify endian conversions for Ethernet/IP/TCP/UDP/DNS/MQTT/TLS fields before bounds checks and allocations.
|
||||
- Look for platform branches where one backend performs validation and another does not, especially across UNIX, Windows, lwIP, Zephyr, FreeRTOS-like systems, bare-metal, and built-in TCP/IP stack modes.
|
||||
- Flag portability bugs only when they create a realistic vulnerability on a supported platform, not merely theoretical undefined behavior.
|
||||
|
||||
**Authentication, Authorization, and Access-Control Patterns in Mongoose Integrations:**
|
||||
- Review examples and helper APIs that implement HTTP auth, cookies, sessions, tokens, device dashboards, REST APIs, MQTT topic access, upload endpoints, and firmware update endpoints.
|
||||
- Flag authentication bypasses caused by prefix route matching, glob matching, decoded-vs-raw URI confusion, case normalization mistakes, missing boundary checks, or default-open sensitive endpoints.
|
||||
- Check for authorization decisions made in client-side JavaScript only. Client-side checks are not security boundaries.
|
||||
- Review CORS and origin checks only when they protect credentialed sensitive operations and the exploit path is concrete.
|
||||
- Look for credential comparison bugs, truncation of usernames/passwords, accepting empty credentials, parsing only part of an Authorization header, or treating malformed credentials as anonymous-but-authorized.
|
||||
- Flag sensitive data exposure when secrets, credentials, tokens, private keys, firmware signing material, PII, or device identity values can be read by unauthorized remote clients or logged from attacker-triggered paths.
|
||||
@@ -1,24 +1,34 @@
|
||||
# Full Codebase Security Scan Prompt
|
||||
# Security Scan Master Prompt
|
||||
|
||||
You are an expert security reviewer performing a full-codebase security audit of the checked-out repository.
|
||||
You are an expert security reviewer performing a focused security audit centered on one assigned scan area in the checked-out repository.
|
||||
|
||||
You must analyze the repository as a whole and identify credible, externally reachable security vulnerabilities in the current implementation.
|
||||
You must analyze the assigned target and security surface and identify credible, externally reachable security vulnerabilities in the current implementation.
|
||||
|
||||
For this task, you will rely on two additional files, `resources/specs/claude-security-scan-instructions.md` detailing security scan instructions specific to the repository and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those two files are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account.
|
||||
Every full security scan uses:
|
||||
|
||||
YOU MUST READ AND ANALYZE BOTH OF THESE FILES BEFORE COMMENCING THE SECURITY AUDIT. THESE FILES, ALONG WITH SECTIONS FROM THEM, WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK.
|
||||
1. the shared master prompt: `resources/specs/claude-security-scan.md`;
|
||||
2. one area prompt selected by the workflow: `resources/specs/areas/<area>.md`;
|
||||
3. the common filtering prompt: `resources/specs/claude-false-positive-filtering.md`.
|
||||
|
||||
The area prompt defines what is being scanned. This master prompt defines how security analysis and reporting are performed. The filtering prompt defines what must not be reported and which historical precedents must be honored.
|
||||
|
||||
YOU MUST READ AND ANALYZE THE ASSIGNED AREA PROMPT AND THE COMMON FILTERING PROMPT BEFORE COMMENCING THE SECURITY AUDIT. THE INSTRUCTIONS, RULES, AND CRITERIA IN ALL THREE PROMPTS ARE MANDATORY TO BE HONORED.
|
||||
|
||||
## OBJECTIVE
|
||||
|
||||
You are reviewing a full source repository for security vulnerabilities.
|
||||
You are reviewing one assigned security area in a source repository for security vulnerabilities.
|
||||
|
||||
The repository may contain application code, protocol parsers, networking code, embedded code, library code, examples, tests, build scripts, documentation, generated files, and third-party/vendor code.
|
||||
The `src/` directory contains the protocol parsers, networking code, embedded code, library code, generated code, and platform-specific code in scope for this review.
|
||||
|
||||
Your task is to determine which parts of the repository are security-relevant, identify externally reachable attack surfaces, and report only findings with a concrete vulnerability pattern, credible attacker influence, and meaningful security impact.
|
||||
The area-specific prompt defines the primary files and security surface for this invocation. Your task is to identify externally reachable attack surfaces within that assigned area and report only findings with a concrete vulnerability pattern, credible attacker influence, and meaningful security impact.
|
||||
|
||||
The security review target is limited to code under `src/`. Do not analyze, count, or report code outside `src/` as part of the security review.
|
||||
|
||||
You may inspect code outside the listed target files only when it is also under `src/` and is necessary to understand data flow, reachability, call chains, state transitions, memory ownership, mitigations, authentication or authorization, protocol interaction, exploitability, or impact. Inspecting supporting code must not turn into an independent security review of unrelated areas. Findings must remain tied to the assigned target and security surface and must identify an affected file under `src/`.
|
||||
|
||||
DO NOT modify files, create commits, open PRs, create issues or post comments! You must only produce the final JSON report requested below.
|
||||
|
||||
Identify high-confidence security vulnerabilities in the full codebase. For this prompt, high-confidence means confidence >= 0.7 under the scoring rules below. Focus on vulnerabilities that are reachable through normal execution paths and attacker-controlled inputs. Report only findings that have a concrete security impact.
|
||||
Identify high-confidence security vulnerabilities in the assigned scan area. For this prompt, high-confidence means confidence >= 0.7 under the scoring rules below. Focus on vulnerabilities that are reachable through normal execution paths and attacker-controlled inputs. Report only findings that have a concrete security impact.
|
||||
|
||||
Do not report vague best-practice concerns, speculative hardening suggestions, style issues, maintainability concerns, generic bug risks, or findings without a clear attacker-controlled path.
|
||||
|
||||
@@ -26,9 +36,26 @@ Existing vulnerabilities in the current codebase are in scope.
|
||||
|
||||
Previously introduced vulnerabilities are in scope.
|
||||
|
||||
Any security-relevant issue present in the checked-out repository is in scope unless explicitly excluded below.
|
||||
Any security-relevant issue tied to the assigned target or security surface is in scope unless explicitly excluded below.
|
||||
|
||||
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model and the code review context.
|
||||
## SECURITY SCAN CONTEXT
|
||||
|
||||
**External Attacker Threat Model:**
|
||||
- Analyze vulnerabilities from the perspective of an external attacker, not from the perspective of a developer, local user, debugger, test harness, or code running inside the process.
|
||||
- The attacker can provide bytes through real external interfaces exposed by Mongoose or a Mongoose-based application: TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS handshakes/certificates, Ethernet/IP/TCP/UDP packets, uploaded files, request paths, query strings, headers, message bodies, malicious server responses, and other protocol inputs.
|
||||
- The attacker cannot directly call internal C functions, pass arbitrary invalid pointers, mutate internal structs, invoke callbacks manually, edit compile-time macros, alter build flags, write to hardware registers, control DMA descriptors directly, attach a debugger, or run code inside the target process.
|
||||
- For a finding to be valid, show how attacker-controlled external input reaches the vulnerable code path through normal library/application execution.
|
||||
- Do not report findings that require impossible internal states unless the finding also explains how an external input can cause that state.
|
||||
- Do not report “API misuse” as a library vulnerability unless the API contract makes the insecure use likely, undocumented, or unavoidable.
|
||||
- For embedded networking and driver code, assume the attacker can send malformed network traffic to the device, but cannot directly manipulate hardware registers, memory-mapped I/O, DMA rings, or interrupt state except as a consequence of packets processed by the driver.
|
||||
|
||||
**Mongoose-Specific Security Review Scope:**
|
||||
- Treat this repository as a security-sensitive embedded C/C++ networking stack. Mongoose processes attacker-controlled bytes from TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS, filesystem upload/download paths, and built-in TCP/IP drivers.
|
||||
- In this repository, remotely triggerable denial of service is security-relevant when it is caused by a concrete implementation flaw such as memory corruption, parser abort, assertion failure, stack exhaustion, infinite loop, unbounded recursion, IRQ livelock, connection state corruption, descriptor-ring corruption, or a single unauthenticated packet causing process/device crash.
|
||||
- Prioritize HIGH and MEDIUM severity findings. Include LOW only when unusually concrete, externally relevant, and useful. Do not report vague robustness concerns. Every finding must identify the attacker-controlled input, affected parser/state machine/buffer, concrete impact, and the exact code path.
|
||||
- Prefer findings with a plausible packet/request/message shape. For network protocol issues, describe the relevant malformed HTTP request, MQTT packet, WebSocket frame, DNS message, TLS handshake/certificate input, Ethernet/IP/TCP/UDP packet, or filesystem request.
|
||||
- Because this is C/C++, memory safety vulnerabilities are in scope. Buffer overflows, stack overflows, heap overflows, out-of-bounds reads/writes, use-after-free, double free, integer overflow leading to memory corruption, invalid pointer lifetime, and unsafe length conversions must be reviewed carefully.
|
||||
- Do not dismiss issues merely because they are “only DoS” if the issue is remotely triggerable against an embedded server, broker, device dashboard, firmware-update endpoint, or network-facing parser.
|
||||
|
||||
## CRITICAL INSTRUCTIONS
|
||||
|
||||
@@ -56,12 +83,14 @@ Do not report:
|
||||
* Purely defensive hardening suggestions.
|
||||
* Issues that only matter under unrealistic assumptions.
|
||||
|
||||
### 3. Analyze the repository as a whole
|
||||
### 3. Analyze the assigned scan area
|
||||
|
||||
This is a full-codebase scan. You should inspect the repository structure, identify major components, and prioritize security-sensitive code paths.
|
||||
This is a targeted scan centered on the primary files and security surface defined by the assigned area prompt. Follow relevant supporting code under `src/` when necessary to establish reachability, data flow, state, validation, mitigation, exploitability, or impact, but do not inspect code outside `src/` as part of the security review or independently review unrelated areas.
|
||||
|
||||
Do not restrict analysis to a diff. Do not assume only changed files matter. Do not assume an issue is irrelevant because it appears in old or long-standing code.
|
||||
|
||||
Findings must remain tied to the assigned target and security surface.
|
||||
|
||||
### 4. Prioritize externally reachable attack surfaces
|
||||
|
||||
Prioritize code reachable from attacker-controlled or peer-controlled inputs. Relevant inputs may include, but are not limited to:
|
||||
@@ -94,25 +123,40 @@ The exclusions section below is mandatory. If a potential finding falls under an
|
||||
|
||||
If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact.
|
||||
|
||||
## SECURITY CATEGORIES TO EXAMINE
|
||||
## GENERAL SECURITY CATEGORIES TO EXAMINE
|
||||
|
||||
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read all the security categories found in the `SECURITY CATEGORIES TO EXAMINE` main section. THESE ARE THE SECURITY CATEGORIES WHICH NEED TO BE EXAMINED.
|
||||
The following common security categories apply to every assigned scan area. The assigned area prompt provides additional area-specific security guidance.
|
||||
|
||||
Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item.
|
||||
|
||||
**C Memory Safety and Length-Handling Vulnerabilities:**
|
||||
- Look for writes to fixed-size stack or heap buffers where the loop bound is derived from attacker-controlled protocol fields, including topic counts, header counts, chunk counts, multipart parts, DNS labels, WebSocket fragments, TCP/IP options, or filesystem path components.
|
||||
- Check all conversions between `size_t`, `int`, `long`, `uint16_t`, `uint32_t`, and signed protocol lengths. Flag integer truncation, wraparound, negative-to-large conversion, or off-by-one behavior that can affect allocation, parsing, copying, or bounds checks.
|
||||
- Review any `memcpy`, `memmove`, `memcmp`, `snprintf`, `mg_snprintf`, `mg_xprintf`, iobuf append/delete/resize operation, and manual pointer increment where source length can originate from network input.
|
||||
- Treat `struct mg_str` values as non-null-terminated unless the code proves otherwise. Flag use of `strlen`, `%s`, `strcmp`, `strstr`, or C-string APIs on attacker-controlled `mg_str.buf` unless length and termination are guaranteed.
|
||||
- Check for parser code that advances pointers without proving that enough bytes remain. Flag any read of fixed-size fields, variable-length integers, DNS labels, TLS vectors, MQTT remaining length, WebSocket extended lengths, or HTTP chunks before bounds are validated.
|
||||
- Look for inconsistent ownership and lifetime of buffers referenced by `mg_str`, connection receive/send iobufs, TLS buffers, filesystem buffers, and event callback data.
|
||||
- Check for reentrancy hazards in event callbacks where user handlers may close a connection, mutate iobufs, start TLS, change protocol handlers, or free state while protocol code continues using stale pointers.
|
||||
|
||||
**Cross-Platform C Portability With Security Impact:**
|
||||
- Review code paths whose security depends on struct packing, alignment, endianness, pointer size, signedness of `char`, integer width, or platform-specific filesystem/network behavior.
|
||||
- Check for unaligned memory access on embedded targets when parsing attacker-controlled network packets.
|
||||
- Verify endian conversions for Ethernet/IP/TCP/UDP/DNS/MQTT/TLS fields before bounds checks and allocations.
|
||||
- Look for platform branches where one backend performs validation and another does not, especially across UNIX, Windows, lwIP, Zephyr, FreeRTOS-like systems, bare-metal, and built-in TCP/IP stack modes.
|
||||
- Flag portability bugs only when they create a realistic vulnerability on a supported platform, not merely theoretical undefined behavior.
|
||||
|
||||
## ANALYSIS METHODOLOGY
|
||||
|
||||
Follow this methodology before producing the final JSON.
|
||||
|
||||
### Phase 1: Repository context research
|
||||
|
||||
First, understand the repository.
|
||||
First, understand the assigned area within `src/`.
|
||||
|
||||
Identify:
|
||||
|
||||
* Main source directories.
|
||||
* Build systems.
|
||||
* Public APIs.
|
||||
* Relevant directories under `src/`.
|
||||
* Public APIs declared under `src/`.
|
||||
* Protocol implementations.
|
||||
* Network-facing components.
|
||||
* File parsing components.
|
||||
@@ -120,14 +164,13 @@ Identify:
|
||||
* TLS, crypto, or certificate validation logic.
|
||||
* Memory management patterns.
|
||||
* Platform-specific code.
|
||||
* Example, test, fuzz, generated, and vendor directories.
|
||||
* Components most likely to process attacker-controlled input.
|
||||
* Components in the assigned area most likely to process attacker-controlled input.
|
||||
|
||||
Use repository files to infer the actual architecture instead of assuming a generic web application model.
|
||||
Use files under `src/` to infer the actual architecture instead of assuming a generic web application model.
|
||||
|
||||
### Phase 2: Attack surface mapping
|
||||
|
||||
Map the externally reachable attack surfaces.
|
||||
Map the externally reachable attack surfaces within the assigned area.
|
||||
|
||||
For each important surface, determine:
|
||||
|
||||
@@ -166,6 +209,7 @@ Before including any finding in the final output, confirm:
|
||||
* The severity matches the impact.
|
||||
* The finding is not a duplicate of another finding.
|
||||
* The finding is not excluded.
|
||||
* The finding is tied to the assigned target or security surface.
|
||||
|
||||
If you cannot provide this level of specificity, do not report the finding.
|
||||
|
||||
@@ -218,6 +262,8 @@ The value of `files_reviewed` should reflect the number of files substantially i
|
||||
|
||||
Do not inflate this number by counting files that were only listed or briefly skimmed.
|
||||
|
||||
The value `review_completed: true` means the assigned scan area was meaningfully reviewed, not that the entire repository was reviewed.
|
||||
|
||||
## SEVERITY GUIDELINES
|
||||
|
||||
Use these severity definitions.
|
||||
@@ -313,10 +359,9 @@ Take into account the project-specific filtering and reporting precedents found
|
||||
|
||||
## DIRECTORY AND FILE EXCLUSIONS
|
||||
|
||||
Do not analyze or report findings from the following paths unless they are directly used by production/library code:
|
||||
- .github/
|
||||
- resources/
|
||||
- test/
|
||||
Only analyze and report findings from files under `src/`.
|
||||
|
||||
Do not analyze or count code outside `src/` as part of the security review.
|
||||
|
||||
## OUTPUT REQUIREMENTS
|
||||
|
||||
@@ -336,4 +381,4 @@ Do not include commentary.
|
||||
|
||||
Do not reply again after outputting the JSON.
|
||||
|
||||
Begin the full-codebase security analysis now.
|
||||
Begin the assigned-area security analysis now.
|
||||
+26
-20
@@ -3,9 +3,9 @@
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"serialNumber": "urn:uuid:d3f8b1e2-4a7c-4d9e-bf2a-1c3e5f7a9b0d",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"metadata": {
|
||||
"timestamp": "2026-08-12T00:00:00Z",
|
||||
"timestamp": "2026-10-13T00:00:00Z",
|
||||
"component": {
|
||||
"type": "library",
|
||||
"bom-ref": "mongoose",
|
||||
@@ -34,14 +34,31 @@
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"bom-ref": "micro-ecc",
|
||||
"author": "Kenneth MacKay",
|
||||
"name": "micro-ecc",
|
||||
"description": "ECC point operations on GF(p) curves (secp256r1). Used by Mongoose built-in TLS 1.3 for ECDH key exchange and ECDSA signatures. Source: src/tls_uecc.c, src/tls_uecc.h.",
|
||||
"licenses": [ { "expression": "BSD-2-Clause" } ],
|
||||
"purl": "pkg:github/kmackay/micro-ecc",
|
||||
"bom-ref": "bearssl-ec",
|
||||
"author": "Thomas Pornin",
|
||||
"name": "BearSSL EC (P-256, P-384, ECDSA)",
|
||||
"description": "P-256 (ec_p256_m31) and P-384 (ec_prime_i31) elliptic curve arithmetic, ECDSA sign and verify, and the i31 bignum code they use, derived from BearSSL. Used by Mongoose built-in TLS 1.3 for ECDHE key exchange and ECDSA signatures, by JWT ES256 and by OTA firmware signature verification. Source: src/tls_p256.c, src/tls_p256.h, src/tls_p384.c, src/tls_p384.h.",
|
||||
"licenses": [ { "expression": "MIT" } ],
|
||||
"externalReferences": [
|
||||
{ "type": "vcs", "url": "https://github.com/kmackay/micro-ecc" }
|
||||
{ "type": "website", "url": "https://bearssl.org" }
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "upstream-commit", "value": "7bea48e5e850ab4cafbe68d3765cdaba13a86d6f" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bom-ref": "p256-cortex-m4",
|
||||
"author": "Emil Lenngren, Shortcut Labs AB",
|
||||
"name": "P256-Cortex-M4",
|
||||
"description": "P-256 ECDH and ECDSA in Cortex-M assembly. Used by Mongoose built-in TLS 1.3 instead of the BearSSL P-256 code on Cortex-M cores with the DSP extension (M4, M7, M33, M55, M85). Source: src/tls_p256_cortex.c.",
|
||||
"licenses": [ { "expression": "MIT" } ],
|
||||
"purl": "pkg:github/Emill/P256-Cortex-M4@ecd3ec2222fc9e18b6b44c86bb7183971a2041fc",
|
||||
"externalReferences": [
|
||||
{ "type": "vcs", "url": "https://github.com/Emill/P256-Cortex-M4" }
|
||||
],
|
||||
"properties": [
|
||||
{ "name": "upstream-commit", "value": "ecd3ec2222fc9e18b6b44c86bb7183971a2041fc" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -55,17 +72,6 @@
|
||||
{ "type": "website", "url": "https://bearssl.org" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bom-ref": "strobe-x25519",
|
||||
"author": "Cryptography Research, Inc. / Mike Hamburg",
|
||||
"name": "STROBE x25519 (adapted)",
|
||||
"description": "X25519 Diffie-Hellman function adapted from the STROBE project. Used by Mongoose built-in TLS 1.3 for ECDH key exchange. Source: src/tls_x25519.c.",
|
||||
"licenses": [ { "expression": "MIT" } ],
|
||||
"externalReferences": [
|
||||
{ "type": "website", "url": "https://strobe.sourceforge.io/" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"bom-ref": "sha1-reid",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#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 <-- DEPRECATED !!!
|
||||
|
||||
@@ -50,7 +49,6 @@
|
||||
#include "arch_armgcc.h"
|
||||
#include "arch_cube.h"
|
||||
#include "arch_esp32.h"
|
||||
#include "arch_esp8266.h"
|
||||
#include "arch_rtx.h"
|
||||
#include "arch_threadx.h"
|
||||
#include "arch_unix.h"
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef MG_PATH_MAX
|
||||
#define MG_PATH_MAX 100
|
||||
#define MG_ENABLE_DIRLIST 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+2
-1
@@ -18,7 +18,8 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef MG_PATH_MAX
|
||||
#define MG_PATH_MAX 100
|
||||
#define MG_ENABLE_DIRLIST 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+5
-68
@@ -25,10 +25,6 @@
|
||||
#define MG_PATH_MAX 100
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MG_ENABLE_TCPIP
|
||||
#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack
|
||||
@@ -36,6 +32,7 @@
|
||||
|
||||
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32C) && \
|
||||
!defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
|
||||
!defined(MG_ENABLE_DRIVER_ST67W6)
|
||||
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
|
||||
@@ -44,6 +41,8 @@
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#elif defined(STM32N6)
|
||||
#define MG_ENABLE_DRIVER_STM32N 1
|
||||
#elif defined(STM32C5)
|
||||
#define MG_ENABLE_DRIVER_STM32C 1
|
||||
#else
|
||||
#error Select a driver in mongoose_config.h
|
||||
#endif
|
||||
@@ -60,70 +59,8 @@
|
||||
#define MG_OTA MG_OTA_STM32H5
|
||||
#elif !defined(MG_OTA) && defined(STM32H7)
|
||||
#define MG_OTA MG_OTA_STM32H7
|
||||
#endif
|
||||
|
||||
// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler
|
||||
// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds
|
||||
// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop
|
||||
// hangs for 10 s, the IWDG fires and the device rolls back.
|
||||
// STM32H7 names the peripheral IWDG1; all others use IWDG.
|
||||
// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS.
|
||||
// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG.
|
||||
#if defined(IWDG1) && !defined(IWDG)
|
||||
#define MG_IWDG IWDG1
|
||||
#else
|
||||
#define MG_IWDG IWDG
|
||||
#endif
|
||||
// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz.
|
||||
// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init().
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_START
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() \
|
||||
do { \
|
||||
MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \
|
||||
MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \
|
||||
MG_IWDG->PR = 6U; /* /256 */ \
|
||||
MG_IWDG->RLR = 1250U; /* 10 s */ \
|
||||
while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \
|
||||
MG_IWDG->KR = 0xAAAAU; /* reload */ \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms.
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU)
|
||||
#endif
|
||||
|
||||
// OTA state in a backup register that survives warm resets but clears on POR.
|
||||
// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock
|
||||
// and unlocks backup domain write access via direct register writes, so it is
|
||||
// safe to call from any context (before or after HAL_Init).
|
||||
// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R).
|
||||
#ifndef MG_OTA_STATE_GET
|
||||
#if defined(PWR_DBPCR_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP)
|
||||
#elif defined(PWR_CR1_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP)
|
||||
#else
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP)
|
||||
#endif
|
||||
|
||||
#if defined(TAMP)
|
||||
#define MG_OTA_STATE_GET() \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
|
||||
#elif defined(RCC_APB4ENR_RTCAPBEN)
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#else
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#endif
|
||||
#elif !defined(MG_OTA) && defined(STM32C5)
|
||||
#define MG_OTA MG_OTA_STM32C5
|
||||
#endif
|
||||
|
||||
// use HAL-defined execute-in-ram section
|
||||
|
||||
+4
-57
@@ -22,10 +22,8 @@
|
||||
#include <esp_system.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_idf_version.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <soc/soc.h>
|
||||
#include <soc/rtc_cntl_reg.h>
|
||||
// #include <esp_memory_utils.h> // For health record coredumps
|
||||
// #include <esp_core_dump.h> // For health record coredumps
|
||||
|
||||
#define MG_PATH_MAX 128
|
||||
|
||||
@@ -33,59 +31,8 @@
|
||||
#define MG_ENABLE_POSIX_FS 1
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 1
|
||||
#endif
|
||||
|
||||
#ifndef MG_OTA_ESP32_WDT_TIMEOUT_MS
|
||||
#define MG_OTA_ESP32_WDT_TIMEOUT_MS 10000U
|
||||
#endif
|
||||
|
||||
static inline void mg_ota_esp32_timer_start(void) {
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
esp_task_wdt_config_t cfg = {0};
|
||||
esp_err_t err;
|
||||
|
||||
cfg.timeout_ms = MG_OTA_ESP32_WDT_TIMEOUT_MS;
|
||||
cfg.idle_core_mask = 0;
|
||||
cfg.trigger_panic = true;
|
||||
|
||||
err = esp_task_wdt_init(&cfg);
|
||||
if (err == ESP_ERR_INVALID_STATE) esp_task_wdt_reconfigure(&cfg);
|
||||
#else
|
||||
esp_task_wdt_init(MG_OTA_ESP32_WDT_TIMEOUT_MS / 1000U, true);
|
||||
#endif
|
||||
esp_task_wdt_add(NULL);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
|
||||
if (state == 0) esp_ota_mark_app_valid_cancel_rollback();
|
||||
REG_WRITE(RTC_CNTL_STORE0_REG, state);
|
||||
return REG_READ(RTC_CNTL_STORE0_REG);
|
||||
}
|
||||
|
||||
#define MG_OTA_STATE_GET() \
|
||||
REG_READ(RTC_CNTL_STORE0_REG)
|
||||
|
||||
#define MG_OTA_STATE_SET(v) mg_ota_esp32_state_set((uint32_t) (v))
|
||||
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_START
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() mg_ota_esp32_timer_start()
|
||||
#endif
|
||||
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() \
|
||||
do { \
|
||||
(void) esp_task_wdt_reset(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef MG_OTA_ROLLBACK
|
||||
#define MG_OTA_ROLLBACK() \
|
||||
do { \
|
||||
(void) esp_ota_mark_app_invalid_rollback_and_reboot(); \
|
||||
} while (0)
|
||||
#ifndef MG_HEALTH_DATA_SIZE
|
||||
#define MG_HEALTH_DATA_SIZE 20
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if MG_ARCH == MG_ARCH_ESP8266
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <esp_system.h>
|
||||
|
||||
#define MG_PATH_MAX 128
|
||||
|
||||
#endif
|
||||
+83
-6
@@ -27,16 +27,12 @@
|
||||
#define MG_PATH_MAX 100
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 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_IMXRT10) && \
|
||||
!defined(MG_ENABLE_DRIVER_IMXRT11)
|
||||
!defined(MG_ENABLE_DRIVER_IMXRT11) && !defined(MG_ENABLE_DRIVER_NETC)
|
||||
#if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
|
||||
defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \
|
||||
defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \
|
||||
@@ -51,6 +47,8 @@
|
||||
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
|
||||
defined(_MIMXRT1176_H_)
|
||||
#define MG_ENABLE_DRIVER_IMXRT11 1
|
||||
#elif defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
|
||||
#define MG_ENABLE_DRIVER_NETC 1
|
||||
#else
|
||||
#error Select an Ethernet driver in mongoose_config.h
|
||||
#endif
|
||||
@@ -82,13 +80,31 @@
|
||||
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
|
||||
defined(_MIMXRT1176_H_))
|
||||
#define MG_OTA MG_OTA_RT1170
|
||||
#elif !defined(MG_OTA) && \
|
||||
(defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_))
|
||||
#define MG_OTA MG_OTA_RT1180
|
||||
#endif
|
||||
|
||||
#ifndef MG_IRAM
|
||||
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
|
||||
#define MG_IRAM \
|
||||
__attribute__((noinline, section(".ramfunc.$SRAM_ITC_cm33")))
|
||||
#else
|
||||
#define MG_IRAM __attribute__((noinline, section(".data_RAM2")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MG_ETH_RAM
|
||||
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
|
||||
#define MG_ETH_RAM __attribute__((section(".bss.$ETH_RAM")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MG_SET_MAC_ADDRESS
|
||||
#if defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
|
||||
// No OCOTP_FUSES present on RT1186 to generate the UUID
|
||||
#define MG_SET_MAC_ADDRESS(mac) (void) (mac)
|
||||
#else
|
||||
#if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11
|
||||
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900)
|
||||
#else
|
||||
@@ -104,11 +120,70 @@
|
||||
mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \
|
||||
} while (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MG_IMXRT_WDOG1_TIMEOUT_MS
|
||||
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000
|
||||
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000U
|
||||
#endif
|
||||
|
||||
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
|
||||
|
||||
#define MG_IMXRT_RTWDOG1_UNLOCK() \
|
||||
do { \
|
||||
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
|
||||
RTWDOG1->CNT = RTWDOG_UPDATE_KEY; \
|
||||
} else { \
|
||||
RTWDOG1->CNT = RTWDOG_UPDATE_KEY & 0xffffU; \
|
||||
RTWDOG1->CNT = RTWDOG_UPDATE_KEY >> 16; \
|
||||
} \
|
||||
while ((RTWDOG1->CS & RTWDOG_CS_ULK_MASK) == 0U) (void) 0; \
|
||||
} while (0)
|
||||
|
||||
#define MG_IMXRT_RTWDOG1_FEED() \
|
||||
do { \
|
||||
uint32_t primask_ = __get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
|
||||
RTWDOG1->CNT = RTWDOG_REFRESH_KEY; \
|
||||
} else { \
|
||||
RTWDOG1->CNT = RTWDOG_REFRESH_KEY & 0xffffU; \
|
||||
RTWDOG1->CNT = RTWDOG_REFRESH_KEY >> 16; \
|
||||
} \
|
||||
if (primask_ == 0U) __enable_irq(); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_START
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() \
|
||||
do { \
|
||||
uint32_t primask_ = __get_PRIMASK(); \
|
||||
SRC_GENERAL_REG->SRMASK &= ~SRC_GENERAL_SRMASK_WDOG1_MASK_MASK; \
|
||||
__disable_irq(); \
|
||||
MG_IMXRT_RTWDOG1_UNLOCK(); \
|
||||
RTWDOG1->WIN = 0U; \
|
||||
RTWDOG1->TOVAL = (MG_IMXRT_WDOG1_TIMEOUT_MS + 7U) / 8U; \
|
||||
RTWDOG1->CS = RTWDOG_CS_EN_MASK | RTWDOG_CS_UPDATE_MASK | \
|
||||
RTWDOG_CS_CLK(1U) | RTWDOG_CS_PRES_MASK | \
|
||||
RTWDOG_CS_CMD32EN_MASK; \
|
||||
while ((RTWDOG1->CS & RTWDOG_CS_RCS_MASK) == 0U) (void) 0; \
|
||||
if (primask_ == 0U) __enable_irq(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() MG_IMXRT_RTWDOG1_FEED()
|
||||
#endif
|
||||
|
||||
// GPR5 survives RTWDOG global resets and is not reserved by the boot ROM.
|
||||
#ifndef MG_OTA_STATE_GET
|
||||
#define MG_OTA_STATE_GET() SRC_GENERAL_REG->GPR[5]
|
||||
#endif
|
||||
|
||||
#ifndef MG_OTA_STATE_SET
|
||||
#define MG_OTA_STATE_SET(v) (SRC_GENERAL_REG->GPR[5] = (uint32_t) (v))
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define MG_IMXRT_WDOG1_FEED() \
|
||||
do { \
|
||||
WDOG1->WSR = 0x5555; \
|
||||
@@ -150,3 +225,5 @@
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -50,10 +50,6 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef MG_ENABLE_DIRLIST
|
||||
#define MG_ENABLE_DIRLIST 1
|
||||
#endif
|
||||
|
||||
#ifndef MG_PATH_MAX
|
||||
#define MG_PATH_MAX FILENAME_MAX
|
||||
#endif
|
||||
|
||||
@@ -166,10 +166,6 @@ typedef int socklen_t;
|
||||
#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
|
||||
|
||||
@@ -4,22 +4,64 @@
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_BSD_SOCKETS
|
||||
void mg_bsd_init(struct mg_mgr *);
|
||||
void mg_bsd_poll(struct mg_mgr *);
|
||||
|
||||
#ifndef MG_ENABLE_BSD_PROTOTYPES
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/un.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef EAGAIN
|
||||
#define EAGAIN 11
|
||||
#endif
|
||||
#ifndef EWOULDBLOCK
|
||||
#define EWOULDBLOCK EAGAIN
|
||||
#endif
|
||||
#ifndef EINPROGRESS
|
||||
#define EINPROGRESS 36
|
||||
#endif
|
||||
#ifndef ENOTCONN
|
||||
#define ENOTCONN 107
|
||||
#endif
|
||||
#ifndef ECONNREFUSED
|
||||
#define ECONNREFUSED 111
|
||||
#endif
|
||||
#ifndef ECONNRESET
|
||||
#define ECONNRESET 104
|
||||
#endif
|
||||
#ifndef EADDRINUSE
|
||||
#define EADDRINUSE 98
|
||||
#endif
|
||||
#ifndef ETIMEDOUT
|
||||
#define ETIMEDOUT 110
|
||||
#endif
|
||||
#ifndef EBADF
|
||||
#define EBADF 9
|
||||
#endif
|
||||
#ifndef EMFILE
|
||||
#define EMFILE 24
|
||||
#endif
|
||||
#ifndef ENOMEM
|
||||
#define ENOMEM 12
|
||||
#endif
|
||||
#ifndef EIO
|
||||
#define EIO 5
|
||||
#endif
|
||||
#ifndef EINVAL
|
||||
#define EINVAL 22
|
||||
#endif
|
||||
#ifndef EPERM
|
||||
#define EPERM 1
|
||||
#endif
|
||||
#ifndef EPIPE
|
||||
#define EPIPE 32
|
||||
#endif
|
||||
#ifndef EPROTONOSUPPORT
|
||||
#define EPROTONOSUPPORT 93
|
||||
#endif
|
||||
|
||||
#if MG_ENABLE_BSD_PROTOTYPES
|
||||
typedef unsigned int socklen_t;
|
||||
typedef unsigned long nfds_t;
|
||||
typedef uint32_t in_addr_t;
|
||||
struct in_addr { in_addr_t s_addr; };
|
||||
struct in6_addr { uint8_t s6_addr[16]; };
|
||||
@@ -45,7 +87,6 @@ struct hostent {
|
||||
int h_length;
|
||||
char **h_addr_list;
|
||||
};
|
||||
#define h_addr h_addr_list[0]
|
||||
struct addrinfo {
|
||||
int ai_flags;
|
||||
int ai_family;
|
||||
@@ -57,6 +98,32 @@ struct addrinfo {
|
||||
struct addrinfo *ai_next;
|
||||
};
|
||||
struct pollfd { int fd; short events; short revents; };
|
||||
|
||||
#if !defined(FD_SET) && !defined(FD_ZERO) && !defined(FD_ISSET)
|
||||
#define MG_BSD_SELECT_H
|
||||
typedef unsigned long __fd_mask;
|
||||
#ifndef FD_SETSIZE
|
||||
#define FD_SETSIZE 64
|
||||
#endif
|
||||
#define _NFDBITS ((int) sizeof(__fd_mask) * 8)
|
||||
#define _howmany(x, y) (((x) + ((y) - 1)) / (y))
|
||||
typedef struct fd_set {
|
||||
__fd_mask __fds_bits[_howmany(FD_SETSIZE, _NFDBITS)];
|
||||
} fd_set;
|
||||
#define __fdset_mask(n) ((__fd_mask) 1 << ((n) % _NFDBITS))
|
||||
#define FD_CLR(n, p) ((p)->__fds_bits[(n) / _NFDBITS] &= ~__fdset_mask(n))
|
||||
#define FD_ISSET(n, p) \
|
||||
(((p)->__fds_bits[(n) / _NFDBITS] & __fdset_mask(n)) != 0)
|
||||
#define FD_SET(n, p) ((p)->__fds_bits[(n) / _NFDBITS] |= __fdset_mask(n))
|
||||
#define FD_ZERO(p) \
|
||||
do { \
|
||||
fd_set *_p = (p); \
|
||||
size_t _n = _howmany(FD_SETSIZE, _NFDBITS); \
|
||||
while (_n > 0) _p->__fds_bits[--_n] = 0; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define h_addr h_addr_list[0]
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
#define AF_UNSPEC 0
|
||||
@@ -69,6 +136,7 @@ struct pollfd { int fd; short events; short revents; };
|
||||
#define IPPROTO_UDP 17
|
||||
#define INADDR_ANY 0
|
||||
#define INADDR_LOOPBACK 0x7f000001
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define SOL_SOCKET 0xffff
|
||||
#define SO_REUSEADDR 2
|
||||
#define SO_REUSEPORT 15
|
||||
@@ -77,79 +145,37 @@ struct pollfd { int fd; short events; short revents; };
|
||||
#define SO_BROADCAST 6
|
||||
#define SO_RCVBUF 8
|
||||
#define SO_SNDBUF 7
|
||||
#define SO_NOSIGPIPE 0x1022
|
||||
#define TCP_NODELAY 1
|
||||
#define MSG_DONTWAIT 0x40
|
||||
#define MSG_NOSIGNAL 0x4000
|
||||
#define MSG_PEEK 0x02
|
||||
#ifndef F_GETFL
|
||||
#define F_GETFL 3
|
||||
#endif
|
||||
#ifndef F_SETFL
|
||||
#define F_SETFL 4
|
||||
#define O_NONBLOCK 0x0004
|
||||
#endif
|
||||
#ifndef O_NONBLOCK
|
||||
#define O_NONBLOCK 0x4000
|
||||
#endif
|
||||
#define SHUT_RD 0
|
||||
#define SHUT_WR 1
|
||||
#define SHUT_RDWR 2
|
||||
#define AI_PASSIVE 0x0001
|
||||
#define NI_MAXHOST 1025
|
||||
#define NI_MAXSERV 32
|
||||
#ifndef EAGAIN
|
||||
#define EAGAIN 11
|
||||
#endif
|
||||
#ifndef EWOULDBLOCK
|
||||
#define EWOULDBLOCK EAGAIN
|
||||
#endif
|
||||
#ifndef EINPROGRESS
|
||||
#define EINPROGRESS 36
|
||||
#endif
|
||||
#ifndef ENOTCONN
|
||||
#define ENOTCONN 107
|
||||
#endif
|
||||
#ifndef ECONNREFUSED
|
||||
#define ECONNREFUSED 111
|
||||
#endif
|
||||
#ifndef ECONNRESET
|
||||
#define ECONNRESET 104
|
||||
#endif
|
||||
#ifndef EADDRINUSE
|
||||
#define EADDRINUSE 98
|
||||
#endif
|
||||
#ifndef ETIMEDOUT
|
||||
#define ETIMEDOUT 110
|
||||
#endif
|
||||
#define POLLIN 0x001
|
||||
#define POLLOUT 0x004
|
||||
#define POLLERR 0x008
|
||||
#define POLLHUP 0x010
|
||||
#define POLLNVAL 0x020
|
||||
uint16_t htons(uint16_t);
|
||||
uint16_t ntohs(uint16_t);
|
||||
uint32_t htonl(uint32_t);
|
||||
uint32_t ntohl(uint32_t);
|
||||
#endif // MG_ENABLE_BSD_PROTOTYPES
|
||||
|
||||
#define closesocket(a) close(a)
|
||||
#define htons(x) mg_ntohs(x)
|
||||
#define ntohs(x) mg_ntohs(x)
|
||||
#define htonl(x) mg_ntohl(x)
|
||||
#define ntohl(x) mg_ntohl(x)
|
||||
|
||||
#ifndef MG_BSD_BACKLOG
|
||||
#define MG_BSD_BACKLOG 5
|
||||
#endif
|
||||
|
||||
#ifndef MG_BSD_CONNECT_TIMEOUT_MS
|
||||
#define MG_BSD_CONNECT_TIMEOUT_MS 10000
|
||||
#endif
|
||||
|
||||
// Mongoose-specific API
|
||||
void mg_bsd_init(void); // must be called before socket()
|
||||
void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands
|
||||
|
||||
// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend
|
||||
void *mg_bsd_transport_new(int domain, int type, int proto);
|
||||
void mg_bsd_transport_free(void *t);
|
||||
int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr);
|
||||
void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock);
|
||||
ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock);
|
||||
ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock);
|
||||
int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock);
|
||||
void mg_bsd_transport_close(void *t);
|
||||
|
||||
// Standard BSD socket API
|
||||
int socket(int, int, int);
|
||||
int bind(int, const struct sockaddr *, socklen_t);
|
||||
int listen(int, int);
|
||||
@@ -157,23 +183,40 @@ int accept(int, struct sockaddr *, socklen_t *);
|
||||
int connect(int, const struct sockaddr *, socklen_t);
|
||||
ssize_t send(int, const void *, size_t, int);
|
||||
ssize_t recv(int, void *, size_t, int);
|
||||
ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
|
||||
ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *,
|
||||
socklen_t);
|
||||
ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *);
|
||||
int close(int);
|
||||
int shutdown(int, int);
|
||||
int fcntl(int, int, int);
|
||||
int setsockopt(int, int, int, const void *, socklen_t);
|
||||
int getsockopt(int, int, int, void *, socklen_t *);
|
||||
int getsockname(int, struct sockaddr *, socklen_t *);
|
||||
int getpeername(int, struct sockaddr *, socklen_t *);
|
||||
int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
|
||||
int poll(struct pollfd *, unsigned int, int);
|
||||
int poll(struct pollfd *, nfds_t, int);
|
||||
struct hostent *gethostbyname(const char *);
|
||||
int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **);
|
||||
int getaddrinfo(const char *, const char *, const struct addrinfo *,
|
||||
struct addrinfo **);
|
||||
void freeaddrinfo(struct addrinfo *);
|
||||
int inet_pton(int, const char *, void *);
|
||||
const char *inet_ntop(int, const void *, char *, socklen_t);
|
||||
in_addr_t inet_addr(const char *);
|
||||
char *inet_ntoa(struct in_addr);
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#endif // MG_ENABLE_BSD_PROTOTYPES
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#else
|
||||
#define mg_bsd_init(mgr)
|
||||
#define mg_bsd_poll(mgr)
|
||||
#endif // MG_ENABLE_BSD_SOCKETS
|
||||
+31
-9
@@ -82,10 +82,6 @@
|
||||
#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
|
||||
@@ -135,7 +131,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_POSIX_FS
|
||||
#define MG_ENABLE_POSIX_FS 0
|
||||
#define MG_ENABLE_POSIX_FS MG_ENABLE_LFS
|
||||
#endif
|
||||
|
||||
#ifndef MG_INVALID_SOCKET
|
||||
@@ -175,6 +171,14 @@
|
||||
#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for
|
||||
#endif // Mongoose built-in network stack
|
||||
|
||||
#ifndef MG_ENABLE_TCPIP_TCPRTX
|
||||
#define MG_ENABLE_TCPIP_TCPRTX 0 // TCP retransmission queue
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_DHCPNAME_SIZE
|
||||
#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size
|
||||
#endif
|
||||
|
||||
#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
|
||||
@@ -211,10 +215,6 @@
|
||||
#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(ifp, driver_data)
|
||||
#endif
|
||||
@@ -223,6 +223,28 @@
|
||||
#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
|
||||
#endif
|
||||
|
||||
#ifndef MG_TLS_RECORD_SIZE
|
||||
#define MG_TLS_RECORD_SIZE 16384 // Max plaintext per sent TLS record, builtin
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_CHACHA20
|
||||
#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN
|
||||
#endif
|
||||
|
||||
// P-256 implementation, for MG_TLS_BUILTIN
|
||||
#define MG_P256_LENNGREN 1 // Cortex-M4/M7/M33 asm, see tls_p256_cortex.c
|
||||
#define MG_P256_BEAR 3 // Portable C, BearSSL, see tls_p256.c
|
||||
|
||||
#ifndef MG_P256
|
||||
#if defined(__GNUC__) && defined(__ARM_ARCH_PROFILE) && \
|
||||
__ARM_ARCH_PROFILE == 'M' && defined(__ARM_FEATURE_DSP) && \
|
||||
!defined(__ARM_BIG_ENDIAN)
|
||||
#define MG_P256 MG_P256_LENNGREN // Cortex-M with DSP: M4, M7, M33, M55, M85
|
||||
#else
|
||||
#define MG_P256 MG_P256_BEAR
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_HEALTH
|
||||
#define MG_ENABLE_HEALTH 0
|
||||
#endif
|
||||
+41
-179
@@ -1,5 +1,6 @@
|
||||
#include "dash.h"
|
||||
#include "http.h"
|
||||
#include "ota.h"
|
||||
#include "util.h"
|
||||
|
||||
#define MG_NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
|
||||
@@ -77,12 +78,7 @@ static int mg_dash_array_size(struct mg_field_set *set,
|
||||
struct mg_dash_user *u) {
|
||||
int saved = *set->index, sz = -1;
|
||||
*set->index = -1;
|
||||
if (set->fn) {
|
||||
if (set->fn(MG_DASH_READ, u)) sz = *set->index;
|
||||
} else if (set->get_dir) {
|
||||
mg_dash_dir_read(set, u);
|
||||
sz = *set->index;
|
||||
}
|
||||
if (set->fn && set->fn(MG_DASH_READ, u)) sz = *set->index;
|
||||
*set->index = saved;
|
||||
return sz;
|
||||
}
|
||||
@@ -100,10 +96,7 @@ static size_t mg_dash_print_array(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||
for (;;) {
|
||||
bool done = to >= 0 && *set->index > to;
|
||||
if (!done) {
|
||||
if (set->fn)
|
||||
set->fn(MG_DASH_READ, u);
|
||||
else if (set->get_dir)
|
||||
mg_dash_dir_read(set, u);
|
||||
if (set->fn) set->fn(MG_DASH_READ, u);
|
||||
done = *set->index < 0;
|
||||
}
|
||||
if (done) break;
|
||||
@@ -170,6 +163,11 @@ void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
|
||||
struct mg_dash_cdata *d;
|
||||
struct mg_dash_user *u;
|
||||
if (!c->is_websocket) continue;
|
||||
if (c->send.len > MG_DASH_MAX_SEND_BUF_SIZE) { // Skip slow consumers
|
||||
MG_ERROR(("skipping, send buf %lu > %d", (unsigned long) c->send.len,
|
||||
MG_DASH_MAX_SEND_BUF_SIZE));
|
||||
continue;
|
||||
}
|
||||
d = (struct mg_dash_cdata *) c->data;
|
||||
u = d->u;
|
||||
if (u == NULL) continue;
|
||||
@@ -180,8 +178,7 @@ void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
|
||||
MG_ESC("change"), MG_ESC("params"), MG_ESC(set->name), sz);
|
||||
} else {
|
||||
int saved_idx = set->index != NULL ? *set->index : 0;
|
||||
bool ok = set->fn ? set->fn(MG_DASH_READ, u)
|
||||
: (set->get_dir ? mg_dash_dir_read(set, u) : true);
|
||||
bool ok = set->fn ? set->fn(MG_DASH_READ, u) : true;
|
||||
if (!ok) {
|
||||
if (set->index != NULL) *set->index = saved_idx;
|
||||
continue;
|
||||
@@ -228,8 +225,7 @@ static int mg_dash_parse_field(struct mg_str json, struct mg_field *f) {
|
||||
mg_json_get_num(json, json_path, (double *) f->value);
|
||||
} else if (f->type == MG_VAL_STR && f->value_size > 0) {
|
||||
struct mg_str tok = mg_json_get_tok(json, json_path);
|
||||
ok = tok.len >= 2 && tok.buf[0] == '"' &&
|
||||
tok.buf[tok.len - 1] == '"';
|
||||
ok = tok.len >= 2 && tok.buf[0] == '"' && tok.buf[tok.len - 1] == '"';
|
||||
if (ok) mg_json_unescape(json, json_path, (char *) f->value, f->value_size);
|
||||
} else if (f->type == MG_VAL_RAW && f->value_size > 0) {
|
||||
ok = mg_snprintf((char *) f->value, f->value_size, "%.*s", json.len,
|
||||
@@ -266,68 +262,6 @@ static int mg_dash_apply(struct mg_connection *c, struct mg_dash *dash,
|
||||
return total_count;
|
||||
}
|
||||
|
||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u) {
|
||||
char dir[256], fname[128] = "";
|
||||
struct mg_fs *fs = u->dash->upload_fs ? u->dash->upload_fs : &mg_fs_posix;
|
||||
struct mg_field *name_field = NULL, *size_field = NULL;
|
||||
size_t i;
|
||||
|
||||
if (!set->get_dir(u, dir, sizeof(dir))) return false;
|
||||
|
||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
||||
if (name_field == NULL && set->fields[i].type == MG_VAL_STR &&
|
||||
strcmp(set->fields[i].name, "name") == 0)
|
||||
name_field = &set->fields[i];
|
||||
if (size_field == NULL && strcmp(set->fields[i].name, "size") == 0)
|
||||
size_field = &set->fields[i];
|
||||
}
|
||||
if (name_field == NULL) return false;
|
||||
|
||||
if (*set->index == -1) { // Size query: count all files
|
||||
int count = 0;
|
||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) count++;
|
||||
*set->index = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
{ // Regular read: scan to *set->index
|
||||
int target = *set->index, cur = 0;
|
||||
while (mg_fs_ls(fs, dir, fname, sizeof(fname))) {
|
||||
if (cur++ == target) {
|
||||
mg_snprintf((char *) name_field->value, name_field->value_size, "%s",
|
||||
fname);
|
||||
if (size_field != NULL) {
|
||||
char path[512];
|
||||
size_t sz = 0;
|
||||
mg_snprintf(path, sizeof(path), "%s/%s", dir, fname);
|
||||
fs->st(path, &sz, NULL);
|
||||
if (size_field->type == MG_VAL_UINT64)
|
||||
*(uint64_t *) size_field->value = (uint64_t) sz;
|
||||
else if (size_field->type == MG_VAL_INT)
|
||||
*(int *) size_field->value = (int) sz;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
*set->index = -1; // No more entries
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mg_dash_set_file_name(struct mg_field_set *set,
|
||||
struct mg_str name) {
|
||||
size_t i;
|
||||
for (i = 0; set->fields[i].name != NULL; i++) {
|
||||
struct mg_field *f = &set->fields[i];
|
||||
if (f->type == MG_VAL_STR && strcmp(f->name, "name") == 0) {
|
||||
mg_snprintf((char *) f->value, f->value_size, "%.*s", (int) name.len,
|
||||
name.buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void mg_log_http_req(struct mg_connection *c,
|
||||
struct mg_http_message *hm) {
|
||||
int len = 0;
|
||||
@@ -360,33 +294,6 @@ static inline void mg_log_http_req(struct mg_connection *c,
|
||||
c->send.buf + n));
|
||||
}
|
||||
|
||||
static void mg_dash_ota_cb(struct mg_connection *c, const char *errmsg) {
|
||||
mg_http_reply(c, errmsg ? 500 : 200, NULL, errmsg ? errmsg : "ok\n");
|
||||
c->is_draining = 1;
|
||||
}
|
||||
|
||||
static void mg_dash_upload_cb(struct mg_connection *c, const char *errmsg) {
|
||||
if (errmsg) {
|
||||
mg_http_reply(c, 500, NULL, "%s\n", errmsg);
|
||||
} else {
|
||||
// mg_http_start_upload() repurposes c->data for its own bookkeeping,
|
||||
// so the field set can't be cached there. Re-derive the dashboard from
|
||||
// c->fn_data instead, and notify every file-backed array: the upload
|
||||
// could belong to any of them, and re-querying get_dir() per recipient
|
||||
// is what mg_dash_send_change() does anyway (directories can be
|
||||
// user-specific)
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_field_set *fs;
|
||||
mg_http_reply(c, 200, NULL, "ok\n");
|
||||
for (fs = dash->sets; fs != NULL; fs = fs->next) {
|
||||
if (fs->get_dir == NULL) continue;
|
||||
*fs->index = -1; // Signal mg_dash_send_change() to broadcast new size
|
||||
mg_dash_send_change(c->mgr, fs);
|
||||
}
|
||||
}
|
||||
c->is_draining = 1;
|
||||
}
|
||||
|
||||
static uint64_t mg_dash_make_expiration_time(struct mg_dash *dash) {
|
||||
unsigned t = (unsigned) dash->session_auto_expiration_seconds;
|
||||
if (t == 0) t = 3600; // Default expiration time in seconds
|
||||
@@ -626,6 +533,25 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_dash_user *u, bool stream) {
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
char root[128] = "/fs/=";
|
||||
if (u == NULL) {
|
||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||
} else if (dash->files_dir(u, root + 5, sizeof(root) - 5) == false) {
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||
} else {
|
||||
struct mg_http_serve_opts o;
|
||||
memset(&o, 0, sizeof(o));
|
||||
o.root_dir = root;
|
||||
o.fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
||||
o.enable_delete = o.enable_upload = o.enable_dir_listing = true;
|
||||
if (stream) mg_http_serve_upload(c, hm, &o);
|
||||
else mg_http_serve_dir(c, hm, &o);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
|
||||
@@ -639,9 +565,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
// Received headers - check authentication and possibly start uploads/ota
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
||||
struct mg_str parts[3];
|
||||
memset(parts, 0, sizeof(parts));
|
||||
|
||||
if (mg_match(hm->uri, mg_str("/api/hi"), NULL) ||
|
||||
mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
|
||||
// Do nothing, handle them MG_EV_HTTP_MSG. We bypass auth for those
|
||||
@@ -651,49 +574,21 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
} else if (mg_match(hm->uri, mg_str("/api/login"), NULL) && u != NULL) {
|
||||
mg_handle_login(c, u);
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (u != NULL && dash->files_dir != NULL &&
|
||||
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||
mg_serve_fs(c, hm, u, true);
|
||||
return; // Important - the serve_fs() call can invalidate hm
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||
mg_http_start_ota(c, hm, mg_dash_ota_cb);
|
||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts) &&
|
||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
||||
struct mg_str name = parts[1];
|
||||
int len =
|
||||
mg_url_decode(name.buf, name.len, (char *) name.buf, name.len + 1, 0);
|
||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
||||
if (set == NULL || set->get_dir == NULL) {
|
||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (u == NULL) {
|
||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else if (!mg_path_is_sane(name)) {
|
||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else {
|
||||
mg_dash_set_file_name(set, name);
|
||||
if (set->fn != NULL && !set->fn(MG_DASH_WRITE, u)) {
|
||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else {
|
||||
char dir[256];
|
||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||
d->marker = CONN_HANDLED;
|
||||
} else {
|
||||
mg_http_start_upload(c, hm, name, mg_str(dir), fs,
|
||||
mg_dash_upload_cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Authenticated above. Streaming owns the connection and c->data.
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
return;
|
||||
}
|
||||
if (d->marker != '\0') mg_log_http_req(c, hm);
|
||||
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
||||
// The response has been send in EV_HDRS path, so we're not reponding
|
||||
// anything but clearing the marker for the next request.
|
||||
d->marker = 0;
|
||||
c->is_resp = 0;
|
||||
c->pfn_data = NULL; // Response done
|
||||
} else if (ev == MG_EV_HTTP_MSG && d->marker == '\0') {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash);
|
||||
@@ -709,42 +604,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
} else if (mg_match(hm->uri, mg_str("/api/websocket"), NULL)) {
|
||||
d->u = u;
|
||||
mg_ws_upgrade(c, hm, NULL);
|
||||
} else if (mg_match(hm->uri, mg_str("/fs/*/*"), parts)) {
|
||||
struct mg_field_set *set = mg_dash_find_field_set(dash, parts[0]);
|
||||
if (set == NULL || set->get_dir == NULL) {
|
||||
mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found");
|
||||
} else if (u == NULL) {
|
||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||
} else {
|
||||
char dir[256], path[512];
|
||||
struct mg_fs *fs = dash->upload_fs ? dash->upload_fs : &mg_fs_posix;
|
||||
struct mg_str name = parts[1];
|
||||
int len = mg_url_decode(name.buf, name.len, (char *) name.buf,
|
||||
name.len + 1, 0);
|
||||
if (len > 0 && (size_t) len <= name.len) name.len = (size_t) len;
|
||||
if (!mg_path_is_sane(name)) {
|
||||
mg_http_reply(c, 400, MG_JSON_HEADERS, "Bad file name\n");
|
||||
return;
|
||||
}
|
||||
if (!set->get_dir(u, dir, sizeof(dir))) {
|
||||
mg_http_reply(c, 500, MG_JSON_HEADERS, "Upload dir error\n");
|
||||
return;
|
||||
}
|
||||
mg_snprintf(path, sizeof(path), "%s/%.*s", dir, name.len, name.buf);
|
||||
if (mg_strcasecmp(hm->method, mg_str("DELETE")) == 0) {
|
||||
mg_dash_set_file_name(set, name);
|
||||
if (set->fn != NULL && !set->fn(MG_DASH_DELETE, u)) {
|
||||
mg_http_reply(c, 403, MG_JSON_HEADERS, "Not Authorised\n");
|
||||
} else {
|
||||
fs->rm(path);
|
||||
*set->index = -1; // Signal mg_dash_send_change() to send new size
|
||||
mg_dash_send_change(c->mgr, set);
|
||||
mg_http_reply(c, 200, NULL, "true");
|
||||
}
|
||||
} else {
|
||||
mg_http_serve_file(c, hm, path, NULL);
|
||||
}
|
||||
}
|
||||
} else if (dash->files_dir != NULL &&
|
||||
mg_match(hm->uri, mg_str("/fs/#"), NULL)) {
|
||||
mg_serve_fs(c, hm, u, false);
|
||||
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
|
||||
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
|
||||
mg_dash_handle_del(c, dash, u, parts);
|
||||
@@ -775,7 +637,7 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found");
|
||||
if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n");
|
||||
mg_log_http_req(c, hm);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-40
@@ -73,9 +73,7 @@ struct mg_field_set {
|
||||
// fn sets *index = -1 to signal end of iteration.
|
||||
// Size query: framework sets *index = -1 before fn(READ); fn
|
||||
// sets *index = total size
|
||||
bool (*get_dir)(const struct mg_dash_user *u, char *buf,
|
||||
size_t len); // Non-NULL: file array
|
||||
struct mg_field_set *next; // Next set in the list
|
||||
struct mg_field_set *next; // Next set in the list
|
||||
};
|
||||
|
||||
// Custom URI handler, registered with MG_DASH_REGISTER_CUSTOM_HANDLER()
|
||||
@@ -96,6 +94,9 @@ struct mg_dash {
|
||||
int session_auto_expiration_seconds; // Session lifetime, 0: 1 hour default
|
||||
struct mg_dash_user *guest; // Used when authenticate == NULL
|
||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
||||
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||
// upload directories. NULL: file manager disabled
|
||||
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||
};
|
||||
|
||||
#define MG_DASH_ADD_FIELD_SET(dash_, set_) \
|
||||
@@ -121,43 +122,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data);
|
||||
// array size if *set->index < 0, or a single element update otherwise
|
||||
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *);
|
||||
|
||||
// Default reader for file-backed array sets (set->get_dir != NULL)
|
||||
// Usage example - this is a complete implemenation of the file manager:
|
||||
// ```c
|
||||
// static struct file {
|
||||
// int index;
|
||||
// char name[64];
|
||||
// size_t size;
|
||||
// uint64_t checksum;
|
||||
// } s_file;
|
||||
// static struct mg_field_set set_files;
|
||||
//
|
||||
// static bool get_dir(const struct mg_dash_user *u, char *buf, size_t len) {
|
||||
// (void) u;
|
||||
// mkdir("/tmp/dashboard", 0755);
|
||||
// mg_snprintf(buf, len, "%s", "/tmp/dashboard");
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// static struct mg_field fields_files[] = {
|
||||
// {"name", MG_VAL_STR, s_file.name, sizeof(s_file.name)},
|
||||
// {"size", MG_VAL_UINT64, &s_file.size, 0},
|
||||
// {"checksum", MG_VAL_UINT64, &s_file.checksum, sizeof(s_file.checksum)},
|
||||
// {NULL, MG_VAL_INT, NULL, 0},
|
||||
// };
|
||||
//
|
||||
// static bool files_fn(enum mg_dash_op op, struct mg_dash_user *u) {
|
||||
// if (op == MG_DASH_WRITE) return s_uploads_enabled && u->level >= 7;
|
||||
// if (op != MG_DASH_READ) return false;
|
||||
// if (!mg_dash_dir_read(&set_files, u)) return false;
|
||||
// s_file.checksum = 0; // Mock to show how to set custom fields
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// static struct mg_field_set set_files = {"files", fields_files, files_fn, &s_file.index, get_dir, NULL};
|
||||
// ```
|
||||
bool mg_dash_dir_read(struct mg_field_set *set, struct mg_dash_user *u);
|
||||
|
||||
// Helper forward declarations for Mongoose CMSIS pack modules
|
||||
extern struct mg_mgr g_mgr;
|
||||
extern void mg_dash_init(struct mg_mgr *);
|
||||
|
||||
@@ -263,14 +263,14 @@ static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
||||
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()");
|
||||
MG_ERROR(("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;
|
||||
}
|
||||
dnsc->c->pfn = dns_cb; // do it always, DHCP-assigned DNS bootstrap needs it
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||
MG_ENABLE_DRIVER_MCXW71
|
||||
|
||||
// ZLL, 802.15.4 link layer. See MCXW71 reference manual and NXP
|
||||
// ieee_802_15_4 PHY sources: PhyPacketProcessor.c, PhyPlmeData.c, PhyISR.c
|
||||
struct mg_zll {
|
||||
volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP,
|
||||
T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI,
|
||||
MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER,
|
||||
CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1,
|
||||
MACSHORTADDRS1, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL,
|
||||
CHANNEL_NUM1, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX,
|
||||
SEQ_CTRL_STS, ACKDELAY, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2,
|
||||
SLOT_PRELOAD, SEQ_STATE, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB,
|
||||
PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0;
|
||||
};
|
||||
|
||||
// TrustZone: code built for the secure state (-mcmse) uses the secure alias
|
||||
// of the radio registers, 0x5xxxxxxx. Otherwise, the non-secure alias
|
||||
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE & 2)
|
||||
#define MGI_ZLL ((struct mg_zll *) 0x58A01000)
|
||||
#define MGI_PKT_RAM ((volatile uint8_t *) 0x58A08000)
|
||||
#else
|
||||
#define MGI_ZLL ((struct mg_zll *) 0x48A01000)
|
||||
#define MGI_PKT_RAM ((volatile uint8_t *) 0x48A08000)
|
||||
#endif
|
||||
|
||||
#define MGI_TX_PB MGI_PKT_RAM // Length byte (incl. FCS), then frame
|
||||
#define MGI_RX_PB (MGI_PKT_RAM + 0x80) // Received frame, no length byte
|
||||
|
||||
#define MGI_IRQSTS_SEQIRQ MG_BIT(0)
|
||||
#define MGI_IRQSTS_RXIRQ MG_BIT(2)
|
||||
#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS
|
||||
#define MGI_PHY_CTRL_XCVSEQ 7U
|
||||
#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs
|
||||
#define MGI_SEQ_STATE_MASK 0x1fU
|
||||
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
|
||||
#define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5)
|
||||
#define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2
|
||||
|
||||
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 }; // PHY_CTRL[XCVSEQ]
|
||||
#define MGI_TX_TIMEOUT 400U // Symbols, 16 us: 127 bytes
|
||||
|
||||
// Symbol timer, 16 us, 24 bits
|
||||
static uint32_t mg_mcxw71_symbols(void) {
|
||||
return MGI_ZLL->EVENT_TMR >> 8;
|
||||
}
|
||||
|
||||
// Wait for the sequence to end, up to MGI_TX_TIMEOUT symbols
|
||||
static bool mg_mcxw71_wait(void) {
|
||||
uint32_t start = mg_mcxw71_symbols();
|
||||
while (((mg_mcxw71_symbols() - start) & 0xffffffU) < MGI_TX_TIMEOUT) {
|
||||
if (MGI_ZLL->IRQSTS & MGI_IRQSTS_SEQIRQ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
|
||||
static void mg_mcxw71_idle(void) {
|
||||
if (MGI_ZLL->PHY_CTRL & MGI_PHY_CTRL_XCVSEQ) {
|
||||
while ((MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) == MGI_SEQ_STATE_RX_WU)
|
||||
(void) 0;
|
||||
MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ;
|
||||
while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0;
|
||||
}
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; // Clear all flags, write 1 to clear
|
||||
}
|
||||
|
||||
// Start a new sequence: MGI_SEQ_RX or MGI_SEQ_TX
|
||||
static void mg_mcxw71_start(uint32_t seq) {
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
MGI_ZLL->PHY_CTRL |= seq;
|
||||
}
|
||||
|
||||
static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_l2_802154_data *d =
|
||||
(struct mg_l2_802154_data *) ifp->driver_data;
|
||||
if (d == NULL || d->channel < 11 || d->channel > 26) {
|
||||
MG_ERROR(("Set driver_data, channel 11..26"));
|
||||
return false;
|
||||
}
|
||||
MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
|
||||
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
MGI_ZLL->SAM_CTRL = 0;
|
||||
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
|
||||
MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol
|
||||
MGI_ZLL->CHANNEL_NUM0 = d->channel;
|
||||
MGI_ZLL->MACSHORTADDRS0 = (0xfffeU << 16) | d->pan_id; // Filter by PAN ID
|
||||
MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power
|
||||
mg_mcxw71_start(MGI_SEQ_RX);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send one radio frame: MAC header and payload, no FCS
|
||||
static size_t mg_mcxw71_send(const uint8_t *p, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
size_t i;
|
||||
bool ok;
|
||||
mg_mcxw71_idle();
|
||||
MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware
|
||||
for (i = 0; i < len; i++) MGI_TX_PB[i + 1] = p[i]; // 8-bit access only
|
||||
mg_mcxw71_start(MGI_SEQ_TX);
|
||||
ok = mg_mcxw71_wait();
|
||||
mg_mcxw71_idle();
|
||||
mg_mcxw71_start(MGI_SEQ_RX);
|
||||
(void) ifp;
|
||||
return ok ? len : 0;
|
||||
}
|
||||
|
||||
static size_t mg_mcxw71_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
return mg_l2_802154_tx(ifp, buf, len, mg_mcxw71_send);
|
||||
}
|
||||
|
||||
static size_t mg_mcxw71_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
uint32_t irqsts = MGI_ZLL->IRQSTS;
|
||||
uint8_t f[125];
|
||||
size_t i, n = 0;
|
||||
if ((irqsts & MGI_IRQSTS_SEQIRQ) == 0) return 0; // Still receiving
|
||||
if (irqsts & MGI_IRQSTS_RXIRQ) {
|
||||
n = MGI_IRQSTS_RX_LEN(irqsts);
|
||||
n = n >= 2 ? n - 2 : 0; // Strip FCS
|
||||
if (n > sizeof(f)) n = sizeof(f);
|
||||
for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i];
|
||||
}
|
||||
mg_mcxw71_idle();
|
||||
mg_mcxw71_start(MGI_SEQ_RX);
|
||||
return mg_l2_802154_rx(ifp, f, n, buf, len);
|
||||
}
|
||||
|
||||
static bool mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
(void) ifp, (void) s1;
|
||||
return true; // Radio link is always up
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {mg_mcxw71_init, mg_mcxw71_tx,
|
||||
mg_mcxw71_rx, mg_mcxw71_poll};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||
MG_ENABLE_DRIVER_MCXW71
|
||||
|
||||
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface:
|
||||
// Ethernet frames are split into radio frames, see mg_l2_802154_tx().
|
||||
// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(),
|
||||
// see hal_radio_init() in the FRDM-MCXW71 tutorial.
|
||||
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
|
||||
|
||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||
do { \
|
||||
static struct mg_l2_802154_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
driver_data_.channel = MG_L2_802154_CHANNEL; \
|
||||
driver_data_.pan_id = MG_L2_802154_PAN_ID; \
|
||||
mif_.driver = &mg_tcpip_driver_mcxw71; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mg_tcpip_init(mgr, &mif_); \
|
||||
MG_INFO(("Driver: mcxw71, channel %d", MG_L2_802154_CHANNEL)); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,468 @@
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
|
||||
defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
|
||||
|
||||
// IMXRT1180 RM - Chapter 53 - Ethernet Controller (NETC)
|
||||
|
||||
#define ETH_DESC_CNT 8 // Descriptor count must be multiple of 8 (53.4.6.11.97)
|
||||
#define ETH_PKT_SIZE 1536
|
||||
|
||||
struct netc_pci_function { // 53.4.6.1.1
|
||||
volatile uint32_t DID_VID;
|
||||
volatile uint16_t CMD, STAT;
|
||||
volatile uint8_t reserved0[0x40];
|
||||
volatile uint16_t DEV_CTL, DEV_STAT;
|
||||
volatile uint8_t reserved1[0x34];
|
||||
volatile uint16_t MSIX_CAP_LIST, MSIX_MSG_CTL;
|
||||
};
|
||||
|
||||
struct netc_emdio { // 53.4.6.12
|
||||
volatile uint32_t EMDIO_CFG, EMDIO_CTL, EMDIO_DATA, EMDIO_ADDR, EMDIO_STAT;
|
||||
};
|
||||
|
||||
struct netc_enetc { // 53.4.6.6
|
||||
volatile uint32_t ECAPR0, ECAPR1, ECAPR2, reserved0, PMR;
|
||||
volatile uint8_t reserved1[0x1ec];
|
||||
volatile uint32_t PSIPMMR, PSIPVMR;
|
||||
volatile uint8_t reserved2[0x5f8];
|
||||
volatile uint32_t SMCAPR;
|
||||
volatile uint8_t reserved3[0x17fc];
|
||||
struct {
|
||||
volatile uint32_t PSIPMAR0, PSIPMAR1, PSIVLANR, reserved0, PSICFGR0,
|
||||
PSICFGR1, PSICFGR2;
|
||||
volatile uint8_t reserved1[0x3c];
|
||||
volatile uint32_t PSIMMHFR0, PSIMMHFR1;
|
||||
volatile uint8_t reserved2[0x20];
|
||||
} PSI[1];
|
||||
};
|
||||
|
||||
struct netc_port { // 53.4.6.8
|
||||
volatile uint32_t PCAPR, PMCAPR, PIOCAPR, reserved0, PCR;
|
||||
volatile uint8_t reserved1[0x0c];
|
||||
volatile uint32_t PMAR0, PMAR1;
|
||||
volatile uint8_t reserved2[0xd8];
|
||||
volatile uint32_t POR, PSR;
|
||||
volatile uint8_t reserved3[0x30];
|
||||
volatile uint32_t PDGSR;
|
||||
volatile uint8_t reserved4[0xc4];
|
||||
struct {
|
||||
volatile uint32_t PTGSTCSR, reserved0, PTCTMSDUR, reserved1, PTCCBSR0,
|
||||
PTCCBSR1;
|
||||
volatile uint8_t reserved2[8];
|
||||
} TCT[8];
|
||||
volatile uint8_t reserved5[0x220];
|
||||
volatile uint32_t BPSTGSR;
|
||||
};
|
||||
|
||||
struct netc_port_mac { // 53.4.6.9
|
||||
volatile uint8_t reserved0[8];
|
||||
volatile uint32_t COMMAND_CONFIG, MAC_ADDR_0, MAC_ADDR_1, MAXFRM, MINFRM;
|
||||
volatile uint8_t reserved1[0x24];
|
||||
volatile uint32_t IEVENT;
|
||||
volatile uint8_t reserved2[0x1dc];
|
||||
volatile uint64_t TFRMN;
|
||||
volatile uint8_t reserved3[0xd8];
|
||||
volatile uint32_t IF_MODE;
|
||||
volatile uint8_t reserved4[0xfc];
|
||||
};
|
||||
|
||||
struct netc_eth_link {
|
||||
struct netc_port_mac PM[2];
|
||||
};
|
||||
|
||||
struct netc_pseudo_link {
|
||||
volatile uint32_t PPMSR;
|
||||
volatile uint8_t reserved0[0x0c];
|
||||
volatile uint32_t PPMCR;
|
||||
};
|
||||
|
||||
struct netc_switch { // 53.4.6.5
|
||||
volatile uint8_t reserved0[0x400];
|
||||
volatile uint32_t MPCR;
|
||||
volatile uint8_t reserved1[0x1c0c];
|
||||
volatile uint32_t VFHTDECR0, VFHTDECR1, VFHTDECR2;
|
||||
};
|
||||
|
||||
struct netc_bdr {
|
||||
volatile uint32_t TBMR, TBSR;
|
||||
volatile uint8_t reserved0[8];
|
||||
volatile uint32_t TBBAR0, TBBAR1, TBPIR, TBCIR, TBLENR;
|
||||
volatile uint8_t reserved1[0x7c];
|
||||
volatile uint32_t TBIER, TBIDR, TBICR0, TBICR1;
|
||||
volatile uint8_t reserved2[0x50];
|
||||
volatile uint32_t RBMR, RBSR, RBBSR, RBCIR, RBBAR0, RBBAR1, RBPIR,
|
||||
reserved3, RBLENR;
|
||||
volatile uint8_t reserved4[0x5c];
|
||||
volatile uint32_t RBDCR;
|
||||
volatile uint8_t reserved5[0x1c];
|
||||
volatile uint32_t RBIER, RBIDR, RBICR0, RBICR1;
|
||||
volatile uint8_t reserved6[0x50];
|
||||
};
|
||||
|
||||
struct netc_si { // 53.4.6.11
|
||||
volatile uint32_t SIMR, SISR;
|
||||
volatile uint8_t reserved0[0x30];
|
||||
volatile uint32_t SIRBGCR;
|
||||
volatile uint8_t reserved1[0x2ec];
|
||||
volatile uint32_t SITFRM0;
|
||||
volatile uint8_t reserved2[0x6fc];
|
||||
volatile uint32_t SIRXIDR0;
|
||||
volatile uint8_t reserved3[0x154];
|
||||
volatile uint32_t SIMSIRRVR[1];
|
||||
volatile uint8_t reserved4[0x747c];
|
||||
struct netc_bdr BDR[1];
|
||||
};
|
||||
|
||||
struct netc_msix_entry { // 53.4.2.11
|
||||
volatile uint32_t MSG_ADDR, MSG_UPPER_ADDR, MSG_DATA, VECTOR_CTRL;
|
||||
};
|
||||
|
||||
struct netc_msgintr { // 54.4
|
||||
struct {
|
||||
volatile uint32_t MSIIR, MSIR;
|
||||
} MSI[3];
|
||||
};
|
||||
|
||||
#define NETC_EMDIO_PCI \
|
||||
((struct netc_pci_function *) (uintptr_t) 0x60001000U)
|
||||
#define NETC_SWITCH_PCI \
|
||||
((struct netc_pci_function *) (uintptr_t) 0x60002000U)
|
||||
#define NETC_ENETC1_PCI \
|
||||
((struct netc_pci_function *) (uintptr_t) 0x60004000U)
|
||||
#define NETC_EMDIO ((struct netc_emdio *) (uintptr_t) 0x60ba1c00U)
|
||||
|
||||
#define NETC_ENETC1 ((struct netc_enetc *) (uintptr_t) 0x60b50000U)
|
||||
#define NETC_ENETC1_PORT ((struct netc_port *) (uintptr_t) 0x60b54000U)
|
||||
#define NETC_ENETC1_PSEUDO_LINK \
|
||||
((struct netc_pseudo_link *) (uintptr_t) 0x60b55000U)
|
||||
#define NETC_ENETC1_SI0 ((struct netc_si *) (uintptr_t) 0x60b40000U)
|
||||
#define NETC_ENETC1_MSIX \
|
||||
((struct netc_msix_entry *) (uintptr_t) 0x60c00000U)
|
||||
|
||||
#define NETC_SWITCH ((struct netc_switch *) (uintptr_t) 0x60a00000U)
|
||||
#define NETC_SW0_PORT(link) \
|
||||
((struct netc_port *) (uintptr_t) (0x60a04000U + \
|
||||
(uint32_t) (link) * 0x4000U))
|
||||
#define NETC_SW0_ETH(link) \
|
||||
((struct netc_eth_link *) (uintptr_t) (0x60a05000U + \
|
||||
(uint32_t) (link) * 0x4000U))
|
||||
#define NETC_SW0_PORT4 ((struct netc_port *) (uintptr_t) 0x60a14000U)
|
||||
#define NETC_MSGINTR1 \
|
||||
((struct netc_msgintr *) (uintptr_t) 0x428a0000U)
|
||||
|
||||
enum {
|
||||
NETC_RX_MSIX_ENTRY = 0,
|
||||
NETC_RX_MSG_INDEX = 2,
|
||||
};
|
||||
|
||||
struct netc_tx_bd {
|
||||
uint64_t buffer_address;
|
||||
uint16_t buffer_length, frame_length;
|
||||
uint32_t control;
|
||||
};
|
||||
|
||||
struct netc_rx_descriptor {
|
||||
uint64_t buffer_address, reserved;
|
||||
};
|
||||
|
||||
struct netc_rx_writeback {
|
||||
uint16_t internet_checksum, parser_summary;
|
||||
uint32_t rss_hash;
|
||||
uint16_t buffer_length, vlan;
|
||||
uint32_t status;
|
||||
};
|
||||
|
||||
union netc_rx_bd {
|
||||
struct netc_rx_descriptor descriptor;
|
||||
struct netc_rx_writeback writeback;
|
||||
};
|
||||
|
||||
static volatile struct netc_tx_bd s_txdesc[ETH_DESC_CNT] MG_ETH_RAM
|
||||
__attribute__((aligned(128)));
|
||||
static volatile union netc_rx_bd s_rxdesc[ETH_DESC_CNT] MG_ETH_RAM
|
||||
__attribute__((aligned(128)));
|
||||
static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM
|
||||
__attribute__((aligned(64)));
|
||||
static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM
|
||||
__attribute__((aligned(64)));
|
||||
|
||||
static uint16_t s_tx_prod;
|
||||
static uint16_t s_tx_clean;
|
||||
static uint16_t s_rx_cons;
|
||||
static struct mg_tcpip_if *s_ifp;
|
||||
|
||||
static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
|
||||
NETC_EMDIO->EMDIO_CFG &= ~MG_BIT(6); // ENC45: use Clause 22
|
||||
NETC_EMDIO->EMDIO_CTL = MG_BIT(15) | ((uint32_t) (addr & 0x1fU) << 5) |
|
||||
(reg & 0x1fU); // READ, PORT_ADDR, DEV_ADDR
|
||||
while (NETC_EMDIO->EMDIO_CFG & MG_BIT(31)) (void) 0; // BSY1
|
||||
return (uint16_t) (NETC_EMDIO->EMDIO_DATA & 0xffffU);
|
||||
}
|
||||
|
||||
static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t value) {
|
||||
NETC_EMDIO->EMDIO_CFG &= ~MG_BIT(6); // ENC45: use Clause 22
|
||||
NETC_EMDIO->EMDIO_CTL = ((uint32_t) (addr & 0x1fU) << 5) |
|
||||
(reg & 0x1fU); // PORT_ADDR, DEV_ADDR
|
||||
NETC_EMDIO->EMDIO_DATA = value;
|
||||
while (NETC_EMDIO->EMDIO_CFG & MG_BIT(31)) (void) 0; // BSY1
|
||||
}
|
||||
|
||||
static bool mg_tcpip_driver_netc_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_netc_data *d =
|
||||
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
|
||||
struct mg_phy phy = {enet_read_phy, enet_write_phy};
|
||||
struct netc_port *switch_port;
|
||||
struct netc_eth_link *eth_link;
|
||||
uint64_t tx_base = (uint64_t) (uintptr_t) s_txdesc;
|
||||
uint64_t rx_base = (uint64_t) (uintptr_t) s_rxdesc;
|
||||
uint32_t mac0 = (uint32_t) ifp->mac[0] |
|
||||
((uint32_t) ifp->mac[1] << 8) |
|
||||
((uint32_t) ifp->mac[2] << 16) |
|
||||
((uint32_t) ifp->mac[3] << 24);
|
||||
uint32_t mac1 = (uint32_t) ifp->mac[4] |
|
||||
((uint32_t) ifp->mac[5] << 8);
|
||||
uint32_t mdc_cr =
|
||||
(uint32_t) (d->mdc_cr < 0 ? MG_DRIVER_MDC_CR : d->mdc_cr);
|
||||
|
||||
switch_port = NETC_SW0_PORT(d->link);
|
||||
eth_link = NETC_SW0_ETH(d->link);
|
||||
s_ifp = ifp;
|
||||
for (int i = 0; i < ETH_DESC_CNT; i++) {
|
||||
s_txdesc[i].buffer_address = (uint64_t) (uintptr_t) s_txbuf[i];
|
||||
s_rxdesc[i].descriptor.buffer_address =
|
||||
(uint64_t) (uintptr_t) s_rxbuf[i];
|
||||
}
|
||||
|
||||
NETC_EMDIO_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
|
||||
while (NETC_EMDIO_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
|
||||
NETC_EMDIO_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
|
||||
NETC_EMDIO->EMDIO_CFG =
|
||||
((mdc_cr & 0x1ffU) << 7) | MG_BIT(2); // MDIO_CLK_DIV, MDIO_HOLD
|
||||
mg_phy_init(&phy, d->phy_addr, 0);
|
||||
|
||||
// Initialize ENETC1 PSI0 and port
|
||||
NETC_ENETC1_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
|
||||
NETC_ENETC1_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
|
||||
NETC_ENETC1_PORT->POR |= MG_BIT(1) | MG_BIT(0); // RXDIS, TXDIS
|
||||
NETC_ENETC1_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
|
||||
while (NETC_ENETC1_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
|
||||
NETC_ENETC1_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
|
||||
NETC_ENETC1_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
|
||||
NETC_ENETC1_PORT->PCR = 99U << 16; // PSPEED: 1 Gbps
|
||||
NETC_ENETC1_PORT->PMAR0 = mac0;
|
||||
NETC_ENETC1_PORT->PMAR1 = mac1;
|
||||
NETC_ENETC1_PSEUDO_LINK->PPMCR = MG_BIT(16); // TXPAD
|
||||
NETC_ENETC1->PSIPMMR &= ~(MG_BIT(16) | MG_BIT(0));
|
||||
NETC_ENETC1_SI0->SIMR &=
|
||||
~(MG_BIT(31) | MG_BIT(3) | MG_BIT(2) | MG_BIT(1));
|
||||
NETC_ENETC1_SI0->SIRBGCR = 0;
|
||||
NETC_ENETC1->PSI[0].PSICFGR0 =
|
||||
MG_BIT(16) | MG_BIT(0); // one Rx BDR, one Tx BDR
|
||||
|
||||
NETC_ENETC1->PSI[0].PSICFGR2 = 0; // NUM_MSIX: one entry
|
||||
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].VECTOR_CTRL = MG_BIT(0); // MASK
|
||||
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_ADDR =
|
||||
(uint32_t) (uintptr_t) &NETC_MSGINTR1->MSI[0].MSIIR;
|
||||
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_UPPER_ADDR = 0;
|
||||
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_DATA = NETC_RX_MSG_INDEX;
|
||||
NETC_ENETC1_SI0->SIMSIRRVR[0] = NETC_RX_MSIX_ENTRY; // Rx ring 0 -> entry 0
|
||||
|
||||
// Initialize switch function 2 to drive the selected physical port
|
||||
NETC_SWITCH_PCI->CMD |= MG_BIT(2) | MG_BIT(1);
|
||||
NETC_SWITCH_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
|
||||
switch_port->POR |= MG_BIT(1) | MG_BIT(0);
|
||||
eth_link->PM[0].COMMAND_CONFIG |= MG_BIT(26); // SWR
|
||||
while (eth_link->PM[0].COMMAND_CONFIG & MG_BIT(26)) (void) 0;
|
||||
eth_link->PM[1].COMMAND_CONFIG |= MG_BIT(26); // SWR
|
||||
while (eth_link->PM[1].COMMAND_CONFIG & MG_BIT(26)) (void) 0;
|
||||
NETC_SWITCH_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
|
||||
while (NETC_SWITCH_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
|
||||
NETC_SWITCH_PCI->CMD |= MG_BIT(2) | MG_BIT(1);
|
||||
NETC_SWITCH_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
|
||||
|
||||
// Configure switch physical port and ethernet link (ETH_X)
|
||||
switch_port->PCR = 99U << 16; // PSPEED: 1 Gbps
|
||||
switch_port->PMAR0 = mac0;
|
||||
switch_port->PMAR1 = mac1;
|
||||
switch_port->TCT[0].PTCTMSDUR = MG_BIT(24) | MG_BIT(16) | ETH_PKT_SIZE;
|
||||
eth_link->PM[0].MAXFRM = ETH_PKT_SIZE;
|
||||
eth_link->PM[0].MINFRM = 64;
|
||||
eth_link->PM[0].IF_MODE = MG_BIT(14) | 4U; // 1G RGMII
|
||||
eth_link->PM[0].COMMAND_CONFIG =
|
||||
MG_BIT(15) | MG_BIT(1) | MG_BIT(0); // TXP, RX_EN, TX_EN
|
||||
eth_link->PM[1].MAXFRM = ETH_PKT_SIZE;
|
||||
eth_link->PM[1].MINFRM = 64;
|
||||
eth_link->PM[1].IF_MODE = MG_BIT(14) | 4U;
|
||||
eth_link->PM[1].COMMAND_CONFIG =
|
||||
MG_BIT(15) | MG_BIT(1) | MG_BIT(0);
|
||||
|
||||
// Bridge the selected physical port with management port 4 (54.4.1.1)
|
||||
NETC_SWITCH->VFHTDECR0 = MG_BIT(4) | MG_BIT(d->link);
|
||||
NETC_SWITCH->VFHTDECR2 = (1UL << 27) | (1UL << 24); // MFO=1, MLO=1
|
||||
switch_port->BPSTGSR = 2U; // STG0: forwarding
|
||||
NETC_SW0_PORT4->BPSTGSR = 2U; // STG0: forwarding
|
||||
switch_port->POR &= ~(MG_BIT(1) | MG_BIT(0));
|
||||
NETC_SW0_PORT4->POR &= ~(MG_BIT(1) | MG_BIT(0));
|
||||
|
||||
// Initialize Tx BD ring 0 (direct switch enqueue)
|
||||
NETC_ENETC1_SI0->BDR[0].TBBAR0 = (uint32_t) tx_base;
|
||||
NETC_ENETC1_SI0->BDR[0].TBBAR1 = (uint32_t) (tx_base >> 32);
|
||||
NETC_ENETC1_SI0->BDR[0].TBLENR = ETH_DESC_CNT;
|
||||
|
||||
// Initialize Rx BD ring 0
|
||||
NETC_ENETC1_SI0->BDR[0].RBICR0 =
|
||||
MG_BIT(31) | MG_BIT(0); // ICEN, interrupt after one frame
|
||||
NETC_ENETC1_SI0->BDR[0].RBBAR0 = (uint32_t) rx_base;
|
||||
NETC_ENETC1_SI0->BDR[0].RBBAR1 = (uint32_t) (rx_base >> 32);
|
||||
NETC_ENETC1_SI0->BDR[0].RBBSR = ETH_PKT_SIZE;
|
||||
NETC_ENETC1_SI0->BDR[0].RBLENR = ETH_DESC_CNT;
|
||||
|
||||
// Clear stale device and MSGINTR state before the ring becomes active
|
||||
NETC_ENETC1_SI0->SIRXIDR0 = MG_BIT(0); // RX0, W1C
|
||||
(void) NETC_MSGINTR1->MSI[0].MSIR; // clear pending messages
|
||||
NETC_ENETC1_SI0->BDR[0].RBIER = MG_BIT(0); // RXTIE
|
||||
NETC_ENETC1_SI0->BDR[0].TBMR = MG_BIT(31);
|
||||
NETC_ENETC1_SI0->BDR[0].RBMR = MG_BIT(31); // EN, CRC=0: strip FCS
|
||||
NETC_ENETC1_PORT->POR &= ~(MG_BIT(1) | MG_BIT(0));
|
||||
NETC_ENETC1->PMR |= MG_BIT(16); // SI0EN
|
||||
NETC_ENETC1_SI0->SIMR |= MG_BIT(31); // EN
|
||||
MG_DSB();
|
||||
if ((NETC_ENETC1->SMCAPR & MG_BIT(0)) == 0U ||
|
||||
(NETC_SWITCH->MPCR & 0x1fU) != 4U) {
|
||||
MG_ERROR(("NETC fixed management path unavailable: SMCAPR=%08lx MPCR=%08lx",
|
||||
(unsigned long) NETC_ENETC1->SMCAPR,
|
||||
(unsigned long) NETC_SWITCH->MPCR));
|
||||
return false;
|
||||
}
|
||||
|
||||
NETC_ENETC1_PCI->MSIX_MSG_CTL =
|
||||
MG_BIT(15); // Enable RX interrupt, MSIX_EN=1, FUNC_MASK=0
|
||||
MG_DSB();
|
||||
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].VECTOR_CTRL = 0; // MASK=0, last
|
||||
MG_DSB();
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t mg_tcpip_driver_netc_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_netc_data *d =
|
||||
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
|
||||
uint16_t next;
|
||||
s_tx_clean =
|
||||
(uint16_t) (NETC_ENETC1_SI0->BDR[0].TBCIR & 0xffffU); // BDR_INDEX
|
||||
next = (uint16_t) (s_tx_prod + 1U);
|
||||
if (next >= ETH_DESC_CNT) next = 0;
|
||||
if (len > sizeof(s_txbuf[0])) {
|
||||
MG_ERROR(("Frame too big, %ld", (long) len));
|
||||
len = (size_t) -1; // fail
|
||||
} else if (next == s_tx_clean) {
|
||||
ifp->nerr++;
|
||||
MG_ERROR(("No descriptors available"));
|
||||
len = 0; // retry later
|
||||
} else {
|
||||
memcpy(s_txbuf[s_tx_prod], buf, len); // Copy data
|
||||
s_txdesc[s_tx_prod].buffer_address =
|
||||
(uint64_t) (uintptr_t) s_txbuf[s_tx_prod];
|
||||
s_txdesc[s_tx_prod].buffer_length = (uint16_t) len; // BUFF_LEN
|
||||
s_txdesc[s_tx_prod].frame_length = (uint16_t) len; // FRAME_LEN
|
||||
s_txdesc[s_tx_prod].control =
|
||||
MG_BIT(31) | (2UL << 24) | MG_BIT(23) | ((uint32_t) d->link << 16);
|
||||
// F=1, FLQ=2, SMSO=1, EGR_PORT=link, IPV=0, DR=0
|
||||
MG_DSB();
|
||||
if (++s_tx_prod >= ETH_DESC_CNT) s_tx_prod = 0;
|
||||
NETC_ENETC1_SI0->BDR[0].TBPIR = s_tx_prod; // BDR_INDEX
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void mg_tcpip_driver_netc_update_hash_table(struct mg_tcpip_if *ifp) {
|
||||
// mcast address has NETC XOR hash index 56 (RM 53.4.2.3.1.3.8.4.1)
|
||||
NETC_ENETC1->PSI[0].PSIMMHFR0 = 0;
|
||||
NETC_ENETC1->PSI[0].PSIMMHFR1 = MG_BIT(24);
|
||||
(void) ifp;
|
||||
}
|
||||
|
||||
static bool mg_tcpip_driver_netc_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
if (ifp->update_mac_hash_table) {
|
||||
mg_tcpip_driver_netc_update_hash_table(ifp);
|
||||
ifp->update_mac_hash_table = false;
|
||||
}
|
||||
if (!s1) return false;
|
||||
struct mg_tcpip_driver_netc_data *d =
|
||||
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
|
||||
struct netc_port *switch_port = NETC_SW0_PORT(d->link);
|
||||
struct netc_eth_link *eth_link = NETC_SW0_ETH(d->link);
|
||||
uint8_t speed = MG_PHY_SPEED_10M;
|
||||
bool up, full_duplex = false;
|
||||
struct mg_phy phy = {enet_read_phy, enet_write_phy};
|
||||
|
||||
up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
|
||||
if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {
|
||||
uint16_t port_speed = speed == MG_PHY_SPEED_1000M ? 99U
|
||||
: speed == MG_PHY_SPEED_100M ? 9U
|
||||
: 0U;
|
||||
uint32_t config = switch_port->PCR;
|
||||
uint32_t mode = eth_link->PM[0].IF_MODE;
|
||||
config &= ~(0x3fffU << 16); // PCR[29:16] PSPEED
|
||||
config |= (uint32_t) port_speed << 16;
|
||||
mode &= ~(MG_BIT(14) | MG_BIT(13) | MG_BIT(6)); // SSP, HD
|
||||
if (speed == MG_PHY_SPEED_1000M) {
|
||||
mode |= MG_BIT(14); // SSP: 1 Gbps, full duplex
|
||||
} else {
|
||||
if (speed == MG_PHY_SPEED_10M) mode |= MG_BIT(13); // SSP
|
||||
if (!full_duplex) mode |= MG_BIT(6); // HD
|
||||
}
|
||||
switch_port->PCR = config;
|
||||
eth_link->PM[0].IF_MODE = mode;
|
||||
eth_link->PM[1].IF_MODE = mode;
|
||||
|
||||
// TX_CSD, RX_CSD, and SPD_DUP are latched while the PHY is reset/down.
|
||||
eth_link->PM[0].IEVENT = MG_BIT(23) | MG_BIT(22) | MG_BIT(21);
|
||||
MG_DSB();
|
||||
MG_DEBUG(("Link is %uM %s-duplex",
|
||||
speed == MG_PHY_SPEED_1000M ? 1000U
|
||||
: speed == MG_PHY_SPEED_100M ? 100U
|
||||
: 10U,
|
||||
full_duplex ? "full" : "half"));
|
||||
}
|
||||
return up;
|
||||
}
|
||||
|
||||
void MSGINTR1_IRQHandler(void);
|
||||
void MSGINTR1_IRQHandler(void) {
|
||||
if (NETC_MSGINTR1->MSI[0].MSIR & MG_BIT(NETC_RX_MSG_INDEX)) {
|
||||
NETC_ENETC1_SI0->SIRXIDR0 = MG_BIT(0); // RX0, W1C
|
||||
MG_DSB();
|
||||
for (int i = 0; i < 10; i++) { // Read as they arrive but not forever
|
||||
uint32_t status;
|
||||
size_t len;
|
||||
if (s_rx_cons ==
|
||||
(uint16_t) (NETC_ENETC1_SI0->BDR[0].RBPIR & 0xffffU))
|
||||
break;
|
||||
status = s_rxdesc[s_rx_cons].writeback.status;
|
||||
if (!(status & MG_BIT(30))) break; // R: writeback is ready
|
||||
len = s_rxdesc[s_rx_cons].writeback.buffer_length;
|
||||
if ((status & (MG_BIT(31) | MG_BIT(30))) ==
|
||||
(MG_BIT(31) | MG_BIT(30)) && // F, R
|
||||
!(status & (0xffU << 16)) && // ERROR
|
||||
!(status & (0xfU << 2)) && // HR=0: regular frame
|
||||
len > 0 && len <= sizeof(s_rxbuf[0])) {
|
||||
mg_tcpip_qwrite(s_rxbuf[s_rx_cons], len, s_ifp); // FCS stripped
|
||||
}
|
||||
s_rxdesc[s_rx_cons].descriptor.buffer_address =
|
||||
(uint64_t) (uintptr_t) s_rxbuf[s_rx_cons];
|
||||
s_rxdesc[s_rx_cons].descriptor.reserved = 0;
|
||||
MG_DSB();
|
||||
if (++s_rx_cons >= ETH_DESC_CNT) s_rx_cons = 0;
|
||||
NETC_ENETC1_SI0->BDR[0].RBCIR = s_rx_cons;
|
||||
}
|
||||
}
|
||||
MG_DSB();
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_netc = {mg_tcpip_driver_netc_init,
|
||||
mg_tcpip_driver_netc_tx, NULL,
|
||||
mg_tcpip_driver_netc_poll};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
|
||||
defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
|
||||
|
||||
struct mg_tcpip_driver_netc_data {
|
||||
int mdc_cr;
|
||||
uint8_t phy_addr;
|
||||
|
||||
// Selects the physical Ethernet link (ETH0 through ETH3)
|
||||
// Its switch port is bridged to pseudo-port 4,
|
||||
// which connects internally to ENETC1
|
||||
// IMXRT1180 RM 53.4.1.1 NETC Block diagram
|
||||
uint8_t link;
|
||||
};
|
||||
|
||||
#ifndef MG_TCPIP_PHY_ADDR
|
||||
#define MG_TCPIP_PHY_ADDR 5
|
||||
#endif
|
||||
|
||||
#ifndef MG_DRIVER_MDC_CR
|
||||
#define MG_DRIVER_MDC_CR 48
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_NETC_LINK
|
||||
#define MG_TCPIP_NETC_LINK 0
|
||||
#endif
|
||||
|
||||
struct mg_tcpip_driver;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||
|
||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_netc_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_.link = MG_TCPIP_NETC_LINK; \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.driver = &mg_tcpip_driver_netc; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mg_tcpip_init(mgr, &mif_); \
|
||||
MG_INFO(("Driver: netc, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
+30
-2
@@ -11,7 +11,9 @@ enum { // ID1 ID2
|
||||
MG_PHY_RTL8201 = 0xc816, // 001c c816 - RTL8201F
|
||||
MG_PHY_RTL8211 = 0xc916, // 001c c916 - RTL8211F
|
||||
MG_PHY_ICS1894x = 0x15,
|
||||
MG_PHY_ICS189432 = 0xf450 // 0015 f450 - ICS1894
|
||||
MG_PHY_ICS189432 = 0xf450, // 0015 f450 - ICS1894
|
||||
MG_PHY_YT85x = 0x4f51,
|
||||
MG_PHY_YT8531 = 0xe91a // 4f51 e91a - Motorcomm YT8531
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -29,7 +31,10 @@ enum {
|
||||
MG_PHY_RTL82x_REG_PAGESEL = 31,
|
||||
MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
|
||||
MG_PHY_RTL8211_REG_PHYSR = 26, // in page a43
|
||||
MG_PHY_ICS189432_REG_POLL = 17
|
||||
MG_PHY_ICS189432_REG_POLL = 17,
|
||||
MG_PHY_YT8531_REG_PHYSTS = 17,
|
||||
MG_PHY_YT8531_REG_EXT_ADDR = 30,
|
||||
MG_PHY_YT8531_REG_EXT_DATA = 31
|
||||
};
|
||||
|
||||
static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
|
||||
@@ -60,6 +65,8 @@ static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
|
||||
}
|
||||
case MG_PHY_ICS1894x:
|
||||
return "ICS1894x";
|
||||
case MG_PHY_YT85x:
|
||||
return id2 == MG_PHY_YT8531 ? "YT8531" : "YT85x";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
@@ -75,6 +82,18 @@ void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) {
|
||||
id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
|
||||
MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2)));
|
||||
|
||||
if (id1 == MG_PHY_YT85x && id2 == MG_PHY_YT8531) {
|
||||
uint16_t value;
|
||||
// TODO (robertc2000): Autonegotiation remains disabled after PHY is reset
|
||||
// We temporarily force enabling autoneg, until a workaround is found.
|
||||
phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(12));
|
||||
phy->write_reg(phy_addr, MG_PHY_YT8531_REG_EXT_ADDR, 0xa003);
|
||||
value = phy->read_reg(phy_addr, MG_PHY_YT8531_REG_EXT_DATA);
|
||||
value &= (uint16_t) ~(MG_BIT(3) | MG_BIT(2) | MG_BIT(1) | MG_BIT(0));
|
||||
phy->write_reg(phy_addr, MG_PHY_YT8531_REG_EXT_DATA,
|
||||
(uint16_t) (value | MG_BIT(1))); // TXDLY: 2 * 150 ps
|
||||
}
|
||||
|
||||
if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
|
||||
phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to IO_MUX_CFG (0x0170)
|
||||
phy->write_reg(phy_addr, 0x0e, 0x170);
|
||||
@@ -168,6 +187,15 @@ bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex,
|
||||
uint16_t poll_reg = phy->read_reg(phy_addr, MG_PHY_ICS189432_REG_POLL);
|
||||
*full_duplex = poll_reg & MG_BIT(14);
|
||||
*speed = (poll_reg & MG_BIT(15)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
|
||||
} else if (id1 == MG_PHY_YT85x) {
|
||||
uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
|
||||
if (id2 == MG_PHY_YT8531) {
|
||||
uint16_t physts = phy->read_reg(phy_addr, MG_PHY_YT8531_REG_PHYSTS);
|
||||
*full_duplex = physts & MG_BIT(13);
|
||||
*speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
|
||||
: (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
|
||||
: MG_PHY_SPEED_10M;
|
||||
}
|
||||
}
|
||||
}
|
||||
return up;
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
|
||||
MG_ENABLE_DRIVER_RW612_154
|
||||
|
||||
// The 802.15.4 radio runs NXP combo firmware on CPU2, an OpenThread RCP. We
|
||||
// are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC
|
||||
// messages, IMUMC messages carry HDLC framed Spinel. See NXP sources:
|
||||
// mcuxsdk-core drivers/imu/fsl_imu.c, mcux-component
|
||||
// imu_adapter/fsl_adapter_imu.c, connectivity framework
|
||||
// platform/rw61x/fwk_platform_hdlc.c; OpenThread src/lib/spinel/spinel.h
|
||||
|
||||
// IMU message FIFO registers, one block per direction. See fsl_imu.h
|
||||
struct mg_rw612_imu {
|
||||
volatile uint32_t WR_MSG, RD_MSG, STATUS, CNTL, RD_MSG_DBG;
|
||||
};
|
||||
#define MGI_IMU ((struct mg_rw612_imu *) 0x443ff804) // CPU3 -> CPU2
|
||||
#define MGI_IMU_PEER ((struct mg_rw612_imu *) 0x443ff818) // CPU2 -> CPU3
|
||||
#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked
|
||||
#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full
|
||||
#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty
|
||||
#define MGI_IMU_WR_PTR(s) (((s) >> 16) & 15U)
|
||||
#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ
|
||||
#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO
|
||||
#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO
|
||||
#define MGI_IMU_WATERMARK(x) ((uint32_t) (x) << 20)
|
||||
#define MGI_IMU_WATERMARK_MASK (15U << 20)
|
||||
|
||||
// IMU message: header, then payload words. See IMU_Msg_t
|
||||
struct mg_rw612_msg {
|
||||
uint8_t type, subtype, seq, len; // len: number of payload words
|
||||
uint32_t payload[8];
|
||||
};
|
||||
|
||||
// Mailbox in SMU2 shared memory, at the address CPU2 firmware expects
|
||||
struct mg_rw612_mbox {
|
||||
uint32_t magic[2]; // Sync pattern: [0] CPU2, [1] us
|
||||
struct mg_rw612_msg q[2][16]; // Message slots: [0] CPU2 -> us, [1] ours
|
||||
};
|
||||
#define MGI_MBOX ((volatile struct mg_rw612_mbox *) 0x443c0000)
|
||||
#define MGI_TXBUF 0x443c1508U // Our IMUMC buffers: 16 x 264 bytes
|
||||
#define MGI_TXBUF_CNT 16
|
||||
#define MGI_TXBUF_LEN 264U
|
||||
#define MGI_SLEEP_FLAG (*(volatile uint32_t *) 0x443c2588)
|
||||
#define MGI_SYNC_MAGIC 0xabcdef89U
|
||||
|
||||
// IMUMC message, carried in IMU payload words. See imumc_std_msg
|
||||
struct mg_rw612_imumc {
|
||||
uint32_t src, dst; // Endpoint addresses
|
||||
uint16_t rfu, idx; // Reserved
|
||||
uint16_t len; // Payload length, bytes
|
||||
uint16_t flags; // Message flags
|
||||
uint32_t data; // Payload buffer address
|
||||
};
|
||||
#define MGI_EPT_LOCAL 10 // Our 802.15.4 endpoint
|
||||
#define MGI_EPT_REMOTE 20 // Firmware 802.15.4 endpoint
|
||||
|
||||
enum { MGI_MSG_CONTROL = 0, MGI_MSG_IMUMC = 8 }; // IMU message types
|
||||
enum { // Control message subtypes
|
||||
MGI_CTL_SYNC = 1,
|
||||
MGI_CTL_SYNC_ACK = 2,
|
||||
MGI_CTL_ACK_FOR_UNLOCK = 3,
|
||||
MGI_CTL_CMD_BUF_ADDR = 6,
|
||||
MGI_CTL_EPT_QUERY = 11,
|
||||
MGI_CTL_EPT_QUERY_RSP = 12,
|
||||
MGI_CTL_BUF_FREE = 13
|
||||
};
|
||||
|
||||
// Spinel commands, properties and statuses we use
|
||||
enum { MGI_CMD_RESET = 1, MGI_CMD_GET = 2, MGI_CMD_SET = 3, MGI_CMD_IS = 6 };
|
||||
enum {
|
||||
MGI_PROP_LAST_STATUS = 0,
|
||||
MGI_PROP_NCP_VERSION = 2,
|
||||
MGI_PROP_PHY_ENABLED = 0x20,
|
||||
MGI_PROP_PHY_CHAN = 0x21,
|
||||
MGI_PROP_PANID = 0x36,
|
||||
MGI_PROP_RAW_STREAM = 0x37,
|
||||
MGI_PROP_STREAM_RAW = 0x71
|
||||
};
|
||||
#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only
|
||||
#define MGI_STATUS_RESET_MIN 112 // LAST_STATUS: RCP has reset
|
||||
#define MGI_STATUS_RESET_MAX 127
|
||||
#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries
|
||||
#define MGI_RSP_TIMEOUT_MS 500
|
||||
#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames
|
||||
|
||||
// Driver state, one radio
|
||||
struct mg_rw612 {
|
||||
bool up; // IMU link synchronised
|
||||
bool ept_ready; // IMUMC endpoints connected
|
||||
bool reset; // RCP reported a reset
|
||||
uint8_t seq; // IMU message sequence number
|
||||
uint16_t free_bufs; // Free IMUMC buffers, bit per buffer
|
||||
uint8_t hdlc[300]; // HDLC frame being received, unescaped
|
||||
size_t hlen; // Bytes in hdlc
|
||||
bool esc; // Previous byte was an escape
|
||||
uint8_t tid; // Spinel transaction ID of the pending request
|
||||
bool rsp; // Response to the pending request received
|
||||
uint32_t rsp_status; // Its status, if LAST_STATUS, else 0
|
||||
char version[64]; // RCP version string
|
||||
uint8_t rxq[MGI_RXQ_CNT][127]; // Received frames, no FCS
|
||||
uint8_t rxlen[MGI_RXQ_CNT]; // Their lengths
|
||||
uint8_t rxhead, rxtail; // Queue indexes
|
||||
};
|
||||
static struct mg_rw612 s_rw612;
|
||||
|
||||
static void mg_rw612_imu_send(uint8_t type, uint8_t subtype,
|
||||
const uint32_t *payload, uint8_t len) {
|
||||
volatile struct mg_rw612_msg *m;
|
||||
uint64_t expire = mg_millis() + 100;
|
||||
uint8_t i;
|
||||
while ((MGI_IMU->STATUS & (MGI_IMU_ALMOST_FULL | MGI_IMU_LOCKED)) &&
|
||||
mg_millis() < expire) {
|
||||
(void) 0;
|
||||
}
|
||||
m = &MGI_MBOX->q[1][MGI_IMU_WR_PTR(MGI_IMU->STATUS)];
|
||||
m->type = type, m->subtype = subtype, m->seq = s_rw612.seq++, m->len = len;
|
||||
for (i = 0; i < len; i++) m->payload[i] = payload[i];
|
||||
MGI_IMU->WR_MSG = (uint32_t) (uintptr_t) m;
|
||||
}
|
||||
|
||||
static void mg_rw612_control(uint8_t subtype, uint32_t a, uint32_t b,
|
||||
uint8_t len) {
|
||||
uint32_t payload[2] = {a, b};
|
||||
mg_rw612_imu_send(MGI_MSG_CONTROL, subtype, payload, len);
|
||||
}
|
||||
|
||||
static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
uint8_t next = (uint8_t) ((s->rxhead + 1) % MGI_RXQ_CNT);
|
||||
if (next == s->rxtail || len > sizeof(s->rxq[0])) return; // Full: drop
|
||||
memcpy(s->rxq[s->rxhead], frame, len);
|
||||
s->rxlen[s->rxhead] = (uint8_t) len;
|
||||
s->rxhead = next;
|
||||
}
|
||||
|
||||
// Spinel packed unsigned integer: 7 bits per byte, LSB first
|
||||
static size_t mg_rw612_unpack(const uint8_t *p, size_t len, uint32_t *v) {
|
||||
size_t i;
|
||||
*v = 0;
|
||||
for (i = 0; i < len && i < 4; i++) {
|
||||
*v |= (uint32_t) (p[i] & 0x7f) << (7 * i);
|
||||
if ((p[i] & 0x80) == 0) return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle a received Spinel frame: header, command, property, value
|
||||
static void mg_rw612_spinel(const uint8_t *f, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
uint32_t cmd, prop, status = 0;
|
||||
size_t n, ofs = 1;
|
||||
uint8_t tid = f[0] & 15;
|
||||
if (len < 3 || (n = mg_rw612_unpack(f + ofs, len - ofs, &cmd)) == 0) return;
|
||||
ofs += n;
|
||||
if ((n = mg_rw612_unpack(f + ofs, len - ofs, &prop)) == 0) return;
|
||||
ofs += n;
|
||||
if (cmd != MGI_CMD_IS) return;
|
||||
if (prop == MGI_PROP_STREAM_RAW && tid == 0 && len - ofs >= 2) {
|
||||
size_t flen = (size_t) (f[ofs] | (f[ofs + 1] << 8)); // Incl. FCS
|
||||
if (flen >= 2 && ofs + 2 + flen <= len) {
|
||||
mg_rw612_queue_rx(f + ofs + 2, flen - 2);
|
||||
}
|
||||
} else if (prop == MGI_PROP_LAST_STATUS) {
|
||||
mg_rw612_unpack(f + ofs, len - ofs, &status);
|
||||
if (tid == 0 && status >= MGI_STATUS_RESET_MIN &&
|
||||
status <= MGI_STATUS_RESET_MAX) {
|
||||
s->reset = true;
|
||||
}
|
||||
} else if (prop == MGI_PROP_NCP_VERSION) {
|
||||
n = len - ofs < sizeof(s->version) - 1 ? len - ofs : sizeof(s->version) - 1;
|
||||
memcpy(s->version, f + ofs, n);
|
||||
s->version[n] = '\0';
|
||||
}
|
||||
if (tid != 0 && tid == s->tid) s->rsp = true, s->rsp_status = status;
|
||||
}
|
||||
|
||||
// HDLC-lite: 0x7e delimits frames, 0x7d escapes, FCS-16 at the end
|
||||
static uint16_t mg_rw612_fcs(uint16_t fcs, uint8_t c) {
|
||||
int i;
|
||||
fcs ^= c;
|
||||
for (i = 0; i < 8; i++) fcs = (fcs & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1;
|
||||
return fcs;
|
||||
}
|
||||
|
||||
static void mg_rw612_hdlc_rx(const uint8_t *p, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
size_t i, j;
|
||||
for (i = 0; i < len; i++) {
|
||||
uint8_t c = p[i];
|
||||
if (c == 0x7e) {
|
||||
if (s->hlen >= 3) {
|
||||
uint16_t fcs = 0xffff;
|
||||
for (j = 0; j < s->hlen; j++) fcs = mg_rw612_fcs(fcs, s->hdlc[j]);
|
||||
if (fcs == 0xf0b8) mg_rw612_spinel(s->hdlc, s->hlen - 2);
|
||||
}
|
||||
s->hlen = 0, s->esc = false;
|
||||
} else if (c == 0x7d) {
|
||||
s->esc = true;
|
||||
} else if (s->hlen < sizeof(s->hdlc)) {
|
||||
s->hdlc[s->hlen++] = s->esc ? c ^ 0x20 : c;
|
||||
s->esc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_rw612_control_rx(volatile struct mg_rw612_msg *m) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
switch (m->subtype) {
|
||||
case MGI_CTL_ACK_FOR_UNLOCK:
|
||||
MGI_IMU->CNTL &= ~MGI_IMU_WAIT_FOR_ACK;
|
||||
break;
|
||||
case MGI_CTL_SYNC: // As HAL_ImuCtrlHandler(): acknowledge twice
|
||||
mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0);
|
||||
mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0);
|
||||
s->up = true;
|
||||
break;
|
||||
case MGI_CTL_SYNC_ACK:
|
||||
case MGI_CTL_CMD_BUF_ADDR:
|
||||
s->up = true;
|
||||
break;
|
||||
case MGI_CTL_EPT_QUERY: { // Firmware asks if our endpoint exists
|
||||
bool ours = m->payload[0] == MGI_EPT_LOCAL;
|
||||
mg_rw612_control(MGI_CTL_EPT_QUERY_RSP, m->payload[0], ours, 2);
|
||||
if (ours) s->ept_ready = true;
|
||||
break;
|
||||
}
|
||||
case MGI_CTL_EPT_QUERY_RSP:
|
||||
if (m->payload[0] == MGI_EPT_REMOTE && m->payload[1] != 0) {
|
||||
s->ept_ready = true;
|
||||
}
|
||||
break;
|
||||
case MGI_CTL_BUF_FREE: { // Firmware is done with one of our buffers
|
||||
uint32_t i = (m->payload[0] - MGI_TXBUF) / MGI_TXBUF_LEN;
|
||||
if (i < MGI_TXBUF_CNT) s->free_bufs |= (uint16_t) MG_BIT(i);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process all received IMU messages. See HAL_ImuReceive()
|
||||
static void mg_rw612_poll_imu(void) {
|
||||
bool unlock = false;
|
||||
while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) {
|
||||
struct mg_rw612_msg m;
|
||||
volatile struct mg_rw612_msg *p;
|
||||
uint8_t i;
|
||||
MGI_SLEEP_FLAG &= ~1U;
|
||||
p = (volatile struct mg_rw612_msg *) (uintptr_t) MGI_IMU->RD_MSG;
|
||||
if (MGI_IMU_PEER->STATUS & MGI_IMU_LOCKED) unlock = true;
|
||||
if (p == NULL) continue;
|
||||
m.type = p->type, m.subtype = p->subtype, m.len = p->len;
|
||||
for (i = 0; i < 8; i++) m.payload[i] = p->payload[i];
|
||||
if (m.type == MGI_MSG_CONTROL) {
|
||||
mg_rw612_control_rx(&m);
|
||||
} else if (m.type == MGI_MSG_IMUMC) {
|
||||
struct mg_rw612_imumc im;
|
||||
memcpy(&im, m.payload, sizeof(im));
|
||||
if (im.dst == MGI_EPT_LOCAL) {
|
||||
mg_rw612_hdlc_rx((const uint8_t *) (uintptr_t) im.data, im.len);
|
||||
}
|
||||
mg_rw612_control(MGI_CTL_BUF_FREE, im.data, 0, 1);
|
||||
}
|
||||
}
|
||||
MGI_IMU->CNTL |= MGI_IMU_INT_CLR;
|
||||
MGI_SLEEP_FLAG |= 1U;
|
||||
if (unlock) mg_rw612_control(MGI_CTL_ACK_FOR_UNLOCK, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Send bytes to the firmware endpoint, in IMUMC buffer sized chunks
|
||||
static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
while (len > 0) {
|
||||
size_t n = len < MGI_TXBUF_LEN ? len : MGI_TXBUF_LEN;
|
||||
uint64_t expire = mg_millis() + 100;
|
||||
struct mg_rw612_imumc im;
|
||||
uint32_t i;
|
||||
while (s->free_bufs == 0) {
|
||||
mg_rw612_poll_imu();
|
||||
if (mg_millis() > expire) return false;
|
||||
}
|
||||
for (i = 0; (s->free_bufs & MG_BIT(i)) == 0; i++) (void) 0;
|
||||
s->free_bufs &= (uint16_t) ~MG_BIT(i);
|
||||
memset(&im, 0, sizeof(im));
|
||||
im.src = MGI_EPT_LOCAL, im.dst = MGI_EPT_REMOTE, im.len = (uint16_t) n;
|
||||
im.data = MGI_TXBUF + i * MGI_TXBUF_LEN;
|
||||
memcpy((void *) (uintptr_t) im.data, p, n);
|
||||
mg_rw612_imu_send(MGI_MSG_IMUMC, 0, (uint32_t *) &im, sizeof(im) / 4);
|
||||
p += n, len -= n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send a Spinel frame, HDLC encoded, and wait for the response with the
|
||||
// same transaction ID. Return false on timeout
|
||||
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
uint8_t buf[2 * 160 + 6];
|
||||
uint16_t fcs = 0xffff;
|
||||
size_t i, n = 0;
|
||||
uint64_t expire = mg_millis() + timeout_ms;
|
||||
s->tid = (uint8_t) (s->tid % 15 + 1);
|
||||
f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID
|
||||
s->rsp = false;
|
||||
buf[n++] = 0x7e;
|
||||
for (i = 0; i < len + 2 && n + 3 < sizeof(buf); i++) {
|
||||
uint8_t c;
|
||||
if (i < len) {
|
||||
c = f[i], fcs = mg_rw612_fcs(fcs, c);
|
||||
} else {
|
||||
c = (uint8_t) (i == len ? ~fcs & 0xff : ~fcs >> 8);
|
||||
}
|
||||
if (c == 0x7e || c == 0x7d || c == 0x11 || c == 0x13 || c == 0xf8) {
|
||||
buf[n++] = 0x7d, c ^= 0x20;
|
||||
}
|
||||
buf[n++] = c;
|
||||
}
|
||||
buf[n++] = 0x7e;
|
||||
if (!mg_rw612_imumc_send(buf, n)) return false;
|
||||
while (!s->rsp && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
return s->rsp;
|
||||
}
|
||||
|
||||
static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) {
|
||||
uint8_t f[16];
|
||||
if (len > sizeof(f) - 3) return false;
|
||||
f[1] = MGI_CMD_SET, f[2] = (uint8_t) prop; // Properties used are < 0x80
|
||||
memcpy(f + 3, val, len);
|
||||
return mg_rw612_request(f, len + 3, MGI_RSP_TIMEOUT_MS) &&
|
||||
s_rw612.rsp_status == 0;
|
||||
}
|
||||
|
||||
// Log an init error, return false
|
||||
static bool mg_rw612_fail(const char *msg) {
|
||||
MG_ERROR(("%s", msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
struct mg_l2_802154_data *d =
|
||||
(struct mg_l2_802154_data *) ifp->driver_data;
|
||||
uint8_t f[8], one = 1, pan[2];
|
||||
uint64_t expire;
|
||||
if (d == NULL || d->channel < 11 || d->channel > 26) {
|
||||
MG_ERROR(("Set driver_data, channel 11..26"));
|
||||
return false;
|
||||
}
|
||||
if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) {
|
||||
MG_ERROR(("Radio firmware is not running on CPU2"));
|
||||
return false;
|
||||
}
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->free_bufs = 0xffff;
|
||||
// Reset our send FIFO, drop stale messages. See IMU_Init()
|
||||
MGI_IMU->CNTL |= MGI_IMU_FLUSH;
|
||||
MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK);
|
||||
MGI_IMU->CNTL = (MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) |
|
||||
MGI_IMU_WATERMARK(15);
|
||||
while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG;
|
||||
// Synchronise the IMU link, then connect the IMUMC endpoints
|
||||
MGI_MBOX->magic[1] = MGI_SYNC_MAGIC;
|
||||
mg_rw612_control(MGI_CTL_SYNC, 0, 0, 0);
|
||||
expire = mg_millis() + 1000;
|
||||
while (!s->up && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
if (!s->up) return mg_rw612_fail("IMU sync timeout");
|
||||
expire = mg_millis() + 2000;
|
||||
while (!s->ept_ready && mg_millis() < expire) {
|
||||
uint64_t retry = mg_millis() + 100;
|
||||
mg_rw612_control(MGI_CTL_EPT_QUERY, MGI_EPT_REMOTE, 0, 1);
|
||||
while (!s->ept_ready && mg_millis() < retry) mg_rw612_poll_imu();
|
||||
}
|
||||
if (!s->ept_ready) return mg_rw612_fail("IMUMC endpoint timeout");
|
||||
// Reset the RCP stack, wait for its reset status, read its version
|
||||
f[1] = MGI_CMD_RESET, f[2] = MGI_RESET_STACK;
|
||||
mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS);
|
||||
expire = mg_millis() + 1000;
|
||||
while (!s->reset && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
if (!s->reset) return mg_rw612_fail("RCP reset timeout");
|
||||
f[1] = MGI_CMD_GET, f[2] = MGI_PROP_NCP_VERSION;
|
||||
mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS);
|
||||
MG_INFO(("RCP: %s", s->version));
|
||||
if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) {
|
||||
return mg_rw612_fail("PHY enable failed");
|
||||
}
|
||||
pan[0] = (uint8_t) d->pan_id, pan[1] = (uint8_t) (d->pan_id >> 8);
|
||||
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &d->channel, 1) ||
|
||||
!mg_rw612_set(MGI_PROP_PANID, pan, 2)) {
|
||||
return mg_rw612_fail("Channel or PAN ID set failed");
|
||||
}
|
||||
if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
|
||||
return mg_rw612_fail("Raw stream enable failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send one radio frame: MAC header and payload, no FCS
|
||||
static size_t mg_rw612_send(const uint8_t *p, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_l2_802154_data *d =
|
||||
(struct mg_l2_802154_data *) ifp->driver_data;
|
||||
uint8_t f[3 + 2 + 127 + 4];
|
||||
size_t n = 3;
|
||||
f[1] = MGI_CMD_SET, f[2] = MGI_PROP_STREAM_RAW;
|
||||
f[n++] = (uint8_t) (len + 2), f[n++] = 0; // PSDU length, incl. FCS
|
||||
memcpy(f + n, p, len), n += len;
|
||||
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio
|
||||
f[n++] = d->channel;
|
||||
f[n++] = 4; // Max CSMA backoffs
|
||||
f[n++] = 0; // Max frame retries: broadcast frames are not acknowledged
|
||||
f[n++] = 1; // CSMA-CA enabled
|
||||
if (!mg_rw612_request(f, n, MGI_TX_TIMEOUT_MS)) {
|
||||
MG_ERROR(("TX timeout"));
|
||||
return 0;
|
||||
}
|
||||
if (s_rw612.rsp_status != 0) {
|
||||
MG_VERBOSE(("TX failed, status %lu", (unsigned long) s_rw612.rsp_status));
|
||||
return 0;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t mg_rw612_154_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
return mg_l2_802154_tx(ifp, buf, len, mg_rw612_send);
|
||||
}
|
||||
|
||||
static size_t mg_rw612_154_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
size_t res = 0;
|
||||
mg_rw612_poll_imu();
|
||||
while (s->rxtail != s->rxhead && res == 0) {
|
||||
res = mg_l2_802154_rx(ifp, s->rxq[s->rxtail], s->rxlen[s->rxtail], buf,
|
||||
len);
|
||||
s->rxtail = (uint8_t) ((s->rxtail + 1) % MGI_RXQ_CNT);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool mg_rw612_154_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
(void) ifp, (void) s1;
|
||||
return s_rw612.up;
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_rw612_154 = {
|
||||
mg_rw612_154_init, mg_rw612_154_tx, mg_rw612_154_rx, mg_rw612_154_poll};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
|
||||
MG_ENABLE_DRIVER_RW612_154
|
||||
|
||||
// NXP RW612 IEEE 802.15.4 radio, as an Ethernet interface: Ethernet frames
|
||||
// are split into radio frames, see mg_l2_802154_tx(). The radio runs on
|
||||
// CPU2: NXP BLE/802.15.4 combo firmware, an OpenThread radio co-processor
|
||||
// (RCP). The firmware must be loaded and running before mg_tcpip_init(), see
|
||||
// the FRDM-RW612 eth_radio_bridge tutorial. The driver talks to it over the
|
||||
// CPU2 <-> CPU3 IMU mailbox: IMUMC messages carrying HDLC framed Spinel.
|
||||
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||
do { \
|
||||
static struct mg_l2_802154_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
driver_data_.channel = MG_L2_802154_CHANNEL; \
|
||||
driver_data_.pan_id = MG_L2_802154_PAN_ID; \
|
||||
mif_.driver = &mg_tcpip_driver_rw612_154; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mg_tcpip_init(mgr, &mif_); \
|
||||
MG_INFO(("Driver: rw612_154, channel %d", MG_L2_802154_CHANNEL)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -365,7 +365,7 @@ static bool st67w6_init(uint8_t *mac) {
|
||||
}
|
||||
// set clock, who cares ???
|
||||
if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false;
|
||||
MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE
|
||||
MG_VERBOSE(("%.*s", s_at_resp_len, at_resp));
|
||||
// BT-ENABLED DEPENDENCY
|
||||
|
||||
// MODULE DEPENDENCY
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \
|
||||
MG_ENABLE_DRIVER_STM32N)
|
||||
// STM32C: vendor modded single-queue Synopsys v5.4, same settings as STM32H
|
||||
// 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
|
||||
@@ -310,7 +311,7 @@ static uint32_t s_rxno;
|
||||
#if MG_ENABLE_DRIVER_MCXN
|
||||
void ETHERNET_IRQHandler(void);
|
||||
void ETHERNET_IRQHandler(void) {
|
||||
#elif MG_ENABLE_DRIVER_STM32H
|
||||
#elif MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C
|
||||
void ETH_IRQHandler(void);
|
||||
void ETH_IRQHandler(void) {
|
||||
#else
|
||||
@@ -339,8 +340,10 @@ void ETH1_IRQHandler(void) {
|
||||
}
|
||||
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
|
||||
// Resume RX: tail is the last descriptor given back to DMA. It must differ
|
||||
// from the DMA current descriptor, or some DMAs, e.g. STM32C5, stay suspended
|
||||
ETH->DMACRDTPR = (uint32_t) (uintptr_t) &s_rxdesc[(s_rxno + ETH_DESC_CNT - 1) %
|
||||
ETH_DESC_CNT];
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
|
||||
|
||||
+11
-2
@@ -4,6 +4,13 @@
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32H)
|
||||
#define MG_ENABLE_DRIVER_STM32H 0
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32C)
|
||||
#define MG_ENABLE_DRIVER_STM32C 0
|
||||
#endif
|
||||
#if MG_ENABLE_DRIVER_STM32C // STM32C5: STM32H5 MAC, the IRQ is ETH1
|
||||
#undef MG_ENABLE_DRIVER_STM32H
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_MCXN)
|
||||
#define MG_ENABLE_DRIVER_MCXN 0
|
||||
#endif
|
||||
@@ -43,9 +50,11 @@ struct mg_tcpip_driver_stm32h_data {
|
||||
#define MG_DRIVER_MDC_CR 4
|
||||
#endif
|
||||
|
||||
#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#if MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn)
|
||||
#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#elif (MG_ENABLE_DRIVER_STM32N || MG_ENABLE_DRIVER_STM32C) && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
|
||||
#elif !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ()
|
||||
|
||||
+3
-4
@@ -4,7 +4,7 @@
|
||||
#include "ota.h"
|
||||
#ifdef MG_OTA_PUBLIC_KEY
|
||||
#include "sha256.h"
|
||||
#include "tls_uecc.h"
|
||||
#include "tls_p256.h"
|
||||
#endif
|
||||
|
||||
#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM
|
||||
@@ -76,9 +76,8 @@ bool mg_ota_flash_end(struct mg_flash *flash) {
|
||||
uint8_t hash[32];
|
||||
size_t fw_size = s_size - 68; // strip 64-byte sig + 4-byte magic
|
||||
mg_sha256(hash, (uint8_t *) base, fw_size);
|
||||
ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash),
|
||||
(uint8_t *) base + fw_size,
|
||||
mg_uecc_secp256r1()) == 1;
|
||||
ok = mg_p256_verify(s_pubkey, hash, sizeof(hash),
|
||||
(uint8_t *) base + fw_size) == 1;
|
||||
MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
|
||||
} else {
|
||||
ok = false;
|
||||
|
||||
@@ -34,8 +34,8 @@ typedef size_t (*mg_pm_t)(mg_pfn_t fn, void *arg, va_list *);
|
||||
// - mg_print_base64 - prints a buffer as a base64-encoded string
|
||||
// - mg_print_esc - prints a JSON-escaped string
|
||||
// - mg_print_hex - prints a buffer as a hex string
|
||||
// - mg_print_ip - prints an IP address in a struct mg_str
|
||||
// - mg_print_ip_port - prints IP address and port in a struct mg_str
|
||||
// - mg_print_ip - prints an IP address in a struct mg_addr
|
||||
// - mg_print_ip_port - prints IP address and port in a struct mg_addr
|
||||
// - mg_print_ip4 - prints an IPv4 address
|
||||
// - mg_print_ip6 - prints an IPv6 address
|
||||
// - mg_print_mac - prints a MAC address
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "fs.h"
|
||||
#include "log.h"
|
||||
#include "printf.h"
|
||||
#include "str.h"
|
||||
#include "util.h"
|
||||
@@ -47,6 +48,7 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
|
||||
char tmp[MG_PATH_MAX], rnd[10];
|
||||
size_t path_len = mg_snprintf(tmp, sizeof(tmp), "%s..%s", path,
|
||||
mg_random_str(rnd, sizeof(rnd)));
|
||||
fs->rm(tmp);
|
||||
if (path_len < sizeof(tmp) &&
|
||||
(fd = mg_fs_open(fs, tmp, MG_FS_WRITE | MG_FS_EXCL)) != NULL) {
|
||||
result = fs->wr(fd->fd, buf, len) == len;
|
||||
@@ -54,8 +56,11 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
|
||||
if (result) {
|
||||
fs->rm(path);
|
||||
result = fs->mv(tmp, path);
|
||||
MG_DEBUG(("mv %s -> %s result=%d", tmp, path, result));
|
||||
}
|
||||
fs->rm(tmp);
|
||||
} else {
|
||||
MG_ERROR(("mg_fs_open(%s) failed, path_len=%zu", tmp, path_len));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -67,8 +72,10 @@ bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
|
||||
va_start(ap, fmt);
|
||||
data = mg_vmprintf(fmt, &ap);
|
||||
va_end(ap);
|
||||
result = mg_file_write(fs, path, data, strlen(data));
|
||||
mg_free(data);
|
||||
if (data != NULL) {
|
||||
result = mg_file_write(fs, path, data, strlen(data));
|
||||
mg_free(data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "arch.h"
|
||||
#include "config.h"
|
||||
#include "lfs.h"
|
||||
|
||||
// Flags returned by mg_fs.st() and passed to mg_fs.open().
|
||||
enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4, MG_FS_EXCL = 8 };
|
||||
@@ -40,6 +41,7 @@ void mg_fs_close(struct mg_fd *fd);
|
||||
|
||||
// Sequential directory iterator. Call repeatedly with the same buf/len;
|
||||
// each call fills buf with the next entry name. Returns false when done.
|
||||
// Note: make sure buf[0] == 0 before the first call
|
||||
bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len);
|
||||
|
||||
// Reads the entire file into a heap-allocated buffer. The returned mg_str is
|
||||
|
||||
+2
-2
@@ -50,9 +50,9 @@ static void *ff_open(const char *path, int flags) {
|
||||
if (flags & MG_FS_WRITE) {
|
||||
mode |= FA_WRITE;
|
||||
if (flags & MG_FS_EXCL) {
|
||||
mode |= FA_OPEN_ALWAYS | FA_OPEN_APPEND;
|
||||
} else {
|
||||
mode |= FA_CREATE_NEW;
|
||||
} else {
|
||||
mode |= FA_OPEN_ALWAYS | FA_OPEN_APPEND;
|
||||
}
|
||||
}
|
||||
if ((fp = mg_calloc(1, sizeof(*fp))) != NULL &&
|
||||
|
||||
+4
-4
@@ -10,6 +10,10 @@
|
||||
#define MG_STAT_FUNC stat
|
||||
#endif
|
||||
|
||||
#if MG_ENABLE_LFS
|
||||
#include <dirent.h>
|
||||
#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));
|
||||
@@ -161,7 +165,6 @@ struct dirent *readdir(DIR *d) {
|
||||
|
||||
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;
|
||||
@@ -170,9 +173,6 @@ static void p_list(const char *dir, void (*fn)(const char *, void *),
|
||||
fn(dp->d_name, userdata);
|
||||
}
|
||||
closedir(dirp);
|
||||
#else
|
||||
(void) dir, (void) fn, (void) userdata;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *p_open(const char *path, int flags) {
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#include "config.h"
|
||||
#include "health.h"
|
||||
|
||||
// The one health record. Lives in RAM that survives a warm reset, see
|
||||
// MG_HEALTH_RAM and the .mg_health region in the linker script
|
||||
struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||
|
||||
// STM32 H723 support. Needs the _estack symbol from the linker script
|
||||
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||
// Fault handler body. Runs in exception context: no printf, no malloc, no
|
||||
// blocking calls. Copies the unwind registers and raw stack into the health
|
||||
// record, then resets.
|
||||
// "used" keeps the linker from garbage-collecting this section: the only
|
||||
// reference is the "b fault_c" branch in the naked handler below
|
||||
__attribute__((used, noinline)) static void fault_c(uint32_t *frame,
|
||||
uint32_t exc_return) {
|
||||
extern uint32_t _estack;
|
||||
uint32_t *stack = frame + 8 + ((exc_return & (1U << 4)) ? 0 : 18);
|
||||
uint32_t *r = mg_health_record.data;
|
||||
|
||||
if (frame[7] & (1U << 9)) stack++; // Eight-byte stack alignment padding
|
||||
r[0] = (uint32_t) (uintptr_t) stack; // SP
|
||||
r[1] = frame[5]; // LR
|
||||
r[2] = frame[6]; // PC
|
||||
for (size_t i = 0; i < MG_HEALTH_DATA_SIZE - 3; i++) { // Raw stack
|
||||
r[3 + i] = stack + i < &_estack ? stack[i] : 0;
|
||||
}
|
||||
mg_health_record.cpuid = SCB->CPUID;
|
||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
// Common fault entry. EXC_RETURN bit 2 tells which stack was in use:
|
||||
// 0 = MSP, 1 = PSP. Load the faulting SP into r0 and hand it to fault_c()
|
||||
__attribute__((naked)) void HardFault_Handler(void) {
|
||||
__asm volatile(
|
||||
"mov r1, lr\n\t"
|
||||
"tst lr, #4\n\t"
|
||||
"ite eq\n\t"
|
||||
"mrseq r0, msp\n\t"
|
||||
"mrsne r0, psp\n\t"
|
||||
"b fault_c\n\t");
|
||||
}
|
||||
|
||||
// Route the other fault types through the same entry point
|
||||
void MemManage_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
#endif
|
||||
|
||||
// Start a new boot: validate the record, count the boot, store the reset
|
||||
// reason. The current boot moves to prev_* slots first, so the previous
|
||||
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
|
||||
void mg_health_init(void) {
|
||||
char magic[] = MG_HEALTH_MAGIC;
|
||||
// struct mg_health *h = mg_health_get();
|
||||
if (!mg_health_valid()) { // First boot, or RAM lost its contents
|
||||
memset(&mg_health_record, 0, sizeof(mg_health_record));
|
||||
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
|
||||
}
|
||||
|
||||
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
{
|
||||
// After a crash, copy the backtrace from the core dump in flash: the same
|
||||
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
||||
// The core dump stays in flash, so check the reset reason, otherwise an
|
||||
// old crash is reported on every boot
|
||||
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack
|
||||
esp_reset_reason_t rr = esp_reset_reason();
|
||||
uint32_t *out = mg_health_record.data;
|
||||
size_t n = 0, max = MG_HEALTH_DATA_SIZE;
|
||||
if (rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && rr != ESP_RST_TASK_WDT)
|
||||
return; // Not a crash that went through the panic handler
|
||||
if (max == 0 || esp_core_dump_get_summary(&s) != ESP_OK) return;
|
||||
|
||||
mg_health_record.cpuid = 42; // ESP32
|
||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||
|
||||
memset(out, 0, sizeof(mg_health_record.data));
|
||||
out[n++] = s.exc_pc;
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) {
|
||||
if (s.exc_bt_info.bt[i] != s.exc_pc) out[n++] = s.exc_bt_info.bt[i];
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// No unwinder: take the return address, then words on the stack that
|
||||
// point into executable memory (a heuristic, may include stale values)
|
||||
if (n < max) out[n++] = s.ex_info.ra;
|
||||
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) {
|
||||
uint32_t w;
|
||||
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
|
||||
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
size_t n = va_arg(*ap, size_t);
|
||||
uint32_t *p = va_arg(*ap, uint32_t *);
|
||||
size_t len = 0;
|
||||
while (n--) len += mg_xprintf(fn, arg, "%s%lu", len == 0 ? "" : ",", *p++);
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
size_t len = 0;
|
||||
if (mg_health_valid() &&
|
||||
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
|
||||
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
||||
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%u,%m:[%M]}", //
|
||||
MG_ESC("version"), mg_health_record.magic[3], //
|
||||
MG_ESC("cpuid"), mg_health_record.cpuid, //
|
||||
MG_ESC("data"), print_uint32, n, mg_health_record.data);
|
||||
} else {
|
||||
len += mg_xprintf(fn, arg, "{}");
|
||||
}
|
||||
(void) ap;
|
||||
return len;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Device health monitoring
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "printf.h"
|
||||
|
||||
#ifndef MG_HEALTH_MAGIC
|
||||
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||
#define MG_HEALTH_DATA_SIZE 250
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_DATA_SIZE
|
||||
#define MG_HEALTH_DATA_SIZE 10
|
||||
#endif
|
||||
|
||||
// Places the record in RAM that survives a reset. Define in mongoose_config.h
|
||||
// to match the linker script, e.g. __attribute__((section(".mg_health")))
|
||||
#ifndef MG_HEALTH_RAM
|
||||
#define MG_HEALTH_RAM
|
||||
#endif
|
||||
|
||||
// Why the device restarted. Hardware usually reports several flags at once,
|
||||
// so these are ordered by how much they tell you: the most specific cause a
|
||||
// device reports wins
|
||||
enum mg_health_reason {
|
||||
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
|
||||
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
|
||||
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
|
||||
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
|
||||
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
|
||||
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
|
||||
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
|
||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||
};
|
||||
|
||||
struct mg_health {
|
||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
||||
uint32_t cpuid; // ARM CPU ID
|
||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||
};
|
||||
|
||||
extern struct mg_health mg_health_record; // Defined in health.c
|
||||
|
||||
// Return true if the record holds valid data, i.e. it survived the reset and
|
||||
// was written by this firmware layout
|
||||
static inline bool mg_health_valid(void) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
return memcmp(mg_health_record.magic, magic,
|
||||
sizeof(mg_health_record.magic)) == 0;
|
||||
}
|
||||
|
||||
static inline int mg_health_reason(void) {
|
||||
return mg_health_valid() ? (int) mg_health_record.reset_reason
|
||||
: MG_HEALTH_RESET_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline const char *mg_health_reason_str(int reason) {
|
||||
switch (reason) {
|
||||
case MG_HEALTH_RESET_POWER: return "power";
|
||||
case MG_HEALTH_RESET_BROWNOUT: return "brownout";
|
||||
case MG_HEALTH_RESET_PIN: return "pin";
|
||||
case MG_HEALTH_RESET_SOFTWARE: return "software";
|
||||
case MG_HEALTH_RESET_LOWPOWER: return "lowpower";
|
||||
case MG_HEALTH_RESET_WATCHDOG: return "watchdog";
|
||||
case MG_HEALTH_RESET_FAULT: return "fault";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#define MG_HEALTH_INIT() mg_health_init()
|
||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||
extern void mg_health_init(void);
|
||||
+242
-238
@@ -3,7 +3,6 @@
|
||||
#include "fmt.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
#include "ota.h"
|
||||
#include "printf.h"
|
||||
#include "ssi.h"
|
||||
#include "util.h"
|
||||
@@ -170,6 +169,7 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
|
||||
mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t));
|
||||
i += 2;
|
||||
} else {
|
||||
dst[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
} else if (is_form_url_encoded && src[i] == '+') {
|
||||
@@ -358,7 +358,7 @@ static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
|
||||
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 (c->send.len == len + 10) c->pfn_data = NULL; // Last chunk, done
|
||||
}
|
||||
if (!mg_send(c, "\r\n", 2)) mg_error(c, "OOM");
|
||||
}
|
||||
@@ -373,7 +373,7 @@ void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
|
||||
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;
|
||||
if (len == 0) c->pfn_data = NULL; // Last chunk, response done
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
@@ -462,15 +462,14 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers,
|
||||
(unsigned long) (c->send.len - len));
|
||||
c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space
|
||||
}
|
||||
c->is_resp = 0;
|
||||
c->pfn_data = NULL; // Response done
|
||||
}
|
||||
|
||||
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_data = NULL; // Response done
|
||||
c->pfn = http_cb;
|
||||
c->is_resp = 0;
|
||||
}
|
||||
|
||||
char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
|
||||
@@ -658,7 +657,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
|
||||
etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
|
||||
range, hdrs);
|
||||
if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0 || c->is_closing) {
|
||||
c->is_resp = 0;
|
||||
c->pfn_data = NULL; // Response done
|
||||
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
|
||||
@@ -676,159 +675,97 @@ struct printdirentrydata {
|
||||
struct mg_http_message *hm;
|
||||
const struct mg_http_serve_opts *opts;
|
||||
const char *dir;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
#if MG_ENABLE_DIRLIST
|
||||
// Print file name, escaping HTML chars
|
||||
static size_t html_esc(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
const char *s = va_arg(*ap, const char *);
|
||||
size_t i, len = 0;
|
||||
for (i = 0; s[i] != '\0'; i++) {
|
||||
if (s[i] == '<') {
|
||||
len += mg_xprintf(fn, arg, "%s", "<");
|
||||
} else if (s[i] == '>') {
|
||||
len += mg_xprintf(fn, arg, "%s", ">");
|
||||
} else if (s[i] == '&') {
|
||||
len += mg_xprintf(fn, arg, "%s", "&");
|
||||
} else {
|
||||
len += mg_xprintf(fn, arg, "%c", s[i]);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
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;
|
||||
char path[MG_PATH_MAX];
|
||||
int flags;
|
||||
|
||||
// MG_DEBUG(("[%s] [%s]", d->dir, name));
|
||||
if (mg_snprintf(path, sizeof(path), "%s%c%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]");
|
||||
mg_printf(d->c, "%s\n {%m: %m, %m: true}", //
|
||||
d->count == 0 ? "" : ",", //
|
||||
MG_ESC("name"), MG_ESC(name), MG_ESC("dir"));
|
||||
} else {
|
||||
mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
|
||||
mg_printf(d->c, "%s\n {%m: %m, %m: %zu, %m: %llu, %m: false}", //
|
||||
d->count == 0 ? "" : ",", //
|
||||
MG_ESC("name"), MG_ESC(name), //
|
||||
MG_ESC("size"), size, //
|
||||
MG_ESC("modified"), (uint64_t) t, MG_ESC("dir"));
|
||||
}
|
||||
#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,
|
||||
" <tr><td><a href=\"%.*s%s\">%M%s</a></td>"
|
||||
"<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
|
||||
n, path, slash, html_esc, name, slash, (unsigned long) t, mod,
|
||||
flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
|
||||
d->count++;
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
"<script>function srt(tb, sc, so, d) {"
|
||||
"var tr = Array.prototype.slice.call(tb.rows, 0),"
|
||||
"tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
|
||||
"n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
|
||||
"t1 = a.cells[2].getAttribute('name'), "
|
||||
"t2 = b.cells[2].getAttribute('name'); "
|
||||
"return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
|
||||
"n1 ? parseInt(n2) - parseInt(n1) : "
|
||||
"c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
|
||||
const char *sort_js_code2 =
|
||||
"for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
|
||||
"if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
|
||||
"};"
|
||||
"window.onload = function() {"
|
||||
"var tb = document.getElementById('tb');"
|
||||
"var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
|
||||
"var sc = m[1], so = m[2]; document.onclick = function(ev) { "
|
||||
"var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
|
||||
"sc = c; ev.preventDefault();}};"
|
||||
"srt(tb, sc, so, true);"
|
||||
"}"
|
||||
"</script>";
|
||||
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];
|
||||
struct printdirentrydata d = {c, hm, opts, dir, 0};
|
||||
char tmp[10];
|
||||
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);
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json; 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,
|
||||
"<!DOCTYPE html><html><head><title>Index of %M</title>%s%s"
|
||||
"<style>th,td {text-align: left; padding-right: 1em; "
|
||||
"font-family: monospace; }</style></head>"
|
||||
"<body><h1>Index of %M</h1><table cellpadding=\"0\"><thead>"
|
||||
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||||
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||||
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||||
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||||
"</thead>"
|
||||
"<tbody id=\"tb\">\n",
|
||||
mg_print_html_esc, (int) uri.len, uri.buf, sort_js_code, sort_js_code2,
|
||||
mg_print_html_esc, (int) uri.len, uri.buf);
|
||||
mg_printf(c, "%s",
|
||||
" <tr><td><a href=\"..\">..</a></td>"
|
||||
"<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
|
||||
mg_printf(c, "[");
|
||||
fs->ls(dir, printdirentry, &d);
|
||||
mg_printf(c,
|
||||
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||||
"</table><address>Mongoose v.%s</address></body></html>\n",
|
||||
MG_VERSION);
|
||||
mg_printf(c, "\n]\n");
|
||||
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
|
||||
c->pfn_data = NULL; // Response done
|
||||
}
|
||||
|
||||
// Map requested URI to the file path (buf,len). Use root directory r.
|
||||
// r could be a path optionally followed by map: PATH,/PREFIX1=PATH1,...
|
||||
static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) {
|
||||
struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u;
|
||||
size_t n;
|
||||
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, d = v;
|
||||
if (uri.len < k.len) continue;
|
||||
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue;
|
||||
u = k, d = v;
|
||||
}
|
||||
n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
|
||||
if (len == 0 || n + 2 >= len) return false; // Path overflow
|
||||
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
|
||||
if (mg_url_decode(uri.buf + u.len, uri.len - u.len, buf + n, len - n, 0) < 0) {
|
||||
return false;
|
||||
}
|
||||
buf[len - 1] = '\0'; // Double-check
|
||||
n = strlen(buf);
|
||||
if (!mg_path_is_sane(mg_str_n(buf, n))) return false;
|
||||
while (n > 1 && buf[n - 1] == '/') buf[--n] = 0; // Trim trailing slashes
|
||||
return true;
|
||||
}
|
||||
#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) {
|
||||
static int uri_to_file_status(struct mg_connection *c,
|
||||
struct mg_http_message *hm,
|
||||
struct mg_fs *fs, const char *root_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
|
||||
n = strlen(path);
|
||||
if (!mg_path_is_sane(mg_str_n(path, n))) {
|
||||
size_t n;
|
||||
if (!uri2path(root_dir, hm->uri, path, path_size)) {
|
||||
mg_http_reply(c, 400, "", "Invalid path");
|
||||
return -1;
|
||||
}
|
||||
while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes
|
||||
n = strlen(path);
|
||||
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,
|
||||
@@ -843,7 +780,7 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
|
||||
"Content-Length: 0\r\n"
|
||||
"\r\n",
|
||||
(int) hm->uri.len, hm->uri.buf);
|
||||
c->is_resp = 0;
|
||||
c->pfn_data = NULL; // Response done
|
||||
flags = -1;
|
||||
} else if (flags & MG_FS_DIR) {
|
||||
if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 &&
|
||||
@@ -865,34 +802,36 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
|
||||
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];
|
||||
struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
|
||||
const char *sp = opts->ssi_pattern;
|
||||
int flags = uri_to_path(c, hm, opts, path, sizeof(path));
|
||||
int flags = uri_to_file_status(c, hm, fs, opts->root_dir, path, sizeof(path));
|
||||
if (flags < 0) {
|
||||
// Do nothing: the response has already been sent by uri_to_path()
|
||||
// Do nothing: the response has already been sent by uri_to_file_status()
|
||||
} else if (flags & MG_FS_DIR) {
|
||||
#if MG_ENABLE_DIRLIST
|
||||
listdir(c, hm, opts, path);
|
||||
#else
|
||||
mg_http_reply(c, 403, "", "Forbidden\n");
|
||||
#endif
|
||||
if (opts->enable_dir_listing) {
|
||||
listdir(c, hm, opts, path);
|
||||
} else {
|
||||
mg_http_reply(c, 403, "", "Forbidden\n");
|
||||
}
|
||||
} else if (opts->enable_delete &&
|
||||
mg_strcasecmp(hm->method, mg_str("DELETE")) == 0) {
|
||||
mg_http_reply(c, flags && fs->rm(path) ? 200 : 404, "", "");
|
||||
} else if (opts->enable_upload &&
|
||||
(mg_strcasecmp(hm->method, mg_str("POST")) == 0 ||
|
||||
mg_strcasecmp(hm->method, mg_str("PUT")) == 0)) {
|
||||
// Small or already fully buffered body. mg_http_serve_upload() handles
|
||||
// the same thing without buffering, for big uploads, from MG_EV_HTTP_HDRS
|
||||
void *fd;
|
||||
bool ok = false;
|
||||
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
|
||||
if ((fd = fs->op(path, MG_FS_WRITE)) != NULL) {
|
||||
ok = fs->wr(fd, hm->body.buf, hm->body.len) == hm->body.len;
|
||||
fs->cl(fd);
|
||||
}
|
||||
mg_http_reply(c, ok ? 200 : 500, "", "");
|
||||
} else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) {
|
||||
mg_http_serve_ssi(c, opts->root_dir, path);
|
||||
} else {
|
||||
@@ -900,6 +839,52 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
|
||||
}
|
||||
}
|
||||
|
||||
// mg_http_stream_body() callback for mg_http_serve_upload(). The file is
|
||||
// opened by the caller before streaming starts, so this only writes and closes
|
||||
static bool serve_upload_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
struct mg_fd *fd = (struct mg_fd *) *p;
|
||||
if (hm != NULL) return fd != NULL; // Start
|
||||
if (data != NULL) { // Next chunk
|
||||
return fd != NULL && fd->fs->wr(fd->fd, data->buf, data->len) == data->len;
|
||||
}
|
||||
if (fd != NULL) mg_fs_close(fd); // End
|
||||
*p = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Starts a streaming upload for a big POST/PUT body, so it never sits fully
|
||||
// buffered in memory. Companion to mg_http_serve_dir(): call this from
|
||||
// MG_EV_HTTP_HDRS, and keep calling mg_http_serve_dir() from MG_EV_HTTP_MSG
|
||||
// as before - it handles uploads too, for bodies this function skips.
|
||||
// Does nothing unless opts->enable_upload is set, the method is POST or PUT,
|
||||
// and Content-Length is known and >= 2 * MG_IO_SIZE.
|
||||
void mg_http_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
const struct mg_http_serve_opts *opts) {
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
|
||||
if (!opts->enable_upload) {
|
||||
// Not enabled, nothing to do
|
||||
} else if (mg_strcasecmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcasecmp(hm->method, mg_str("PUT")) != 0) {
|
||||
// Not an upload request, nothing to do
|
||||
} else if (hm->body.len == (size_t) ~0 || hm->body.len < 2 * MG_IO_SIZE) {
|
||||
// Unknown length, or small enough for mg_http_serve_dir() to buffer it
|
||||
} else if (!uri2path(opts->root_dir, hm->uri, path, sizeof(path))) {
|
||||
MG_ERROR(("Invalid upload path: %.*s", (int) hm->uri.len, hm->uri.buf));
|
||||
} else if (fs->st(path, NULL, NULL) & MG_FS_DIR) {
|
||||
// Target is a directory, let mg_http_serve_dir() list it
|
||||
} else {
|
||||
struct mg_fd *fd;
|
||||
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
|
||||
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
|
||||
MG_ERROR(("Cannot open %s for upload", path));
|
||||
} else {
|
||||
mg_http_stream_body(c, hm, serve_upload_cb, fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 == '~';
|
||||
@@ -972,8 +957,14 @@ 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));
|
||||
if (mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)) == -3) {
|
||||
mg_http_reply(c, 400, "", "invalid offset");
|
||||
return -3;
|
||||
}
|
||||
if (mg_http_get_var(&hm->query, "file", file, sizeof(file)) == -3) {
|
||||
mg_http_reply(c, 400, "", "invalid file");
|
||||
return -2;
|
||||
}
|
||||
offset = strtol(buf, NULL, 0);
|
||||
mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
|
||||
if (hm->body.len == 0) {
|
||||
@@ -1016,96 +1007,106 @@ int mg_http_status(const struct mg_http_message *hm) {
|
||||
return atoi(hm->uri.buf);
|
||||
}
|
||||
|
||||
struct mg_upload_priv {
|
||||
size_t expected;
|
||||
size_t received;
|
||||
struct mg_fd *fd; // non-NULL: file upload; NULL: OTA
|
||||
void (*fn)(struct mg_connection *, const char *);
|
||||
// State of a streamed request or response body, kept in c->data. The callback
|
||||
// is kept in c->fn_data, which is free because we own the connection
|
||||
struct mg_upload_state {
|
||||
size_t expected; // Number of body bytes to receive
|
||||
size_t received; // Number of body bytes received so far
|
||||
void *user_data; // Passed to cb, free for cb to use
|
||||
bool failed; // Set when cb returned false
|
||||
bool finished; // End callback has been called
|
||||
};
|
||||
|
||||
static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (p->fn == NULL) return;
|
||||
if (ev == MG_EV_READ && c->recv.len > 0) {
|
||||
size_t alignment = 512;
|
||||
size_t left = p->expected > p->received ? p->expected - p->received : 0;
|
||||
size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment)
|
||||
: left;
|
||||
bool ok = true;
|
||||
if (aligned > 0) {
|
||||
if (p->fd != NULL) {
|
||||
ok = p->fd->fs->wr(p->fd->fd, c->recv.buf, aligned) == aligned;
|
||||
} else {
|
||||
ok = mg_ota_write(c->recv.buf, aligned);
|
||||
}
|
||||
}
|
||||
p->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned);
|
||||
if (!ok) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, "write error");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
} else if (p->received >= p->expected) {
|
||||
const char *errmsg = NULL;
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else if (!mg_ota_end()) errmsg = "OTA finalize failed";
|
||||
p->fn(c, errmsg);
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||
if (p->fd != NULL) { mg_fs_close(p->fd); p->fd = NULL; }
|
||||
else mg_ota_end();
|
||||
p->fn(c, ev == MG_EV_ERROR ? (const char *) ev_data : "connection closed");
|
||||
mg_free(c->pfn_data); c->pfn_data = NULL; p->fn = NULL;
|
||||
}
|
||||
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
||||
// callback, then replies (servers only). It does not restore the previous
|
||||
// handler: the connection is closed after the reply.
|
||||
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
||||
(ptrdiff_t) c->fn_data;
|
||||
(void) ev_data;
|
||||
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
||||
if (!us->finished) {
|
||||
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
||||
us->finished = true; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||
fn(NULL, NULL, &us->user_data);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && !c->is_draining && !us->finished) {
|
||||
if (!us->failed && c->recv.len > 0 && us->received < us->expected) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = us->expected - us->received, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
|
||||
if (data.len > 0 && !us->failed) {
|
||||
us->failed = !fn(NULL, &data, &us->user_data);
|
||||
}
|
||||
us->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
||||
}
|
||||
if (!us->finished && (us->failed || us->received >= us->expected)) {
|
||||
// Finished upload. Always call cb, even after error, so it can clean up
|
||||
us->finished = true;
|
||||
us->failed |= !fn(NULL, NULL, &us->user_data);
|
||||
if (c->is_client && us->failed) {
|
||||
mg_error(c, "Body streaming failed");
|
||||
} else if (!c->is_client) {
|
||||
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
|
||||
us->received, us->failed ? "fail" : "ok");
|
||||
}
|
||||
c->is_draining = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_upload_default_cb(struct mg_connection *c, const char *status) {
|
||||
MG_INFO(("%lu %s", c->id, status ? status : "ok"));
|
||||
mg_http_reply(c, status ? 500 : 200, "", "%s\n", status ? status : "ok");
|
||||
}
|
||||
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **),
|
||||
void *user_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
int code = 0;
|
||||
size_t head_len = hm->head.len;
|
||||
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
char path[MG_PATH_MAX];
|
||||
struct mg_fd *fd;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; }
|
||||
mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf,
|
||||
MG_DIRSEP, (int) name.len, name.buf);
|
||||
fd = mg_fs_open(fs, path, MG_FS_WRITE);
|
||||
if (fd == NULL) { fn(c, "open failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = fd;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn_data = strdup(path);
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
}
|
||||
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *)) {
|
||||
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
|
||||
if (fn == NULL) fn = mg_upload_default_cb;
|
||||
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
|
||||
if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; }
|
||||
p->expected = hm->body.len;
|
||||
p->received = 0;
|
||||
p->fd = NULL;
|
||||
p->fn = fn;
|
||||
c->fn = mg_upload_handler;
|
||||
c->pfn = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len);
|
||||
mg_call(c, MG_EV_READ, &c->recv.len);
|
||||
// Catch upload requests early, without buffering whole body. Called on the
|
||||
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
|
||||
// the full HTTP body
|
||||
if (!c->is_client && mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||
code = 405;
|
||||
} else if (mg_http_get_header(hm, "Transfer-Encoding") != NULL) {
|
||||
code = 400;
|
||||
} else if (hm->body.len == (size_t) ~0 ||
|
||||
(!c->is_client && mg_http_get_header(hm, "Content-Length") == NULL)) {
|
||||
code = 411;
|
||||
} else if (sizeof(*us) > sizeof(c->data)) {
|
||||
code = 500;
|
||||
}
|
||||
c->pfn = NULL; // Take ownership even when validation fails
|
||||
if (code != 0) {
|
||||
c->fn = NULL;
|
||||
c->fn_data = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
cb(NULL, NULL, &user_data); // Release resources even if start was skipped
|
||||
if (c->is_client) {
|
||||
mg_error(c, "%s", mg_http_status_code_str(code));
|
||||
} else {
|
||||
mg_http_reply(c, code, code == 405 ? "Allow: POST, PUT\r\n" : NULL, "");
|
||||
}
|
||||
c->is_draining = 1;
|
||||
} else {
|
||||
memset(us, 0, sizeof(*us));
|
||||
us->expected = hm->body.len; // Store number of bytes we expect
|
||||
us->user_data = user_data;
|
||||
c->fn = mg_stream_handler;
|
||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
||||
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
|
||||
if (!c->is_client && !us->failed && !us->finished &&
|
||||
mg_http_get_header(hm, "Expect") != NULL) {
|
||||
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, head_len); // Delete HTTP headers
|
||||
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_hex_digit(int c) {
|
||||
@@ -1134,7 +1135,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
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) {
|
||||
// c->pfn_data is the "response generation in progress" marker. While it
|
||||
// is set, don't parse the next pipelined request, to keep responses in order
|
||||
while (c->pfn_data == NULL && 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
|
||||
@@ -1183,8 +1186,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
// contain a Content-length header (unless the body length is 0, in
|
||||
// which case it can be omitted). Other requests can also contain a
|
||||
// body, but their content has no defined semantics (RFC 7231)
|
||||
// It is a non-chunked request, so the offset (ofs) is updated after
|
||||
// exiting this branch.
|
||||
if (hm.body.len != 0) 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.
|
||||
@@ -1226,9 +1230,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_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) {
|
||||
if (c->is_accepted) c->pfn_data = c; // Response started
|
||||
mg_call(c, MG_EV_HTTP_MSG, &hm); // Handler sets pfn_data NULL when done
|
||||
if (c->is_accepted && c->pfn_data == NULL) {
|
||||
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"
|
||||
|
||||
+50
-17
@@ -32,6 +32,9 @@ struct mg_http_serve_opts {
|
||||
const char *mime_types; // Additional MIME types: "ext1=type1,ext2=type2". NULL for defaults only
|
||||
const char *page404; // Path to a custom 404 page, e.g. "/404.html". NULL for built-in
|
||||
struct mg_fs *fs; // Filesystem to use. NULL defaults to POSIX
|
||||
bool enable_delete; // Allow to DELETE files
|
||||
bool enable_upload; // Allow to POST/PUT files
|
||||
bool enable_dir_listing; // Enable directory listing
|
||||
};
|
||||
|
||||
// A single part of a multipart/form-data body, filled by mg_http_next_multipart().
|
||||
@@ -105,8 +108,11 @@ struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url,
|
||||
// mg_http_listen(), mg_http_serve_file(), mg_http_reply(), mg_match()
|
||||
// Notes:
|
||||
// Call from an MG_EV_HTTP_MSG handler. The uri in hm is mapped under
|
||||
// opts->root_dir. Directory listing depends on MG_ENABLE_DIRLIST; SSI uses
|
||||
// opts->ssi_pattern when configured.
|
||||
// opts->root_dir. opts->enable_dir_listing enables directory listing; SSI
|
||||
// uses opts->ssi_pattern when configured. opts->enable_delete enables
|
||||
// DELETE; opts->enable_upload enables POST/PUT, writing the whole body to
|
||||
// a file - see mg_http_serve_upload() to also stream big uploads without
|
||||
// buffering.
|
||||
void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
|
||||
const struct mg_http_serve_opts *);
|
||||
|
||||
@@ -114,6 +120,15 @@ void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
|
||||
void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm,
|
||||
const char *path, const struct mg_http_serve_opts *);
|
||||
|
||||
// Streams a big POST/PUT upload straight to a file, instead of buffering the
|
||||
// whole body. Optional companion to mg_http_serve_dir(): call this from
|
||||
// MG_EV_HTTP_HDRS with the same opts, and keep calling mg_http_serve_dir()
|
||||
// from MG_EV_HTTP_MSG as usual - it still handles uploads this function skips.
|
||||
// Does nothing unless opts->enable_upload is set, the method is POST or PUT,
|
||||
// and Content-Length is known and big enough.
|
||||
void mg_http_serve_upload(struct mg_connection *, struct mg_http_message *hm,
|
||||
const struct mg_http_serve_opts *);
|
||||
|
||||
// Sends a complete HTTP response with Content-Length.
|
||||
//
|
||||
// Example:
|
||||
@@ -160,20 +175,6 @@ 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);
|
||||
|
||||
// Streams the raw request body into dir/name via fs. Non-blocking: installs an
|
||||
// internal handler and returns immediately. fn is called with NULL on success
|
||||
// or an error string on failure.
|
||||
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_str name, struct mg_str dir,
|
||||
struct mg_fs *fs,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Starts an OTA firmware update from an HTTP POST upload.
|
||||
// Calls mg_ota_begin/write/end internally; fn is called with NULL on success,
|
||||
// or an error string on failure.
|
||||
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void (*fn)(struct mg_connection *, const char *));
|
||||
|
||||
// Sends a 401 Unauthorized response with a Basic Auth WWW-Authenticate challenge.
|
||||
void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
|
||||
|
||||
@@ -192,7 +193,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
|
||||
// Full examples:
|
||||
// tutorials/http/file-upload-html-form, tutorials/http/http-server
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_start_upload()
|
||||
// mg_http_listen(), mg_http_stream_body()
|
||||
// Notes:
|
||||
// Call from an MG_EV_HTTP_MSG handler after the full request body is
|
||||
// received. part.name, part.filename, and part.body are zero-copy slices into
|
||||
@@ -201,3 +202,35 @@ size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
|
||||
|
||||
// Returns the HTTP status code from a parsed response message (e.g. 200, 404).
|
||||
int mg_http_status(const struct mg_http_message *hm);
|
||||
|
||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
||||
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||
// with mg_match(). Always takes over the connection. Invalid requests receive
|
||||
// an HTTP error; client-side failures call mg_error(). Replies and connection
|
||||
// draining are handled internally, including callback failures.
|
||||
//
|
||||
// Callback upload_fn(hm, data, user_data) is called:
|
||||
// - at start: hm is set. Open the file, set *user_data.
|
||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||
// again after a failure.
|
||||
// - at end: both NULL. Always called exactly once, including validation
|
||||
// failures before start and connections that drop early. Release resources
|
||||
// here. A truncated body looks like a complete one: validate before
|
||||
// committing.
|
||||
// The callback returns true on success, false on error. A failure stops
|
||||
// streaming immediately and calls the end callback before closing.
|
||||
// Example:
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL);
|
||||
// }
|
||||
// Full example:
|
||||
// tutorials/http/file-upload-single-post
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_next_multipart()
|
||||
// Notes:
|
||||
// The existing event handler is no longer called. hm is invalid after this
|
||||
// call; c->data and c->fn_data are reserved for streaming state.
|
||||
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data),
|
||||
void *user_data);
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "util.h"
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN
|
||||
#include "tls_uecc.h"
|
||||
#include "tls_p256.h"
|
||||
#endif
|
||||
|
||||
static size_t jwt_split(struct mg_str jwt, struct mg_str *h, struct mg_str *p,
|
||||
@@ -95,39 +95,14 @@ size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
|
||||
}
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN
|
||||
typedef struct {
|
||||
MG_UECC_HashContext uECC;
|
||||
mg_sha256_ctx sha256;
|
||||
} jwt_hash_ctx;
|
||||
|
||||
static void jwt_hash_init(const MG_UECC_HashContext *ctx) {
|
||||
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
|
||||
mg_sha256_init(&c->sha256);
|
||||
}
|
||||
|
||||
static void jwt_hash_update(const MG_UECC_HashContext *ctx, const uint8_t *msg,
|
||||
unsigned len) {
|
||||
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
|
||||
mg_sha256_update(&c->sha256, msg, len);
|
||||
}
|
||||
|
||||
static void jwt_hash_finish(const MG_UECC_HashContext *ctx, uint8_t *hash) {
|
||||
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
|
||||
mg_sha256_final(hash, &c->sha256);
|
||||
}
|
||||
|
||||
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
|
||||
size_t len) {
|
||||
uint8_t hash[32], sig[64], tmp[2 * 32 + 64];
|
||||
jwt_hash_ctx ctx = {
|
||||
{jwt_hash_init, jwt_hash_update, jwt_hash_finish, 64, 32, tmp},
|
||||
{{0}, 0, 0, {0}}};
|
||||
uint8_t hash[32], sig[64];
|
||||
size_t n = jwt_header_payload("ES256", opts, buf, len);
|
||||
size_t m;
|
||||
if (n == 0 || n + 1 >= len) return 0;
|
||||
mg_sha256(hash, (uint8_t *) buf, n);
|
||||
if (mg_uecc_sign_deterministic(opts->private_key, hash, sizeof(hash),
|
||||
&ctx.uECC, sig, mg_uecc_secp256r1()) != 1)
|
||||
if (mg_p256_sign(opts->private_key, hash, sizeof(hash), sig) != 1)
|
||||
return 0;
|
||||
buf[n++] = '.';
|
||||
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - n);
|
||||
@@ -143,8 +118,7 @@ size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
|
||||
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) != 64)
|
||||
return 0;
|
||||
mg_sha256(hash, (uint8_t *) jwt.buf, n);
|
||||
if (mg_uecc_verify(opts->public_key, hash, sizeof(hash), sig,
|
||||
mg_uecc_secp256r1()) != 1)
|
||||
if (mg_p256_verify(opts->public_key, hash, sizeof(hash), sig) != 1)
|
||||
return 0;
|
||||
return mg_base64url_decode(p.buf, p.len, buf, len);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "l2.h"
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
|
||||
// Radio frame: 802.15.4 MAC header, 15 bytes: data frame, PAN ID compression,
|
||||
// broadcast destination, our EUI-64 made from MAC as source. Then 2 bytes:
|
||||
// tag, fragment index << 4 | last fragment index. Then data. 125 bytes max,
|
||||
// 127 minus FCS
|
||||
#define MGI_802154_HDR 17
|
||||
#define MGI_802154_DATA (125 - MGI_802154_HDR)
|
||||
|
||||
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
|
||||
size_t (*send_fn)(const uint8_t *, size_t,
|
||||
struct mg_tcpip_if *)) {
|
||||
struct mg_l2_802154_data *d =
|
||||
(struct mg_l2_802154_data *) ifp->driver_data;
|
||||
const uint8_t *p = (const uint8_t *) buf, *m = ifp->mac;
|
||||
uint8_t f[MGI_802154_HDR + MGI_802154_DATA];
|
||||
size_t i, n, last = (len - 1) / MGI_802154_DATA;
|
||||
if (len == 0 || last > 15) return 0;
|
||||
d->tag++;
|
||||
for (i = 0; i <= last; i++) {
|
||||
n = i == last ? len - i * MGI_802154_DATA : MGI_802154_DATA;
|
||||
f[0] = 0x41, f[1] = 0xd8, f[2] = d->seq++; // 2006 frame, see above
|
||||
f[3] = (uint8_t) d->pan_id, f[4] = (uint8_t) (d->pan_id >> 8);
|
||||
f[5] = f[6] = 0xff;
|
||||
f[7] = m[5], f[8] = m[4], f[9] = m[3], f[10] = 0xfe, f[11] = 0xff;
|
||||
f[12] = m[2], f[13] = m[1], f[14] = m[0]; // Little endian EUI-64
|
||||
f[15] = d->tag, f[16] = (uint8_t) ((i << 4) | last);
|
||||
memcpy(f + MGI_802154_HDR, p + i * MGI_802154_DATA, n);
|
||||
if (send_fn(f, MGI_802154_HDR + n, ifp) == 0) return 0;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n,
|
||||
void *buf, size_t len) {
|
||||
struct mg_l2_802154_data *d =
|
||||
(struct mg_l2_802154_data *) ifp->driver_data;
|
||||
size_t idx, last;
|
||||
if (n <= MGI_802154_HDR || f[0] != 0x41 || f[1] != 0xd8 ||
|
||||
f[3] != (uint8_t) d->pan_id || f[4] != (uint8_t) (d->pan_id >> 8))
|
||||
return 0; // Not ours
|
||||
idx = f[16] >> 4, last = f[16] & 15;
|
||||
if (idx == 0) d->rxtag = f[15], d->rxnext = 0, d->rxlen = 0;
|
||||
if (idx == 0) memcpy(d->rxsrc, f + 7, sizeof(d->rxsrc));
|
||||
n -= MGI_802154_HDR;
|
||||
if (idx != d->rxnext || idx > last || f[15] != d->rxtag ||
|
||||
memcmp(d->rxsrc, f + 7, sizeof(d->rxsrc)) != 0 ||
|
||||
d->rxlen + n > sizeof(d->rxbuf))
|
||||
return 0; // Missed a fragment, or from another sender
|
||||
memcpy(d->rxbuf + d->rxlen, f + MGI_802154_HDR, n);
|
||||
d->rxlen = (uint16_t) (d->rxlen + n), d->rxnext++;
|
||||
if (idx < last || d->rxlen > len) return 0;
|
||||
memcpy(buf, d->rxbuf, d->rxlen);
|
||||
return d->rxlen;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -135,6 +135,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
|
||||
}
|
||||
pay->buf = ((char *) eth) + hdrlen;
|
||||
pay->len = len - hdrlen;
|
||||
if (len > ifp->framesize) return false; // Oversized
|
||||
for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) {
|
||||
if (type == eth_types[i]) break;
|
||||
}
|
||||
|
||||
+4
-1
@@ -101,7 +101,7 @@ static uint16_t s_id;
|
||||
|
||||
void mg_l2_ppp_init(struct mg_tcpip_if *ifp) {
|
||||
ifp->l2mtu = 1500;
|
||||
ifp->framesize = 1500 + sizeof(struct ppp) + sizeof(struct hdlc_);
|
||||
ifp->framesize = ifp->l2mtu + sizeof(struct ppp) + sizeof(struct hdlc_) + 2;
|
||||
}
|
||||
|
||||
extern void mg_l2_eth_init(struct mg_tcpip_if *);
|
||||
@@ -466,6 +466,7 @@ bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
|
||||
pay->buf = (char *) raw->buf;
|
||||
pay->len = raw->len - 2;
|
||||
}
|
||||
if (pay->len > ifp->l2mtu + sizeof(struct ppp)) return false; // Oversized
|
||||
return ppp_rx(ifp, proto, pay, raw);
|
||||
}
|
||||
|
||||
@@ -548,6 +549,8 @@ bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
|
||||
if (!mg_l2_eth_rx(ifp, ð_proto, pay, raw)) return false;
|
||||
pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw
|
||||
if (pay->len < sizeof(*pppoe)) return false; // Truncated
|
||||
if (pay->len - sizeof(*pppoe) > ifp->l2mtu + sizeof(struct ppp))
|
||||
return false; // Oversized
|
||||
if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) {
|
||||
MG_VERBOSE(("PPPoE_DISC"));
|
||||
if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO &&
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
|
||||
#include "flash.h"
|
||||
#include "util.h"
|
||||
#include "lfs.h"
|
||||
|
||||
#if MG_ENABLE_LFS
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <littlefs/lfs.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef LFS_USE_RAM
|
||||
#define LFS_USE_RAM 0 // If 1, use RAM FS. If 0, use flash
|
||||
#endif
|
||||
|
||||
#ifndef MG_LFS_SIZE
|
||||
#define MG_LFS_SIZE (64 * 1024)
|
||||
#endif
|
||||
|
||||
#ifndef MG_LFS_BUF_SIZE
|
||||
#define MG_LFS_BUF_SIZE 64 // Buffer size used for reads, writes, and cache
|
||||
#endif
|
||||
@@ -28,19 +27,6 @@
|
||||
#define MG_LFS_BLOCK_SIZE 8192
|
||||
#endif
|
||||
|
||||
#ifndef DT_DIR
|
||||
#define DT_DIR 4
|
||||
#endif
|
||||
#ifndef DT_REG
|
||||
#define DT_REG 8
|
||||
#endif
|
||||
|
||||
typedef struct mg_lfs_fd DIR;
|
||||
struct dirent {
|
||||
char d_name[LFS_NAME_MAX + 1];
|
||||
unsigned char d_type;
|
||||
};
|
||||
|
||||
struct mg_lfs_fd {
|
||||
struct mg_lfs_fd *next;
|
||||
bool isopen;
|
||||
@@ -52,7 +38,6 @@ struct mg_lfs_fd {
|
||||
static lfs_t s_lfs;
|
||||
static struct mg_lfs_fd *s_fds;
|
||||
static uint8_t *s_fs;
|
||||
static int s_next_fd = MG_LFS_FD_BASE;
|
||||
static bool s_lfs_ready;
|
||||
#if LFS_USE_RAM
|
||||
static uint8_t *s_ram_fs;
|
||||
@@ -110,29 +95,50 @@ static int lfs_driver_sync(const struct lfs_config *cfg) {
|
||||
}
|
||||
|
||||
bool mg_lfs_init(size_t size) {
|
||||
int result = 0;
|
||||
//bool ok = false;
|
||||
MG_DEBUG(("size: %zu", size));
|
||||
if (s_lfs_ready) return true;
|
||||
if (size == 0) return false;
|
||||
if (size == 0) {
|
||||
MG_ERROR(("invalidsize = 0"));
|
||||
} else {
|
||||
#if LFS_USE_RAM
|
||||
s_cfg.block_size = MG_LFS_BLOCK_SIZE;
|
||||
if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size);
|
||||
s_fs = s_ram_fs;
|
||||
s_cfg.block_size = MG_LFS_BLOCK_SIZE;
|
||||
if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size);
|
||||
s_fs = s_ram_fs;
|
||||
#else
|
||||
if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) return false;
|
||||
if (mg_flash->write_fn == NULL) return false;
|
||||
s_cfg.block_size = mg_flash->secsz;
|
||||
s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size;
|
||||
{
|
||||
// Some flash attributes may be initialised on ota_begin
|
||||
int lev = mg_log_level;
|
||||
mg_log_level = MG_LL_NONE;
|
||||
mg_ota_begin(1);
|
||||
mg_ota_end();
|
||||
mg_log_set(lev);
|
||||
}
|
||||
if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) {
|
||||
MG_ERROR(("mg_flash %p secsz %zu flash_size %zu < size %zu", mg_flash, mg_flash->secsz, mg_flash->size, size));
|
||||
} else if (mg_flash->write_fn == NULL) {
|
||||
MG_ERROR(("flash write_fn == NULL"));
|
||||
} else {
|
||||
s_cfg.block_size = mg_flash->secsz;
|
||||
s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size;
|
||||
}
|
||||
#endif
|
||||
if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) {
|
||||
return false;
|
||||
if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) {
|
||||
MG_ERROR(("s_fs %p blk_size %zu size %zu", s_fs, s_cfg.block_size));
|
||||
} else {
|
||||
s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size);
|
||||
if (s_cfg.block_count == 0) {
|
||||
MG_ERROR(("blk_count == 0"));
|
||||
} else {
|
||||
if (lfs_mount(&s_lfs, &s_cfg) != 0) lfs_format(&s_lfs, &s_cfg);
|
||||
if (lfs_mount(&s_lfs, &s_cfg) != 0) {
|
||||
MG_ERROR(("lfs_mount"));
|
||||
} else {
|
||||
s_lfs_ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size);
|
||||
if (s_cfg.block_count == 0) return false;
|
||||
if (lfs_mount(&s_lfs, &s_cfg) != 0) {
|
||||
lfs_format(&s_lfs, &s_cfg);
|
||||
if (lfs_mount(&s_lfs, &s_cfg) != 0) result = -1;
|
||||
}
|
||||
s_lfs_ready = result == 0;
|
||||
return s_lfs_ready;
|
||||
}
|
||||
|
||||
@@ -145,16 +151,12 @@ static struct mg_lfs_fd *find_fd(int fd) {
|
||||
}
|
||||
|
||||
static struct mg_lfs_fd *open_fd(void) {
|
||||
struct mg_lfs_fd *f = NULL;
|
||||
if (s_lfs_ready) {
|
||||
f = (struct mg_lfs_fd *) mg_calloc(1, sizeof(*f));
|
||||
if (f != NULL) {
|
||||
f->isopen = true;
|
||||
f->fd = s_next_fd++;
|
||||
f->next = s_fds;
|
||||
s_fds = f;
|
||||
if (s_next_fd < MG_LFS_FD_BASE) s_next_fd = MG_LFS_FD_BASE;
|
||||
}
|
||||
struct mg_lfs_fd *f = NULL, *p = s_fds;
|
||||
if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE);
|
||||
if (s_lfs_ready && (f = (struct mg_lfs_fd *) mg_calloc(1, sizeof(*f)))) {
|
||||
f->isopen = true, f->fd = MG_LFS_FD_BASE, f->next = s_fds;
|
||||
while (p != NULL) p = p->fd == f->fd ? (f->fd++, s_fds) : p->next;
|
||||
s_fds = f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
@@ -173,6 +175,7 @@ int _open(const char *path, int flags, mode_t mode) {
|
||||
int err, lfs_flags = 0, fd = -1;
|
||||
struct mg_lfs_fd *f = open_fd();
|
||||
(void) mode;
|
||||
MG_DEBUG(("%s -> %p, lfs_ready %d", path, f, s_lfs_ready));
|
||||
if (f == NULL) return -1;
|
||||
fd = f->fd;
|
||||
if ((flags & 3) == O_RDONLY) lfs_flags |= LFS_O_RDONLY;
|
||||
@@ -214,11 +217,21 @@ int _lseek(int fd, int offset, int whence) {
|
||||
}
|
||||
|
||||
int _rename(const char *oldname, const char *newname) {
|
||||
if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE);
|
||||
return s_lfs_ready ? lfs_rename(&s_lfs, oldname, newname) : -1;
|
||||
}
|
||||
|
||||
int _unlink_r(void *r, const char *a) {
|
||||
(void) r;
|
||||
// Some libc rename() implementations have no atomic rename syscall and emulate
|
||||
// it as link() + unlink() instead of calling _rename() directly. LittleFS has
|
||||
// no real hard links, but forwarding to _rename() gives the same end result
|
||||
// (oldname becomes newname), which is all mg_file_write()'s atomic-write
|
||||
// pattern (write to a temp file, then rename it into place) needs
|
||||
int _link(const char *oldname, const char *newname) {
|
||||
return _rename(oldname, newname);
|
||||
}
|
||||
|
||||
int _unlink(const char *a) {
|
||||
if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE);
|
||||
return s_lfs_ready ? lfs_remove(&s_lfs, a) : -1;
|
||||
}
|
||||
|
||||
@@ -227,7 +240,7 @@ DIR *opendir(const char *name) {
|
||||
if (f == NULL) return NULL;
|
||||
if (lfs_dir_open(&s_lfs, &f->dir, name) != 0) {
|
||||
close_fd(f);
|
||||
return NULL;
|
||||
f = NULL;
|
||||
}
|
||||
return (DIR *) f;
|
||||
}
|
||||
@@ -254,7 +267,9 @@ struct dirent *readdir(DIR *dir) {
|
||||
|
||||
int _stat(const char *path, struct stat *st) {
|
||||
struct lfs_info info;
|
||||
if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE);
|
||||
if (!s_lfs_ready || lfs_stat(&s_lfs, path, &info) != 0) return -1;
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_mode = info.type == LFS_TYPE_DIR ? S_IFDIR : S_IFREG;
|
||||
st->st_size = info.size;
|
||||
return 0;
|
||||
@@ -268,7 +283,7 @@ int _fstat(int fd, struct stat *st) {
|
||||
|
||||
int mkdir(const char *path, mode_t mode) {
|
||||
(void) mode;
|
||||
if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE);
|
||||
return s_lfs_ready ? lfs_mkdir(&s_lfs, path) : -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_LFS
|
||||
#include <fcntl.h>
|
||||
#include <lfs.h>
|
||||
|
||||
#ifndef DT_DIR
|
||||
#define DT_DIR 4
|
||||
#endif
|
||||
#ifndef DT_REG
|
||||
#define DT_REG 8
|
||||
#endif
|
||||
|
||||
struct dirent {
|
||||
char d_name[LFS_NAME_MAX + 1];
|
||||
unsigned char d_type;
|
||||
};
|
||||
|
||||
typedef struct mg_lfs_fd DIR;
|
||||
#endif
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
#include "mdash.h"
|
||||
|
||||
#if MG_ENABLE_MDASH
|
||||
|
||||
#include "base64.h"
|
||||
#include "health.h"
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
#include "printf.h"
|
||||
#include "rpc.h"
|
||||
#include "sntp.h"
|
||||
#include "tls.h"
|
||||
#include "url.h"
|
||||
#include "ws.h"
|
||||
|
||||
static const char *mg_mdash_ca_pem =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
|
||||
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
|
||||
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
|
||||
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
|
||||
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
|
||||
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
|
||||
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
|
||||
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
|
||||
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
|
||||
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
|
||||
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
|
||||
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
|
||||
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
|
||||
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
|
||||
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
|
||||
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
|
||||
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
|
||||
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
|
||||
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
|
||||
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
|
||||
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
|
||||
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
|
||||
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
|
||||
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
|
||||
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
|
||||
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
|
||||
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
|
||||
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
|
||||
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static struct mg_connection *mg_mdash_conn = NULL;
|
||||
// static struct mg_str mg_mdash_key;
|
||||
// static void mg_connection *mg_mdash_conn = NULL;
|
||||
|
||||
static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
// c->is_hexdumping = true;
|
||||
if (mg_url_is_ssl(MG_MDASH_URL)) {
|
||||
struct mg_str host = mg_url_host(MG_MDASH_URL);
|
||||
struct mg_tls_opts opts;
|
||||
memset(&opts, 0, sizeof(struct mg_tls_opts));
|
||||
opts.ca = mg_str(mg_mdash_ca_pem);
|
||||
opts.name = host;
|
||||
mg_tls_init(c, &opts);
|
||||
}
|
||||
}
|
||||
if (ev == MG_EV_WS_OPEN) {
|
||||
MG_INFO(("Connected to %s", MG_MDASH_URL));
|
||||
// s_mDashState = MDASH_STATE_CLOUD_CONNECTED;
|
||||
// mDashTriggerEvent(s_mDashState, NULL);
|
||||
} else if (ev == MG_EV_WS_MSG) {
|
||||
struct mg_iobuf io = {0, 0, 0, 512};
|
||||
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
|
||||
struct mg_str s = wm->data;
|
||||
struct mg_rpc_req r;
|
||||
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
|
||||
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
|
||||
memset(&r, 0, sizeof(struct mg_rpc_req));
|
||||
r.head = &c->mgr->rpcs;
|
||||
r.rpc = NULL;
|
||||
r.pfn = mg_pfn_iobuf;
|
||||
r.pfn_data = &io;
|
||||
r.req_data = NULL;
|
||||
r.frame = s;
|
||||
mg_rpc_process(&r);
|
||||
if (io.buf != NULL) {
|
||||
MG_INFO((" %s", io.buf));
|
||||
mg_ws_send(c, (const char *) io.buf, io.len, WEBSOCKET_OP_TEXT);
|
||||
} else {
|
||||
MG_DEBUG(("%s", "RPC unhandled"));
|
||||
}
|
||||
mg_iobuf_free(&io);
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
mg_mdash_conn = NULL;
|
||||
MG_INFO(("disconnected, ram %lu", 0));
|
||||
}
|
||||
(void) c;
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
|
||||
const char *reset_reason = mg_health_reason_str(mg_health_reason());
|
||||
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:%M}", //
|
||||
MG_ESC("fw_version"), MG_ESC(MG_FIRMWARE_VERSION), //
|
||||
MG_ESC("uptime"), (uint64_t) (mg_millis() / 1000), //
|
||||
MG_ESC("reboot_reason"), MG_ESC(reset_reason), //
|
||||
MG_ESC("arch"), MG_ARCH, //
|
||||
MG_ESC("crash"), mg_print_crash_record);
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
|
||||
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
|
||||
if (mg_ota_begin(total)) {
|
||||
mg_rpc_ok(r, "true");
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
|
||||
int len = 0, ofs = 0;
|
||||
if ((ofs = mg_json_get(r->frame, "$.params.data", &len)) <= 0 ||
|
||||
r->frame.buf[ofs] != '"') {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
|
||||
} else {
|
||||
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
|
||||
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
|
||||
if (n == 0) {
|
||||
mg_rpc_ok(r, "%m", MG_ESC("finished"));
|
||||
mg_ota_end();
|
||||
} else if (mg_ota_write(s.buf, n)) {
|
||||
mg_rpc_ok(r, "{%m:%d}", MG_ESC("queued"), n);
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("write failed"));
|
||||
mg_ota_end();
|
||||
}
|
||||
// mDashNotify("State.Set", "{%m:{%m:%d}}", MG_ESC("ota"),
|
||||
// MG_ESC("percent"), (int) (s_ota_written * 100 / s_ota_size));
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
|
||||
if (mg_ota_end()) {
|
||||
mg_rpc_ok(r, "true");
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||
}
|
||||
// mDashNotify("State.Set", "{%m:{%m:null}}", MG_ESC("ota"),
|
||||
// MG_ESC("percent"));
|
||||
}
|
||||
|
||||
void mg_mdash_init(struct mg_mgr *mgr) {
|
||||
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
|
||||
}
|
||||
|
||||
void mg_mdash_poll(struct mg_mgr *mgr) {
|
||||
#if MG_TLS == MG_TLS_NONE
|
||||
(void) mgr, (void) mg_mdash_fn; // mdash needs wss://, nothing to do
|
||||
#else
|
||||
static uint64_t timer = 0;
|
||||
// No RPC handlers means mdash was never set up, or mg_mdash_free() ran.
|
||||
// No key means mdash would reject us, so do not connect at all
|
||||
if (mg_mdash_conn == NULL && mgr->rpcs != NULL && MG_MDASH_KEY[0] != '\0' &&
|
||||
mg_timer_expired(&timer, 1000, mg_millis())) {
|
||||
MG_INFO(("Connecting to %s", MG_MDASH_URL));
|
||||
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
|
||||
"Origin: %s\r\n"
|
||||
"Authorization: Bearer %s\r\n",
|
||||
MG_MDASH_URL, MG_MDASH_KEY);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void mg_mdash_free(struct mg_mgr *mgr) {
|
||||
mg_rpc_del(&mgr->rpcs, NULL);
|
||||
}
|
||||
|
||||
#else
|
||||
void mg_mdash_init(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
}
|
||||
void mg_mdash_poll(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
}
|
||||
void mg_mdash_free(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
}
|
||||
#endif
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Cloud connector API to https://mdash.net - device management cloud
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net.h"
|
||||
|
||||
#ifndef MG_MDASH_URL
|
||||
#define MG_MDASH_URL "wss://mdash.net/api/v2/rpc"
|
||||
#endif
|
||||
|
||||
#ifndef MG_MDASH_KEY
|
||||
#define MG_MDASH_KEY ""
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_MDASH
|
||||
#define MG_ENABLE_MDASH 0
|
||||
#endif
|
||||
|
||||
void mg_mdash_init(struct mg_mgr *);
|
||||
void mg_mdash_poll(struct mg_mgr *);
|
||||
void mg_mdash_free(struct mg_mgr *);
|
||||
+10
@@ -438,6 +438,16 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
|
||||
case MQTT_CMD_CONNACK:
|
||||
if (end - p < 2) return MQTT_MALFORMED;
|
||||
m->ack = p[1];
|
||||
p += 2;
|
||||
if (version == 5) {
|
||||
if (p >= end) return MQTT_MALFORMED;
|
||||
len_len = (uint32_t) decode_varint(p, (size_t) (end - p), &tmp);
|
||||
if (!len_len) return MQTT_MALFORMED;
|
||||
m->props_size = (size_t) tmp;
|
||||
m->props_start = (size_t) (p + len_len - buf);
|
||||
p += len_len + m->props_size;
|
||||
if (p > end) return MQTT_MALFORMED;
|
||||
}
|
||||
break;
|
||||
case MQTT_CMD_PUBACK:
|
||||
case MQTT_CMD_PUBREC:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "net.h"
|
||||
#include "dns.h"
|
||||
#include "fmt.h"
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
#include "mdash.h"
|
||||
#include "net.h"
|
||||
#include "printf.h"
|
||||
#include "profile.h"
|
||||
#include "timer.h"
|
||||
@@ -39,10 +39,10 @@ bool mg_dscp(struct mg_connection *c, uint8_t dscp) {
|
||||
int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS;
|
||||
c->dscp = (unsigned) (dscp & 63);
|
||||
#if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP
|
||||
if (c->loc.is_ip6)
|
||||
level = IPPROTO_IPV6, optname = IPV6_TCLASS;
|
||||
if (c->loc.is_ip6) level = IPPROTO_IPV6, optname = IPV6_TCLASS;
|
||||
#endif
|
||||
return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname, (char *) &tos, sizeof(tos)) == 0;
|
||||
return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname,
|
||||
(char *) &tos, sizeof(tos)) == 0;
|
||||
#endif
|
||||
#elif MG_ENABLE_TCPIP
|
||||
c->dscp = (unsigned) (dscp & 63);
|
||||
@@ -163,10 +163,13 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mg_aton_(struct mg_str str, struct mg_addr *addr);
|
||||
bool mg_aton_(struct mg_str str, struct mg_addr *addr) {
|
||||
return mg_aton4(str, addr) || mg_aton6(str, addr);
|
||||
}
|
||||
|
||||
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);
|
||||
return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton_(str, addr);
|
||||
}
|
||||
|
||||
struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
|
||||
@@ -296,6 +299,9 @@ void mg_mgr_free(struct mg_mgr *mgr) {
|
||||
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;
|
||||
#if MG_ENABLE_MDASH
|
||||
mg_mdash_free(mgr); // Before the final poll, so mdash does not reconnect
|
||||
#endif
|
||||
mg_mgr_poll(mgr, 0);
|
||||
#if MG_ENABLE_FREERTOS_TCP
|
||||
FreeRTOS_DeleteSocketSet(mgr->ss);
|
||||
@@ -340,13 +346,18 @@ void mg_mgr_init(struct mg_mgr *mgr) {
|
||||
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_DEBUG(("MG_IO_SIZE: %lu, TLS: %s, conn size: %zu, mgr size: %zu",
|
||||
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"));
|
||||
: "custom",
|
||||
sizeof(struct mg_connection), sizeof(struct mg_mgr)));
|
||||
#if MG_ENABLE_MDASH
|
||||
mg_mdash_init(mgr);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
|
||||
@@ -50,6 +50,7 @@ struct mg_mgr {
|
||||
struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer
|
||||
size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection
|
||||
union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup()
|
||||
struct mg_rpc *rpcs; // Linked list of registered RPC handlers
|
||||
#if MG_ENABLE_FREERTOS_TCP
|
||||
SocketSet_t ss; // FreeRTOS-TCP socket set
|
||||
#endif
|
||||
@@ -91,12 +92,12 @@ struct mg_connection {
|
||||
unsigned is_mqtt5 : 1; // MQTT5 connection (set by mg_mqtt_login)
|
||||
unsigned is_hexdumping : 1; // Log hex dump of all in/out traffic
|
||||
unsigned is_draining : 1; // Flush send buffer, then close
|
||||
unsigned is_closing : 1; // Close and free immediately on next poll
|
||||
unsigned is_closing : 1; // Request Close and free on next poll
|
||||
unsigned is_full : 1; // Pause incoming reads until cleared
|
||||
unsigned is_tls_throttled : 1; // TLS write was throttled; retry pending
|
||||
unsigned is_resp : 1; // HTTP: response is still being generated
|
||||
unsigned is_readable : 1; // Socket is ready to read (epoll/select)
|
||||
unsigned is_writable : 1; // Socket is ready to write (epoll/select)
|
||||
unsigned is_bsd : 1; // Built-in TCP/IP stack BSD socket
|
||||
};
|
||||
|
||||
// Runs one iteration of the event loop.
|
||||
|
||||
+347
-150
@@ -1,4 +1,5 @@
|
||||
#include "net_builtin.h"
|
||||
#include "bsd.h"
|
||||
#include "profile.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
@@ -13,19 +14,34 @@
|
||||
#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
|
||||
#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1)
|
||||
#define MG_TCPIP_RTX_MAX_MS 60000 // Minimum permitted RTO cap (RFC-6298, 2.5)
|
||||
#define MG_TCPIP_RTX_R1 3 // Delivery-problem threshold (RFC-9293, 3.8.3)
|
||||
// No R2: MG_TCPIP_KEEPALIVE_MS eventually closes non-responsive peers
|
||||
|
||||
#ifndef MG_TCPIP_WIN
|
||||
#define MG_TCPIP_WIN 6000 // TCP window size
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_MIN_MSS
|
||||
#define MG_TCPIP_MIN_MSS 64 // Minimum peer MSS
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_TXQ_ALIGN
|
||||
#define MG_TCPIP_TXQ_ALIGN 1024 // Retransmit queue allocation quantum
|
||||
#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
|
||||
uint32_t maxseq; // Max send seq (ack + window)
|
||||
uint16_t win; // destination current window size
|
||||
uint16_t dmss; // destination MSS (from TCP opts)
|
||||
uint32_t seq, ack; // TCP seq/ack counters
|
||||
uint64_t timer; // TCP timer (see 'ttype' below)
|
||||
uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5)
|
||||
uint32_t acked; // Last ACK-ed number
|
||||
size_t unacked; // Not acked bytes
|
||||
uint32_t maxseq; // Max send seq (ack + window)
|
||||
uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8)
|
||||
uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4)
|
||||
uint16_t win; // destination current window size
|
||||
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
|
||||
@@ -34,8 +50,14 @@ struct connstate {
|
||||
#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
|
||||
uint8_t txq_retries; // Retransmissions since ACK progress (RFC-9293, 3.8.3)
|
||||
bool fin_rcvd; // We have received FIN from the peer
|
||||
bool twclosure; // 3-way closure done
|
||||
bool retransmit; // Retain sent data until acknowledged
|
||||
bool is_full; // Advertise a zero receive window
|
||||
bool is_closing; // Finished, remove connection (TCP after FIN)
|
||||
bool send_rst; // Send RST before removing a TCP connection
|
||||
struct mg_iobuf txq; // Length-prefixed sent TCP segments (RFC-9293, 3.8)
|
||||
};
|
||||
|
||||
#if defined(__DCC__)
|
||||
@@ -291,8 +313,10 @@ static bool tcpcsum_ok(void *d, void *t) {
|
||||
0);
|
||||
}
|
||||
|
||||
#define MG_IPISLOCAL(i, a) ((a & i->mask) == (i->ip & i->mask))
|
||||
|
||||
#if MG_ENABLE_IPV6
|
||||
static uint16_t p6csum(void *d, void *p, size_t plen) {
|
||||
static uint16_t p6csum(void *d, void *p, uint8_t proto, size_t plen) {
|
||||
uint32_t sum;
|
||||
struct ip6 *ip6 = (struct ip6 *) d;
|
||||
#if defined(__DCC__)
|
||||
@@ -303,7 +327,7 @@ static uint16_t p6csum(void *d, void *p, size_t plen) {
|
||||
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;
|
||||
pip6.next = proto;
|
||||
sum = csumup(0, &pip6, sizeof(pip6)); // even length
|
||||
sum = csumup(sum, p, plen); // possibly odd length: last
|
||||
return csumfin(sum);
|
||||
@@ -313,17 +337,15 @@ 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);
|
||||
return (p6csum(d, u, 17, (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 bool tcp6csum_ok(void *d, void *t, size_t len) {
|
||||
return p6csum(d, t, 6, len) == 0;
|
||||
}
|
||||
|
||||
static bool icmp6csum_ok(void *d, void *i, size_t len) {
|
||||
return p6csum(d, i, 58, len) == 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
|
||||
@@ -401,12 +423,13 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
|
||||
ifp->is_ip_changed = false;
|
||||
}
|
||||
} else if (ifp->state == MG_TCPIP_STATE_IP) {
|
||||
if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH)
|
||||
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request
|
||||
if (ifp->l2type == MG_TCPIP_L2_ETH) {
|
||||
ifp->gw_ready = false; // remove any prior state
|
||||
if (ifp->gw != 0) // send an unsolicited GW ARP request
|
||||
mg_tcpip_arp_request(ifp, ifp->gw, NULL);
|
||||
}
|
||||
} 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_STATE_CHANGE, &ifp->state);
|
||||
}
|
||||
@@ -468,7 +491,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
|
||||
memmove(udp + 1, buf, len);
|
||||
#if MG_ENABLE_IPV6
|
||||
if (ip_dst->is_ip6) {
|
||||
udp->csum = p6csum(ip6, udp, sizeof(*udp) + len);
|
||||
udp->csum = p6csum(ip6, udp, 17, sizeof(*udp) + len);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
@@ -655,6 +678,7 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
}
|
||||
|
||||
static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip);
|
||||
static void conndns4(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);
|
||||
@@ -715,16 +739,15 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
uint64_t rand;
|
||||
ifp->lease_expire = ifp->now + (uint64_t) 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, pkt->l2), sizeof(ifp->gwmac));
|
||||
ifp->gw_ready = true; // NOTE(): actual gw ARP won't retry now
|
||||
if (ifp->ip != ip) ifp->is_ip_changed = true;
|
||||
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);
|
||||
setdns4(ifp, &dns); // configure the provided DNS
|
||||
if (MG_IPISLOCAL(ifp, dns)) // connect to a local DNS at this point
|
||||
conndns4(ifp, dns);
|
||||
mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns);
|
||||
}
|
||||
if (ifp->enable_req_sntp && sntp != 0)
|
||||
@@ -775,7 +798,7 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
res.xid = pkt->dhcp->xid;
|
||||
mac = mg_l2_getaddr(ifp, pkt->l2);
|
||||
if (ifp->enable_get_gateway) {
|
||||
ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's
|
||||
ifp->gw = res.yiaddr; // set gw IP, and MAC
|
||||
memcpy(ifp->gwmac, mac, sizeof(ifp->gwmac));
|
||||
}
|
||||
tx_udp4(ifp, mac, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
|
||||
@@ -792,7 +815,7 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
|
||||
struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6,
|
||||
ifp->mac, l2_dst, l2p);
|
||||
memset(ip6, 0, sizeof(*ip6));
|
||||
ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class
|
||||
ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class
|
||||
ip6->label[0] = (uint8_t) (dscp << 6);
|
||||
ip6->plen = mg_htons((uint16_t) plen);
|
||||
ip6->next = next;
|
||||
@@ -816,7 +839,7 @@ static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src,
|
||||
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);
|
||||
icmp6->csum = p6csum(ip6, icmp6, 58, sizeof(*icmp6) + len);
|
||||
driver_output(ifp,
|
||||
mg_l2_trailer(ifp, sizeof(*ip6) + sizeof(*icmp6) + len,
|
||||
(uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len));
|
||||
@@ -1032,6 +1055,7 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return;
|
||||
if (changed) ifp->is_ip6_changed = true;
|
||||
ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1];
|
||||
ifp->gw6_ready = false; // Remove any prior state
|
||||
if (gotl2addr) memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac));
|
||||
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP ||
|
||||
ifp->l2type == MG_TCPIP_L2_PPPoE) {
|
||||
@@ -1046,16 +1070,15 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
mtu));
|
||||
ifp->mtu = (uint16_t) mtu;
|
||||
}
|
||||
if (ifp->state6 != MG_TCPIP_STATE_READY) {
|
||||
tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution
|
||||
if (ifp->state6 != MG_TCPIP_STATE_READY)
|
||||
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;
|
||||
if (!icmp6csum_ok(pkt->ip6, pkt->icmp6, sizeof(*pkt->icmp6) + pkt->pay.len))
|
||||
return;
|
||||
switch (pkt->icmp6->type) {
|
||||
case 128: { // Echo Request, RFC-4443 4.1
|
||||
uint64_t target[2];
|
||||
@@ -1164,7 +1187,7 @@ static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (ifp->ip != 0 && ((rem->addr.ip4 & ifp->mask) == (ifp->ip & ifp->mask)))
|
||||
if (ifp->ip != 0 && MG_IPISLOCAL(ifp, rem->addr.ip4))
|
||||
return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame
|
||||
if (ifp->gw_ready) // use the router
|
||||
return ifp->gwmac; // ignore source address in frame
|
||||
@@ -1174,6 +1197,20 @@ static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
|
||||
char buf[64];
|
||||
va_list ap;
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
va_start(ap, f);
|
||||
mg_vsnprintf(buf, sizeof(buf), f, &ap);
|
||||
va_end(ap);
|
||||
MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
|
||||
c->is_closing = 1; // Stop Mongoose I/O
|
||||
s->is_closing = 1; // Transport-terminal; do not send FIN
|
||||
if (r && !c->is_udp) s->send_rst = true; // honot TCP RST request
|
||||
mg_call(c, MG_EV_ERROR, buf);
|
||||
}
|
||||
|
||||
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
|
||||
struct connstate *s;
|
||||
@@ -1196,10 +1233,10 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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");
|
||||
proto_error(c, false, "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");
|
||||
proto_error(c, false, "oom");
|
||||
} else {
|
||||
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
|
||||
c->recv.len += pkt->pay.len;
|
||||
@@ -1211,7 +1248,7 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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 dscp, uint8_t flags, uint32_t seq, uint32_t ack,
|
||||
const void *buf, size_t len) {
|
||||
uint16_t win, const void *buf, size_t len) {
|
||||
uint8_t *l3p;
|
||||
struct ip *ip = NULL;
|
||||
struct tcp *tcp;
|
||||
@@ -1256,11 +1293,11 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
|
||||
tcp->seq = seq;
|
||||
tcp->ack = ack;
|
||||
tcp->flags = flags;
|
||||
tcp->win = mg_htons(MG_TCPIP_WIN);
|
||||
tcp->win = mg_htons(win);
|
||||
tcp->off = (uint8_t) (hlen / 4 << 4);
|
||||
#if MG_ENABLE_IPV6
|
||||
if (ip_dst->is_ip6) {
|
||||
tcp->csum = p6csum(ip6, tcp, hlen + len);
|
||||
tcp->csum = p6csum(ip6, tcp, 6, hlen + len);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
@@ -1294,7 +1331,8 @@ static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
|
||||
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, 0, flags, seqno, ackno, NULL, 0);
|
||||
return tx_tcp(ifp, l2addr, &ips, &ipd, 0, flags, seqno, ackno, MG_TCPIP_WIN,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
|
||||
@@ -1313,6 +1351,8 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
|
||||
return NULL;
|
||||
}
|
||||
s = (struct connstate *) (c + 1);
|
||||
s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit;
|
||||
s->txq.align = MG_TCPIP_TXQ_ALIGN;
|
||||
s->dmss = mss; // from options in client SYN
|
||||
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
|
||||
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
|
||||
@@ -1403,6 +1443,26 @@ static bool udp_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len);
|
||||
}
|
||||
|
||||
static size_t txq_next(struct connstate *s, uint8_t **buf) {
|
||||
uint32_t len;
|
||||
if (s->txq.len == 0) return 0;
|
||||
assert(s->txq.len >= sizeof(len));
|
||||
memcpy(&len, s->txq.buf, sizeof(len));
|
||||
assert(len > 0 && len <= s->txq.len - sizeof(len));
|
||||
if (buf != NULL) *buf = s->txq.buf + sizeof(len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool txq_add(struct connstate *s, const void *buf, size_t len) {
|
||||
uint32_t n = (uint32_t) len;
|
||||
size_t off = s->txq.len, total = sizeof(n) + len;
|
||||
assert(len > 0);
|
||||
if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false;
|
||||
memcpy(s->txq.buf + off, &n, sizeof(n));
|
||||
memcpy(s->txq.buf + off + sizeof(n), buf, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1412,19 +1472,35 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
struct mg_tcpip_if *ifp = c->mgr->ifp;
|
||||
size_t sent;
|
||||
uint32_t room = s->maxseq - s->seq;
|
||||
if (room == 0) return MG_IO_WAIT;
|
||||
if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts
|
||||
if ((uint32_t) len > room) len = room;
|
||||
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, 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);
|
||||
bool was_empty = s->txq.len == 0;
|
||||
if (s->retransmit) {
|
||||
uint32_t in_flight = s->txq.len > 0 ? s->seq - s->txq_una : 0;
|
||||
uint32_t qroom = in_flight < MG_TCPIP_WIN ? MG_TCPIP_WIN - in_flight : 0;
|
||||
room = s->win > in_flight ? s->win - in_flight : 0;
|
||||
if (room > qroom) room = qroom;
|
||||
}
|
||||
if (room == 0) return MG_IO_WAIT;
|
||||
if (len > s->dmss) len = s->dmss; // RFC-6691, reduce if sending opts
|
||||
if (len > room) len = room;
|
||||
if (s->retransmit) {
|
||||
if (was_empty) s->txq_seq = s->txq_una = s->seq;
|
||||
if (!txq_add(s, buf, len)) return MG_IO_WAIT;
|
||||
}
|
||||
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
|
||||
mg_htonl(s->seq), mg_htonl(s->ack),
|
||||
s->is_full ? 0 : MG_TCPIP_WIN, buf, len);
|
||||
if (sent == 0 || sent == (size_t) -1) {
|
||||
if (s->retransmit)
|
||||
mg_iobuf_del(&s->txq, s->txq.len - len - sizeof(uint32_t),
|
||||
len + sizeof(uint32_t));
|
||||
return sent == 0 ? MG_IO_WAIT : MG_IO_ERR;
|
||||
}
|
||||
s->seq += (uint32_t) len;
|
||||
if (s->retransmit && was_empty) {
|
||||
s->txq_timer = ifp->now + MG_TCPIP_RTX_MS;
|
||||
s->txq_retries = 0;
|
||||
}
|
||||
if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
|
||||
}
|
||||
return (long) len;
|
||||
}
|
||||
@@ -1434,7 +1510,7 @@ static void handle_tls_recv(struct mg_connection *c) {
|
||||
size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail;
|
||||
struct mg_iobuf *io = &c->recv; // allocated on first avail > 0
|
||||
if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) {
|
||||
mg_error(c, "oom");
|
||||
proto_error(c, true, "oom");
|
||||
} else {
|
||||
// Decrypt data directly into c->recv. If io->buf = NULL or
|
||||
// io->len = io->size (no room), there can be outstanding data that can't be
|
||||
@@ -1443,7 +1519,7 @@ static void handle_tls_recv(struct mg_connection *c) {
|
||||
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");
|
||||
proto_error(c, true, "TLS recv error");
|
||||
} else if (n > 0) {
|
||||
// Decrypted successfully - trigger MG_EV_READ
|
||||
io->len += (size_t) n;
|
||||
@@ -1452,22 +1528,66 @@ static void handle_tls_recv(struct mg_connection *c) {
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) {
|
||||
if (ackno < (s->seq - s->win) || ackno > s->seq) return;
|
||||
static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win,
|
||||
uint64_t now) {
|
||||
if (ackno > s->seq) return;
|
||||
if (s->retransmit && s->txq.len > 0) {
|
||||
if (ackno < s->txq_una) return;
|
||||
if (ackno > s->txq_una) {
|
||||
size_t len;
|
||||
// Remove only entirely acknowledged segments (RFC-9293, 3.10.7.4)
|
||||
while ((len = txq_next(s, NULL)) > 0 && ackno >= s->txq_seq + len) {
|
||||
mg_iobuf_del(&s->txq, 0, len + sizeof(uint32_t));
|
||||
s->txq_seq += (uint32_t) len;
|
||||
}
|
||||
s->txq_una = ackno;
|
||||
s->txq_retries = 0;
|
||||
// Restart RTO when an ACK acknowledges new data (RFC-6298, 5.3)
|
||||
s->txq_timer = now + MG_TCPIP_RTX_MS;
|
||||
} // else: dup ack, ignore it but handle its window upddate
|
||||
} else if (ackno < (s->seq - s->win)) {
|
||||
return;
|
||||
}
|
||||
s->maxseq = (uint32_t) (ackno + win);
|
||||
s->win = win;
|
||||
}
|
||||
|
||||
static void retransmit(struct mg_connection *c) {
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
struct mg_tcpip_if *ifp = c->mgr->ifp;
|
||||
uint8_t *buf, i;
|
||||
uint64_t rto;
|
||||
size_t len = txq_next(s, &buf);
|
||||
// Send the front retransmission-queue segment only (RFC-9293, 3.10.8)
|
||||
if (len == 0 ||
|
||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
|
||||
mg_htonl(s->txq_seq), mg_htonl(s->ack),
|
||||
s->is_full ? 0 : MG_TCPIP_WIN, buf, len) == (size_t) -1) {
|
||||
proto_error(c, false, "retransmit"); // should RST, but sending just failed
|
||||
return;
|
||||
}
|
||||
// Back off RTO after every timeout (RFC-6298, 5.5)
|
||||
for (i = 0, rto = MG_TCPIP_RTX_MS;
|
||||
i < s->txq_retries && rto < MG_TCPIP_RTX_MAX_MS; i++) {
|
||||
rto *= 2;
|
||||
if (rto > MG_TCPIP_RTX_MAX_MS) rto = MG_TCPIP_RTX_MAX_MS;
|
||||
}
|
||||
s->txq_timer = ifp->now + rto;
|
||||
}
|
||||
|
||||
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);
|
||||
// Evaluation order is important: 1) process FIN, 2) process keep-alive,
|
||||
// 3) discard invalid SEG.SEQ, 4) process ACK and payload.
|
||||
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, c->dscp, TH_ACK,
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
|
||||
"", 0);
|
||||
return;
|
||||
}
|
||||
// If we initiated the closure, we reply with ACK upon receiving FIN
|
||||
@@ -1476,66 +1596,63 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
|
||||
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
|
||||
}
|
||||
// Simultaneous closure ?
|
||||
s->twclosure = true;
|
||||
} else {
|
||||
// Peer closed first: send ACK only, enter CLOSE_WAIT.
|
||||
// The connection loop will call init_closure after pending send data
|
||||
// The connection loop will initiate closure after pending send data
|
||||
// is flushed, then send our FIN.
|
||||
c->is_draining = 1;
|
||||
}
|
||||
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, flags,
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
|
||||
if (pkt->pay.len == 0) return; // if no data, we're done
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
|
||||
"", 0);
|
||||
} 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, c->dscp, 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 (pkt->tcp->flags & TH_ACK)
|
||||
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
|
||||
if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true;
|
||||
return; // no data to process
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
|
||||
NULL, 0);
|
||||
return; // RFC-9293 3.10.7.4 discard (incorrect) payload, ACK, window
|
||||
} 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, c->dscp, 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");
|
||||
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
|
||||
"", 0);
|
||||
return; // drop it, RFC-9293 3.10.7.4: ignore ACKno
|
||||
}
|
||||
// Now process the segment for ACK and payload
|
||||
if (pkt->tcp->flags & TH_ACK) {
|
||||
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win),
|
||||
c->mgr->ifp->now);
|
||||
if (pkt->pay.len == 0 && s->fin_rcvd && s->ttype == MIP_TTYPE_FIN)
|
||||
s->twclosure = true;
|
||||
}
|
||||
if (pkt->pay.len == 0) return;
|
||||
if (!c->is_closing && io->size - io->len < pkt->pay.len &&
|
||||
!mg_iobuf_resize(io, io->len + pkt->pay.len)) {
|
||||
proto_error(c, true, "oom");
|
||||
return; // drop it
|
||||
}
|
||||
if (pkt->tcp->flags & TH_ACK)
|
||||
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
|
||||
// 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;
|
||||
if (!(pkt->tcp->flags & TH_FIN)) { // FIN already advanced 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) {
|
||||
if ((s->unacked > MG_TCPIP_WIN / 2 && s->acked != s->ack) ||
|
||||
s->ttype == MIP_TTYPE_FIN) {
|
||||
// Send ACK immediately
|
||||
MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
|
||||
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
|
||||
mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
|
||||
NULL, 0);
|
||||
s->unacked = 0;
|
||||
s->acked = s->ack;
|
||||
if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
|
||||
@@ -1543,6 +1660,14 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
|
||||
// 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_closing) return; // we have a "closed socket", ignore data
|
||||
// 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;
|
||||
if (c->is_tls) {
|
||||
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
|
||||
} else {
|
||||
@@ -1612,8 +1737,11 @@ static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) {
|
||||
if (kind == 0) break; // End of Option List
|
||||
if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed
|
||||
optlen = opts[1];
|
||||
if (kind == 2 && optlen == 4) // set received MSS
|
||||
s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]);
|
||||
if (kind == 2 && optlen == 4) { // set received MSS
|
||||
uint16_t mss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]);
|
||||
if (mss == 0 || mss < MG_TCPIP_MIN_MSS) return false;
|
||||
s->dmss = mss;
|
||||
}
|
||||
}
|
||||
MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len));
|
||||
opts += optlen;
|
||||
@@ -1626,7 +1754,10 @@ 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;
|
||||
if (pkt->ip6 != NULL &&
|
||||
!tcp6csum_ok(pkt->ip6, pkt->tcp,
|
||||
PDIFF(pkt->tcp, pkt->pay.buf) + pkt->pay.len))
|
||||
return;
|
||||
#endif
|
||||
if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return;
|
||||
pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?)
|
||||
@@ -1645,11 +1776,11 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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");
|
||||
proto_error(c, false, "connection refused");
|
||||
} else if (c != NULL && pkt->tcp->flags & TH_RST) {
|
||||
uint32_t seqno = mg_ntohl(pkt->tcp->seq);
|
||||
if (seqno >= s->ack && seqno < (s->ack + MG_TCPIP_WIN)) // RFC-9293 3.5.3
|
||||
mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
|
||||
proto_error(c, false, "peer RST"); // RFC-1122 4.2.2.13
|
||||
} else if (c != NULL) {
|
||||
// process segment
|
||||
s->tmiss = 0; // Reset missed keep-alive counter
|
||||
@@ -1657,6 +1788,12 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
settmout(c,
|
||||
MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
|
||||
read_conn(c, pkt); // Override timer with ACK timeout if needed
|
||||
if (c->is_full && !s->is_full) {
|
||||
// The receive handler paused the connection: advertise zero window now
|
||||
s->is_full = true;
|
||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
|
||||
mg_htonl(s->ack), 0, NULL, 0);
|
||||
}
|
||||
} 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
|
||||
@@ -1725,7 +1862,7 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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");
|
||||
if (c) proto_error(c, false, "Received fragmented packet");
|
||||
} else if (pkt->ip->proto == 1) {
|
||||
pkt->icmp = (struct icmp *) (pkt->pay.buf);
|
||||
if (pkt->pay.len < sizeof(*pkt->icmp)) return;
|
||||
@@ -1761,7 +1898,8 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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;
|
||||
if (off < sizeof(*pkt->tcp) / 4 || 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,
|
||||
@@ -1807,7 +1945,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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");
|
||||
if (c) proto_error(c, false, "Received fragmented packet");
|
||||
}
|
||||
return;
|
||||
case 59: // No Next Header 4.7
|
||||
@@ -1857,7 +1995,8 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
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;
|
||||
if (off < sizeof(*pkt->tcp) / 4 || 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,
|
||||
@@ -2000,7 +2139,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
||||
}
|
||||
// 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
|
||||
ifp->state = MG_TCPIP_STATE_READY; // gw MAC not resolved, we'll retry
|
||||
onstatechange(ifp);
|
||||
}
|
||||
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready &&
|
||||
@@ -2009,10 +2148,10 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
||||
#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
|
||||
ifp->state6 = MG_TCPIP_STATE_READY; // gw6 MAC not resolved, we'll retry
|
||||
onstate6change(ifp);
|
||||
}
|
||||
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw6_ready &&
|
||||
if (expired_1000ms && ifp->state6 == 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
|
||||
@@ -2056,28 +2195,42 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving)
|
||||
continue;
|
||||
if (!c->is_full && s->is_full) {
|
||||
// Application resumed the connection: advertise the restored window
|
||||
s->is_full = false;
|
||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
|
||||
mg_htonl(s->ack), MG_TCPIP_WIN, NULL, 0);
|
||||
}
|
||||
if (s->retransmit && s->txq.len > 0 && ifp->now > s->txq_timer) {
|
||||
if (s->txq_retries < 255) s->txq_retries++;
|
||||
// R1 reports a delivery problem; it does not close (RFC-9293, 3.8.3).
|
||||
if (s->txq_retries == MG_TCPIP_RTX_R1)
|
||||
MG_ERROR(("%lu retransmit", c->id));
|
||||
retransmit(c);
|
||||
}
|
||||
if (ifp->now > s->timer) {
|
||||
if (s->ttype == MIP_TTYPE_ARP) {
|
||||
mg_error(c, "ARP timeout");
|
||||
proto_error(c, false, "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, c->dscp, TH_ACK, mg_htonl(s->seq),
|
||||
mg_htonl(s->ack), NULL, 0);
|
||||
mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
|
||||
s->acked = s->ack;
|
||||
} else if (s->ttype == MIP_TTYPE_SYN) {
|
||||
mg_error(c, "Connection timeout");
|
||||
proto_error(c, false, "Connection timeout");
|
||||
} else if (s->ttype == MIP_TTYPE_FIN) {
|
||||
c->is_closing = 1;
|
||||
s->is_closing = 1;
|
||||
continue;
|
||||
} else {
|
||||
if (s->tmiss++ > 2) {
|
||||
mg_error(c, "keepalive");
|
||||
proto_error(c, false, "keepalive");
|
||||
} else {
|
||||
MG_VERBOSE(("%lu keepalive", c->id));
|
||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
||||
mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
|
||||
mg_htonl(s->seq - 1), mg_htonl(s->ack),
|
||||
s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2104,6 +2257,7 @@ 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);
|
||||
ifp->mtu = ifp->l2mtu;
|
||||
if (MG_ENABLE_TCPIP_TCPRTX) ifp->enable_tcp_retransmit = true;
|
||||
|
||||
if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip"
|
||||
memcpy(ifp->dhcp_name, "mip", 4);
|
||||
@@ -2128,10 +2282,20 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
|
||||
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
|
||||
// MG_EPHEMERAL_PORT_BASE to 65535
|
||||
if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
|
||||
MG_DEBUG(("Builtin TCP/IP init done. Queue size: %zu, extra conn size: %zu",
|
||||
ifp->recv_queue.size, mgr->extraconnsize));
|
||||
}
|
||||
mg_bsd_init(mgr);
|
||||
}
|
||||
|
||||
static void close_conn(struct mg_connection *c) {
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
mg_iobuf_free(&s->txq);
|
||||
mg_close_conn(c);
|
||||
}
|
||||
|
||||
void mg_tcpip_free(struct mg_tcpip_if *ifp) {
|
||||
while (ifp->mgr->conns != NULL) close_conn(ifp->mgr->conns); // TCP FIN wait
|
||||
mg_free(ifp->recv_queue.buf);
|
||||
mg_free(ifp->tx.buf);
|
||||
mg_free(ifp->dns4_url);
|
||||
@@ -2140,8 +2304,8 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
|
||||
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, c->dscp, TH_SYN, isn, 0, NULL,
|
||||
0);
|
||||
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_SYN, isn, 0,
|
||||
MG_TCPIP_WIN, NULL, 0);
|
||||
}
|
||||
|
||||
static void l2addr_resolved(struct mg_connection *c) {
|
||||
@@ -2157,6 +2321,9 @@ static void l2addr_resolved(struct mg_connection *c) {
|
||||
void mg_connect_resolved(struct mg_connection *c) {
|
||||
struct mg_tcpip_if *ifp = c->mgr->ifp;
|
||||
uint8_t *l2addr;
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
s->retransmit = ifp->enable_tcp_retransmit;
|
||||
s->txq.align = MG_TCPIP_TXQ_ALIGN;
|
||||
c->is_resolving = 0;
|
||||
if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
|
||||
c->loc.port = mg_htons(ifp->eport++);
|
||||
@@ -2178,7 +2345,6 @@ void mg_connect_resolved(struct mg_connection *c) {
|
||||
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
|
||||
@@ -2193,16 +2359,15 @@ void mg_connect_resolved(struct mg_connection *c) {
|
||||
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"));
|
||||
mg_error(c, "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)) &&
|
||||
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
|
||||
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));
|
||||
@@ -2210,11 +2375,10 @@ void mg_connect_resolved(struct mg_connection *c) {
|
||||
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"));
|
||||
mg_error(c, "No gateway, can't connect");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2233,29 +2397,13 @@ static void write_conn(struct mg_connection *c) {
|
||||
: 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");
|
||||
proto_error(c, false, "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, c->dscp, 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_close_conn(c);
|
||||
(void) s;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -2266,32 +2414,60 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
||||
uint64_t now = mg_millis();
|
||||
mg_timer_poll(&mgr->timers, now);
|
||||
mg_ota_poll(mgr);
|
||||
mg_mdash_poll(mgr);
|
||||
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);
|
||||
long flush = 0;
|
||||
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',
|
||||
MG_VERBOSE(("%lu .. %c%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))
|
||||
s->is_closing ? 'F' : 'f', mg_tls_pending(c), c->rtls.len));
|
||||
if (!c->is_closing) {
|
||||
// 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);
|
||||
if (can_write(c)) write_conn(c);
|
||||
if (is_tls && c->send.len == 0) flush = mg_tls_flush(c);
|
||||
}
|
||||
if (flush == MG_IO_ERR) proto_error(c, false, "tx err"); // should RST but
|
||||
if (c->is_draining && c->send.len == 0 &&
|
||||
(c->is_udp ||
|
||||
(flush == 0 && s->txq.len == 0 && s->ttype != MIP_TTYPE_FIN)))
|
||||
c->is_closing = 1;
|
||||
// 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);
|
||||
(!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0))) {
|
||||
s->is_closing = 1;
|
||||
} else if (c->is_closing && !s->is_closing) {
|
||||
if (c->is_udp || c->is_listening || c->is_resolving || c->is_arplooking ||
|
||||
c->is_connecting) {
|
||||
close_conn(c);
|
||||
continue;
|
||||
} else if (s->ttype != MIP_TTYPE_FIN) { // active TCP: initiate closure
|
||||
size_t tx = tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp,
|
||||
TH_FIN | TH_ACK, mg_htonl(s->seq), mg_htonl(s->ack),
|
||||
s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
|
||||
if (tx == (size_t) -1) {
|
||||
s->is_closing = 1;
|
||||
} else if (tx > 0) {
|
||||
s->seq++;
|
||||
settmout(c, MIP_TTYPE_FIN);
|
||||
} // else tx = 0, should recover later or we have worse problems
|
||||
}
|
||||
}
|
||||
if (s->send_rst) // proto_error requested TCP RST, send it and close
|
||||
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_RST,
|
||||
mg_htonl(s->seq), 0, 0, NULL, 0);
|
||||
if (s->is_closing) close_conn(c); // then removes the conn
|
||||
}
|
||||
mg_bsd_poll(mgr);
|
||||
(void) ms;
|
||||
}
|
||||
|
||||
@@ -2299,10 +2475,10 @@ 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");
|
||||
proto_error(c, false, "net down");
|
||||
#if MG_ENABLE_IPV6
|
||||
} else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
|
||||
mg_error(c, "net down");
|
||||
proto_error(c, false, "net down");
|
||||
#endif
|
||||
} else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
|
||||
// Fail to send, no target MAC or IP
|
||||
@@ -2336,10 +2512,31 @@ static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) {
|
||||
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"));
|
||||
}
|
||||
|
||||
static void conndns4(struct mg_tcpip_if *ifp, uint32_t ip) {
|
||||
struct mg_connection *c;
|
||||
struct mg_dns *dnsc = &ifp->mgr->dns4;
|
||||
if (ifp->mgr->use_dns6) return;
|
||||
if ((c = mg_alloc_conn(ifp->mgr)) == NULL) {
|
||||
MG_ERROR(("OOM, DNS connection creation failed"));
|
||||
return;
|
||||
} // mg_connect_svc() would block us, so we bypass it here
|
||||
LIST_ADD_HEAD(struct mg_connection, &ifp->mgr->conns, c);
|
||||
c->is_udp = true;
|
||||
c->fd = (void *) (size_t) MG_INVALID_SOCKET;
|
||||
c->is_client = true;
|
||||
dnsc->c = c;
|
||||
mg_call(c, MG_EV_OPEN, (void *) dnsc->url);
|
||||
MG_DEBUG(("%lu %ld %s", c->id, c->fd, dnsc->url));
|
||||
c->rem.addr.ip4 = ip;
|
||||
c->rem.port = mg_htons(53);
|
||||
mg_connect_resolved(c);
|
||||
mg_dnsc_init(ifp->mgr, dnsc); // will fill c->pfn = dns_cb
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
@@ -59,6 +59,7 @@ struct mg_tcpip_if {
|
||||
bool enable_req_sntp; // DHCP client requests an SNTP server address
|
||||
bool enable_fcs_check; // Verify and strip FCS from received frames
|
||||
bool enable_mac_check; // Drop frames not addressed to this MAC
|
||||
bool enable_tcp_retransmit; // Enable optional TCP retransmission queue
|
||||
bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table
|
||||
bool is_ip_changed; // Set by stack when IP changes; triggers connection restart
|
||||
struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init()
|
||||
@@ -152,6 +153,42 @@ extern struct mg_tcpip_driver mg_tcpip_driver_cyw;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_st67w6;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_atcmd;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_rw612_154;
|
||||
|
||||
// IEEE 802.15.4 radio channel and PAN ID. Settable in mongoose_config.h
|
||||
#ifndef MG_L2_802154_CHANNEL
|
||||
#define MG_L2_802154_CHANNEL 15 // 11..26
|
||||
#endif
|
||||
#ifndef MG_L2_802154_PAN_ID
|
||||
#define MG_L2_802154_PAN_ID 0xabcd
|
||||
#endif
|
||||
|
||||
// driver_data for IEEE 802.15.4 radio drivers. The interface is Ethernet:
|
||||
// each Ethernet frame is split into broadcast radio frames, and reassembled
|
||||
// on the other side, see mg_l2_802154_tx() and mg_l2_802154_rx()
|
||||
struct mg_l2_802154_data {
|
||||
uint8_t channel; // User-set channel, 11..26
|
||||
uint16_t pan_id; // User-set PAN ID
|
||||
uint8_t seq, tag; // MAC sequence number, tag of the last sent frame
|
||||
uint8_t rxtag, rxnext; // Frame being reassembled: tag, next fragment
|
||||
uint8_t rxsrc[8]; // Its sender
|
||||
uint16_t rxlen; // Its bytes received so far
|
||||
uint8_t rxbuf[1514]; // Reassembly buffer, one Ethernet frame
|
||||
};
|
||||
|
||||
// Splits an Ethernet frame into radio frames, sends each by calling send().
|
||||
// Called by radio drivers' tx(). Return len, or 0 on error
|
||||
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
|
||||
size_t (*send)(const uint8_t *, size_t,
|
||||
struct mg_tcpip_if *));
|
||||
|
||||
// Adds a received radio frame f of n bytes to the frame being reassembled.
|
||||
// Called by radio drivers' rx(). Return the Ethernet frame length, copied to
|
||||
// buf, when it is complete, otherwise 0
|
||||
size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n,
|
||||
void *buf, size_t len);
|
||||
|
||||
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
|
||||
// Populate and assign to the driver's driver_data field.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
// Scannable version tag embedded in every firmware binary, for server-side
|
||||
// version extraction. The version string starts after the "MG_VERSION:" prefix.
|
||||
static const char s_fw_version[] = "MG_VERSION:" MG_OTA_FIRMWARE_VERSION;
|
||||
static const char s_fw_version[] = "MG_VERSION:" MG_FIRMWARE_VERSION;
|
||||
|
||||
static bool s_autocommit_ok; // True after OTA server confirms "same version"
|
||||
|
||||
@@ -23,6 +23,9 @@ static struct mg_ota_state {
|
||||
size_t size;
|
||||
uint8_t sha256[32];
|
||||
void (*fn)(const char *error_message);
|
||||
size_t received; // Firmware bytes written so far
|
||||
bool open; // mg_ota_begin() succeeded, mg_ota_end() is pending
|
||||
const char *err; // First error during download, NULL if none
|
||||
} *s_ota;
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data);
|
||||
@@ -143,11 +146,38 @@ static void status_fn(const char *errmsg) {
|
||||
if (errmsg) MG_ERROR(("OTA failed: %s", errmsg));
|
||||
}
|
||||
|
||||
static void status_fn_2(struct mg_connection *c, const char *errmsg) {
|
||||
if (s_ota) s_ota->fn(errmsg);
|
||||
// Firmware download callback for mg_http_stream_body(). Writes chunks to the
|
||||
// OTA target and reports the result via s_ota->fn(). The end call also happens
|
||||
// when the connection drops before the whole body arrived.
|
||||
static bool s_firmware_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
bool finalized = false, ok;
|
||||
(void) p;
|
||||
if (s_ota == NULL) return false;
|
||||
if (hm != NULL) { // Start
|
||||
s_ota->received = 0;
|
||||
s_ota->open = mg_ota_begin(hm->body.len);
|
||||
if (!s_ota->open) s_ota->err = "OTA begin failed";
|
||||
return s_ota->open;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
if (!mg_ota_write(data->buf, data->len)) {
|
||||
s_ota->err = "OTA write failed";
|
||||
return false;
|
||||
}
|
||||
s_ota->received += data->len;
|
||||
return true;
|
||||
}
|
||||
// End
|
||||
if (s_ota->open) finalized = mg_ota_end();
|
||||
if (s_ota->err == NULL && s_ota->received != s_ota->size) {
|
||||
s_ota->err = "Connection closed";
|
||||
}
|
||||
if (s_ota->err == NULL && !finalized) s_ota->err = "OTA finalize failed";
|
||||
ok = s_ota->err == NULL;
|
||||
s_ota->fn(s_ota->err);
|
||||
mg_free(s_ota);
|
||||
s_ota = NULL;
|
||||
mg_http_reply(c, errmsg ? 500 : 200, "", "%s\n", errmsg ? errmsg : "ok");
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
@@ -178,7 +208,7 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
|
||||
} else {
|
||||
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
||||
mg_http_start_ota(c, hm, status_fn_2);
|
||||
mg_http_stream_body(c, hm, s_firmware_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
s_ota->fn((char *) ev_data);
|
||||
@@ -211,6 +241,21 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url,
|
||||
}
|
||||
}
|
||||
|
||||
// Uses *p as a flag: non-NULL means an OTA session is open and must be
|
||||
// closed by mg_ota_end()
|
||||
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
if (!mg_ota_begin(hm->body.len)) return false;
|
||||
*p = (void *) (ptrdiff_t) 1;
|
||||
return true;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
return mg_ota_write(data->buf, data->len);
|
||||
}
|
||||
if (*p == NULL) return false; // End. Begin failed, nothing to finish
|
||||
*p = NULL;
|
||||
return mg_ota_end();
|
||||
}
|
||||
|
||||
void mg_ota_poll(struct mg_mgr *mgr) {
|
||||
static uint64_t t = 5000; // Fire first time 5 sec after boot
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// This is a low-level OTA API, not intended to be called directly by users.
|
||||
// Users are provided with a higher-level API:
|
||||
// - defining MG_OTA_URL enables HTTP or HTTPS pull-based updates
|
||||
// - mg_http_start_ota() enables push-based updates
|
||||
// - mg_http_stream_body() enables push-based updates
|
||||
//
|
||||
// However, it is possible to use the mg_ota_* API directly. Below is the
|
||||
// example code of a function that is called repeatedly. It expects the
|
||||
@@ -26,6 +26,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "http.h"
|
||||
#include "net.h"
|
||||
|
||||
// Supported OTA targets
|
||||
@@ -34,6 +35,7 @@
|
||||
#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_STM32C5 5 // STM32 C5, same flash controller as H5
|
||||
#define MG_OTA_CH32V307 100 // WCH CH32V307
|
||||
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
|
||||
#define MG_OTA_RT1020 300 // IMXRT1020
|
||||
@@ -41,7 +43,9 @@
|
||||
#define MG_OTA_RT1060 302 // IMXRT1060
|
||||
#define MG_OTA_RT1064 303 // IMXRT1064
|
||||
#define MG_OTA_RT1170 304 // IMXRT1170
|
||||
#define MG_OTA_RT1180 305 // IMXRT1180
|
||||
#define MG_OTA_MCXN 310 // MCXN947
|
||||
#define MG_OTA_MCXW71 311 // MCXW71
|
||||
#define MG_OTA_RW612 320 // FRDM-RW612
|
||||
#define MG_OTA_FLASH 900 // OTA via internal flash
|
||||
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
|
||||
@@ -71,6 +75,18 @@ bool mg_ota_write(const void *buf, size_t len);
|
||||
// Ends the OTA process and commits the written firmware.
|
||||
bool mg_ota_end(void);
|
||||
|
||||
// Push-based OTA: mg_http_stream_body() callback that flashes the request
|
||||
//
|
||||
// ```c
|
||||
// if (ev == MG_EV_HTTP_HDRS) {
|
||||
// struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
// if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p);
|
||||
|
||||
// MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the
|
||||
// default calls swap_fn() (no-op on single-bank) then resets. Override in
|
||||
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
|
||||
@@ -153,7 +169,7 @@ enum {
|
||||
// needs to be defined. Fetches metadata_url, which must
|
||||
// return a JSON object like:
|
||||
// { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 }
|
||||
// If the server version differs from MG_OTA_FIRMWARE_VERSION, downloads
|
||||
// If the server version differs from MG_FIRMWARE_VERSION, downloads
|
||||
// FIRMWARE_URL and performs the OTA update. fn is called on every outcome:
|
||||
// NULL on successful flash, "Same version" when already up to date, or an
|
||||
// error string on failure. Pass NULL to use the default handler which logs
|
||||
@@ -180,8 +196,8 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url,
|
||||
#endif
|
||||
|
||||
// Firmware version for mg_ota_poll(). Settable in mongoose_config.h
|
||||
#ifndef MG_OTA_FIRMWARE_VERSION
|
||||
#define MG_OTA_FIRMWARE_VERSION "1.0.0"
|
||||
#ifndef MG_FIRMWARE_VERSION
|
||||
#define MG_FIRMWARE_VERSION "1.0.0"
|
||||
#endif
|
||||
|
||||
// Maximum version string length
|
||||
|
||||
+41
-10
@@ -2,7 +2,7 @@
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
|
||||
#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1170
|
||||
#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1180
|
||||
|
||||
static bool mg_imxrt_write(void *, const void *, size_t);
|
||||
static bool mg_imxrt_swap(void);
|
||||
@@ -13,9 +13,12 @@ static bool mg_imxrt_swap(void);
|
||||
#elif MG_OTA == MG_OTA_RT1064
|
||||
#define MG_IMXRT_FLASH_START 0x70000000
|
||||
#define FLEXSPI_NOR_INSTANCE 1
|
||||
#else // RT1170
|
||||
#elif MG_OTA == MG_OTA_RT1170
|
||||
#define MG_IMXRT_FLASH_START 0x30000000
|
||||
#define FLEXSPI_NOR_INSTANCE 1
|
||||
#else // RT1180 FlexSPI2
|
||||
#define MG_IMXRT_FLASH_START 0x04000000
|
||||
#define FLEXSPI_NOR_INSTANCE 2
|
||||
#endif
|
||||
|
||||
#if MG_OTA == MG_OTA_RT1050
|
||||
@@ -324,7 +327,7 @@ struct mg_flexspi_nor_driver_interface {
|
||||
uint32_t *option);
|
||||
};
|
||||
#else
|
||||
// RT117x support ROM API version 1.7
|
||||
// RT117x and RT118x
|
||||
struct mg_flexspi_nor_driver_interface {
|
||||
uint32_t version;
|
||||
int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
|
||||
@@ -359,11 +362,16 @@ struct mg_flexspi_nor_driver_interface {
|
||||
#define flexspi_nor \
|
||||
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \
|
||||
16)))
|
||||
#else
|
||||
#elif MG_OTA == MG_OTA_RT1170
|
||||
#define MG_FLEXSPI_BASE 0x400CC000
|
||||
#define flexspi_nor \
|
||||
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \
|
||||
12)))
|
||||
#else
|
||||
#define MG_FLEXSPI_BASE 0x445E0000
|
||||
#define flexspi_nor \
|
||||
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x1000001c + \
|
||||
12)))
|
||||
#endif
|
||||
|
||||
static bool s_flash_irq_disabled;
|
||||
@@ -408,6 +416,13 @@ static struct mg_flexspi_nor_config default_config = {
|
||||
#else
|
||||
// Note: this QSPI configuration works for RTs supporting QSPI
|
||||
// Configuration for QSPI memory
|
||||
#if MG_OTA == MG_OTA_RT1180
|
||||
#define MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ 5 // 100MHz
|
||||
#define MG_FLEXSPI_QSPI_FLASH_SIZE (16 * 1024 * 1024)
|
||||
#else
|
||||
#define MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ 7 // 133MHz
|
||||
#define MG_FLEXSPI_QSPI_FLASH_SIZE (8 * 1024 * 1024)
|
||||
#endif
|
||||
static struct mg_flexspi_nor_config default_config = {
|
||||
.memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
|
||||
.version = MG_FLEXSPI_CFG_BLK_VERSION,
|
||||
@@ -417,8 +432,8 @@ static struct mg_flexspi_nor_config default_config = {
|
||||
.controllerMiscOption = MG_BIT(4),
|
||||
.deviceType = 1, // serial NOR
|
||||
.sflashPadType = 4,
|
||||
.serialClkFreq = 7, // 133MHz
|
||||
.sflashA1Size = 8 * 1024 * 1024,
|
||||
.serialClkFreq = MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ,
|
||||
.sflashA1Size = MG_FLEXSPI_QSPI_FLASH_SIZE,
|
||||
.lookupTable = MG_FLEXSPI_QSPI_LUT},
|
||||
.pageSize = 256,
|
||||
.sectorSize = 4 * 1024,
|
||||
@@ -453,6 +468,7 @@ MG_IRAM static int flexspi_nor_get_config(
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050
|
||||
MG_IRAM static void mg_spin(volatile uint32_t count) {
|
||||
while (count--) (void) 0;
|
||||
}
|
||||
@@ -461,6 +477,7 @@ MG_IRAM static void flash_wait(void) {
|
||||
while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0)
|
||||
mg_spin(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config,
|
||||
void *addr) {
|
||||
@@ -509,6 +526,8 @@ MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) {
|
||||
uint32_t *dst = (uint32_t *) addr;
|
||||
uint32_t *src = (uint32_t *) buf;
|
||||
uint32_t *end = (uint32_t *) ((char *) buf + len);
|
||||
char *flash_start = (char *) s_mg_flash_imxrt.start;
|
||||
char *flash_end = flash_start + s_mg_flash_imxrt.size;
|
||||
ok = true;
|
||||
|
||||
while (ok && src < end) {
|
||||
@@ -516,20 +535,32 @@ MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
uint32_t status;
|
||||
uint32_t status = 0;
|
||||
uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start;
|
||||
if ((char *) buf >= (char *) s_mg_flash_imxrt.start) {
|
||||
if ((char *) buf >= flash_start && (char *) buf < flash_end) {
|
||||
// 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];
|
||||
|
||||
#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050
|
||||
// ROM API 1.4 does not provide flexspi_nor_flash_read().
|
||||
// Manually copy byte by byte into the RAM buffer.
|
||||
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
|
||||
// When supported, prefer the read API call.
|
||||
uint32_t src_ofs =
|
||||
(uint32_t) src - (uint32_t) s_mg_flash_imxrt.start;
|
||||
status = flexspi_nor->read(FLEXSPI_NOR_INSTANCE, config_ptr, tmp,
|
||||
src_ofs, s_mg_flash_imxrt.align);
|
||||
#endif
|
||||
if (status == 0) {
|
||||
status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr,
|
||||
dst_ofs, tmp);
|
||||
}
|
||||
} else {
|
||||
status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr,
|
||||
(uint32_t) dst_ofs, src);
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
#include "flash.h"
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
|
||||
#if MG_OTA == MG_OTA_MCXW71
|
||||
|
||||
// MCXW71SRM, section 11.2: ROM flash API, 8 KB sectors, 16-byte phrases,
|
||||
// 128-byte pages. Only the CM33 application flash is used; RF flash and IFR
|
||||
// (including the write-once ROM configuration) are left alone.
|
||||
struct mg_mcxw71_flash_config {
|
||||
struct {
|
||||
uint32_t base, size, blocks;
|
||||
uint32_t ifr_base, ifr_size;
|
||||
} mem[2];
|
||||
};
|
||||
|
||||
struct mg_mcxw71_flash_api {
|
||||
uint32_t (*init)(struct mg_mcxw71_flash_config *);
|
||||
uint32_t (*erase)(struct mg_mcxw71_flash_config *, void *, uint32_t, uint32_t,
|
||||
uint32_t);
|
||||
uint32_t (*phrase)(struct mg_mcxw71_flash_config *, void *, uint32_t,
|
||||
uint32_t *, uint32_t);
|
||||
uint32_t (*page)(struct mg_mcxw71_flash_config *, void *, uint32_t,
|
||||
uint32_t *, uint32_t);
|
||||
};
|
||||
|
||||
// The flash API pointer is the second entry in the ROM API table.
|
||||
#define MG_MCXW71_FLASH_API \
|
||||
(*(const struct mg_mcxw71_flash_api *const *) 0x14816fe4U)
|
||||
#define MG_MCXW71_ERASE_KEY 0x6b65666cU // FOUR_CHAR_CODE('l', 'f', 'e', 'k')
|
||||
|
||||
static struct mg_mcxw71_flash_config s_mcxw71_config;
|
||||
static size_t s_mcxw71_size, s_mcxw71_received, s_mcxw71_pending;
|
||||
static uint32_t s_mcxw71_buf[4];
|
||||
static bool s_mcxw71_failed;
|
||||
|
||||
static bool mg_mcxw71_write(void *, const void *, size_t);
|
||||
static bool mg_mcxw71_swap(void);
|
||||
static struct mg_flash s_mg_flash_mcxw71 = {
|
||||
(void *) 0, 0x100000, 8192, 16, mg_mcxw71_write, mg_mcxw71_swap};
|
||||
struct mg_flash *mg_flash = &s_mg_flash_mcxw71;
|
||||
|
||||
// Everything called while flash is busy, or after the application has been
|
||||
// erased, must execute from RAM or ROM. Do not log or call libc here.
|
||||
MG_IRAM static bool mg_mcxw71_write(void *addr, const void *buf, size_t len) {
|
||||
uintptr_t dst = (uintptr_t) addr;
|
||||
const volatile uint8_t *src = (const volatile uint8_t *) buf;
|
||||
uint32_t irq, status = 0;
|
||||
if (dst >= s_mg_flash_mcxw71.size || len > s_mg_flash_mcxw71.size - dst ||
|
||||
(dst % 16) != 0 || (len % 16) != 0)
|
||||
return false;
|
||||
|
||||
irq = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
while (len > 0 && status == 0) {
|
||||
uint32_t words[32]; // ROM needs a word-aligned source in RAM
|
||||
volatile uint8_t *p = (volatile uint8_t *) words;
|
||||
size_t i, n = (dst % 128) == 0 && len >= 128 ? 128 : 16;
|
||||
for (i = 0; i < n; i++) p[i] = src[i];
|
||||
|
||||
// Match the MCUXpresso ROM wrappers' cache maintenance. Interrupts stay
|
||||
// masked until the flash command completes and stale cache data is gone.
|
||||
MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK;
|
||||
__DSB();
|
||||
__ISB();
|
||||
if ((dst % 8192) == 0) {
|
||||
status = MG_MCXW71_FLASH_API->erase(
|
||||
&s_mcxw71_config, FMU0, (uint32_t) dst, 8192, MG_MCXW71_ERASE_KEY);
|
||||
}
|
||||
if (status == 0) {
|
||||
if (n == 128) {
|
||||
status = MG_MCXW71_FLASH_API->page(&s_mcxw71_config, FMU0,
|
||||
(uint32_t) dst, words, 128);
|
||||
} else {
|
||||
status = MG_MCXW71_FLASH_API->phrase(&s_mcxw71_config, FMU0,
|
||||
(uint32_t) dst, words, 16);
|
||||
}
|
||||
}
|
||||
MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK;
|
||||
__DSB();
|
||||
__ISB();
|
||||
dst += n, src += n, len -= n;
|
||||
}
|
||||
__set_PRIMASK(irq);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
static bool mg_mcxw71_swap(void) {
|
||||
// The common helper verifies the staged image before the RAM copy below.
|
||||
// A failed write must never commit, even if all expected bytes arrived.
|
||||
return !s_mcxw71_failed;
|
||||
}
|
||||
|
||||
MG_IRAM static void mg_mcxw71_apply(size_t size) {
|
||||
size_t ofs, half = s_mg_flash_mcxw71.size / 2;
|
||||
__disable_irq();
|
||||
SysTick->CTRL = 0;
|
||||
for (ofs = 0; ofs < size; ofs += 8192) {
|
||||
if (!mg_mcxw71_write((void *) (uintptr_t) ofs,
|
||||
(const void *) (uintptr_t) (half + ofs), 8192)) {
|
||||
// The application may already be erased. Stay in RAM for debugger
|
||||
// recovery rather than return to partially overwritten flash.
|
||||
for (;;) __NOP();
|
||||
}
|
||||
}
|
||||
__DSB();
|
||||
SCB->AIRCR = 0x05fa0004U;
|
||||
__DSB();
|
||||
for (;;) __NOP();
|
||||
}
|
||||
|
||||
bool mg_ota_begin(size_t new_firmware_size) {
|
||||
uint32_t status;
|
||||
if (s_mcxw71_size != 0 || new_firmware_size == 0) return false;
|
||||
status = MG_MCXW71_FLASH_API->init(&s_mcxw71_config);
|
||||
if (status != 0 || s_mcxw71_config.mem[0].base != 0 ||
|
||||
s_mcxw71_config.mem[0].size < 16384 ||
|
||||
(s_mcxw71_config.mem[0].size % 16384) != 0) {
|
||||
MG_ERROR(("Flash init failed: %lu", (unsigned long) status));
|
||||
return false;
|
||||
}
|
||||
s_mg_flash_mcxw71.size = s_mcxw71_config.mem[0].size;
|
||||
if (!mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxw71)) return false;
|
||||
s_mcxw71_size = new_firmware_size;
|
||||
s_mcxw71_received = s_mcxw71_pending = 0;
|
||||
s_mcxw71_failed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mg_ota_write(const void *buf, size_t len) {
|
||||
const uint8_t *p = (const uint8_t *) buf;
|
||||
uint8_t *pending = (uint8_t *) s_mcxw71_buf;
|
||||
if (s_mcxw71_size == 0 || s_mcxw71_failed) return false;
|
||||
if (len > s_mcxw71_size - s_mcxw71_received || (len > 0 && buf == NULL)) {
|
||||
s_mcxw71_failed = true;
|
||||
return false;
|
||||
}
|
||||
s_mcxw71_received += len;
|
||||
if (s_mcxw71_pending > 0) {
|
||||
size_t n = 16 - s_mcxw71_pending;
|
||||
if (n > len) n = len;
|
||||
if (n > 0) memcpy(pending + s_mcxw71_pending, p, n);
|
||||
s_mcxw71_pending += n;
|
||||
if (n > 0) p += n;
|
||||
len -= n;
|
||||
if (s_mcxw71_pending == 16) {
|
||||
s_mcxw71_failed = !mg_ota_flash_write(pending, 16, &s_mg_flash_mcxw71);
|
||||
s_mcxw71_pending = 0;
|
||||
}
|
||||
}
|
||||
if (!s_mcxw71_failed && len >= 16) {
|
||||
size_t n = MG_ROUND_DOWN(len, 16);
|
||||
s_mcxw71_failed = !mg_ota_flash_write(p, n, &s_mg_flash_mcxw71);
|
||||
p += n, len -= n;
|
||||
}
|
||||
if (!s_mcxw71_failed && len > 0) {
|
||||
memcpy(pending + s_mcxw71_pending, p, len);
|
||||
s_mcxw71_pending += len;
|
||||
}
|
||||
return !s_mcxw71_failed;
|
||||
}
|
||||
|
||||
bool mg_ota_end(void) {
|
||||
bool ok;
|
||||
size_t size = s_mcxw71_size;
|
||||
if (size == 0) return false;
|
||||
if (s_mcxw71_received != size) s_mcxw71_failed = true;
|
||||
if (!s_mcxw71_failed && s_mcxw71_pending > 0) {
|
||||
s_mcxw71_failed =
|
||||
!mg_ota_flash_write(s_mcxw71_buf, s_mcxw71_pending, &s_mg_flash_mcxw71);
|
||||
}
|
||||
ok = mg_ota_flash_end(&s_mg_flash_mcxw71);
|
||||
s_mcxw71_size = s_mcxw71_pending = 0;
|
||||
if (ok) {
|
||||
MG_INFO(("Applying %lu bytes from staging flash", (unsigned long) size));
|
||||
mg_mcxw71_apply(size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
+5
-7
@@ -291,24 +291,22 @@ MG_IRAM static bool mg_frdm_write(void *addr, const void *buf, size_t len) {
|
||||
}
|
||||
uint32_t status;
|
||||
uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_frdm.start;
|
||||
size_t tmp_buf_size = s_mg_flash_frdm.align / sizeof(uint32_t);
|
||||
uint32_t tmp[tmp_buf_size];
|
||||
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);
|
||||
memcpy(tmp, src, sizeof(tmp));
|
||||
}
|
||||
status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config,
|
||||
(uint32_t) dst_ofs, tmp, false);
|
||||
src = (uint32_t *) ((char *) src + s_mg_flash_frdm.align);
|
||||
dst = (uint32_t *) ((char *) dst + s_mg_flash_frdm.align);
|
||||
if (status != 0) {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
|
||||
#if MG_OTA == MG_OTA_STM32H5
|
||||
#if MG_OTA == MG_OTA_STM32H5 || MG_OTA == MG_OTA_STM32C5
|
||||
|
||||
static bool mg_stm32h5_write(void *, const void *, size_t);
|
||||
static bool mg_stm32h5_swap(void);
|
||||
|
||||
+3
-3
@@ -67,10 +67,10 @@ MG_IRAM static void flash_unlock(void) {
|
||||
}
|
||||
}
|
||||
|
||||
MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
|
||||
MG_IRAM static bool flash_page_start(uint32_t *dst) {
|
||||
char *base = (char *) s_mg_flash_stm32h7.start,
|
||||
*end = base + s_mg_flash_stm32h7.size;
|
||||
volatile char *p = (char *) dst;
|
||||
char *p = (char *) dst;
|
||||
return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ MG_IRAM static uint32_t flash_bank(void *addr) {
|
||||
// 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) {
|
||||
if (flash_page_start((uint32_t *) addr) == false) {
|
||||
MG_ERROR(("%p is not on a sector boundary", addr));
|
||||
} else {
|
||||
uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start;
|
||||
|
||||
+3
-1
@@ -71,8 +71,10 @@ char *mg_mprintf(const char *fmt, ...) {
|
||||
}
|
||||
|
||||
void mg_pfn_stdout(char c, void *param) {
|
||||
#if MG_ENABLE_POSIX_FS
|
||||
putchar(c);
|
||||
(void) param;
|
||||
#endif
|
||||
(void) param, (void) c;
|
||||
}
|
||||
|
||||
static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
|
||||
|
||||
+3
-3
@@ -34,15 +34,15 @@ char *mg_mprintf(const char *fmt, ...);
|
||||
size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
|
||||
|
||||
// Built-in %M/%m printer functions. Each reads its argument(s) from ap.
|
||||
size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len
|
||||
size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
|
||||
size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
|
||||
size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len
|
||||
size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
|
||||
size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
|
||||
size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
|
||||
size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4
|
||||
size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6
|
||||
size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac
|
||||
size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint64_t
|
||||
size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[8] ieee64
|
||||
size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address
|
||||
size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ uint64_t mg_now(void);
|
||||
// MG_INFO(("every second"));
|
||||
// }
|
||||
// Full examples:
|
||||
// tutorials/http/http-server/arduino/teensy41-http,
|
||||
// tutorials/mqtt/mqtt-client
|
||||
// Related APIs:
|
||||
// mg_millis(), mg_now()
|
||||
|
||||
+7
-8
@@ -1,6 +1,6 @@
|
||||
#include "dns.h"
|
||||
#include "event.h"
|
||||
#include "log.h"
|
||||
#include "mdash.h"
|
||||
#include "net.h"
|
||||
#include "printf.h"
|
||||
#include "str.h"
|
||||
@@ -768,15 +768,12 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
||||
now = mg_millis();
|
||||
mg_timer_poll(&mgr->timers, now);
|
||||
mg_ota_poll(mgr);
|
||||
mg_mdash_poll(mgr);
|
||||
|
||||
for (c = mgr->conns; c != NULL; c = tmp) {
|
||||
bool is_resp = c->is_resp;
|
||||
long flush = 0;
|
||||
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',
|
||||
@@ -791,10 +788,12 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
||||
} 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_tls && !c->is_tls_hs && c->send.len == 0)
|
||||
flush = mg_tls_flush(c);
|
||||
}
|
||||
|
||||
if (c->is_draining && c->send.len == 0) c->is_closing = 1;
|
||||
if (flush == MG_IO_ERR) mg_error(c, "tx err");
|
||||
if (c->is_draining && c->send.len == 0 && flush == 0) c->is_closing = 1;
|
||||
if (c->is_closing) close_conn(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ 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 *);
|
||||
long mg_tls_flush(struct mg_connection *);
|
||||
void mg_tls_handshake(struct mg_connection *);
|
||||
void mg_tls_ctx_init(struct mg_mgr *);
|
||||
void mg_tls_ctx_free(struct mg_mgr *);
|
||||
@@ -90,7 +90,7 @@ void mg_tls_ctx_free(struct mg_mgr *);
|
||||
#if MG_TLS == MG_TLS_BUILTIN
|
||||
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
|
||||
// KEY input. Accepts PEM or DER. Returns key size, or 0 on error.
|
||||
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
|
||||
size_t mg_p256_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
|
||||
#endif
|
||||
|
||||
// Low-level IO primitives used by TLS layer
|
||||
|
||||
+319
-207
@@ -6,8 +6,8 @@
|
||||
#include "tls_aes128.h"
|
||||
#include "tls_chacha20.h"
|
||||
#include "tls_rsa.h"
|
||||
#include "tls_uecc.h"
|
||||
#include "tls_x25519.h"
|
||||
#include "tls_p256.h"
|
||||
#include "tls_p384.h"
|
||||
#include "util.h"
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN
|
||||
@@ -61,6 +61,7 @@ enum mg_tls_hs_state {
|
||||
|
||||
// Server state machine:
|
||||
MG_TLS_STATE_SERVER_START, // Wait for ClientHello
|
||||
MG_TLS_STATE_SERVER_RETRY, // Wait for ClientHello after HelloRetry
|
||||
MG_TLS_STATE_SERVER_WAIT_CERT, // Wait for Certificate
|
||||
MG_TLS_STATE_SERVER_WAIT_CV, // Wait for CertificateVerify
|
||||
MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finish
|
||||
@@ -106,8 +107,9 @@ struct tls_data {
|
||||
|
||||
uint8_t random[32]; // client random from ClientHello
|
||||
uint8_t session_id[32]; // client session ID between the handshake states
|
||||
uint8_t x25519_cli[32]; // client X25519 key between the handshake states
|
||||
uint8_t x25519_sec[32]; // x25519 secret between the handshake states
|
||||
uint8_t p256_cli[32]; // client P-256 key between the handshake states
|
||||
uint8_t p256_peer[64]; // server: client P-256 public key X || Y
|
||||
uint8_t ecdhe_sec[32]; // P-256 ECDHE shared secret between the states
|
||||
|
||||
bool skip_verification; // do not perform checks on server certificate
|
||||
bool cert_requested; // client received a CertificateRequest
|
||||
@@ -130,6 +132,9 @@ struct tls_data {
|
||||
// verify cert
|
||||
size_t pubkeysz; // size of the server public key
|
||||
uint8_t sighash[32]; // calculated signature verification hash
|
||||
uint8_t sighash384[48]; // same, SHA-384, for ecdsa_secp384r1_sha384
|
||||
uint64_t hs_start; // mg_millis() when the handshake started
|
||||
uint64_t hs_proc; // ms spent processing handshake messages
|
||||
|
||||
struct tls_enc enc; // actual keys in use at this time
|
||||
struct tls_enc app_keys; // storage during two-way auth handshake
|
||||
@@ -267,9 +272,14 @@ static void mg_der_debug(struct mg_der_tlv *tlv, int depth) {
|
||||
|
||||
// Did we receive a full TLS record in the c->rtls buffer?
|
||||
static bool mg_tls_got_record(struct mg_connection *c) {
|
||||
return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
|
||||
c->rtls.len >=
|
||||
(size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
|
||||
uint16_t rl;
|
||||
if (c->rtls.len < (size_t) TLS_RECHDR_SIZE) return false;
|
||||
rl = MG_LOAD_BE16(c->rtls.buf + 3);
|
||||
if (rl > 16384 + 256) { // TLS 1.3 record max length (RFC-9846 5.1)
|
||||
mg_error(c, "TLS record too large");
|
||||
return false;
|
||||
}
|
||||
return c->rtls.len >= (size_t) (TLS_RECHDR_SIZE + rl);
|
||||
}
|
||||
|
||||
// Remove a single TLS record from the recv buffer
|
||||
@@ -316,7 +326,7 @@ static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz,
|
||||
memmove(hash, secret, hashsz);
|
||||
}
|
||||
|
||||
// at this point we have x25519 shared secret, we can generate a set of derived
|
||||
// at this point we have ECDHE shared secret, we can generate a set of derived
|
||||
// handshake encryption keys
|
||||
static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
@@ -337,8 +347,11 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
|
||||
mg_tls_derive_secret("tls13 derived", early_secret, 32, zeros_sha256_digest,
|
||||
32, pre_extract_secret, 32);
|
||||
mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
|
||||
sizeof(pre_extract_secret), tls->x25519_sec,
|
||||
sizeof(tls->x25519_sec));
|
||||
sizeof(pre_extract_secret), tls->ecdhe_sec,
|
||||
sizeof(tls->ecdhe_sec));
|
||||
// ECDHE keys and secret are not needed anymore, wipe for forward secrecy
|
||||
mg_bzero(tls->p256_cli, sizeof(tls->p256_cli));
|
||||
mg_bzero(tls->ecdhe_sec, sizeof(tls->ecdhe_sec));
|
||||
mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);
|
||||
|
||||
// mg_sha256_final is not idempotent, need to copy sha256 context to calculate
|
||||
@@ -456,7 +469,7 @@ static bool mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
|
||||
c->is_client ? tls->enc.client_write_iv : tls->enc.server_write_iv;
|
||||
|
||||
if (msgsz > 16384) {
|
||||
MG_ERROR(("msg longer than recordsz"));
|
||||
mg_error(c, "msg longer than recordsz");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -501,19 +514,20 @@ static bool mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
|
||||
|
||||
static void verbose_alert(uint8_t *buf) {
|
||||
uint8_t level = buf[0], desc = buf[1];
|
||||
MG_INFO(("TLS ALERT received: level=%d, desc=%d (%s)", level, desc,
|
||||
desc == 0 ? "close_notify"
|
||||
: desc == 10 ? "unexpected_message"
|
||||
: desc == 20 ? "bad_record_mac"
|
||||
: desc == 21 ? "decryption_failed"
|
||||
: desc == 40 ? "handshake_failure"
|
||||
: desc == 42 ? "bad_certificate"
|
||||
: desc == 43 ? "unsupported_certificate"
|
||||
: desc == 44 ? "certificate_revoked"
|
||||
: desc == 45 ? "certificate_expired"
|
||||
: desc == 46 ? "certificate_unknown"
|
||||
: desc == 48 ? "unknown_ca"
|
||||
: "unknown"));
|
||||
MG_LOG(desc == 0 ? MG_LL_VERBOSE : MG_LL_INFO, // close_notify is normal
|
||||
("TLS ALERT received: level=%d, desc=%d (%s)", level, desc,
|
||||
desc == 0 ? "close_notify"
|
||||
: desc == 10 ? "unexpected_message"
|
||||
: desc == 20 ? "bad_record_mac"
|
||||
: desc == 21 ? "decryption_failed"
|
||||
: desc == 40 ? "handshake_failure"
|
||||
: desc == 42 ? "bad_certificate"
|
||||
: desc == 43 ? "unsupported_certificate"
|
||||
: desc == 44 ? "certificate_revoked"
|
||||
: desc == 45 ? "certificate_expired"
|
||||
: desc == 46 ? "certificate_unknown"
|
||||
: desc == 48 ? "unknown_ca"
|
||||
: "unknown"));
|
||||
}
|
||||
|
||||
// read an encrypted record, decrypt it in place
|
||||
@@ -621,8 +635,11 @@ static int mg_tls_recv_record(struct mg_connection *c) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Hash the CertificateVerify content: SHA-256 into hash, and if hash384 is not
|
||||
// NULL, SHA-384 into hash384 for ecdsa_secp384r1_sha384
|
||||
static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
|
||||
uint8_t hash[32], bool is_client) {
|
||||
uint8_t hash[32], uint8_t *hash384,
|
||||
bool is_client) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
uint8_t sig_content[130];
|
||||
mg_sha256_ctx sha256;
|
||||
@@ -642,9 +659,11 @@ static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
|
||||
mg_sha256_init(&sha256);
|
||||
mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
|
||||
mg_sha256_final(hash, &sha256);
|
||||
if (hash384 != NULL) mg_sha384(hash384, sig_content, sizeof(sig_content));
|
||||
}
|
||||
|
||||
// read and parse ClientHello record
|
||||
// read and parse ClientHello record. Return 0 if it has a P-256 key share,
|
||||
// 1 if it does not and HelloRetryRequest must be sent, negative otherwise
|
||||
static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
struct mg_iobuf *rio = &c->rtls;
|
||||
@@ -654,9 +673,13 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
||||
uint16_t ext_len;
|
||||
uint8_t *ext;
|
||||
uint16_t msgsz;
|
||||
bool have_p256 = false;
|
||||
bool is_retry = tls->state == MG_TLS_STATE_SERVER_RETRY;
|
||||
|
||||
if (!mg_tls_got_record(c)) {
|
||||
return MG_IO_WAIT;
|
||||
for (;;) {
|
||||
if (!mg_tls_got_record(c)) return MG_IO_WAIT;
|
||||
if (!is_retry || rio->buf[0] != MG_TLS_CHANGE_CIPHER) break;
|
||||
mg_tls_drop_record(c); // compatibility mode CCS before 2nd ClientHello
|
||||
}
|
||||
if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
|
||||
mg_error(c, "not a client hello packet");
|
||||
@@ -700,16 +723,21 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
||||
goto fail;
|
||||
for (k = 0; k < key_exchange_len;) {
|
||||
uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
|
||||
uint16_t group = MG_LOAD_BE16(key_exchange + k);
|
||||
if (((uint32_t) m + k + 4) > key_exchange_len) goto fail;
|
||||
if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) {
|
||||
memmove(tls->x25519_cli, key_exchange + k + 4, m);
|
||||
mg_tls_drop_record(c);
|
||||
return 0;
|
||||
// secp256r1, uncompressed point 0x04 || X || Y
|
||||
if (group == 0x0017 && m == 65 && key_exchange[k + 4] == 0x04) {
|
||||
memmove(tls->p256_peer, key_exchange + k + 5, 64);
|
||||
have_p256 = true;
|
||||
}
|
||||
k += (uint16_t) (m + 4);
|
||||
}
|
||||
j += (uint16_t) (n + 4);
|
||||
}
|
||||
if (have_p256 || !is_retry) { // No P-256 share: ask for it, but only once
|
||||
mg_tls_drop_record(c);
|
||||
return have_p256 ? 0 : 1;
|
||||
}
|
||||
fail:
|
||||
mg_error(c, "bad client hello");
|
||||
return -1;
|
||||
@@ -723,58 +751,98 @@ fail:
|
||||
static bool mg_tls_server_send_hello(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
struct mg_iobuf *wio = &tls->send;
|
||||
uint8_t hello[155], pub[65], prv[32], hdr[5] = {0x16, 0x03, 0x03, 0, 0};
|
||||
size_t n = sizeof(hello); // ServerHello length, including 4-byte header
|
||||
|
||||
// clang-format off
|
||||
uint8_t msg_server_hello[122] = {
|
||||
// server hello, tls 1.2
|
||||
0x02, 0x00, 0x00, 0x76, 0x03, 0x03,
|
||||
// random (32 bytes)
|
||||
PLACEHOLDER_32B,
|
||||
// session ID length + session ID (32 bytes)
|
||||
0x20, PLACEHOLDER_32B,
|
||||
#if MG_ENABLE_CHACHA20
|
||||
// TLS_CHACHA20_POLY1305_SHA256 + no compression
|
||||
0x13, 0x03, 0x00,
|
||||
#else
|
||||
// TLS_AES_128_GCM_SHA256 + no compression
|
||||
0x13, 0x01, 0x00,
|
||||
#endif
|
||||
// extensions + keyshare
|
||||
0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
|
||||
// x25519 keyshare
|
||||
PLACEHOLDER_32B,
|
||||
// supported versions (tls1.3 == 0x304)
|
||||
0x00, 0x2b, 0x00, 0x02, 0x03, 0x04};
|
||||
// clang-format on
|
||||
|
||||
// calculate keyshare
|
||||
uint8_t x25519_pub[X25519_BYTES];
|
||||
uint8_t x25519_prv[X25519_BYTES];
|
||||
if (!mg_random(x25519_prv, sizeof(x25519_prv))) mg_error(c, "RNG");
|
||||
if( mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1) < 0 || mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1) < 0) {
|
||||
// calculate P-256 keyshare and the shared secret
|
||||
pub[0] = 0x04; // uncompressed point 0x04 || X || Y
|
||||
if (!mg_p256_make_key(pub + 1, prv) ||
|
||||
!mg_p256_shared_secret(tls->p256_peer, prv, tls->ecdhe_sec)) {
|
||||
mg_bzero(prv, sizeof(prv));
|
||||
mg_error(c, "bad key");
|
||||
return false;
|
||||
}
|
||||
mg_tls_hexdump("s x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec));
|
||||
mg_bzero(prv, sizeof(prv));
|
||||
mg_tls_hexdump("s ecdhe sec", tls->ecdhe_sec, sizeof(tls->ecdhe_sec));
|
||||
|
||||
// fill in the gaps: random + session ID + keyshare
|
||||
memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
|
||||
memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id));
|
||||
memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
|
||||
hello[0] = 0x02; // server hello, tls 1.2
|
||||
MG_STORE_BE24(hello + 1, n - 4);
|
||||
hello[4] = 0x03, hello[5] = 0x03;
|
||||
if (!mg_random(hello + 6, 32)) { // server random, RFC 8446 4.1.3
|
||||
mg_error(c, "RNG");
|
||||
return false;
|
||||
}
|
||||
hello[38] = 0x20; // session ID length + session ID (32 bytes)
|
||||
memmove(hello + 39, tls->session_id, sizeof(tls->session_id));
|
||||
#if MG_ENABLE_CHACHA20
|
||||
hello[71] = 0x13, hello[72] = 0x03; // TLS_CHACHA20_POLY1305_SHA256
|
||||
#else
|
||||
hello[71] = 0x13, hello[72] = 0x01; // TLS_AES_128_GCM_SHA256
|
||||
#endif
|
||||
hello[73] = 0x00; // no compression
|
||||
MG_STORE_BE16(hello + 74, 65 + 14); // extensions length
|
||||
MG_STORE_BE16(hello + 76, 0x0033); // key share
|
||||
MG_STORE_BE16(hello + 78, 65 + 4);
|
||||
MG_STORE_BE16(hello + 80, 0x0017); // secp256r1
|
||||
MG_STORE_BE16(hello + 82, 65);
|
||||
memmove(hello + 84, pub, sizeof(pub));
|
||||
memmove(hello + 149, "\x00\x2b\x00\x02\x03\x04", 6); // tls1.3 == 0x304
|
||||
MG_STORE_BE16(hdr + 3, n);
|
||||
|
||||
// server hello message
|
||||
if (mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5) == 0 ||
|
||||
mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello)) ==
|
||||
0)
|
||||
if (mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr)) == 0 ||
|
||||
mg_iobuf_add(wio, wio->len, hello, n) == 0)
|
||||
return false;
|
||||
mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello));
|
||||
mg_sha256_update(&tls->sha256, hello, n);
|
||||
|
||||
// change cipher message
|
||||
if (mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6) == 0)
|
||||
// change cipher message, unless it was already sent after HelloRetryRequest
|
||||
if (tls->state != MG_TLS_STATE_SERVER_RETRY &&
|
||||
mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// HelloRetryRequest random, SHA-256 of "HelloRetryRequest", RFC 8446 4.1.3
|
||||
static const uint8_t hrr_random[32] = {
|
||||
0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c,
|
||||
0x02, 0x1e, 0x65, 0xb8, 0x91, 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb,
|
||||
0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c};
|
||||
|
||||
// put HelloRetryRequest record into wio buffer, asking for a P-256 key share.
|
||||
// Replace ClientHello in the transcript with message_hash, RFC 8446 4.4.1
|
||||
static bool mg_tls_server_send_hrr(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
struct mg_iobuf *wio = &tls->send;
|
||||
uint8_t hrr[88], hdr[5] = {0x16, 0x03, 0x03, 0, sizeof(hrr)};
|
||||
uint8_t mh[4 + 32] = {0xfe, 0, 0, 32}; // message_hash(ClientHello1)
|
||||
|
||||
hrr[0] = 0x02; // server hello, tls 1.2
|
||||
MG_STORE_BE24(hrr + 1, sizeof(hrr) - 4);
|
||||
hrr[4] = 0x03, hrr[5] = 0x03;
|
||||
memmove(hrr + 6, hrr_random, sizeof(hrr_random));
|
||||
hrr[38] = 0x20; // session ID length + session ID (32 bytes)
|
||||
memmove(hrr + 39, tls->session_id, sizeof(tls->session_id));
|
||||
#if MG_ENABLE_CHACHA20
|
||||
hrr[71] = 0x13, hrr[72] = 0x03; // TLS_CHACHA20_POLY1305_SHA256
|
||||
#else
|
||||
hrr[71] = 0x13, hrr[72] = 0x01; // TLS_AES_128_GCM_SHA256
|
||||
#endif
|
||||
hrr[73] = 0x00; // no compression
|
||||
// extensions: supported versions tls1.3, key share secp256r1 (group only)
|
||||
memmove(hrr + 74, "\x00\x0c\x00\x2b\x00\x02\x03\x04\x00\x33\x00\x02\x00\x17",
|
||||
14);
|
||||
|
||||
mg_sha256_final(mh + 4, &tls->sha256);
|
||||
mg_sha256_init(&tls->sha256);
|
||||
mg_sha256_update(&tls->sha256, mh, sizeof(mh));
|
||||
mg_sha256_update(&tls->sha256, hrr, sizeof(hrr));
|
||||
|
||||
// HelloRetryRequest message, followed by change cipher message
|
||||
return mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr)) != 0 &&
|
||||
mg_iobuf_add(wio, wio->len, hrr, sizeof(hrr)) != 0 &&
|
||||
mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6) != 0;
|
||||
}
|
||||
|
||||
static bool mg_tls_server_send_ext(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
// server extensions
|
||||
@@ -783,10 +851,11 @@ static bool mg_tls_server_send_ext(struct mg_connection *c) {
|
||||
return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
|
||||
}
|
||||
|
||||
// signature algorithms we actually support:
|
||||
// rsa_pkcs1_sha256, rsa_pss_rsae_sha256 and ecdsa_secp256r1_sha256
|
||||
static const uint8_t secp256r1_sig_algs[12] = {
|
||||
0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01};
|
||||
// signature algorithms we actually support: ecdsa_secp256r1_sha256,
|
||||
// ecdsa_secp384r1_sha384, rsa_pss_rsae_sha256, rsa_pkcs1_sha256
|
||||
static const uint8_t secp256r1_sig_algs[] = {
|
||||
0x00, 0x0d, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03,
|
||||
0x05, 0x03, 0x08, 0x04, 0x04, 0x01};
|
||||
|
||||
static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
@@ -858,28 +927,6 @@ static bool mg_tls_send_cert(struct mg_connection *c, bool is_client) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// type adapter between uECC hash context and our sha256 implementation
|
||||
typedef struct SHA256_HashContext {
|
||||
MG_UECC_HashContext uECC;
|
||||
mg_sha256_ctx ctx;
|
||||
} SHA256_HashContext;
|
||||
|
||||
static void init_SHA256(const MG_UECC_HashContext *base) {
|
||||
SHA256_HashContext *c = (SHA256_HashContext *) base;
|
||||
mg_sha256_init(&c->ctx);
|
||||
}
|
||||
|
||||
static void update_SHA256(const MG_UECC_HashContext *base,
|
||||
const uint8_t *message, unsigned message_size) {
|
||||
SHA256_HashContext *c = (SHA256_HashContext *) base;
|
||||
mg_sha256_update(&c->ctx, message, message_size);
|
||||
}
|
||||
static void finish_SHA256(const MG_UECC_HashContext *base,
|
||||
uint8_t *hash_result) {
|
||||
SHA256_HashContext *c = (SHA256_HashContext *) base;
|
||||
mg_sha256_final(hash_result, &c->ctx);
|
||||
}
|
||||
|
||||
static void mg_tls_mgf1(uint8_t *mask, size_t mask_len, const uint8_t *seed,
|
||||
size_t seed_len) {
|
||||
uint32_t counter = 0;
|
||||
@@ -1066,7 +1113,7 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
uint8_t hash[32] = {0};
|
||||
|
||||
mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, is_client);
|
||||
mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, NULL, is_client);
|
||||
|
||||
if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) {
|
||||
// RSA certificate verify packet
|
||||
@@ -1083,19 +1130,19 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
||||
|
||||
// Check bounds
|
||||
if (emlen > sizeof(em) || verifysz > sizeof(verify)) {
|
||||
MG_ERROR(("RSA key too large for static buffers"));
|
||||
mg_error(c, "RSA key too large for static buffers");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mg_tls_pss_encode(hash, sizeof(hash), &rn, em)) {
|
||||
MG_ERROR(("Failed PSS encode"));
|
||||
mg_error(c, "Failed PSS encode");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate PSS encoded message format
|
||||
if (em[emlen - 1] != 0xbc) {
|
||||
MG_ERROR(("Invalid PSS encoding: last byte is 0x%02x, expected 0xbc",
|
||||
em[emlen - 1]));
|
||||
mg_error(c, "Invalid PSS encoding: last byte is 0x%02x, expected 0xbc",
|
||||
em[emlen - 1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1108,7 +1155,7 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
||||
// Sign directly into the verify buffer (verify + 8 = signature location)
|
||||
memset(verify + 8, 0, emlen); // Initialize signature area
|
||||
if (!mg_tls_rsa_sign(tls, em, emlen, verify + 8)) {
|
||||
MG_ERROR(("Failed RSA sign"));
|
||||
mg_error(c, "Failed RSA sign");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1122,15 +1169,13 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
||||
} else {
|
||||
// EC certificate verify packet
|
||||
uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
|
||||
uint8_t tmp[2 * 32 + 64] = {0};
|
||||
struct SHA256_HashContext ctx = {
|
||||
{&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
|
||||
{{0}, 0, 0, {0}}};
|
||||
size_t sigsz, verifysz = 0;
|
||||
int neg1, neg2;
|
||||
uint8_t sig[64] = {0};
|
||||
mg_uecc_sign_deterministic(tls->ec_key, hash, sizeof(hash), &ctx.uECC, sig,
|
||||
mg_uecc_secp256r1());
|
||||
if (!mg_p256_sign(tls->ec_key, hash, sizeof(hash), sig)) {
|
||||
mg_error(c, "P-256 sign");
|
||||
return false;
|
||||
}
|
||||
|
||||
neg1 = !!(sig[0] & 0x80);
|
||||
neg2 = !!(sig[32] & 0x80);
|
||||
@@ -1193,8 +1238,6 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
struct mg_iobuf *wio = &tls->send;
|
||||
|
||||
uint8_t x25519_pub[X25519_BYTES];
|
||||
|
||||
// - "signature algorithms we actually support", see above
|
||||
// uint8_t secp256r1_sig_algs[]
|
||||
// - all popular signature algorithms (if we don't care about verification)
|
||||
@@ -1206,11 +1249,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
||||
0xfe, 0x00, 0x00, 0xfe};
|
||||
|
||||
// clang-format off
|
||||
uint8_t msg_client_hello[145] = {
|
||||
uint8_t msg_client_hello[178] = {
|
||||
// TLS Client Hello header reported as TLS1.2 (5)
|
||||
0x16, 0x03, 0x03, 0x00, 0xfe,
|
||||
0x16, 0x03, 0x03, 0x00, 0xd8,
|
||||
// client hello, tls 1.2 (6)
|
||||
0x01, 0x00, 0x00, 0x8c, 0x03, 0x03,
|
||||
0x01, 0x00, 0x00, 0xd4, 0x03, 0x03,
|
||||
// random (32 bytes)
|
||||
PLACEHOLDER_32B,
|
||||
// session ID length + session ID (32 bytes)
|
||||
@@ -1227,15 +1270,15 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
||||
0x01, 0x00,
|
||||
// extensions + keyshare
|
||||
0x00, 0xfe,
|
||||
// x25519 keyshare
|
||||
0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
|
||||
PLACEHOLDER_32B,
|
||||
// supported groups (x25519)
|
||||
0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d,
|
||||
// keyshare: secp256r1, uncompressed point
|
||||
0x00, 0x33, 0x00, 0x47, 0x00, 0x45,
|
||||
0x00, 0x17, 0x00, 0x41, 0x04, PLACEHOLDER_32B, PLACEHOLDER_32B,
|
||||
// supported groups (secp256r1)
|
||||
0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17,
|
||||
// supported versions (tls1.3 == 0x304)
|
||||
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
|
||||
// session ticket (none)
|
||||
0x00, 0x23, 0x00, 0x00, // 144 bytes till here
|
||||
0x00, 0x23, 0x00, 0x00, // 177 bytes till here
|
||||
};
|
||||
// clang-format on
|
||||
const char *hostname = tls->hostname;
|
||||
@@ -1248,11 +1291,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
||||
|
||||
// patch ClientHello with correct hostname ext length (if any)
|
||||
MG_STORE_BE16(msg_client_hello + 3,
|
||||
hostname_extsz + 183 - 9 - 34 + sig_alg_sz);
|
||||
hostname_extsz + 216 - 9 - 34 + sig_alg_sz);
|
||||
MG_STORE_BE16(msg_client_hello + 7,
|
||||
hostname_extsz + 179 - 9 - 34 + sig_alg_sz);
|
||||
hostname_extsz + 212 - 9 - 34 + sig_alg_sz);
|
||||
MG_STORE_BE16(msg_client_hello + 82,
|
||||
hostname_extsz + 104 - 9 - 34 + sig_alg_sz);
|
||||
hostname_extsz + 137 - 9 - 34 + sig_alg_sz);
|
||||
|
||||
if (hostnamesz > 0) {
|
||||
MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
|
||||
@@ -1260,16 +1303,20 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
||||
MG_STORE_BE16(server_name_ext + 7, hostnamesz);
|
||||
}
|
||||
|
||||
// calculate keyshare
|
||||
if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG");
|
||||
mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
|
||||
// calc+fill keyshare; X || Y, sent as an uncompressed point 0x04 || X || Y
|
||||
if (!mg_p256_make_key(msg_client_hello + 95, tls->p256_cli)) {
|
||||
mg_error(c, "P-256 key");
|
||||
return false;
|
||||
}
|
||||
|
||||
// fill in the gaps: random + session ID + keyshare
|
||||
if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG");
|
||||
if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG");
|
||||
// fill in the gaps: random + session ID
|
||||
if (!mg_random(tls->session_id, sizeof(tls->session_id)) ||
|
||||
!mg_random(tls->random, sizeof(tls->random))) {
|
||||
mg_error(c, "RNG");
|
||||
return false;
|
||||
}
|
||||
memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
|
||||
memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
|
||||
memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
|
||||
|
||||
// client hello message
|
||||
if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
|
||||
@@ -1339,17 +1386,20 @@ static int mg_tls_client_recv_hello(struct mg_connection *c) {
|
||||
}
|
||||
if (ext_len2 < (2 + 2 + 32)) goto fail;
|
||||
group = MG_LOAD_BE16(ext + j + 4);
|
||||
if (group != 0x001d) {
|
||||
key_exchange_len = MG_LOAD_BE16(ext + j + 6);
|
||||
key_exchange = ext + j + 8;
|
||||
if (key_exchange_len > ext_len2 - 4) goto fail;
|
||||
if (group != 0x0017) { // secp256r1, uncompressed point 0x04 || X || Y
|
||||
mg_error(c, "bad key exchange group");
|
||||
return -1;
|
||||
}
|
||||
key_exchange_len = MG_LOAD_BE16(ext + j + 6);
|
||||
key_exchange = ext + j + 8;
|
||||
if (key_exchange_len != 32 || mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) < 0) {
|
||||
if (key_exchange_len != 65 || key_exchange[0] != 0x04 ||
|
||||
!mg_p256_shared_secret(key_exchange + 1, tls->p256_cli,
|
||||
tls->ecdhe_sec)) { // Validates the point
|
||||
mg_error(c, "bad key");
|
||||
return -1;
|
||||
}
|
||||
mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
|
||||
}
|
||||
// mg_tls_hexdump("c ecdhe sec", tls->ecdhe_sec, 32);
|
||||
mg_tls_drop_record(c);
|
||||
/* generate handshake keys */
|
||||
mg_tls_generate_handshake_keys(c);
|
||||
@@ -1614,14 +1664,15 @@ static int countdots(struct mg_str s) {
|
||||
char *p = s.buf;
|
||||
while (len--) {
|
||||
if (*(p++) == '.') ++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
|
||||
const char *server_name,
|
||||
struct mg_addr *server_ip) {
|
||||
struct mg_addr *ref_ip) {
|
||||
struct mg_der_tlv root, field, name;
|
||||
bool is_addr = ref_ip != NULL;
|
||||
if (mg_der_parse((uint8_t *) der, dersz, &root) < 0) {
|
||||
MG_ERROR(("failed to parse certificate"));
|
||||
return -1;
|
||||
@@ -1635,20 +1686,24 @@ static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
|
||||
return -1;
|
||||
}
|
||||
while (mg_der_next(&field, &name) > 0) {
|
||||
if (name.type == 0x87 && name.len == 4) { // this is an IPv4 address
|
||||
MG_VERBOSE(("Found SAN, IP: %M", mg_print_ip4, name.value));
|
||||
if (!server_ip->is_ip6 &&
|
||||
*((uint32_t *) name.value) == server_ip->addr.ip4)
|
||||
return 1; // and matches the one we're connected to
|
||||
if (is_addr) { // match ref IP to IP and name to DNS name (RFC-9525 6.2)
|
||||
if (name.type == 0x87 && name.len == 4) { // this is an IPv4 address
|
||||
uint32_t cert_ip;
|
||||
MG_VERBOSE(("Found SAN, IP: %M", mg_print_ip4, name.value));
|
||||
memcpy(&cert_ip, name.value, 4);
|
||||
if (!ref_ip->is_ip6 && cert_ip == ref_ip->addr.ip4) // RFC-9525 6.4
|
||||
return 1; // and byte-matches the one we wanted to connected to
|
||||
}
|
||||
#if MG_ENABLE_IPV6
|
||||
} else if (name.type == 0x87 && name.len == 16) { // is an IPv6 address
|
||||
MG_VERBOSE(("Found SAN, IPv6: %M", mg_print_ip6, name.value));
|
||||
if (server_ip->is_ip6 && memcmp(name.value, server_ip->addr.ip6, 16) == 0)
|
||||
return 1; // and matches the one we're connected to
|
||||
else if (name.type == 0x87 && name.len == 16) { // is an IPv6 address
|
||||
MG_VERBOSE(("Found SAN, IPv6: %M", mg_print_ip6, name.value));
|
||||
if (ref_ip->is_ip6 && memcmp(name.value, ref_ip->addr.ip6, 16) == 0)
|
||||
return 1; // and byte-matches the one we wanted to connected to
|
||||
}
|
||||
#endif
|
||||
} else { // this is a text SAN
|
||||
} else if (name.type == 0x82) { // this is a DNS name
|
||||
struct mg_str sn, tn;
|
||||
MG_VERBOSE(("Found SAN, (%u): %.*s", name.type, name.len, name.value));
|
||||
MG_VERBOSE(("Found SAN, DNS: %.*s", name.len, name.value));
|
||||
sn = mg_str(server_name), tn = mg_str_n((char *) name.value, name.len);
|
||||
if (countdots(sn) == countdots(tn) && mg_match(sn, tn, NULL))
|
||||
return 1; // and matches the host name
|
||||
@@ -1669,7 +1724,6 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
|
||||
MG_ERROR(("cert verification error"));
|
||||
return 0;
|
||||
}
|
||||
#if MG_UECC_SUPPORTS_secp256r1
|
||||
if (issuer->pubkey.len == 64) {
|
||||
const uint32_t N = 32;
|
||||
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
|
||||
@@ -1678,24 +1732,19 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
|
||||
memmove(sig + (N - a.len), a.value, a.len);
|
||||
memset(sig + N, 0, N - b.len);
|
||||
memmove(sig + N + (N - b.len), b.value, b.len);
|
||||
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||
(unsigned) cert->tbshashsz, sig,
|
||||
mg_uecc_secp256r1());
|
||||
} else
|
||||
#endif
|
||||
#if MG_UECC_SUPPORTS_secp384r1
|
||||
if (issuer->pubkey.len == 96) {
|
||||
return mg_p256_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||
cert->tbshashsz, sig);
|
||||
} else if (issuer->pubkey.len == 96) {
|
||||
const uint32_t N = 48;
|
||||
if (a.len > N) a.value += (a.len - N), a.len = N;
|
||||
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
|
||||
if (b.len > N) b.value += (b.len - N), b.len = N;
|
||||
memmove(sig, a.value, N);
|
||||
memmove(sig + N, b.value, N);
|
||||
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||
(unsigned) cert->tbshashsz, sig,
|
||||
mg_uecc_secp384r1());
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
memset(sig, 0, N - a.len); // short encoding
|
||||
memmove(sig + (N - a.len), a.value, a.len);
|
||||
memset(sig + N, 0, N - b.len);
|
||||
memmove(sig + N + (N - b.len), b.value, b.len);
|
||||
return mg_p384_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||
cert->tbshashsz, sig);
|
||||
} else {
|
||||
MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len));
|
||||
return 0;
|
||||
}
|
||||
@@ -1735,17 +1784,18 @@ static int mg_tls_verify_cert_cn(struct mg_der_tlv *subj, const char *host) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
// 1: found, 0: not found, < 0: parse error: -1: bundle (unused), -2: name
|
||||
static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name,
|
||||
struct mg_tls_cert *cert) {
|
||||
size_t i;
|
||||
struct mg_der_tlv v;
|
||||
struct mg_str *p = tls->ca_bundle_der, tgt;
|
||||
if (!mg_der_find_oid(name, (uint8_t *) "\x55\x04\x03", 3, &v)) return false;
|
||||
if (mg_der_find_oid(name, (uint8_t *) "\x55\x04\x03", 3, &v) <= 0) return -2;
|
||||
tgt = mg_str_n((const char *) v.value, v.len);
|
||||
for (i = 0; i < tls->ca_bundle_len; i++, p++) {
|
||||
struct mg_str subj;
|
||||
if (mg_tls_parse_cert_der(p->buf, p->len, cert) < 0 ||
|
||||
!mg_der_find_oid(&cert->subj, (uint8_t *) "\x55\x04\x03", 3, &v)) {
|
||||
mg_der_find_oid(&cert->subj, (uint8_t *) "\x55\x04\x03", 3, &v) <= 0) {
|
||||
MG_ERROR(("failed to parse certificate #%u in bundle", i + 1));
|
||||
continue; // skip this certificate but don't halt the process
|
||||
}
|
||||
@@ -1756,6 +1806,8 @@ static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool mg_aton_(struct mg_str str, struct mg_addr *addr);
|
||||
|
||||
static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
unsigned char *recv_buf;
|
||||
@@ -1842,11 +1894,16 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
if (ci == certs) {
|
||||
// First certificate in the chain is peer cert, check SAN if requested,
|
||||
// and store public key for further CertVerify step
|
||||
if (tls->hostname[0] != '\0' &&
|
||||
mg_tls_verify_cert_san(cert, certsz, tls->hostname, &c->rem) <= 0 &&
|
||||
mg_tls_verify_cert_cn(&ci->subj, tls->hostname) <= 0) {
|
||||
mg_error(c, "failed to verify hostname");
|
||||
return -1;
|
||||
if (tls->hostname[0] != '\0') {
|
||||
struct mg_addr addr; // use aton_ so localhost evals as a name
|
||||
bool is_addr = mg_aton_(mg_str_s(tls->hostname), &addr);
|
||||
if (mg_tls_verify_cert_san(cert, certsz, tls->hostname,
|
||||
is_addr ? &addr : NULL) <= 0 &&
|
||||
(!is_addr || // DNS CN fallback
|
||||
mg_tls_verify_cert_cn(&ci->subj, tls->hostname) <= 0)) {
|
||||
mg_error(c, "failed to verify hostname");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (ci->pubkey.len > sizeof(tls->pubkey)) {
|
||||
mg_error(c, "peer public key too large");
|
||||
@@ -1865,7 +1922,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
int r;
|
||||
MG_VERBOSE(("Search current cert in bundle"));
|
||||
r = tls_bundle_find(tls, &ci->subj, &ca); // find current cert ci
|
||||
if (r < 0) {
|
||||
if (r == -1) {
|
||||
mg_error(c, "failed to parse CA bundle");
|
||||
return -1;
|
||||
} else if (r > 0 && ca.pubkey.len == ci->pubkey.len &&
|
||||
@@ -1873,7 +1930,19 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
found_ca = true;
|
||||
MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
|
||||
break;
|
||||
} // else either r = 0 => subj not found or pubkey not matching
|
||||
} // else r = 0 or -2 => subj not found or pubkey not matching
|
||||
}
|
||||
|
||||
// Single CA: accept the chain if the CA itself is in it, e.g. a
|
||||
// cross-signed copy of the root. Same subject and same public key
|
||||
// means the same trusted key, and the links below ci are verified
|
||||
if (tls->ca_der.len > 0 && ca.subj.len == ci->subj.len &&
|
||||
memcmp(ca.subj.value, ci->subj.value, ca.subj.len) == 0 &&
|
||||
ca.pubkey.len == ci->pubkey.len &&
|
||||
memcmp(ca.pubkey.buf, ci->pubkey.buf, ca.pubkey.len) == 0) {
|
||||
found_ca = true;
|
||||
MG_VERBOSE(("CA found in chain at depth %d", certnum - 1));
|
||||
break;
|
||||
}
|
||||
|
||||
if (certnum == sizeof(certs) / sizeof(certs[0]) - 1) {
|
||||
@@ -1887,8 +1956,10 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
MG_VERBOSE(("Search bundle for issuer of last cert in chain"));
|
||||
r = tls_bundle_find(tls, &certs[certnum - 1].issuer, &ca);
|
||||
if (r <= 0) {
|
||||
mg_error(c, r < 0 ? "failed to parse CA bundle"
|
||||
: "failed to find issuing CA in bundle");
|
||||
mg_error(c, "failed to %s",
|
||||
r == 0 ? "find issuing CA in bundle"
|
||||
: r == -1 ? "parse CA bundle"
|
||||
: "parse name OID");
|
||||
return -1;
|
||||
} else if (!ca.is_ca || // candidate issuer found
|
||||
!mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) {
|
||||
@@ -1908,7 +1979,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
||||
}
|
||||
}
|
||||
mg_tls_drop_message(c);
|
||||
mg_tls_calc_cert_verify_hash(c, tls->sighash, !is_client);
|
||||
mg_tls_calc_cert_verify_hash(c, tls->sighash, tls->sighash384, !is_client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1971,11 +2042,19 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
||||
return -1;
|
||||
}
|
||||
MG_VERBOSE(("certificate verification successful (RSA)"));
|
||||
} else if (sigalg == 0x0403) { // ecdsa_secp256r1_sha256
|
||||
// Extract certificate signature and verify it using pubkey and sighash
|
||||
uint8_t sig[64];
|
||||
} else if (sigalg == 0x0403 || sigalg == 0x0503) {
|
||||
// ecdsa_secp256r1_sha256 or ecdsa_secp384r1_sha384. Extract certificate
|
||||
// signature and verify it using pubkey and sighash
|
||||
bool is384 = sigalg == 0x0503;
|
||||
uint32_t N = is384 ? 48 : 32; // curve size: r and s are N bytes each
|
||||
int ok;
|
||||
uint8_t sig[96];
|
||||
struct mg_der_tlv seq, r, s;
|
||||
memset(sig, 0, 64);
|
||||
memset(sig, 0, sizeof(sig));
|
||||
if (tls->pubkeysz != 2 * N) {
|
||||
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
|
||||
return -1;
|
||||
}
|
||||
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
|
||||
mg_error(c, "verification message is not an ASN.1 DER sequence");
|
||||
return -1;
|
||||
@@ -1989,15 +2068,21 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
||||
return -1;
|
||||
}
|
||||
// Integers may be padded with zeroes
|
||||
if (r.len > 32) r.value = r.value + (r.len - 32), r.len = 32;
|
||||
if (s.len > 32) s.value = s.value + (s.len - 32), s.len = 32;
|
||||
if (r.len > N) r.value = r.value + (r.len - N), r.len = N;
|
||||
if (s.len > N) s.value = s.value + (s.len - N), s.len = N;
|
||||
|
||||
// r or s may be shorter than 32 bytes, "right-justify" (network order)
|
||||
memmove(sig + (32 - r.len), r.value, r.len);
|
||||
memmove(sig + 32 + (32 - s.len), s.value, s.len);
|
||||
// r or s may be shorter than N bytes, "right-justify" (network order)
|
||||
memmove(sig + (N - r.len), r.value, r.len);
|
||||
memmove(sig + N + (N - s.len), s.value, s.len);
|
||||
|
||||
if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig,
|
||||
mg_uecc_secp256r1()) != 1) {
|
||||
if (is384) {
|
||||
ok = mg_p384_verify(tls->pubkey, tls->sighash384,
|
||||
sizeof(tls->sighash384), sig);
|
||||
} else {
|
||||
ok = mg_p256_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash),
|
||||
sig);
|
||||
}
|
||||
if (ok != 1) {
|
||||
mg_error(c, "failed to verify EC certificate (certverify)");
|
||||
return -1;
|
||||
}
|
||||
@@ -2012,8 +2097,8 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
||||
// 0809 = rsa_pss_pss_sha256
|
||||
// 080A = rsa_pss_pss_sha384
|
||||
// 080B = rsa_pss_pss_sha512
|
||||
MG_ERROR(("unsupported certverify signature scheme: %x of %d bytes",
|
||||
sigalg, siglen));
|
||||
mg_error(c, "unsupported certverify signature scheme: %x of %d bytes",
|
||||
sigalg, siglen);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -2099,16 +2184,23 @@ static bool mg_tls_client_handshake(struct mg_connection *c) {
|
||||
|
||||
static bool mg_tls_server_handshake(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
int r;
|
||||
switch (tls->state) {
|
||||
case MG_TLS_STATE_SERVER_START:
|
||||
if (mg_tls_server_recv_hello(c) < 0) break;
|
||||
case MG_TLS_STATE_SERVER_RETRY:
|
||||
if ((r = mg_tls_server_recv_hello(c)) < 0) break;
|
||||
if (r == 1) { // No P-256 key share, ask for it
|
||||
if (!mg_tls_server_send_hrr(c)) return false;
|
||||
tls->state = MG_TLS_STATE_SERVER_RETRY;
|
||||
break;
|
||||
}
|
||||
if (!mg_tls_server_send_hello(c)) return false;
|
||||
mg_tls_generate_handshake_keys(c);
|
||||
if (!mg_tls_server_send_ext(c)) return false;
|
||||
if (tls->is_twoway && !mg_tls_server_send_cert_request(c)) return false;
|
||||
if (!mg_tls_send_cert(c, false) || !mg_tls_send_cert_verify(c, false) ||
|
||||
!mg_tls_server_send_finish(c))
|
||||
return false;
|
||||
if (!mg_tls_send_cert(c, false)) return false;
|
||||
if (!mg_tls_send_cert_verify(c, false)) return false;
|
||||
if (!mg_tls_server_send_finish(c)) return false;
|
||||
if (tls->is_twoway) {
|
||||
// generate application keys at this point, keep using handshake keys
|
||||
struct tls_enc hs_keys = tls->enc;
|
||||
@@ -2149,16 +2241,27 @@ void mg_tls_handshake(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
long n;
|
||||
bool res;
|
||||
uint64_t t;
|
||||
if (c->is_closing) return; // we don't clear rx buf, so ignore what's left
|
||||
if (tls->is_sntp_pending) return;
|
||||
t = mg_millis();
|
||||
if (c->is_client) {
|
||||
// will clear is_hs when sending last chunk
|
||||
res = mg_tls_client_handshake(c);
|
||||
} else {
|
||||
res = mg_tls_server_handshake(c);
|
||||
}
|
||||
if (!res) {
|
||||
mg_error(c, "TLS OOM");
|
||||
tls->hs_proc += mg_millis() - t;
|
||||
if (res && !c->is_tls_hs) {
|
||||
// CPU: key exchange, signing, certificate verification. Wait: the rest of
|
||||
// the total, waiting for the peer and the network
|
||||
unsigned long total = (unsigned long) (mg_millis() - tls->hs_start);
|
||||
unsigned long cpu = (unsigned long) tls->hs_proc;
|
||||
MG_DEBUG(("%lu TLS handshake done in %lu ms: %lu ms CPU, %lu ms peer wait",
|
||||
c->id, total, cpu, total > cpu ? total - cpu : 0));
|
||||
}
|
||||
if (!res) { // already mg_error()ed at handshake helper ?
|
||||
if (!c->is_closing) mg_error(c, "TLS OOM"); // No, do it
|
||||
return;
|
||||
}
|
||||
while (tls->send.len > 0 &&
|
||||
@@ -2575,7 +2678,7 @@ static int mg_parse_pem_certs(const struct mg_str pem, struct mg_str **ders) {
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len) {
|
||||
size_t mg_p256_parse_private_key(struct mg_str key, uint8_t *buf, size_t len) {
|
||||
struct mg_str der = mg_str_n(NULL, 0);
|
||||
size_t n = 0;
|
||||
if (buf == NULL || len < 32) return 0;
|
||||
@@ -2615,6 +2718,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
||||
mg_error(c, "tls oom");
|
||||
return;
|
||||
}
|
||||
tls->hs_start = mg_millis();
|
||||
|
||||
tls->state =
|
||||
c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
|
||||
@@ -2658,7 +2762,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
||||
if (!c->is_client) tls->is_twoway = true; // server + CA: two-way auth
|
||||
}
|
||||
|
||||
if (opts->cert.buf == NULL) {
|
||||
if (opts->cert.buf == NULL || opts->cert.len == 0) {
|
||||
MG_VERBOSE(("No certificate provided"));
|
||||
goto xit;
|
||||
}
|
||||
@@ -2687,7 +2791,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
||||
}
|
||||
|
||||
// parse PEM or DER EC key
|
||||
if (opts->key.buf == NULL) {
|
||||
if (opts->key.buf == NULL || opts->key.len == 0) {
|
||||
mg_error(c, "Certificate provided without a private key");
|
||||
return;
|
||||
}
|
||||
@@ -2738,7 +2842,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
||||
MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
|
||||
} else {
|
||||
mg_free((void *) key.buf);
|
||||
MG_INFO(("Parsed PKCS#8 EC private key"));
|
||||
MG_VERBOSE(("Parsed PKCS#8 EC private key"));
|
||||
}
|
||||
} else {
|
||||
mg_free((void *) key.buf);
|
||||
@@ -2789,6 +2893,7 @@ void mg_tls_free(struct mg_connection *c) {
|
||||
}
|
||||
mg_free((void *) tls->ca_der.buf);
|
||||
mg_free((void *) tls->rsa_key_der.buf);
|
||||
mg_bzero((unsigned char *) tls, sizeof(*tls)); // Wipe keys and secrets
|
||||
}
|
||||
mg_free(c->tls);
|
||||
c->tls = NULL;
|
||||
@@ -2799,11 +2904,15 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
long n = MG_IO_WAIT;
|
||||
bool was_throttled = c->is_tls_throttled; // see #3074
|
||||
if (!was_throttled) { // encrypt new data
|
||||
size_t i, rs = MG_TLS_RECORD_SIZE > 16384 ? 16384 : MG_TLS_RECORD_SIZE;
|
||||
if (len > MG_IO_SIZE) len = MG_IO_SIZE;
|
||||
if (len > 16384) len = 16384;
|
||||
if (!mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA))
|
||||
return 0; // returning 0 means an OOM condition (iobuf couldn't resize),
|
||||
// yet this is so far recoverable, let the caller decide
|
||||
for (i = 0; i < len; i += rs) { // Split into records, send them at once
|
||||
size_t m = len - i < rs ? len - i : rs;
|
||||
if (!mg_tls_encrypt(c, (const uint8_t *) buf + i, m, MG_TLS_APP_DATA))
|
||||
return 0; // returning 0 means an OOM condition (iobuf couldn't
|
||||
// resize), yet this is so far recoverable, let the caller
|
||||
// decide
|
||||
}
|
||||
} // else, resend outstanding encrypted data in tls->send
|
||||
while (tls->send.len > 0 &&
|
||||
(n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
|
||||
@@ -2846,13 +2955,16 @@ size_t mg_tls_pending(struct mg_connection *c) {
|
||||
return tls != NULL ? tls->recv_len : 0;
|
||||
}
|
||||
|
||||
void mg_tls_flush(struct mg_connection *c) {
|
||||
long mg_tls_flush(struct mg_connection *c) {
|
||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||
long n;
|
||||
long n = 0;
|
||||
while (tls->send.len > 0 &&
|
||||
(n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
|
||||
mg_iobuf_del(&tls->send, 0, (size_t) n);
|
||||
}
|
||||
if (n == MG_IO_ERR) return n;
|
||||
c->is_tls_throttled = tls->send.len > 0;
|
||||
return c->is_tls_throttled ? MG_IO_WAIT : 0;
|
||||
}
|
||||
|
||||
void mg_tls_ctx_init(struct mg_mgr *mgr) {
|
||||
|
||||
@@ -60,9 +60,6 @@ static PORTABLE_8439_DECL void poly1305_finish(poly1305_context *ctx,
|
||||
// ******* END: poly1305-donna/poly1305-donna.h ********
|
||||
// ******* BEGIN: chacha-portable.c ********
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
// this is a fresh implementation of chacha20, based on the description in
|
||||
// rfc8349 it's such a nice compact algorithm that it is easy to do. In
|
||||
// relationship to other c implementation this implementation:
|
||||
|
||||
+2
-1
@@ -21,8 +21,9 @@ size_t mg_tls_pending(struct mg_connection *c) {
|
||||
(void) c;
|
||||
return 0;
|
||||
}
|
||||
void mg_tls_flush(struct mg_connection *c) {
|
||||
long mg_tls_flush(struct mg_connection *c) {
|
||||
(void) c;
|
||||
return 0;
|
||||
}
|
||||
void mg_tls_ctx_init(struct mg_mgr *mgr) {
|
||||
(void) mgr;
|
||||
|
||||
+7
-4
@@ -249,17 +249,20 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
return n;
|
||||
}
|
||||
|
||||
void mg_tls_flush(struct mg_connection *c) {
|
||||
long mg_tls_flush(struct mg_connection *c) {
|
||||
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
||||
long n = 0;
|
||||
if (c->is_tls_throttled && c->is_draining) {
|
||||
long n =
|
||||
mbedtls_ssl_write(&tls->ssl, tls->throttled_buf, tls->throttled_len);
|
||||
n = mbedtls_ssl_write(&tls->ssl, tls->throttled_buf, tls->throttled_len);
|
||||
#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
|
||||
if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) return;
|
||||
if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) return MG_IO_WAIT;
|
||||
#endif
|
||||
c->is_tls_throttled =
|
||||
(n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
if (c->is_tls_throttled) return MG_IO_WAIT;
|
||||
if (n <= 0) return MG_IO_ERR;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mg_tls_ctx_init(struct mg_mgr *mgr) {
|
||||
|
||||
+2
-1
@@ -335,8 +335,9 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
return n;
|
||||
}
|
||||
|
||||
void mg_tls_flush(struct mg_connection *c) {
|
||||
long mg_tls_flush(struct mg_connection *c) {
|
||||
(void) c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mg_tls_ctx_init(struct mg_mgr *mgr) {
|
||||
|
||||
+2553
File diff suppressed because it is too large.
Load diff
+181
@@ -0,0 +1,181 @@
|
||||
// P-256 API, mg_p256_*(), used by built-in TLS, JWT and OTA. Implemented by
|
||||
// BearSSL code below (MG_P256=MG_P256_BEAR) or tls_p256_cortex.c (LENNGREN)
|
||||
// BearSSL code: https://bearssl.org commit 7bea48e. Shared bignum and ECDSA
|
||||
// code is always compiled, it is also used by P-384, see tls_p384.c
|
||||
//
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Mongoose changes: br_ / BR_ names are prefixed with mg_ / MG_, as are the
|
||||
// constant-time helpers from inner.h (NOT, MUX, EQ, ...). Structs are not
|
||||
// typedef-ed. ECDSA signing takes the caller's k instead of HMAC-DRBG, k is
|
||||
// computed by mg_p256_sign() (RFC 6979). ECDSA functions take the curve id and
|
||||
// public key directly
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "config.h"
|
||||
#include "str.h"
|
||||
#include "tls.h"
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
|
||||
#define MG_BR_EC_secp256r1 23 // Curve IDs, same as TLS named groups
|
||||
#define MG_BR_EC_secp384r1 24
|
||||
#define MG_BR_MAX_EC_SIZE 384 // Largest supported curve, bits
|
||||
|
||||
// From BearSSL inc/bearssl_ec.h: an EC implementation
|
||||
struct mg_br_ec_impl {
|
||||
uint32_t supported_curves; // Bit mask of curve IDs
|
||||
const unsigned char *(*generator)(int curve, size_t *len);
|
||||
const unsigned char *(*order)(int curve, size_t *len);
|
||||
size_t (*xoff)(int curve, size_t *len);
|
||||
uint32_t (*mul)(unsigned char *G, size_t Glen, const unsigned char *x,
|
||||
size_t xlen, int curve);
|
||||
size_t (*mulgen)(unsigned char *R, const unsigned char *x, size_t xlen,
|
||||
int curve);
|
||||
uint32_t (*muladd)(unsigned char *A, const unsigned char *B, size_t len,
|
||||
const unsigned char *x, size_t xlen,
|
||||
const unsigned char *y, size_t ylen, int curve);
|
||||
};
|
||||
|
||||
// From BearSSL src/inner.h: constant-time helpers
|
||||
static inline uint32_t MG_BR_NOT(uint32_t ctl) {
|
||||
return ctl ^ 1;
|
||||
}
|
||||
static inline uint32_t MG_BR_MUX(uint32_t ctl, uint32_t x, uint32_t y) {
|
||||
return y ^ (-ctl & (x ^ y));
|
||||
}
|
||||
static inline uint32_t MG_BR_EQ(uint32_t x, uint32_t y) {
|
||||
uint32_t q = x ^ y;
|
||||
return MG_BR_NOT((q | -q) >> 31);
|
||||
}
|
||||
static inline uint32_t MG_BR_NEQ(uint32_t x, uint32_t y) {
|
||||
uint32_t q = x ^ y;
|
||||
return (q | -q) >> 31;
|
||||
}
|
||||
static inline uint32_t MG_BR_GT(uint32_t x, uint32_t y) {
|
||||
uint32_t z = y - x;
|
||||
return (z ^ ((x ^ y) & (x ^ z))) >> 31;
|
||||
}
|
||||
#define MG_BR_GE(x, y) MG_BR_NOT(MG_BR_GT(y, x))
|
||||
#define MG_BR_LT(x, y) MG_BR_GT(y, x)
|
||||
static inline int32_t MG_BR_CMP(uint32_t x, uint32_t y) {
|
||||
return (int32_t) MG_BR_GT(x, y) | -(int32_t) MG_BR_GT(y, x);
|
||||
}
|
||||
static inline uint32_t MG_BR_BIT_LENGTH(uint32_t x) {
|
||||
uint32_t k, c;
|
||||
k = MG_BR_NEQ(x, 0);
|
||||
c = MG_BR_GT(x, 0xFFFF), x = MG_BR_MUX(c, x >> 16, x), k += c << 4;
|
||||
c = MG_BR_GT(x, 0x00FF), x = MG_BR_MUX(c, x >> 8, x), k += c << 3;
|
||||
c = MG_BR_GT(x, 0x000F), x = MG_BR_MUX(c, x >> 4, x), k += c << 2;
|
||||
c = MG_BR_GT(x, 0x0003), x = MG_BR_MUX(c, x >> 2, x), k += c << 1;
|
||||
k += MG_BR_GT(x, 0x0001);
|
||||
return k;
|
||||
}
|
||||
#define MG_BR_MUL31(x, y) ((uint64_t) (x) * (uint64_t) (y))
|
||||
#define MG_BR_MUL31_lo(x, y) \
|
||||
(((uint32_t) (x) * (uint32_t) (y)) & (uint32_t) 0x7FFFFFFF)
|
||||
#define MG_BR_CCOPY mg_br_ccopy
|
||||
void mg_br_ccopy(uint32_t ctl, void *dst, const void *src, size_t len);
|
||||
uint32_t mg_br_divrem(uint32_t hi, uint32_t lo, uint32_t d, uint32_t *r);
|
||||
static inline uint32_t mg_br_rem(uint32_t hi, uint32_t lo, uint32_t d) {
|
||||
uint32_t r;
|
||||
mg_br_divrem(hi, lo, d, &r);
|
||||
return r;
|
||||
}
|
||||
static inline uint32_t mg_br_div(uint32_t hi, uint32_t lo, uint32_t d) {
|
||||
uint32_t r;
|
||||
return mg_br_divrem(hi, lo, d, &r);
|
||||
}
|
||||
static inline void mg_br_enc32be(void *dst, uint32_t x) {
|
||||
unsigned char *buf = (unsigned char *) dst;
|
||||
buf[0] = (unsigned char) (x >> 24), buf[1] = (unsigned char) (x >> 16);
|
||||
buf[2] = (unsigned char) (x >> 8), buf[3] = (unsigned char) x;
|
||||
}
|
||||
static inline void mg_br_i31_zero(uint32_t *x, uint32_t bit_len) {
|
||||
*x++ = bit_len;
|
||||
memset(x, 0, ((bit_len + 31) >> 5) * sizeof *x);
|
||||
}
|
||||
|
||||
// Big integers, "i31" format, see BearSSL src/inner.h
|
||||
uint32_t mg_br_i31_add(uint32_t *a, const uint32_t *b, uint32_t ctl);
|
||||
uint32_t mg_br_i31_sub(uint32_t *a, const uint32_t *b, uint32_t ctl);
|
||||
uint32_t mg_br_i31_bit_length(uint32_t *x, size_t xlen);
|
||||
uint32_t mg_br_i31_decode_mod(uint32_t *x, const void *src, size_t len,
|
||||
const uint32_t *m);
|
||||
void mg_br_i31_decode(uint32_t *x, const void *src, size_t len);
|
||||
void mg_br_i31_encode(void *dst, size_t len, const uint32_t *x);
|
||||
void mg_br_i31_from_monty(uint32_t *x, const uint32_t *m, uint32_t m0i);
|
||||
void mg_br_i31_to_monty(uint32_t *x, const uint32_t *m);
|
||||
uint32_t mg_br_i31_iszero(const uint32_t *x);
|
||||
void mg_br_i31_modpow(uint32_t *x, const unsigned char *e, size_t elen,
|
||||
const uint32_t *m, uint32_t m0i, uint32_t *t1,
|
||||
uint32_t *t2);
|
||||
void mg_br_i31_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y,
|
||||
const uint32_t *m, uint32_t m0i);
|
||||
void mg_br_i31_muladd_small(uint32_t *x, uint32_t z, const uint32_t *m);
|
||||
uint32_t mg_br_i31_ninv31(uint32_t x);
|
||||
void mg_br_i31_rshift(uint32_t *x, int count);
|
||||
|
||||
// ECDSA, see BearSSL src/ec/ecdsa_i31_*.c. q is an uncompressed point
|
||||
void mg_br_ecdsa_i31_bits2int(uint32_t *x, const void *src, size_t len,
|
||||
uint32_t ebitlen);
|
||||
uint32_t mg_br_ecdsa_i31_vrfy_raw(const struct mg_br_ec_impl *impl, int curve,
|
||||
const void *hash, size_t hash_len,
|
||||
const unsigned char *q, size_t qlen,
|
||||
const void *sig, size_t sig_len);
|
||||
size_t mg_br_ecdsa_i31_sign_raw(const struct mg_br_ec_impl *impl, int curve,
|
||||
const void *hash, size_t hash_len,
|
||||
const unsigned char *x, size_t xlen,
|
||||
const unsigned char *k, void *sig);
|
||||
|
||||
// P-256 (secp256r1) API. Keys and signatures are big endian byte strings,
|
||||
// a public key is X || Y, a signature is r || s. Functions return 1 on
|
||||
// success, 0 on failure
|
||||
|
||||
// Generate a random key pair, using mg_random()
|
||||
int mg_p256_make_key(uint8_t public_key[64], uint8_t private_key[32]);
|
||||
|
||||
// Compute public key from a private key. Return 0 if private key is invalid
|
||||
int mg_p256_compute_public_key(const uint8_t private_key[32],
|
||||
uint8_t public_key[64]);
|
||||
|
||||
// ECDH. Validates the peer public key, returns 0 if it is not on the curve
|
||||
int mg_p256_shared_secret(const uint8_t public_key[64],
|
||||
const uint8_t private_key[32], uint8_t secret[32]);
|
||||
|
||||
// ECDSA sign with a deterministic k, RFC 6979 with HMAC-SHA256
|
||||
int mg_p256_sign(const uint8_t private_key[32], const uint8_t *hash,
|
||||
size_t hash_size, uint8_t signature[64]);
|
||||
|
||||
// ECDSA verify. Validates the public key
|
||||
int mg_p256_verify(const uint8_t public_key[64], const uint8_t *hash,
|
||||
size_t hash_size, const uint8_t signature[64]);
|
||||
|
||||
// Internal: ECDSA sign with a given k, 32 bytes big endian, in [1, n-1].
|
||||
// Provided by the selected implementation, used by mg_p256_sign()
|
||||
int mg_p256_sign_with_k(const uint8_t private_key[32], const uint8_t *hash,
|
||||
size_t hash_size, const uint8_t k[32],
|
||||
uint8_t signature[64]);
|
||||
#endif // MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
File diff suppressed because it is too large.
Load diff
+834
@@ -0,0 +1,834 @@
|
||||
// P-384 from BearSSL, https://bearssl.org commit 7bea48e
|
||||
// P-384 signature verification, used by built-in TLS for certificates and
|
||||
// CertificateVerify. Shared bignum and ECDSA code is in tls_p256.c
|
||||
//
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Mongoose changes: br_ / BR_ names are prefixed with mg_ / MG_, static
|
||||
// functions with p384_. Trimmed to P-384. Structs are not typedef-ed
|
||||
|
||||
#include "tls_p384.h"
|
||||
#include "tls_p256.h"
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
|
||||
// From BearSSL src/ec/ec_secp384r1.c
|
||||
static const unsigned char P384_N[] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
|
||||
0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A,
|
||||
0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73
|
||||
};
|
||||
|
||||
static const unsigned char P384_G[] = {
|
||||
0x04, 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05,
|
||||
0x37, 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD,
|
||||
0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B,
|
||||
0x98, 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A,
|
||||
0x38, 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29,
|
||||
0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A,
|
||||
0xB7, 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C,
|
||||
0x6F, 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC,
|
||||
0x29, 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14,
|
||||
0x7C, 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8,
|
||||
0xC0, 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81,
|
||||
0x9D, 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E,
|
||||
0x5F
|
||||
};
|
||||
|
||||
|
||||
// From BearSSL src/ec/ec_prime_i31.c
|
||||
/*
|
||||
* Parameters for supported curves (field modulus, and 'b' equation
|
||||
* parameter; both values use the 'i31' format, and 'b' is in Montgomery
|
||||
* representation).
|
||||
*/
|
||||
|
||||
static const uint32_t P384_P[] = {
|
||||
0x0000018C,
|
||||
0x7FFFFFFF, 0x00000001, 0x00000000, 0x7FFFFFF8,
|
||||
0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
|
||||
0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
|
||||
0x00000FFF
|
||||
};
|
||||
|
||||
static const uint32_t P384_R2[] = {
|
||||
0x0000018C,
|
||||
0x00000000, 0x00000080, 0x7FFFFE00, 0x000001FF,
|
||||
0x00000800, 0x00000000, 0x7FFFE000, 0x00001FFF,
|
||||
0x00008000, 0x00008000, 0x00000000, 0x00000000,
|
||||
0x00000000
|
||||
};
|
||||
|
||||
static const uint32_t P384_B[] = {
|
||||
0x0000018C,
|
||||
0x6E666840, 0x070D0392, 0x5D810231, 0x7651D50C,
|
||||
0x17E218D6, 0x1B192002, 0x44EFE441, 0x3A524E2B,
|
||||
0x2719BA5F, 0x41F02209, 0x36C5643E, 0x5813EFFE,
|
||||
0x000008A5
|
||||
};
|
||||
|
||||
struct p384_curve_params {
|
||||
const uint32_t *p;
|
||||
const uint32_t *b;
|
||||
const uint32_t *R2;
|
||||
uint32_t p0i;
|
||||
size_t point_len;
|
||||
};
|
||||
|
||||
static inline const struct p384_curve_params *
|
||||
p384_id_to_curve(int curve)
|
||||
{
|
||||
static const struct p384_curve_params pp = {
|
||||
P384_P, P384_B, P384_R2, 0x00000001, 97
|
||||
};
|
||||
|
||||
(void)curve;
|
||||
return &pp;
|
||||
}
|
||||
|
||||
#define I31_LEN ((MG_BR_MAX_EC_SIZE + 61) / 31)
|
||||
|
||||
/*
|
||||
* Type for a point in Jacobian coordinates:
|
||||
* -- three values, x, y and z, in Montgomery representation
|
||||
* -- affine coordinates are X = x / z^2 and Y = y / z^3
|
||||
* -- for the point at infinity, z = 0
|
||||
*/
|
||||
struct p384_jacobian {
|
||||
uint32_t c[3][I31_LEN];
|
||||
};
|
||||
|
||||
/*
|
||||
* We use a custom interpreter that uses a dozen registers, and
|
||||
* only six operations:
|
||||
* MSET(d, a) copy a into d
|
||||
* MADD(d, a) d = d+a (modular)
|
||||
* MSUB(d, a) d = d-a (modular)
|
||||
* MMUL(d, a, b) d = a*b (Montgomery multiplication)
|
||||
* MINV(d, a, b) invert d modulo p; a and b are used as scratch registers
|
||||
* MTZ(d) clear return value if d = 0
|
||||
* Destination of MMUL (d) must be distinct from operands (a and b).
|
||||
* There is no such constraint for MSUB and MADD.
|
||||
*
|
||||
* Registers include the operand coordinates, and temporaries.
|
||||
*/
|
||||
#define MSET(d, a) (0x0000 + ((d) << 8) + ((a) << 4))
|
||||
#define MADD(d, a) (0x1000 + ((d) << 8) + ((a) << 4))
|
||||
#define MSUB(d, a) (0x2000 + ((d) << 8) + ((a) << 4))
|
||||
#define MMUL(d, a, b) (0x3000 + ((d) << 8) + ((a) << 4) + (b))
|
||||
#define MINV(d, a, b) (0x4000 + ((d) << 8) + ((a) << 4) + (b))
|
||||
#define MTZ(d) (0x5000 + ((d) << 8))
|
||||
#define ENDCODE 0
|
||||
|
||||
/*
|
||||
* Registers for the input operands.
|
||||
*/
|
||||
#define P1x 0
|
||||
#define P1y 1
|
||||
#define P1z 2
|
||||
#define P2x 3
|
||||
#define P2y 4
|
||||
#define P2z 5
|
||||
|
||||
/*
|
||||
* Alternate names for the first input operand.
|
||||
*/
|
||||
#define Px 0
|
||||
#define Py 1
|
||||
#define Pz 2
|
||||
|
||||
/*
|
||||
* Temporaries.
|
||||
*/
|
||||
#define t1 6
|
||||
#define t2 7
|
||||
#define t3 8
|
||||
#define t4 9
|
||||
#define t5 10
|
||||
#define t6 11
|
||||
#define t7 12
|
||||
|
||||
/*
|
||||
* Extra scratch registers available when there is no second operand (e.g.
|
||||
* for "double" and "affine").
|
||||
*/
|
||||
#define t8 3
|
||||
#define t9 4
|
||||
#define t10 5
|
||||
|
||||
/*
|
||||
* Doubling formulas are:
|
||||
*
|
||||
* s = 4*x*y^2
|
||||
* m = 3*(x + z^2)*(x - z^2)
|
||||
* x' = m^2 - 2*s
|
||||
* y' = m*(s - x') - 8*y^4
|
||||
* z' = 2*y*z
|
||||
*
|
||||
* If y = 0 (P has order 2) then this yields infinity (z' = 0), as it
|
||||
* should. This case should not happen anyway, because our curves have
|
||||
* prime order, and thus do not contain any point of order 2.
|
||||
*
|
||||
* If P is infinity (z = 0), then again the formulas yield infinity,
|
||||
* which is correct. Thus, this code works for all points.
|
||||
*
|
||||
* Cost: 8 multiplications
|
||||
*/
|
||||
static const uint16_t p384_code_double[] = {
|
||||
/*
|
||||
* Compute z^2 (in t1).
|
||||
*/
|
||||
MMUL(t1, Pz, Pz),
|
||||
|
||||
/*
|
||||
* Compute x-z^2 (in t2) and then x+z^2 (in t1).
|
||||
*/
|
||||
MSET(t2, Px),
|
||||
MSUB(t2, t1),
|
||||
MADD(t1, Px),
|
||||
|
||||
/*
|
||||
* Compute m = 3*(x+z^2)*(x-z^2) (in t1).
|
||||
*/
|
||||
MMUL(t3, t1, t2),
|
||||
MSET(t1, t3),
|
||||
MADD(t1, t3),
|
||||
MADD(t1, t3),
|
||||
|
||||
/*
|
||||
* Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3).
|
||||
*/
|
||||
MMUL(t3, Py, Py),
|
||||
MADD(t3, t3),
|
||||
MMUL(t2, Px, t3),
|
||||
MADD(t2, t2),
|
||||
|
||||
/*
|
||||
* Compute x' = m^2 - 2*s.
|
||||
*/
|
||||
MMUL(Px, t1, t1),
|
||||
MSUB(Px, t2),
|
||||
MSUB(Px, t2),
|
||||
|
||||
/*
|
||||
* Compute z' = 2*y*z.
|
||||
*/
|
||||
MMUL(t4, Py, Pz),
|
||||
MSET(Pz, t4),
|
||||
MADD(Pz, t4),
|
||||
|
||||
/*
|
||||
* Compute y' = m*(s - x') - 8*y^4. Note that we already have
|
||||
* 2*y^2 in t3.
|
||||
*/
|
||||
MSUB(t2, Px),
|
||||
MMUL(Py, t1, t2),
|
||||
MMUL(t4, t3, t3),
|
||||
MSUB(Py, t4),
|
||||
MSUB(Py, t4),
|
||||
|
||||
ENDCODE
|
||||
};
|
||||
|
||||
/*
|
||||
* Addtions formulas are:
|
||||
*
|
||||
* u1 = x1 * z2^2
|
||||
* u2 = x2 * z1^2
|
||||
* s1 = y1 * z2^3
|
||||
* s2 = y2 * z1^3
|
||||
* h = u2 - u1
|
||||
* r = s2 - s1
|
||||
* x3 = r^2 - h^3 - 2 * u1 * h^2
|
||||
* y3 = r * (u1 * h^2 - x3) - s1 * h^3
|
||||
* z3 = h * z1 * z2
|
||||
*
|
||||
* If both P1 and P2 are infinity, then z1 == 0 and z2 == 0, implying that
|
||||
* z3 == 0, so the result is correct.
|
||||
* If either of P1 or P2 is infinity, but not both, then z3 == 0, which is
|
||||
* not correct.
|
||||
* h == 0 only if u1 == u2; this happens in two cases:
|
||||
* -- if s1 == s2 then P1 and/or P2 is infinity, or P1 == P2
|
||||
* -- if s1 != s2 then P1 + P2 == infinity (but neither P1 or P2 is infinity)
|
||||
*
|
||||
* Thus, the following situations are not handled correctly:
|
||||
* -- P1 = 0 and P2 != 0
|
||||
* -- P1 != 0 and P2 = 0
|
||||
* -- P1 = P2
|
||||
* All other cases are properly computed. However, even in "incorrect"
|
||||
* situations, the three coordinates still are properly formed field
|
||||
* elements.
|
||||
*
|
||||
* The returned flag is cleared if r == 0. This happens in the following
|
||||
* cases:
|
||||
* -- Both points are on the same horizontal line (same Y coordinate).
|
||||
* -- Both points are infinity.
|
||||
* -- One point is infinity and the other is on line Y = 0.
|
||||
* The third case cannot happen with our curves (there is no valid point
|
||||
* on line Y = 0 since that would be a point of order 2). If the two
|
||||
* source points are non-infinity, then remains only the case where the
|
||||
* two points are on the same horizontal line.
|
||||
*
|
||||
* This allows us to detect the "P1 == P2" case, assuming that P1 != 0 and
|
||||
* P2 != 0:
|
||||
* -- If the returned value is not the point at infinity, then it was properly
|
||||
* computed.
|
||||
* -- Otherwise, if the returned flag is 1, then P1+P2 = 0, and the result
|
||||
* is indeed the point at infinity.
|
||||
* -- Otherwise (result is infinity, flag is 0), then P1 = P2 and we should
|
||||
* use the 'double' code.
|
||||
*
|
||||
* Cost: 16 multiplications
|
||||
*/
|
||||
static const uint16_t p384_code_add[] = {
|
||||
/*
|
||||
* Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3).
|
||||
*/
|
||||
MMUL(t3, P2z, P2z),
|
||||
MMUL(t1, P1x, t3),
|
||||
MMUL(t4, P2z, t3),
|
||||
MMUL(t3, P1y, t4),
|
||||
|
||||
/*
|
||||
* Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4).
|
||||
*/
|
||||
MMUL(t4, P1z, P1z),
|
||||
MMUL(t2, P2x, t4),
|
||||
MMUL(t5, P1z, t4),
|
||||
MMUL(t4, P2y, t5),
|
||||
|
||||
/*
|
||||
* Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4).
|
||||
*/
|
||||
MSUB(t2, t1),
|
||||
MSUB(t4, t3),
|
||||
|
||||
/*
|
||||
* Report cases where r = 0 through the returned flag.
|
||||
*/
|
||||
MTZ(t4),
|
||||
|
||||
/*
|
||||
* Compute u1*h^2 (in t6) and h^3 (in t5).
|
||||
*/
|
||||
MMUL(t7, t2, t2),
|
||||
MMUL(t6, t1, t7),
|
||||
MMUL(t5, t7, t2),
|
||||
|
||||
/*
|
||||
* Compute x3 = r^2 - h^3 - 2*u1*h^2.
|
||||
* t1 and t7 can be used as scratch registers.
|
||||
*/
|
||||
MMUL(P1x, t4, t4),
|
||||
MSUB(P1x, t5),
|
||||
MSUB(P1x, t6),
|
||||
MSUB(P1x, t6),
|
||||
|
||||
/*
|
||||
* Compute y3 = r*(u1*h^2 - x3) - s1*h^3.
|
||||
*/
|
||||
MSUB(t6, P1x),
|
||||
MMUL(P1y, t4, t6),
|
||||
MMUL(t1, t5, t3),
|
||||
MSUB(P1y, t1),
|
||||
|
||||
/*
|
||||
* Compute z3 = h*z1*z2.
|
||||
*/
|
||||
MMUL(t1, P1z, P2z),
|
||||
MMUL(P1z, t1, t2),
|
||||
|
||||
ENDCODE
|
||||
};
|
||||
|
||||
/*
|
||||
* Check that the point is on the curve. This code snippet assumes the
|
||||
* following conventions:
|
||||
* -- Coordinates x and y have been freshly decoded in P1 (but not
|
||||
* converted to Montgomery coordinates yet).
|
||||
* -- P2x, P2y and P2z are set to, respectively, R^2, b*R and 1.
|
||||
*/
|
||||
static const uint16_t p384_code_check[] = {
|
||||
|
||||
/* Convert x and y to Montgomery representation. */
|
||||
MMUL(t1, P1x, P2x),
|
||||
MMUL(t2, P1y, P2x),
|
||||
MSET(P1x, t1),
|
||||
MSET(P1y, t2),
|
||||
|
||||
/* Compute x^3 in t1. */
|
||||
MMUL(t2, P1x, P1x),
|
||||
MMUL(t1, P1x, t2),
|
||||
|
||||
/* Subtract 3*x from t1. */
|
||||
MSUB(t1, P1x),
|
||||
MSUB(t1, P1x),
|
||||
MSUB(t1, P1x),
|
||||
|
||||
/* Add b. */
|
||||
MADD(t1, P2y),
|
||||
|
||||
/* Compute y^2 in t2. */
|
||||
MMUL(t2, P1y, P1y),
|
||||
|
||||
/* Compare y^2 with x^3 - 3*x + b; they must match. */
|
||||
MSUB(t1, t2),
|
||||
MTZ(t1),
|
||||
|
||||
/* Set z to 1 (in Montgomery representation). */
|
||||
MMUL(P1z, P2x, P2z),
|
||||
|
||||
ENDCODE
|
||||
};
|
||||
|
||||
/*
|
||||
* Conversion back to affine coordinates. This code snippet assumes that
|
||||
* the z coordinate of P2 is set to 1 (not in Montgomery representation).
|
||||
*/
|
||||
static const uint16_t p384_code_affine[] = {
|
||||
|
||||
/* Save z*R in t1. */
|
||||
MSET(t1, P1z),
|
||||
|
||||
/* Compute z^3 in t2. */
|
||||
MMUL(t2, P1z, P1z),
|
||||
MMUL(t3, P1z, t2),
|
||||
MMUL(t2, t3, P2z),
|
||||
|
||||
/* Invert to (1/z^3) in t2. */
|
||||
MINV(t2, t3, t4),
|
||||
|
||||
/* Compute y. */
|
||||
MSET(t3, P1y),
|
||||
MMUL(P1y, t2, t3),
|
||||
|
||||
/* Compute (1/z^2) in t3. */
|
||||
MMUL(t3, t2, t1),
|
||||
|
||||
/* Compute x. */
|
||||
MSET(t2, P1x),
|
||||
MMUL(P1x, t2, t3),
|
||||
|
||||
ENDCODE
|
||||
};
|
||||
|
||||
static uint32_t
|
||||
p384_run_code(struct p384_jacobian *P1, const struct p384_jacobian *P2,
|
||||
const struct p384_curve_params *cc, const uint16_t *code)
|
||||
{
|
||||
uint32_t r;
|
||||
uint32_t t[13][I31_LEN];
|
||||
size_t u;
|
||||
|
||||
r = 1;
|
||||
|
||||
/*
|
||||
* Copy the two operands in the dedicated registers.
|
||||
*/
|
||||
memcpy(t[P1x], P1->c, 3 * I31_LEN * sizeof(uint32_t));
|
||||
memcpy(t[P2x], P2->c, 3 * I31_LEN * sizeof(uint32_t));
|
||||
|
||||
/*
|
||||
* Run formulas.
|
||||
*/
|
||||
for (u = 0;; u ++) {
|
||||
unsigned op, d, a, b;
|
||||
|
||||
op = code[u];
|
||||
if (op == 0) {
|
||||
break;
|
||||
}
|
||||
d = (op >> 8) & 0x0F;
|
||||
a = (op >> 4) & 0x0F;
|
||||
b = op & 0x0F;
|
||||
op >>= 12;
|
||||
switch (op) {
|
||||
uint32_t ctl;
|
||||
size_t plen;
|
||||
unsigned char tp[(MG_BR_MAX_EC_SIZE + 7) >> 3];
|
||||
|
||||
case 0:
|
||||
memcpy(t[d], t[a], I31_LEN * sizeof(uint32_t));
|
||||
break;
|
||||
case 1:
|
||||
ctl = mg_br_i31_add(t[d], t[a], 1);
|
||||
ctl |= MG_BR_NOT(mg_br_i31_sub(t[d], cc->p, 0));
|
||||
mg_br_i31_sub(t[d], cc->p, ctl);
|
||||
break;
|
||||
case 2:
|
||||
mg_br_i31_add(t[d], cc->p, mg_br_i31_sub(t[d], t[a], 1));
|
||||
break;
|
||||
case 3:
|
||||
mg_br_i31_montymul(t[d], t[a], t[b], cc->p, cc->p0i);
|
||||
break;
|
||||
case 4:
|
||||
plen = (cc->p[0] - (cc->p[0] >> 5) + 7) >> 3;
|
||||
mg_br_i31_encode(tp, plen, cc->p);
|
||||
tp[plen - 1] -= 2;
|
||||
mg_br_i31_modpow(t[d], tp, plen,
|
||||
cc->p, cc->p0i, t[a], t[b]);
|
||||
break;
|
||||
default:
|
||||
r &= ~mg_br_i31_iszero(t[d]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy back result.
|
||||
*/
|
||||
memcpy(P1->c, t[P1x], 3 * I31_LEN * sizeof(uint32_t));
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
p384_set_one(uint32_t *x, const uint32_t *p)
|
||||
{
|
||||
size_t plen;
|
||||
|
||||
plen = (p[0] + 63) >> 5;
|
||||
memset(x, 0, plen * sizeof *x);
|
||||
x[0] = p[0];
|
||||
x[1] = 0x00000001;
|
||||
}
|
||||
|
||||
static void
|
||||
p384_point_zero(struct p384_jacobian *P, const struct p384_curve_params *cc)
|
||||
{
|
||||
memset(P, 0, sizeof *P);
|
||||
P->c[0][0] = P->c[1][0] = P->c[2][0] = cc->p[0];
|
||||
}
|
||||
|
||||
static inline void
|
||||
p384_point_double(struct p384_jacobian *P, const struct p384_curve_params *cc)
|
||||
{
|
||||
p384_run_code(P, P, cc, p384_code_double);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
p384_point_add(struct p384_jacobian *P1, const struct p384_jacobian *P2, const struct p384_curve_params *cc)
|
||||
{
|
||||
return p384_run_code(P1, P2, cc, p384_code_add);
|
||||
}
|
||||
|
||||
static void
|
||||
p384_point_mul(struct p384_jacobian *P, const unsigned char *x, size_t xlen,
|
||||
const struct p384_curve_params *cc)
|
||||
{
|
||||
/*
|
||||
* We do a simple double-and-add ladder with a 2-bit window
|
||||
* to make only one add every two doublings. We thus first
|
||||
* precompute 2P and 3P in some local buffers.
|
||||
*
|
||||
* We always perform two doublings and one addition; the
|
||||
* addition is with P, 2P and 3P and is done in a temporary
|
||||
* array.
|
||||
*
|
||||
* The addition code cannot handle cases where one of the
|
||||
* operands is infinity, which is the case at the start of the
|
||||
* ladder. We therefore need to maintain a flag that controls
|
||||
* this situation.
|
||||
*/
|
||||
uint32_t qz;
|
||||
struct p384_jacobian P2, P3, Q, T, U;
|
||||
|
||||
memcpy(&P2, P, sizeof P2);
|
||||
p384_point_double(&P2, cc);
|
||||
memcpy(&P3, P, sizeof P3);
|
||||
p384_point_add(&P3, &P2, cc);
|
||||
|
||||
p384_point_zero(&Q, cc);
|
||||
qz = 1;
|
||||
while (xlen -- > 0) {
|
||||
int k;
|
||||
|
||||
for (k = 6; k >= 0; k -= 2) {
|
||||
uint32_t bits;
|
||||
uint32_t bnz;
|
||||
|
||||
p384_point_double(&Q, cc);
|
||||
p384_point_double(&Q, cc);
|
||||
memcpy(&T, P, sizeof T);
|
||||
memcpy(&U, &Q, sizeof U);
|
||||
bits = (*x >> k) & (uint32_t)3;
|
||||
bnz = MG_BR_NEQ(bits, 0);
|
||||
MG_BR_CCOPY(MG_BR_EQ(bits, 2), &T, &P2, sizeof T);
|
||||
MG_BR_CCOPY(MG_BR_EQ(bits, 3), &T, &P3, sizeof T);
|
||||
p384_point_add(&U, &T, cc);
|
||||
MG_BR_CCOPY(bnz & qz, &Q, &T, sizeof Q);
|
||||
MG_BR_CCOPY(bnz & ~qz, &Q, &U, sizeof Q);
|
||||
qz &= ~bnz;
|
||||
}
|
||||
x ++;
|
||||
}
|
||||
memcpy(P, &Q, sizeof Q);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode point into Jacobian coordinates. This function does not support
|
||||
* the point at infinity. If the point is invalid then this returns 0, but
|
||||
* the coordinates are still set to properly formed field elements.
|
||||
*/
|
||||
static uint32_t
|
||||
p384_point_decode(struct p384_jacobian *P, const void *src, size_t len, const struct p384_curve_params *cc)
|
||||
{
|
||||
/*
|
||||
* Points must use uncompressed format:
|
||||
* -- first byte is 0x04;
|
||||
* -- coordinates X and Y use unsigned big-endian, with the same
|
||||
* length as the field modulus.
|
||||
*
|
||||
* We don't support hybrid format (uncompressed, but first byte
|
||||
* has value 0x06 or 0x07, depending on the least significant bit
|
||||
* of Y) because it is rather useless, and explicitly forbidden
|
||||
* by PKIX (RFC 5480, section 2.2).
|
||||
*
|
||||
* We don't support compressed format either, because it is not
|
||||
* much used in practice (there are or were patent-related
|
||||
* concerns about point compression, which explains the lack of
|
||||
* generalised support). Also, point compression support would
|
||||
* need a bit more code.
|
||||
*/
|
||||
const unsigned char *buf;
|
||||
size_t plen, zlen;
|
||||
uint32_t r;
|
||||
struct p384_jacobian Q;
|
||||
|
||||
buf = (const unsigned char *)src;
|
||||
p384_point_zero(P, cc);
|
||||
plen = (cc->p[0] - (cc->p[0] >> 5) + 7) >> 3;
|
||||
if (len != 1 + (plen << 1)) {
|
||||
return 0;
|
||||
}
|
||||
r = mg_br_i31_decode_mod(P->c[0], buf + 1, plen, cc->p);
|
||||
r &= mg_br_i31_decode_mod(P->c[1], buf + 1 + plen, plen, cc->p);
|
||||
|
||||
/*
|
||||
* Check first byte.
|
||||
*/
|
||||
r &= MG_BR_EQ(buf[0], 0x04);
|
||||
/* obsolete
|
||||
r &= MG_BR_EQ(buf[0], 0x04) | (MG_BR_EQ(buf[0] & 0xFE, 0x06)
|
||||
& ~(uint32_t)(buf[0] ^ buf[plen << 1]));
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert coordinates and check that the point is valid.
|
||||
*/
|
||||
zlen = ((cc->p[0] + 63) >> 5) * sizeof(uint32_t);
|
||||
memcpy(Q.c[0], cc->R2, zlen);
|
||||
memcpy(Q.c[1], cc->b, zlen);
|
||||
p384_set_one(Q.c[2], cc->p);
|
||||
r &= ~p384_run_code(P, &Q, cc, p384_code_check);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a point. This method assumes that the point is correct and is
|
||||
* not the point at infinity. Encoded size is always 1+2*plen, where
|
||||
* plen is the field modulus length, in bytes.
|
||||
*/
|
||||
static void
|
||||
p384_point_encode(void *dst, const struct p384_jacobian *P, const struct p384_curve_params *cc)
|
||||
{
|
||||
unsigned char *buf;
|
||||
uint32_t xbl;
|
||||
size_t plen;
|
||||
struct p384_jacobian Q, T;
|
||||
|
||||
buf = (unsigned char *)dst;
|
||||
xbl = cc->p[0];
|
||||
xbl -= (xbl >> 5);
|
||||
plen = (xbl + 7) >> 3;
|
||||
buf[0] = 0x04;
|
||||
memcpy(&Q, P, sizeof *P);
|
||||
p384_set_one(T.c[2], cc->p);
|
||||
p384_run_code(&Q, &T, cc, p384_code_affine);
|
||||
mg_br_i31_encode(buf + 1, plen, Q.c[0]);
|
||||
mg_br_i31_encode(buf + 1 + plen, plen, Q.c[1]);
|
||||
}
|
||||
|
||||
static const unsigned char *
|
||||
p384_api_generator(int curve, size_t *len)
|
||||
{
|
||||
(void)curve;
|
||||
*len = sizeof P384_G;
|
||||
return P384_G;
|
||||
}
|
||||
|
||||
static const unsigned char *
|
||||
p384_api_order(int curve, size_t *len)
|
||||
{
|
||||
(void)curve;
|
||||
*len = sizeof P384_N;
|
||||
return P384_N;
|
||||
}
|
||||
|
||||
static size_t
|
||||
p384_api_xoff(int curve, size_t *len)
|
||||
{
|
||||
p384_api_generator(curve, len);
|
||||
*len >>= 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
p384_api_mul(unsigned char *G, size_t Glen,
|
||||
const unsigned char *x, size_t xlen, int curve)
|
||||
{
|
||||
uint32_t r;
|
||||
const struct p384_curve_params *cc;
|
||||
struct p384_jacobian P;
|
||||
|
||||
cc = p384_id_to_curve(curve);
|
||||
if (Glen != cc->point_len) {
|
||||
return 0;
|
||||
}
|
||||
r = p384_point_decode(&P, G, Glen, cc);
|
||||
p384_point_mul(&P, x, xlen, cc);
|
||||
p384_point_encode(G, &P, cc);
|
||||
return r;
|
||||
}
|
||||
|
||||
static size_t
|
||||
p384_api_mulgen(unsigned char *R,
|
||||
const unsigned char *x, size_t xlen, int curve)
|
||||
{
|
||||
const unsigned char *G;
|
||||
size_t Glen;
|
||||
|
||||
G = p384_api_generator(curve, &Glen);
|
||||
memcpy(R, G, Glen);
|
||||
p384_api_mul(R, Glen, x, xlen, curve);
|
||||
return Glen;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
p384_api_muladd(unsigned char *A, const unsigned char *B, size_t len,
|
||||
const unsigned char *x, size_t xlen,
|
||||
const unsigned char *y, size_t ylen, int curve)
|
||||
{
|
||||
uint32_t r, t, z;
|
||||
const struct p384_curve_params *cc;
|
||||
struct p384_jacobian P, Q;
|
||||
|
||||
/*
|
||||
* TODO: see about merging the two ladders. Right now, we do
|
||||
* two independent point multiplications, which is a bit
|
||||
* wasteful of CPU resources (but yields short code).
|
||||
*/
|
||||
|
||||
cc = p384_id_to_curve(curve);
|
||||
if (len != cc->point_len) {
|
||||
return 0;
|
||||
}
|
||||
r = p384_point_decode(&P, A, len, cc);
|
||||
if (B == NULL) {
|
||||
size_t Glen;
|
||||
|
||||
B = p384_api_generator(curve, &Glen);
|
||||
}
|
||||
r &= p384_point_decode(&Q, B, len, cc);
|
||||
p384_point_mul(&P, x, xlen, cc);
|
||||
p384_point_mul(&Q, y, ylen, cc);
|
||||
|
||||
/*
|
||||
* We want to compute P+Q. Since the base points A and B are distinct
|
||||
* from infinity, and the multipliers are non-zero and lower than the
|
||||
* curve order, then we know that P and Q are non-infinity. This
|
||||
* leaves two special situations to test for:
|
||||
* -- If P = Q then we must use p384_point_double().
|
||||
* -- If P+Q = 0 then we must report an error.
|
||||
*/
|
||||
t = p384_point_add(&P, &Q, cc);
|
||||
p384_point_double(&Q, cc);
|
||||
z = mg_br_i31_iszero(P.c[2]);
|
||||
|
||||
/*
|
||||
* If z is 1 then either P+Q = 0 (t = 1) or P = Q (t = 0). So we
|
||||
* have the following:
|
||||
*
|
||||
* z = 0, t = 0 return P (normal addition)
|
||||
* z = 0, t = 1 return P (normal addition)
|
||||
* z = 1, t = 0 return Q (a 'double' case)
|
||||
* z = 1, t = 1 report an error (P+Q = 0)
|
||||
*/
|
||||
MG_BR_CCOPY(z & ~t, &P, &Q, sizeof Q);
|
||||
p384_point_encode(A, &P, cc);
|
||||
r &= ~(z & t);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static const struct mg_br_ec_impl mg_br_ec_prime_i31 = {
|
||||
(uint32_t)1 << MG_BR_EC_secp384r1,
|
||||
&p384_api_generator,
|
||||
&p384_api_order,
|
||||
&p384_api_xoff,
|
||||
&p384_api_mul,
|
||||
&p384_api_mulgen,
|
||||
&p384_api_muladd
|
||||
};
|
||||
|
||||
// See tls_p384.h
|
||||
int mg_p384_verify(const uint8_t public_key[96], const uint8_t *hash,
|
||||
size_t hash_size, const uint8_t signature[96]) {
|
||||
unsigned char q[97];
|
||||
q[0] = 0x04;
|
||||
memcpy(q + 1, public_key, 96);
|
||||
return mg_br_ecdsa_i31_vrfy_raw(&mg_br_ec_prime_i31, MG_BR_EC_secp384r1, hash,
|
||||
hash_size, q, sizeof(q), signature, 96) == 1;
|
||||
}
|
||||
|
||||
// Undefine local macros, mongoose.c is one translation unit
|
||||
#undef I31_LEN
|
||||
#undef MSET
|
||||
#undef MADD
|
||||
#undef MSUB
|
||||
#undef MMUL
|
||||
#undef MINV
|
||||
#undef MTZ
|
||||
#undef ENDCODE
|
||||
#undef P1x
|
||||
#undef P1y
|
||||
#undef P1z
|
||||
#undef P2x
|
||||
#undef P2y
|
||||
#undef P2z
|
||||
#undef Px
|
||||
#undef Py
|
||||
#undef Pz
|
||||
#undef t1
|
||||
#undef t2
|
||||
#undef t3
|
||||
#undef t4
|
||||
#undef t5
|
||||
#undef t6
|
||||
#undef t7
|
||||
#undef t8
|
||||
#undef t9
|
||||
#undef t10
|
||||
#endif // MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
@@ -0,0 +1,40 @@
|
||||
// P-384 from BearSSL, https://bearssl.org commit 7bea48e
|
||||
// P-384 signature verification, used by built-in TLS for certificates and
|
||||
// CertificateVerify. Shared bignum and ECDSA code is in tls_p256.c
|
||||
//
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Mongoose changes: br_ / BR_ names are prefixed with mg_ / MG_, static
|
||||
// functions with p384_. Trimmed to P-384. Structs are not typedef-ed
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "config.h"
|
||||
#include "tls.h"
|
||||
|
||||
#if MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
// Verify ECDSA P-384 signature r || s over hash. Public key is X || Y.
|
||||
// Return 1 on success, 0 on failure
|
||||
int mg_p384_verify(const uint8_t public_key[96], const uint8_t *hash,
|
||||
size_t hash_size, const uint8_t signature[96]);
|
||||
#endif // MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
|
||||
+578
-604
File diff suppressed because it is too large.
Load diff
-3304
File diff suppressed because it is too large.
Load diff
-659
@@ -1,659 +0,0 @@
|
||||
#pragma once
|
||||
#include "arch.h"
|
||||
|
||||
/* 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
|
||||
#ifndef MG_UECC_SUPPORTS_secp384r1
|
||||
#define MG_UECC_SUPPORTS_secp384r1 1
|
||||
#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
|
||||
#if MG_UECC_SUPPORTS_secp384r1
|
||||
MG_UECC_Curve mg_uecc_secp384r1(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 is required 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().
|
||||
*/
|
||||
typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
|
||||
|
||||
// Mongoose: mg_uecc_make_key() and mg_uecc_sign() use mg_random() when no
|
||||
// explicit uECC RNG is installed. If mg_random() cannot provide a strong source,
|
||||
// those operations fail. Deterministic signing works without RNG, but only an
|
||||
// explicit uECC RNG enables its optional side-channel blinding.
|
||||
|
||||
/* 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.
|
||||
|
||||
Mongoose: this overrides the default mg_random() fallback used by
|
||||
mg_uecc_make_key() and mg_uecc_sign(). Do not call mg_uecc_set_rng() unless the
|
||||
application must provide its own cryptographic RNG.
|
||||
|
||||
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_
|
||||
|
||||
// #include "types.h"
|
||||
// #include "uECC.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
|
||||
Loaded 100 of 2747 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user