mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-08 16:11:18 +00:00
Compare commits
99
Commits
c2d95cc19b
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de037d6ef9 | ||
|
|
e7c8aa017d | ||
|
|
fe8ebced03 | ||
|
|
62cd82df81 | ||
|
|
e2408a2cfc | ||
|
|
da33868409 | ||
|
|
aaf202ba2f | ||
|
|
5c7b7b68bc | ||
|
|
28f88f016c | ||
|
|
e8bb01b547 | ||
|
|
324f59abc5 | ||
|
|
3eb33ab349 | ||
|
|
66eb0384be | ||
|
|
1f276ba70a | ||
|
|
1a9bc9d3f3 | ||
|
|
d39519b92f | ||
|
|
3e11429cac | ||
|
|
9337a78cd0 | ||
|
|
d99cc0959b | ||
|
|
02b5504e60 | ||
|
|
822c6a9489 | ||
|
|
03c022080f | ||
|
|
84198c7e70 | ||
|
|
f3d531938c | ||
|
|
b6975ab08f | ||
|
|
66afa5e551 | ||
|
|
7886a73807 | ||
|
|
ccc953318a | ||
|
|
684ae27dc2 | ||
|
|
fc71bc0588 | ||
|
|
08305b9000 | ||
|
|
69e8c0a92f | ||
|
|
8eceacc0d3 | ||
|
|
b6df410570 | ||
|
|
26b971b4a8 | ||
|
|
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 | ||
|
|
1f2b14bb66 |
No files matched your search
@@ -53,7 +53,7 @@ jobs:
|
|||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/linux_${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/linux_${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
||||||
|
|
||||||
c89:
|
c89:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -137,7 +137,7 @@ jobs:
|
|||||||
make -C test mip_tap_test
|
make -C test mip_tap_test
|
||||||
|
|
||||||
mip89:
|
mip89:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
with: { fetch-depth: 2 }
|
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 && make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
- 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 && make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
||||||
mipbe:
|
mipbe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -165,10 +165,10 @@ jobs:
|
|||||||
- run: |
|
- 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
|
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
|
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:
|
miprtxbe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
name: MIP RTX be s390 port
|
name: MIP RTX be s390 port
|
||||||
env:
|
env:
|
||||||
IPV6: 0
|
IPV6: 0
|
||||||
@@ -180,7 +180,7 @@ jobs:
|
|||||||
- run: |
|
- 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
|
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
|
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 MULTIREC=NO
|
make -C test mip_port_s390
|
||||||
|
|
||||||
l2:
|
l2:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
test/health.awk < log > json
|
test/health.awk < log > json
|
||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
|
||||||
l289:
|
l289:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -216,7 +216,7 @@ jobs:
|
|||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
||||||
l2be:
|
l2be:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -272,7 +272,7 @@ jobs:
|
|||||||
test/health.awk < log > json
|
test/health.awk < log > json
|
||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/ATcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/ATcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||||
atcmd89:
|
atcmd89:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -285,7 +285,7 @@ jobs:
|
|||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- run: make -C test ${{ matrix.target }}
|
- run: make -C test ${{ matrix.target }}
|
||||||
atcmdbe:
|
atcmdbe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -303,6 +303,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
target: [bsd, bsd_nortos]
|
||||||
cc: [gcc, clang, g++, clang++]
|
cc: [gcc, clang, g++, clang++]
|
||||||
name: bsd CC=${{ matrix.cc }}
|
name: bsd CC=${{ matrix.cc }}
|
||||||
steps:
|
steps:
|
||||||
@@ -316,7 +317,7 @@ jobs:
|
|||||||
make -C test/bsd shm_tap_bridge
|
make -C test/bsd shm_tap_bridge
|
||||||
test/bsd/shm_tap_bridge &
|
test/bsd/shm_tap_bridge &
|
||||||
sleep 2
|
sleep 2
|
||||||
make -C test/bsd CC=${{ matrix.cc }} > log
|
make -C test/bsd ${{ matrix.target }} CC=${{ matrix.cc }} > log
|
||||||
- if: success() || failure()
|
- if: success() || failure()
|
||||||
run: |
|
run: |
|
||||||
cat log
|
cat log
|
||||||
@@ -324,7 +325,7 @@ jobs:
|
|||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||||
|
|
||||||
bsdbe:
|
bsdbe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -342,7 +343,7 @@ jobs:
|
|||||||
make -C test/bsd ${{ matrix.target }}
|
make -C test/bsd ${{ matrix.target }}
|
||||||
|
|
||||||
s390:
|
s390:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -353,9 +354,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with: { fetch-depth: 2 }
|
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:
|
armhf:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -366,7 +367,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with: { fetch-depth: 2 }
|
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:
|
unamalgamated-mg_prefix:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -428,7 +429,7 @@ jobs:
|
|||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/macos_test_cc_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/macos_test_cc_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 1 # no parallel runs, to minimize MQTT errors
|
max-parallel: 1 # no parallel runs, to minimize MQTT errors
|
||||||
@@ -478,7 +479,7 @@ jobs:
|
|||||||
- run: make -C test riscv SSL=${{ matrix.ssl }}
|
- run: make -C test riscv SSL=${{ matrix.ssl }}
|
||||||
|
|
||||||
tutorials:
|
tutorials:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # MicroPython v1.22.2 fails with Ubuntu 26.04 GCC 15
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -559,7 +560,8 @@ jobs:
|
|||||||
# - run: make -C tutorials/${{ matrix.example.path }} build
|
# - run: make -C tutorials/${{ matrix.example.path }} build
|
||||||
|
|
||||||
wizard_examples_arm:
|
wizard_examples_arm:
|
||||||
runs-on: ubuntu-latest
|
# Keep rp-ti on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||||
|
runs-on: ${{ matrix.group == 'rp-ti' && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -589,10 +591,12 @@ jobs:
|
|||||||
stm32/nucleo-f746zg/minimal
|
stm32/nucleo-f746zg/minimal
|
||||||
stm32/nucleo-f746zg/minimal-pppoe
|
stm32/nucleo-f746zg/minimal-pppoe
|
||||||
stm32/nucleo-f756zg/minimal
|
stm32/nucleo-f756zg/minimal
|
||||||
stm32/nucleo-h563zi/minimal
|
|
||||||
stm32/nucleo-h723zg/minimal
|
stm32/nucleo-h723zg/minimal
|
||||||
|
stm32/nucleo-h723zg/minimal-bsd-baremetal
|
||||||
|
stm32/nucleo-h723zg/minimal-bsd-freertos
|
||||||
- group: stm32-2
|
- group: stm32-2
|
||||||
examples: |
|
examples: |
|
||||||
|
stm32/nucleo-h563zi/minimal
|
||||||
stm32/nucleo-h743zi/minimal
|
stm32/nucleo-h743zi/minimal
|
||||||
stm32/nucleo-n657x0-q/minimal
|
stm32/nucleo-n657x0-q/minimal
|
||||||
stm32/nucleo-u5a5zj-q/minimal
|
stm32/nucleo-u5a5zj-q/minimal
|
||||||
@@ -633,8 +637,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
example:
|
example:
|
||||||
- path: micropython/esp32
|
- path: micropython/esp32
|
||||||
- path: http/uart-bridge/esp32
|
- path: esp32/uart-bridge
|
||||||
- path: http/http-client/esp8266/http-client-server
|
- path: esp32/minimal
|
||||||
name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
env:
|
env:
|
||||||
GO: 0
|
GO: 0
|
||||||
@@ -652,7 +656,7 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- group: other
|
- group: other
|
||||||
examples: |
|
examples: |
|
||||||
mqtt/mqtt-client/microchip/same54-xpro
|
microchip/same54-xpro/mqtt-client
|
||||||
rp/pico-rmii
|
rp/pico-rmii
|
||||||
stm32/nucleo-f746zg/minimal-rm2
|
stm32/nucleo-f746zg/minimal-rm2
|
||||||
stm32/nucleo-f746zg/minimal-rndis
|
stm32/nucleo-f746zg/minimal-rndis
|
||||||
@@ -691,13 +695,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
example:
|
example:
|
||||||
- path: esp32
|
- path: esp32/device-dashboard
|
||||||
name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- run: echo # nothing specific to install or do
|
- 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:
|
device_dashboard_examples_arm:
|
||||||
@@ -709,12 +713,12 @@ jobs:
|
|||||||
- group: none
|
- group: none
|
||||||
cflags: ""
|
cflags: ""
|
||||||
examples: |
|
examples: |
|
||||||
http/device-dashboard/microchip/same54-xpro
|
microchip/same54-xpro/device-dashboard
|
||||||
renesas/ek-ra6m4-make-baremetal-builtin
|
renesas/ek-ra6m4-make-baremetal-builtin
|
||||||
- group: builtin
|
- group: builtin
|
||||||
cflags: -DMG_TLS=MG_TLS_BUILTIN
|
cflags: -DMG_TLS=MG_TLS_BUILTIN
|
||||||
examples: |
|
examples: |
|
||||||
http/device-dashboard/microchip/same54-xpro
|
microchip/same54-xpro/device-dashboard
|
||||||
renesas/ek-ra6m4-make-baremetal-builtin
|
renesas/ek-ra6m4-make-baremetal-builtin
|
||||||
name: device dashboard ARM ${{ matrix.group }}
|
name: device dashboard ARM ${{ matrix.group }}
|
||||||
steps:
|
steps:
|
||||||
@@ -740,19 +744,19 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit $failed
|
exit $failed
|
||||||
|
|
||||||
# mqtt_dashboard_examples:
|
mqtt_dashboard_examples_arm:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# strategy:
|
strategy:
|
||||||
# fail-fast: false
|
fail-fast: false
|
||||||
# matrix:
|
matrix:
|
||||||
# example:
|
example:
|
||||||
# - path:
|
- path: rp/pico-rndis-device
|
||||||
# name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
# steps:
|
steps:
|
||||||
# - uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
# with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
# - run: echo # nothing specific to install or do
|
- run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
||||||
# - run: make -C tutorials/${{ matrix.example.path }} build
|
- run: make -C tutorials/${{ matrix.example.path }} build
|
||||||
|
|
||||||
|
|
||||||
# dual_examples:
|
# dual_examples:
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ jobs:
|
|||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- name: baremetal
|
- name: baremetal
|
||||||
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
|
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/microchip/same54-xpro/device-dashboard 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/mqtt-client test VCON_API_KEY=${{secrets.VCON_API_KEY}}
|
||||||
|
|
||||||
test_pico_w5500:
|
test_pico_w5500:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
test/health.awk < log > json
|
test/health.awk < log > json
|
||||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||||
atcmd89:
|
atcmd89:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- run: make -C test ${{ matrix.target }}
|
- run: make -C test ${{ matrix.target }}
|
||||||
atcmdbe:
|
atcmdbe:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- if: ${{ env.GO == 1 }}
|
- if: ${{ env.GO == 1 }}
|
||||||
run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }}
|
run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }}
|
||||||
s390:
|
s390:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
TFLAGS: -DMQTT_LOCALHOST
|
TFLAGS: -DMQTT_LOCALHOST
|
||||||
steps:
|
steps:
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
- if: ${{ env.GO == 1 }}
|
- if: ${{ env.GO == 1 }}
|
||||||
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
|
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
|
||||||
armhf:
|
armhf:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
TFLAGS: -DMQTT_LOCALHOST
|
TFLAGS: -DMQTT_LOCALHOST
|
||||||
steps:
|
steps:
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos &
|
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos &
|
||||||
make -C test test ASAN_OPTIONS=
|
make -C test test ASAN_OPTIONS=
|
||||||
windows:
|
windows:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -158,7 +158,8 @@ jobs:
|
|||||||
- path: stm32/nucleo-f746zg/minimal
|
- path: stm32/nucleo-f746zg/minimal
|
||||||
- path: stm32/nucleo-h563zi/minimal
|
- path: stm32/nucleo-h563zi/minimal
|
||||||
- path: stm32/nucleo-h723zg/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/nucleo-h743zi/minimal
|
||||||
- path: stm32/stm32h573i-dk/minimal
|
- path: stm32/stm32h573i-dk/minimal
|
||||||
- path: ti/ek-tm4c1294xl-make-baremetal-builtin
|
- path: ti/ek-tm4c1294xl-make-baremetal-builtin
|
||||||
@@ -184,8 +185,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
example:
|
example:
|
||||||
- path: http/uart-bridge/esp32
|
- path: esp32/uart-bridge
|
||||||
- path: http/http-client/esp8266/http-client-server
|
- path: esp32/minimal
|
||||||
name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
env:
|
env:
|
||||||
GO: 0
|
GO: 0
|
||||||
@@ -200,7 +201,8 @@ jobs:
|
|||||||
run: make -C tutorials/${{ matrix.example.path }} build
|
run: make -C tutorials/${{ matrix.example.path }} build
|
||||||
|
|
||||||
generic_examples_arm:
|
generic_examples_arm:
|
||||||
runs-on: ubuntu-latest
|
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||||
|
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -227,7 +229,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
example:
|
example:
|
||||||
- path: esp32
|
- path: esp32/device-dashboard
|
||||||
name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
env:
|
env:
|
||||||
GO: 0
|
GO: 0
|
||||||
@@ -235,20 +237,21 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with: { fetch-depth: 2 }
|
with: { fetch-depth: 2 }
|
||||||
- run: |
|
- 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
|
echo GO=1 >> $GITHUB_ENV
|
||||||
# nothing specific to install or do
|
# nothing specific to install or do
|
||||||
fi
|
fi
|
||||||
- if: ${{ env.GO == 1 }}
|
- 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:
|
device_dashboard_examples_arm:
|
||||||
runs-on: ubuntu-latest
|
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||||
|
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
example:
|
example:
|
||||||
- path: http/device-dashboard/microchip/same54-xpro
|
- path: microchip/same54-xpro/device-dashboard
|
||||||
- path: renesas/ek-ra6m4-make-baremetal-builtin
|
- path: renesas/ek-ra6m4-make-baremetal-builtin
|
||||||
- path: rp/pico-rndis
|
- path: rp/pico-rndis
|
||||||
- path: rp/pico-w
|
- path: rp/pico-w
|
||||||
@@ -267,3 +270,25 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- if: ${{ env.GO == 1 }}
|
- if: ${{ env.GO == 1 }}
|
||||||
run: make -C tutorials/${{ matrix.example.path }} build
|
run: make -C tutorials/${{ matrix.example.path }} build
|
||||||
|
|
||||||
|
mqtt_dashboard_examples_arm:
|
||||||
|
# Pico SDK 2.1.0 pioasm fails with GCC 15; update SDK to unpin
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
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:
|
example:
|
||||||
- path: esp32/uart-bridge
|
- path: esp32/uart-bridge
|
||||||
- path: esp32/micropython
|
- path: esp32/micropython
|
||||||
- path: esp8266/http-client-server
|
|
||||||
name: ${{ matrix.example.path }}
|
name: ${{ matrix.example.path }}
|
||||||
env:
|
env:
|
||||||
GO: 0
|
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.
|
License: GPLv2 or commercial.
|
||||||
Website: https://mongoose.ws/
|
Website: https://mongoose.ws/
|
||||||
GitHub repo: https://github.com/cesanta/mongoose
|
GitHub repo: https://github.com/cesanta/mongoose
|
||||||
|
|||||||
@@ -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://opensource.org/licenses/gpl-2.0.php)
|
||||||
[](https://github.com/cesanta/mongoose/actions)
|
[](https://github.com/cesanta/mongoose/actions)
|
||||||
@@ -7,11 +7,17 @@
|
|||||||
|
|
||||||
<img src="https://mongoose.ws/images/logo.svg" width="48" height="48"
|
<img src="https://mongoose.ws/images/logo.svg" width="48" height="48"
|
||||||
align="left" style="float:left;" />
|
align="left" style="float:left;" />
|
||||||
Two files. Drop `mongoose.c` and `mongoose.h` into any C/C++ project and get a
|
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
|
||||||
full network stack — HTTP, WebSocket, MQTT, TLS, firmware OTA
|
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
|
||||||
updates, device dashboard — on any microcontroller or desktop OS.
|
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:
|
products - it even runs on the International Space Station! Features include:
|
||||||
|
|
||||||
- Cross-platform:
|
- 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
|
- Source code is both ISO C and ISO C++ compliant
|
||||||
- Very easy to integrate
|
- Very easy to integrate
|
||||||
- Own TCP/IP stack for bare metal / RTOS, or run on top of any existing one (lwIP, Zephyr, ...)
|
- 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
|
- 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
|
- 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/)
|
- 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
|
## Supported platforms
|
||||||
|
|
||||||
Mongoose can work on top of any TCP/IP stack that supports BSD sockets API.
|
Mongoose has its own built-in TCP/IP stack with drivers, so it needs no
|
||||||
Platforms supported by the 3rd party TCP/IP stacks:
|
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 |
|
| 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 |
|
| **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 |
|
| **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
|
## Usage Examples
|
||||||
|
|
||||||
Below are quick snippets that should give an idea how simple the API is and
|
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
|
## Commercial use
|
||||||
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
|
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
|
||||||
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
|
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
|
||||||
@@ -222,6 +246,13 @@ Technical guides and deep dives into embedded web servers, WebUI integration and
|
|||||||
- [lwIP vs Mongoose - TCP/IP Stack Integration Benchmark](https://mongoose.ws/articles/lwip-vs-mongoose-tcpip-stack-integration/)
|
- [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/)
|
- [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/)
|
- [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
|
## 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/>
|
||||||
+9930
-5215
File diff suppressed because it is too large.
Load diff
+734
-891
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,22 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<vendor>Cesanta</vendor>
|
||||||
<name>I-CUBE-Mongoose</name>
|
<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>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>
|
||||||
</description>
|
|
||||||
<url>https://mongoose.ws/downloads/stm32pack/</url>
|
<url>https://mongoose.ws/downloads/stm32pack/</url>
|
||||||
<license>License.txt</license>
|
<license>License.txt</license>
|
||||||
<supportContact>https://mongoose.ws/contact/</supportContact>
|
<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>
|
<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-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-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-05-05" version="7.21.5">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||||
@@ -20,7 +29,7 @@
|
|||||||
<keyword>IMPLEMENTED_WITH_STM32CUBEMX</keyword>
|
<keyword>IMPLEMENTED_WITH_STM32CUBEMX</keyword>
|
||||||
</keywords>
|
</keywords>
|
||||||
<components>
|
<components>
|
||||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.1" maxInstances="1">
|
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.3" maxInstances="1">
|
||||||
<description>Networking Core</description>
|
<description>Networking Core</description>
|
||||||
<files>
|
<files>
|
||||||
<file category="doc" name="README.md"/>
|
<file category="doc" name="README.md"/>
|
||||||
@@ -28,7 +37,15 @@
|
|||||||
<file category="sourceC" name="mongoose.c" />
|
<file category="sourceC" name="mongoose.c" />
|
||||||
<file category="header" name="mongoose_config.h"/>
|
<file category="header" name="mongoose_config.h"/>
|
||||||
<file category="utility" name="README.md" public="false"/>
|
<file category="utility" name="README.md" public="false"/>
|
||||||
|
<file category="genAsset" name=".config/mongoose_parameters.json"/>
|
||||||
</files>
|
</files>
|
||||||
|
<environments>
|
||||||
|
<environment name="STM32Cube">
|
||||||
|
<stm32:component>
|
||||||
|
<middleware-entry-point id="Mongoose" name="Mongoose" category="TCP/IP"/>
|
||||||
|
</stm32:component>
|
||||||
|
</environment>
|
||||||
|
</environments>
|
||||||
</component>
|
</component>
|
||||||
</components>
|
</components>
|
||||||
</package>
|
</package>
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
VERSION = 7.23.1
|
VERSION = 7.23.3
|
||||||
REPO_ROOT = ../..
|
REPO_ROOT = ../..
|
||||||
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
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
|
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)
|
all: $(PACK)
|
||||||
|
|
||||||
Cesanta.I-CUBE-Mongoose.$(VERSION).pack: $(FILES) $(GENERATED)
|
Cesanta.I-CUBE-Mongoose.$(VERSION).pack: $(FILES) $(GENERATED)
|
||||||
@@ -44,5 +50,19 @@ mongoose.h: Mongoose
|
|||||||
mongoose.c: Mongoose
|
mongoose.c: Mongoose
|
||||||
cp $(REPO_ROOT)/mongoose.c $@
|
cp $(REPO_ROOT)/mongoose.c $@
|
||||||
|
|
||||||
|
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:
|
clean:
|
||||||
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED)
|
rm -rf *.pack STM32CubeMX/*.$(VERSION)_*.xml $(GENERATED) mx2test
|
||||||
@@ -13,9 +13,10 @@ Primary files:
|
|||||||
- `src/tls_dummy.c`
|
- `src/tls_dummy.c`
|
||||||
- `src/tls_mbed.c`
|
- `src/tls_mbed.c`
|
||||||
- `src/tls_openssl.c`
|
- `src/tls_openssl.c`
|
||||||
|
- `src/tls_p256_cortex.c`
|
||||||
|
- `src/tls_p256.c`
|
||||||
|
- `src/tls_p384.c`
|
||||||
- `src/tls_rsa.c`
|
- `src/tls_rsa.c`
|
||||||
- `src/tls_uecc.c`
|
|
||||||
- `src/tls_x25519.c`
|
|
||||||
|
|
||||||
Perform a focused security review centered on these files and this security surface.
|
Perform a focused security review centered on these files and this security surface.
|
||||||
|
|
||||||
@@ -38,6 +39,6 @@ Trace attacker-controlled TLS peer input through normal client and server execut
|
|||||||
- Verify that wildcard matching is applied only to certificate presented identifiers, not to arbitrary reference identifiers or general DNS glob matching.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Check that TLS receive/send error paths cannot corrupt connection state, reuse freed buffers, or continue processing unauthenticated plaintext as encrypted data.
|
||||||
+26
-20
@@ -3,9 +3,9 @@
|
|||||||
"bomFormat": "CycloneDX",
|
"bomFormat": "CycloneDX",
|
||||||
"specVersion": "1.6",
|
"specVersion": "1.6",
|
||||||
"serialNumber": "urn:uuid:d3f8b1e2-4a7c-4d9e-bf2a-1c3e5f7a9b0d",
|
"serialNumber": "urn:uuid:d3f8b1e2-4a7c-4d9e-bf2a-1c3e5f7a9b0d",
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2026-08-12T00:00:00Z",
|
"timestamp": "2026-10-13T00:00:00Z",
|
||||||
"component": {
|
"component": {
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bom-ref": "mongoose",
|
"bom-ref": "mongoose",
|
||||||
@@ -34,14 +34,31 @@
|
|||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bom-ref": "micro-ecc",
|
"bom-ref": "bearssl-ec",
|
||||||
"author": "Kenneth MacKay",
|
"author": "Thomas Pornin",
|
||||||
"name": "micro-ecc",
|
"name": "BearSSL EC (P-256, P-384, ECDSA)",
|
||||||
"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.",
|
"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": "BSD-2-Clause" } ],
|
"licenses": [ { "expression": "MIT" } ],
|
||||||
"purl": "pkg:github/kmackay/micro-ecc",
|
|
||||||
"externalReferences": [
|
"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": "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",
|
"type": "library",
|
||||||
"bom-ref": "sha1-reid",
|
"bom-ref": "sha1-reid",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
|
#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
|
||||||
#define MG_ARCH_WIN32 2 // Windows
|
#define MG_ARCH_WIN32 2 // Windows
|
||||||
#define MG_ARCH_ESP32 3 // ESP32
|
#define MG_ARCH_ESP32 3 // ESP32
|
||||||
#define MG_ARCH_ESP8266 4 // ESP8266
|
|
||||||
|
|
||||||
#define MG_ARCH_FREERTOS 5 // FreeRTOS <-- DEPRECATED !!!
|
#define MG_ARCH_FREERTOS 5 // FreeRTOS <-- DEPRECATED !!!
|
||||||
|
|
||||||
@@ -50,7 +49,6 @@
|
|||||||
#include "arch_armgcc.h"
|
#include "arch_armgcc.h"
|
||||||
#include "arch_cube.h"
|
#include "arch_cube.h"
|
||||||
#include "arch_esp32.h"
|
#include "arch_esp32.h"
|
||||||
#include "arch_esp8266.h"
|
|
||||||
#include "arch_rtx.h"
|
#include "arch_rtx.h"
|
||||||
#include "arch_threadx.h"
|
#include "arch_threadx.h"
|
||||||
#include "arch_unix.h"
|
#include "arch_unix.h"
|
||||||
|
|||||||
+5
-64
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
|
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
|
||||||
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
|
!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_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
|
||||||
!defined(MG_ENABLE_DRIVER_ST67W6)
|
!defined(MG_ENABLE_DRIVER_ST67W6)
|
||||||
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
|
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
|
||||||
@@ -40,6 +41,8 @@
|
|||||||
#define MG_ENABLE_DRIVER_STM32H 1
|
#define MG_ENABLE_DRIVER_STM32H 1
|
||||||
#elif defined(STM32N6)
|
#elif defined(STM32N6)
|
||||||
#define MG_ENABLE_DRIVER_STM32N 1
|
#define MG_ENABLE_DRIVER_STM32N 1
|
||||||
|
#elif defined(STM32C5)
|
||||||
|
#define MG_ENABLE_DRIVER_STM32C 1
|
||||||
#else
|
#else
|
||||||
#error Select a driver in mongoose_config.h
|
#error Select a driver in mongoose_config.h
|
||||||
#endif
|
#endif
|
||||||
@@ -56,70 +59,8 @@
|
|||||||
#define MG_OTA MG_OTA_STM32H5
|
#define MG_OTA MG_OTA_STM32H5
|
||||||
#elif !defined(MG_OTA) && defined(STM32H7)
|
#elif !defined(MG_OTA) && defined(STM32H7)
|
||||||
#define MG_OTA MG_OTA_STM32H7
|
#define MG_OTA MG_OTA_STM32H7
|
||||||
#endif
|
#elif !defined(MG_OTA) && defined(STM32C5)
|
||||||
|
#define MG_OTA MG_OTA_STM32C5
|
||||||
// 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
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// use HAL-defined execute-in-ram section
|
// use HAL-defined execute-in-ram section
|
||||||
|
|||||||
+7
-53
@@ -22,10 +22,11 @@
|
|||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
#include <esp_mac.h>
|
#include <esp_mac.h>
|
||||||
#include <esp_idf_version.h>
|
#include <esp_idf_version.h>
|
||||||
#include <esp_ota_ops.h>
|
#if MG_ENABLE_HEALTH
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_memory_utils.h>
|
||||||
#include <soc/soc.h>
|
#include <esp_core_dump.h>
|
||||||
#include <soc/rtc_cntl_reg.h>
|
#include <esp_partition.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MG_PATH_MAX 128
|
#define MG_PATH_MAX 128
|
||||||
|
|
||||||
@@ -33,55 +34,8 @@
|
|||||||
#define MG_ENABLE_POSIX_FS 1
|
#define MG_ENABLE_POSIX_FS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MG_OTA_ESP32_WDT_TIMEOUT_MS
|
#ifndef MG_HEALTH_DATA_SIZE
|
||||||
#define MG_OTA_ESP32_WDT_TIMEOUT_MS 10000U
|
#define MG_HEALTH_DATA_SIZE 20
|
||||||
#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)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -4,22 +4,73 @@
|
|||||||
#include "net_builtin.h"
|
#include "net_builtin.h"
|
||||||
|
|
||||||
#if MG_ENABLE_BSD_SOCKETS
|
#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 <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
#ifndef EAGAIN
|
||||||
#include <netinet/tcp.h>
|
#define EAGAIN 11
|
||||||
#include <poll.h>
|
#endif
|
||||||
#include <sys/select.h>
|
#ifndef EWOULDBLOCK
|
||||||
#include <sys/socket.h>
|
#define EWOULDBLOCK EAGAIN
|
||||||
#include <sys/time.h>
|
#endif
|
||||||
#include <sys/un.h>
|
#ifndef EINPROGRESS
|
||||||
#else
|
#define EINPROGRESS 36
|
||||||
#include <errno.h>
|
#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 EAFNOSUPPORT
|
||||||
|
#define EAFNOSUPPORT 97
|
||||||
|
#endif
|
||||||
|
#ifndef ENOPROTOOPT
|
||||||
|
#define ENOPROTOOPT 92
|
||||||
|
#endif
|
||||||
|
#ifndef ENOSPC
|
||||||
|
#define ENOSPC 28
|
||||||
|
#endif
|
||||||
|
#ifndef EPROTONOSUPPORT
|
||||||
|
#define EPROTONOSUPPORT 93
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MG_ENABLE_BSD_PROTOTYPES
|
||||||
typedef unsigned int socklen_t;
|
typedef unsigned int socklen_t;
|
||||||
|
typedef unsigned long nfds_t;
|
||||||
typedef uint32_t in_addr_t;
|
typedef uint32_t in_addr_t;
|
||||||
struct in_addr { in_addr_t s_addr; };
|
struct in_addr { in_addr_t s_addr; };
|
||||||
struct in6_addr { uint8_t s6_addr[16]; };
|
struct in6_addr { uint8_t s6_addr[16]; };
|
||||||
@@ -45,7 +96,6 @@ struct hostent {
|
|||||||
int h_length;
|
int h_length;
|
||||||
char **h_addr_list;
|
char **h_addr_list;
|
||||||
};
|
};
|
||||||
#define h_addr h_addr_list[0]
|
|
||||||
struct addrinfo {
|
struct addrinfo {
|
||||||
int ai_flags;
|
int ai_flags;
|
||||||
int ai_family;
|
int ai_family;
|
||||||
@@ -57,6 +107,32 @@ struct addrinfo {
|
|||||||
struct addrinfo *ai_next;
|
struct addrinfo *ai_next;
|
||||||
};
|
};
|
||||||
struct pollfd { int fd; short events; short revents; };
|
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_INET 2
|
||||||
#define AF_INET6 10
|
#define AF_INET6 10
|
||||||
#define AF_UNSPEC 0
|
#define AF_UNSPEC 0
|
||||||
@@ -69,6 +145,9 @@ struct pollfd { int fd; short events; short revents; };
|
|||||||
#define IPPROTO_UDP 17
|
#define IPPROTO_UDP 17
|
||||||
#define INADDR_ANY 0
|
#define INADDR_ANY 0
|
||||||
#define INADDR_LOOPBACK 0x7f000001
|
#define INADDR_LOOPBACK 0x7f000001
|
||||||
|
#define INADDR_NONE ((in_addr_t) -1)
|
||||||
|
#define INET_ADDRSTRLEN 16
|
||||||
|
#define INET6_ADDRSTRLEN 46
|
||||||
#define SOL_SOCKET 0xffff
|
#define SOL_SOCKET 0xffff
|
||||||
#define SO_REUSEADDR 2
|
#define SO_REUSEADDR 2
|
||||||
#define SO_REUSEPORT 15
|
#define SO_REUSEPORT 15
|
||||||
@@ -77,79 +156,37 @@ struct pollfd { int fd; short events; short revents; };
|
|||||||
#define SO_BROADCAST 6
|
#define SO_BROADCAST 6
|
||||||
#define SO_RCVBUF 8
|
#define SO_RCVBUF 8
|
||||||
#define SO_SNDBUF 7
|
#define SO_SNDBUF 7
|
||||||
|
#define SO_NOSIGPIPE 0x1022
|
||||||
#define TCP_NODELAY 1
|
#define TCP_NODELAY 1
|
||||||
#define MSG_DONTWAIT 0x40
|
#define MSG_DONTWAIT 0x40
|
||||||
#define MSG_NOSIGNAL 0x4000
|
#define MSG_NOSIGNAL 0x4000
|
||||||
#define MSG_PEEK 0x02
|
#define MSG_PEEK 0x02
|
||||||
|
#ifndef F_GETFL
|
||||||
#define F_GETFL 3
|
#define F_GETFL 3
|
||||||
|
#endif
|
||||||
|
#ifndef F_SETFL
|
||||||
#define F_SETFL 4
|
#define F_SETFL 4
|
||||||
#define O_NONBLOCK 0x0004
|
#endif
|
||||||
|
#ifndef O_NONBLOCK
|
||||||
|
#define O_NONBLOCK 0x4000
|
||||||
|
#endif
|
||||||
#define SHUT_RD 0
|
#define SHUT_RD 0
|
||||||
#define SHUT_WR 1
|
#define SHUT_WR 1
|
||||||
#define SHUT_RDWR 2
|
#define SHUT_RDWR 2
|
||||||
#define AI_PASSIVE 0x0001
|
#define AI_PASSIVE 0x0001
|
||||||
#define NI_MAXHOST 1025
|
#define NI_MAXHOST 1025
|
||||||
#define NI_MAXSERV 32
|
#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 POLLIN 0x001
|
||||||
#define POLLOUT 0x004
|
#define POLLOUT 0x004
|
||||||
#define POLLERR 0x008
|
#define POLLERR 0x008
|
||||||
#define POLLHUP 0x010
|
#define POLLHUP 0x010
|
||||||
#define POLLNVAL 0x020
|
#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 socket(int, int, int);
|
||||||
int bind(int, const struct sockaddr *, socklen_t);
|
int bind(int, const struct sockaddr *, socklen_t);
|
||||||
int listen(int, int);
|
int listen(int, int);
|
||||||
@@ -157,23 +194,40 @@ int accept(int, struct sockaddr *, socklen_t *);
|
|||||||
int connect(int, const struct sockaddr *, socklen_t);
|
int connect(int, const struct sockaddr *, socklen_t);
|
||||||
ssize_t send(int, const void *, size_t, int);
|
ssize_t send(int, const void *, size_t, int);
|
||||||
ssize_t recv(int, 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 *);
|
ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *);
|
||||||
int close(int);
|
int close(int);
|
||||||
int shutdown(int, int);
|
int shutdown(int, int);
|
||||||
int fcntl(int, int, int);
|
|
||||||
int setsockopt(int, int, int, const void *, socklen_t);
|
int setsockopt(int, int, int, const void *, socklen_t);
|
||||||
int getsockopt(int, int, int, void *, socklen_t *);
|
int getsockopt(int, int, int, void *, socklen_t *);
|
||||||
int getsockname(int, struct sockaddr *, socklen_t *);
|
int getsockname(int, struct sockaddr *, socklen_t *);
|
||||||
int getpeername(int, struct sockaddr *, socklen_t *);
|
int getpeername(int, struct sockaddr *, socklen_t *);
|
||||||
int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
|
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 *);
|
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 *);
|
void freeaddrinfo(struct addrinfo *);
|
||||||
int inet_pton(int, const char *, void *);
|
int inet_pton(int, const char *, void *);
|
||||||
const char *inet_ntop(int, const void *, char *, socklen_t);
|
const char *inet_ntop(int, const void *, char *, socklen_t);
|
||||||
in_addr_t inet_addr(const char *);
|
in_addr_t inet_addr(const char *);
|
||||||
char *inet_ntoa(struct in_addr);
|
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
|
#endif // MG_ENABLE_BSD_SOCKETS
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
|
#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_ENABLE_CUSTOM_USAGE
|
||||||
|
#define MG_ENABLE_CUSTOM_USAGE 0 // Let user define mg_usage
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef MG_ENABLE_FREERTOS
|
#ifndef MG_ENABLE_FREERTOS
|
||||||
#define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS
|
#define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS
|
||||||
#endif
|
#endif
|
||||||
@@ -223,10 +227,28 @@
|
|||||||
#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
|
#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_TLS_RECORD_SIZE
|
||||||
|
#define MG_TLS_RECORD_SIZE 16384 // Max plaintext per sent TLS record, builtin
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef MG_ENABLE_CHACHA20
|
#ifndef MG_ENABLE_CHACHA20
|
||||||
#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN
|
#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN
|
||||||
#endif
|
#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
|
#ifndef MG_ENABLE_HEALTH
|
||||||
#define MG_ENABLE_HEALTH 0
|
#define MG_ENABLE_HEALTH 0
|
||||||
#endif
|
#endif
|
||||||
+3
-18
@@ -533,21 +533,6 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
|
|
||||||
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
|
|
||||||
static bool otacb(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
||||||
struct mg_dash_user *u, bool stream) {
|
struct mg_dash_user *u, bool stream) {
|
||||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||||
@@ -594,9 +579,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
mg_serve_fs(c, hm, u, true);
|
mg_serve_fs(c, hm, u, true);
|
||||||
return; // Important - the serve_fs() call can invalidate hm
|
return; // Important - the serve_fs() call can invalidate hm
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||||
// Authenticated above. If streaming started, the stream handler owns
|
// Authenticated above. Streaming owns the connection and c->data.
|
||||||
// the connection and c->data, so skip the marker check below
|
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
return;
|
||||||
}
|
}
|
||||||
if (d->marker != '\0') mg_log_http_req(c, hm);
|
if (d->marker != '\0') mg_log_http_req(c, hm);
|
||||||
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ struct mg_dash {
|
|||||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
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
|
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||||
// upload directories. NULL: file manager disabled
|
// upload directories. NULL: file manager disabled
|
||||||
|
// - The returned directory must be nonempty and must not contain ',' or '=',
|
||||||
|
// because it is used as an mg_http_serve_opts::root_dir mapping target.
|
||||||
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
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) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (dnsc->c == NULL) {
|
if (dnsc->c == NULL) {
|
||||||
dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL);
|
dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL);
|
||||||
if (dnsc->c == NULL) return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#include "l2_mesh.h"
|
||||||
|
#include "log.h"
|
||||||
|
#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_FILTERFAIL MG_BIT(5) // Frame not for us
|
||||||
|
#define MGI_IRQSTS_CRCVALID MG_BIT(15)
|
||||||
|
#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_PHY_CTRL_SEQMSK MG_BIT(8) // Mask the sequence end IRQ
|
||||||
|
#define MGI_PHY_CTRL_TRCV_MSK MG_BIT(31) // Mask all transceiver IRQs
|
||||||
|
#define MGI_IRQN 53 // RF_802_15_4_IRQn
|
||||||
|
#define MGI_NVIC_ISER ((volatile uint32_t *) 0xE000E100) // Enable IRQ
|
||||||
|
#define MGI_NVIC_ICER ((volatile uint32_t *) 0xE000E180) // Disable IRQ
|
||||||
|
#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
|
||||||
|
|
||||||
|
// PHY_CTRL[XCVSEQ]
|
||||||
|
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 };
|
||||||
|
#define MGI_SEND_TIMEOUT_MS 60
|
||||||
|
|
||||||
|
static struct mg_tcpip_if *s_mcxw71_ifp;
|
||||||
|
static struct mg_queue s_mcxw71_rx;
|
||||||
|
static char s_mcxw71_queue[2048];
|
||||||
|
static volatile bool s_mcxw71_txing, s_mcxw71_done;
|
||||||
|
static uint64_t s_mcxw71_sendstart;
|
||||||
|
|
||||||
|
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
|
||||||
|
static void mg_mcxw71_abort(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start receiving, with the sequence end IRQ on, see RF_802_15_4_IRQHandler()
|
||||||
|
static void mg_mcxw71_listen(void) {
|
||||||
|
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||||
|
MGI_ZLL->PHY_CTRL |= MGI_SEQ_RX;
|
||||||
|
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
|
||||||
|
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the IRQ handler out while we change the radio. The NVIC disable is
|
||||||
|
// atomic, so PHY_CTRL updates here and there do not race
|
||||||
|
static void mg_mcxw71_lock(void) {
|
||||||
|
MGI_NVIC_ICER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||||
|
__asm volatile("dsb; isb" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the TX sequence for the loaded frame. Its end: see the IRQ handler
|
||||||
|
static void mg_mcxw71_go(void) {
|
||||||
|
MGI_ZLL->PHY_CTRL |= MGI_PHY_CTRL_TRCV_MSK | MGI_PHY_CTRL_SEQMSK;
|
||||||
|
mg_mcxw71_abort();
|
||||||
|
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||||
|
MGI_ZLL->PHY_CTRL |= MGI_SEQ_TX;
|
||||||
|
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RF_802_15_4_IRQHandler(void);
|
||||||
|
void RF_802_15_4_IRQHandler(void) {
|
||||||
|
uint32_t irqsts = MGI_ZLL->IRQSTS;
|
||||||
|
if ((irqsts & MGI_IRQSTS_SEQIRQ) != 0) {
|
||||||
|
mg_mcxw71_abort();
|
||||||
|
if (s_mcxw71_txing) {
|
||||||
|
s_mcxw71_txing = false;
|
||||||
|
s_mcxw71_done = true;
|
||||||
|
} else if ((irqsts & (MGI_IRQSTS_RXIRQ | MGI_IRQSTS_FILTERFAIL |
|
||||||
|
MGI_IRQSTS_CRCVALID)) ==
|
||||||
|
(MGI_IRQSTS_RXIRQ | MGI_IRQSTS_CRCVALID)) {
|
||||||
|
uint8_t frame[125];
|
||||||
|
size_t i, n = MGI_IRQSTS_RX_LEN(irqsts);
|
||||||
|
n = n >= 2 ? n - 2 : 0;
|
||||||
|
if (n > 0 && n <= sizeof(frame)) {
|
||||||
|
for (i = 0; i < n; i++) frame[i] = MGI_RX_PB[i];
|
||||||
|
{
|
||||||
|
char *dst;
|
||||||
|
if (mg_queue_book(&s_mcxw71_rx, &dst, n) >= n) {
|
||||||
|
memcpy(dst, frame, n);
|
||||||
|
mg_queue_add(&s_mcxw71_rx, n);
|
||||||
|
} else {
|
||||||
|
s_mcxw71_ifp->ndrop++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mg_mcxw71_listen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mcxw71_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
size_t i;
|
||||||
|
(void) ifp;
|
||||||
|
if (len > 125 || s_mcxw71_txing) return 0;
|
||||||
|
mg_mcxw71_lock();
|
||||||
|
MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
MGI_TX_PB[i + 1] = ((const uint8_t *) buf)[i]; // 8-bit access only
|
||||||
|
s_mcxw71_done = false;
|
||||||
|
s_mcxw71_txing = true;
|
||||||
|
s_mcxw71_sendstart = mg_millis();
|
||||||
|
mg_mcxw71_go();
|
||||||
|
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mcxw71_radio_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
s_mcxw71_ifp = ifp;
|
||||||
|
MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
|
||||||
|
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS;
|
||||||
|
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||||
|
MGI_ZLL->SAM_CTRL = 0;
|
||||||
|
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
|
||||||
|
MGI_ZLL->CHANNEL_NUM0 = MG_802154_CHANNEL;
|
||||||
|
MGI_ZLL->MACSHORTADDRS0 =
|
||||||
|
((uint32_t) node->radio_address << 16) | MG_802154_PAN_ID;
|
||||||
|
MGI_ZLL->PA_PWR = 0x3f;
|
||||||
|
mg_mcxw71_listen();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mcxw71_tx(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||||
|
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mcxw71_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||||
|
size_t len = data->mesh->rx_len;
|
||||||
|
if (len > cap) return 0;
|
||||||
|
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||||
|
memcpy(buf, ifp->recv_queue.buf, len);
|
||||||
|
data->mesh->rx_len = 0;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
uint64_t now = mg_micros();
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
(void) s1;
|
||||||
|
if (s_mcxw71_txing &&
|
||||||
|
mg_millis() > s_mcxw71_sendstart + MGI_SEND_TIMEOUT_MS) {
|
||||||
|
mg_mcxw71_lock();
|
||||||
|
if (s_mcxw71_txing) {
|
||||||
|
mg_mcxw71_abort();
|
||||||
|
s_mcxw71_txing = false;
|
||||||
|
s_mcxw71_done = true;
|
||||||
|
mg_mcxw71_listen();
|
||||||
|
} else {
|
||||||
|
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s_mcxw71_done) {
|
||||||
|
s_mcxw71_done = false;
|
||||||
|
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||||
|
}
|
||||||
|
node->now = now;
|
||||||
|
while (node->rx_len == 0 && (len = mg_queue_next(&s_mcxw71_rx, &buf)) != 0) {
|
||||||
|
node->strategy->rx(node, buf, len);
|
||||||
|
mg_queue_del(&s_mcxw71_rx, len);
|
||||||
|
}
|
||||||
|
node->strategy->tick(node, now);
|
||||||
|
return node->joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mcxw71_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||||
|
const struct mg_mesh *strategy = data->strategy;
|
||||||
|
uint16_t address;
|
||||||
|
address =
|
||||||
|
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||||
|
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||||
|
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||||
|
if (data->mesh == NULL) return false;
|
||||||
|
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data->mesh->strategy = strategy;
|
||||||
|
data->mesh->tx = mgi_mcxw71_radio_tx;
|
||||||
|
mg_queue_init(&s_mcxw71_rx, s_mcxw71_queue, sizeof(s_mcxw71_queue));
|
||||||
|
if (mgi_mcxw71_radio_init(ifp)) return true;
|
||||||
|
strategy->free(data->mesh);
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {
|
||||||
|
mgi_mcxw71_init, mgi_mcxw71_tx, mgi_mcxw71_rx, mgi_mcxw71_poll};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||||
|
MG_ENABLE_DRIVER_MCXW71
|
||||||
|
|
||||||
|
struct mg_tcpip_driver_mcxw71_data {
|
||||||
|
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||||
|
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
|
||||||
|
do { \
|
||||||
|
static struct mg_tcpip_driver_mcxw71_data driver_data_; \
|
||||||
|
static struct mg_tcpip_if mif_; \
|
||||||
|
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||||
|
mif_.ip = MG_TCPIP_IP; \
|
||||||
|
mif_.mask = MG_TCPIP_MASK; \
|
||||||
|
mif_.gw = MG_TCPIP_GW; \
|
||||||
|
mif_.mgr = (manager); \
|
||||||
|
mif_.driver = &mg_tcpip_driver_mcxw71; \
|
||||||
|
mif_.driver_data = &driver_data_; \
|
||||||
|
mg_tcpip_init((manager), &mif_); \
|
||||||
|
MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_DRIVER_INIT
|
||||||
|
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
#include "net_builtin.h"
|
||||||
|
#include "l2_mesh.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
|
||||||
|
|
||||||
|
#include <rail.h>
|
||||||
|
#include <rail_ieee802154.h>
|
||||||
|
|
||||||
|
// RAIL delivers raw frames and transmit completion from interrupt context.
|
||||||
|
#define MG_MG24_TX_TIMEOUT_US 60000
|
||||||
|
|
||||||
|
static struct mg_tcpip_if *s_mg24_ifp;
|
||||||
|
static RAIL_Handle_t s_mg24_rail;
|
||||||
|
static struct mg_queue s_mg24_rx;
|
||||||
|
static char s_mg24_rxbuf[2048];
|
||||||
|
static volatile bool s_mg24_txing, s_mg24_tx_done;
|
||||||
|
static RAIL_Time_t s_mg24_txstart; // Send start, us
|
||||||
|
static uint8_t s_mg24_txfifo[256] __attribute__((aligned(4))); // Power of 2
|
||||||
|
|
||||||
|
// Copy a received PSDU, stripping the PHR and any stored FCS.
|
||||||
|
static void mg_mg24_rx_frame(RAIL_Handle_t h) {
|
||||||
|
RAIL_RxPacketInfo_t info;
|
||||||
|
uint8_t f[128];
|
||||||
|
size_t n;
|
||||||
|
char *dst;
|
||||||
|
if (RAIL_GetRxPacketInfo(h, RAIL_RX_PACKET_HANDLE_NEWEST, &info) ==
|
||||||
|
RAIL_RX_PACKET_HANDLE_INVALID ||
|
||||||
|
info.packetStatus != RAIL_RX_PACKET_READY_SUCCESS ||
|
||||||
|
info.packetBytes <= 1 || info.packetBytes > sizeof(f)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RAIL_CopyRxPacket(f, &info);
|
||||||
|
n = info.packetBytes - 1U; // Strip PHR
|
||||||
|
if (n == f[0]) n -= 2; // FCS stored, strip it
|
||||||
|
if (mg_queue_book(&s_mg24_rx, &dst, n) >= n) {
|
||||||
|
memcpy(dst, f + 1, n);
|
||||||
|
mg_queue_add(&s_mg24_rx, n);
|
||||||
|
} else {
|
||||||
|
s_mg24_ifp->ndrop++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAIL event callback, runs in interrupt context
|
||||||
|
static void mg_mg24_events(RAIL_Handle_t h, RAIL_Events_t events) {
|
||||||
|
if (events & RAIL_EVENT_CAL_NEEDED) {
|
||||||
|
RAIL_Calibrate(h, NULL, RAIL_CAL_ALL_PENDING);
|
||||||
|
}
|
||||||
|
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) mg_mg24_rx_frame(h);
|
||||||
|
if (events & RAIL_EVENTS_TX_COMPLETION) s_mg24_tx_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAIL adds the FCS.
|
||||||
|
static size_t mgi_mg24_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
uint8_t phr = (uint8_t) (len + 2); // FCS is added by hardware
|
||||||
|
(void) ifp;
|
||||||
|
if (len > 125 || s_mg24_txing) return 0;
|
||||||
|
RAIL_WriteTxFifo(s_mg24_rail, &phr, 1, true); // Reset FIFO, write PHR
|
||||||
|
RAIL_WriteTxFifo(s_mg24_rail, (const uint8_t *) buf, (uint16_t) len, false);
|
||||||
|
s_mg24_tx_done = false;
|
||||||
|
s_mg24_txing = true, s_mg24_txstart = RAIL_GetTime();
|
||||||
|
if (RAIL_StartTx(s_mg24_rail, MG_802154_CHANNEL, RAIL_TX_OPTIONS_DEFAULT,
|
||||||
|
NULL) != RAIL_STATUS_NO_ERROR) {
|
||||||
|
s_mg24_txing = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mg24_radio_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
RAIL_Config_t cfg = {0};
|
||||||
|
RAIL_IEEE802154_Config_t cfg154 = {0};
|
||||||
|
RAIL_TxPowerConfig_t pwr;
|
||||||
|
RAIL_StateTransitions_t to_rx = {RAIL_RF_STATE_RX, RAIL_RF_STATE_RX};
|
||||||
|
RAIL_Events_t events = RAIL_EVENT_CAL_NEEDED | RAIL_EVENT_RX_PACKET_RECEIVED |
|
||||||
|
RAIL_EVENTS_TX_COMPLETION;
|
||||||
|
cfg.eventsCallback = mg_mg24_events;
|
||||||
|
if ((s_mg24_rail = RAIL_Init(&cfg, NULL)) == NULL) {
|
||||||
|
MG_ERROR(("RAIL_Init failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cfg154.ackConfig.enable = false;
|
||||||
|
cfg154.ackConfig.ackTimeout = 864;
|
||||||
|
cfg154.ackConfig.rxTransitions = to_rx;
|
||||||
|
cfg154.ackConfig.txTransitions = to_rx;
|
||||||
|
cfg154.timings.idleToRx = 100, cfg154.timings.idleToTx = 100;
|
||||||
|
cfg154.timings.rxToTx = 192, cfg154.timings.txToRx = 182;
|
||||||
|
cfg154.framesMask = RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES;
|
||||||
|
cfg154.promiscuousMode = false; // Filter by PAN ID and our short address
|
||||||
|
pwr.mode = RAIL_TX_POWER_MODE_2P4GIG_HP, pwr.voltage = 3300;
|
||||||
|
pwr.rampTime = 10;
|
||||||
|
if (RAIL_ConfigCal(s_mg24_rail, RAIL_CAL_ALL) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_ConfigEvents(s_mg24_rail, RAIL_EVENTS_ALL, events) !=
|
||||||
|
RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_IEEE802154_Config2p4GHzRadio(s_mg24_rail) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_IEEE802154_Init(s_mg24_rail, &cfg154) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_IEEE802154_SetPanId(s_mg24_rail, MG_802154_PAN_ID, 0) !=
|
||||||
|
RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_IEEE802154_SetShortAddress(s_mg24_rail, node->radio_address, 0) !=
|
||||||
|
RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_SetTxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_SetRxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_ConfigTxPower(s_mg24_rail, &pwr) != RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_SetTxPower(s_mg24_rail, RAIL_TX_POWER_LEVEL_2P4_HP_MAX) !=
|
||||||
|
RAIL_STATUS_NO_ERROR ||
|
||||||
|
RAIL_SetTxFifo(s_mg24_rail, s_mg24_txfifo, 0, sizeof(s_mg24_txfifo)) !=
|
||||||
|
sizeof(s_mg24_txfifo)) {
|
||||||
|
MG_ERROR(("RAIL setup failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s_mg24_ifp = ifp;
|
||||||
|
return RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL) ==
|
||||||
|
RAIL_STATUS_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mg24_tx(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||||
|
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mg24_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||||
|
size_t len = data->mesh->rx_len;
|
||||||
|
if (len > cap) return 0;
|
||||||
|
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||||
|
memcpy(buf, ifp->recv_queue.buf, len);
|
||||||
|
data->mesh->rx_len = 0;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mg24_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
uint64_t now = mg_micros();
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
(void) s1;
|
||||||
|
node->now = now;
|
||||||
|
if (s_mg24_txing && (s_mg24_tx_done || RAIL_GetTime() - s_mg24_txstart >
|
||||||
|
MG_MG24_TX_TIMEOUT_US)) {
|
||||||
|
if (!s_mg24_tx_done) {
|
||||||
|
RAIL_Idle(s_mg24_rail, RAIL_IDLE_ABORT, true);
|
||||||
|
RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL);
|
||||||
|
}
|
||||||
|
s_mg24_txing = false;
|
||||||
|
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||||
|
}
|
||||||
|
while (node->rx_len == 0 && (len = mg_queue_next(&s_mg24_rx, &buf)) != 0) {
|
||||||
|
node->strategy->rx(node, buf, len);
|
||||||
|
mg_queue_del(&s_mg24_rx, len);
|
||||||
|
}
|
||||||
|
node->strategy->tick(node, now);
|
||||||
|
return node->joined;
|
||||||
|
}
|
||||||
|
static bool mgi_mg24_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||||
|
const struct mg_mesh *strategy = data->strategy;
|
||||||
|
uint16_t address =
|
||||||
|
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||||
|
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||||
|
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||||
|
if (data->mesh == NULL) return false;
|
||||||
|
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data->mesh->strategy = strategy;
|
||||||
|
data->mesh->tx = mgi_mg24_radio_tx;
|
||||||
|
mg_queue_init(&s_mg24_rx, s_mg24_rxbuf, sizeof(s_mg24_rxbuf));
|
||||||
|
if (mgi_mg24_radio_init(ifp)) return true;
|
||||||
|
data->mesh->strategy->free(data->mesh);
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct mg_tcpip_driver mg_tcpip_driver_mg24 = {mgi_mg24_init, mgi_mg24_tx,
|
||||||
|
mgi_mg24_rx, mgi_mg24_poll};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
|
||||||
|
|
||||||
|
struct mg_tcpip_driver_mg24_data {
|
||||||
|
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||||
|
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mg_tcpip_driver mg_tcpip_driver_mg24;
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_MG24_INIT(manager) \
|
||||||
|
do { \
|
||||||
|
static struct mg_tcpip_driver_mg24_data driver_data_; \
|
||||||
|
static struct mg_tcpip_if mif_; \
|
||||||
|
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||||
|
mif_.ip = MG_TCPIP_IP; \
|
||||||
|
mif_.mask = MG_TCPIP_MASK; \
|
||||||
|
mif_.gw = MG_TCPIP_GW; \
|
||||||
|
mif_.mgr = (manager); \
|
||||||
|
mif_.driver = &mg_tcpip_driver_mg24; \
|
||||||
|
mif_.driver_data = &driver_data_; \
|
||||||
|
mg_tcpip_init((manager), &mif_); \
|
||||||
|
MG_INFO(("Driver: mg24, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_DRIVER_INIT
|
||||||
|
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MG24_INIT(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
#include "net_builtin.h"
|
||||||
|
#include "l2_mesh.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
|
||||||
|
#include <nrf.h>
|
||||||
|
|
||||||
|
static struct mg_tcpip_if *s_nrf52_ifp;
|
||||||
|
static struct mg_queue s_nrf52_rx;
|
||||||
|
static char s_nrf52_queue[2048];
|
||||||
|
static uint8_t s_nrf52_rxbuf[128] __attribute__((aligned(4)));
|
||||||
|
static uint8_t s_nrf52_txbuf[128] __attribute__((aligned(4)));
|
||||||
|
static volatile bool s_nrf52_txing, s_nrf52_done;
|
||||||
|
static uint64_t s_nrf52_started;
|
||||||
|
|
||||||
|
static void mgi_nrf52_listen(void) {
|
||||||
|
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_rxbuf;
|
||||||
|
NRF_RADIO->SHORTS =
|
||||||
|
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
|
||||||
|
NRF_RADIO->EVENTS_FRAMESTART = 0;
|
||||||
|
NRF_RADIO->EVENTS_END = 0;
|
||||||
|
NRF_RADIO->TASKS_RXEN = 1;
|
||||||
|
}
|
||||||
|
static void mgi_nrf52_disable(void) {
|
||||||
|
NRF_RADIO->SHORTS = 0;
|
||||||
|
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||||
|
NRF_RADIO->TASKS_DISABLE = 1;
|
||||||
|
while (!NRF_RADIO->EVENTS_DISABLED) (void) 0;
|
||||||
|
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||||
|
}
|
||||||
|
void RADIO_IRQHandler(void);
|
||||||
|
void RADIO_IRQHandler(void) {
|
||||||
|
if (!NRF_RADIO->EVENTS_DISABLED) return;
|
||||||
|
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||||
|
if (s_nrf52_txing) {
|
||||||
|
s_nrf52_txing = false;
|
||||||
|
s_nrf52_done = true;
|
||||||
|
} else if (NRF_RADIO->CRCSTATUS) {
|
||||||
|
struct mg_tcpip_driver_nrf52_data *data = s_nrf52_ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
size_t len = s_nrf52_rxbuf[0] >= 2 ? s_nrf52_rxbuf[0] - 2U : 0;
|
||||||
|
uint8_t *frame = s_nrf52_rxbuf + 1;
|
||||||
|
uint16_t dst, src;
|
||||||
|
if (len <= 125 &&
|
||||||
|
mg_radio_802154.parse_hdr(&mg_radio_802154, frame, len, &dst, &src) !=
|
||||||
|
0 &&
|
||||||
|
(dst == 0xffffu || dst == node->radio_address)) {
|
||||||
|
char *dst;
|
||||||
|
if (mg_queue_book(&s_nrf52_rx, &dst, len) >= len) {
|
||||||
|
memcpy(dst, frame, len);
|
||||||
|
mg_queue_add(&s_nrf52_rx, len);
|
||||||
|
} else {
|
||||||
|
s_nrf52_ifp->ndrop++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mgi_nrf52_listen();
|
||||||
|
}
|
||||||
|
static size_t mgi_nrf52_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
(void) ifp;
|
||||||
|
if (len > 125) return 0;
|
||||||
|
NVIC_DisableIRQ(RADIO_IRQn);
|
||||||
|
if (s_nrf52_txing || NRF_RADIO->EVENTS_FRAMESTART) {
|
||||||
|
NVIC_EnableIRQ(RADIO_IRQn);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
mgi_nrf52_disable();
|
||||||
|
s_nrf52_txbuf[0] = (uint8_t) (len + 2);
|
||||||
|
memcpy(s_nrf52_txbuf + 1, buf, len);
|
||||||
|
s_nrf52_done = false;
|
||||||
|
s_nrf52_txing = true;
|
||||||
|
s_nrf52_started = mg_micros();
|
||||||
|
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_txbuf;
|
||||||
|
NRF_RADIO->SHORTS =
|
||||||
|
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
|
||||||
|
NRF_RADIO->TASKS_TXEN = 1;
|
||||||
|
NVIC_EnableIRQ(RADIO_IRQn);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
static bool mgi_nrf52_radio_init(struct mg_tcpip_if *ifp) {
|
||||||
|
s_nrf52_ifp = ifp;
|
||||||
|
NRF_RADIO->POWER = 1;
|
||||||
|
NRF_RADIO->MODE = RADIO_MODE_MODE_Ieee802154_250Kbit;
|
||||||
|
NRF_RADIO->PCNF0 = (8UL << RADIO_PCNF0_LFLEN_Pos) |
|
||||||
|
(RADIO_PCNF0_PLEN_32bitZero << RADIO_PCNF0_PLEN_Pos) |
|
||||||
|
RADIO_PCNF0_CRCINC_Msk;
|
||||||
|
NRF_RADIO->PCNF1 = 127;
|
||||||
|
NRF_RADIO->CRCCNF = 0x202;
|
||||||
|
NRF_RADIO->CRCPOLY = 0x11021;
|
||||||
|
NRF_RADIO->CRCINIT = 0;
|
||||||
|
NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_DTX_Center << RADIO_MODECNF0_DTX_Pos) |
|
||||||
|
RADIO_MODECNF0_RU_Fast;
|
||||||
|
NRF_RADIO->SFD = 0xa7;
|
||||||
|
NRF_RADIO->FREQUENCY = 5 * (MG_802154_CHANNEL - 10);
|
||||||
|
NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm;
|
||||||
|
// Correlator threshold 45 matches the RADIO reset value. A threshold of
|
||||||
|
// 20 caused false busy detections and persistent L2 retries.
|
||||||
|
NRF_RADIO->CCACTRL =
|
||||||
|
(RADIO_CCACTRL_CCAMODE_CarrierMode << RADIO_CCACTRL_CCAMODE_Pos) |
|
||||||
|
(2UL << RADIO_CCACTRL_CCACORRCNT_Pos) |
|
||||||
|
(45UL << RADIO_CCACTRL_CCACORRTHRES_Pos);
|
||||||
|
NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk;
|
||||||
|
NVIC_SetPriority(RADIO_IRQn, 0);
|
||||||
|
NVIC_EnableIRQ(RADIO_IRQn);
|
||||||
|
mgi_nrf52_listen();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static size_t mgi_nrf52_tx(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||||
|
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_nrf52_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||||
|
size_t len = data->mesh->rx_len;
|
||||||
|
if (len > cap) return 0;
|
||||||
|
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||||
|
memcpy(buf, ifp->recv_queue.buf, len);
|
||||||
|
data->mesh->rx_len = 0;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_nrf52_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
uint64_t now = mg_micros();
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
(void) s1;
|
||||||
|
if (s_nrf52_txing && mg_micros() - s_nrf52_started > 60000) {
|
||||||
|
NVIC_DisableIRQ(RADIO_IRQn);
|
||||||
|
mgi_nrf52_disable();
|
||||||
|
s_nrf52_txing = false;
|
||||||
|
s_nrf52_done = true;
|
||||||
|
mgi_nrf52_listen();
|
||||||
|
NVIC_EnableIRQ(RADIO_IRQn);
|
||||||
|
}
|
||||||
|
if (s_nrf52_done) {
|
||||||
|
s_nrf52_done = false;
|
||||||
|
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||||
|
}
|
||||||
|
node->now = now;
|
||||||
|
while (node->rx_len == 0 && (len = mg_queue_next(&s_nrf52_rx, &buf)) != 0) {
|
||||||
|
node->strategy->rx(node, buf, len);
|
||||||
|
mg_queue_del(&s_nrf52_rx, len);
|
||||||
|
}
|
||||||
|
node->strategy->tick(node, now);
|
||||||
|
return node->joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_nrf52_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||||
|
const struct mg_mesh *strategy = data->strategy;
|
||||||
|
uint16_t address;
|
||||||
|
address =
|
||||||
|
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||||
|
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||||
|
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||||
|
if (data->mesh == NULL) return false;
|
||||||
|
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data->mesh->strategy = strategy;
|
||||||
|
data->mesh->tx = mgi_nrf52_radio_tx;
|
||||||
|
mg_queue_init(&s_nrf52_rx, s_nrf52_queue, sizeof(s_nrf52_queue));
|
||||||
|
if (mgi_nrf52_radio_init(ifp)) return true;
|
||||||
|
strategy->free(data->mesh);
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mg_tcpip_driver mg_tcpip_driver_nrf52 = {mgi_nrf52_init, mgi_nrf52_tx,
|
||||||
|
mgi_nrf52_rx, mgi_nrf52_poll};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
|
||||||
|
|
||||||
|
struct mg_tcpip_driver_nrf52_data {
|
||||||
|
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||||
|
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mg_tcpip_driver mg_tcpip_driver_nrf52;
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_NRF52_INIT(manager) \
|
||||||
|
do { \
|
||||||
|
static struct mg_tcpip_driver_nrf52_data driver_data_; \
|
||||||
|
static struct mg_tcpip_if mif_; \
|
||||||
|
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||||
|
mif_.ip = MG_TCPIP_IP; \
|
||||||
|
mif_.mask = MG_TCPIP_MASK; \
|
||||||
|
mif_.gw = MG_TCPIP_GW; \
|
||||||
|
mif_.mgr = (manager); \
|
||||||
|
mif_.driver = &mg_tcpip_driver_nrf52; \
|
||||||
|
mif_.driver_data = &driver_data_; \
|
||||||
|
mg_tcpip_init((manager), &mif_); \
|
||||||
|
MG_INFO(("Driver: nrf52, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_DRIVER_INIT
|
||||||
|
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_NRF52_INIT(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -27,6 +27,7 @@ struct mg_tcpip_driver_rw612_data {
|
|||||||
#define MG_DRIVER_MDC_HOLDTIME 3
|
#define MG_DRIVER_MDC_HOLDTIME 3
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_DRIVER_INIT
|
||||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||||
do { \
|
do { \
|
||||||
static struct mg_tcpip_driver_rw612_data driver_data_; \
|
static struct mg_tcpip_driver_rw612_data driver_data_; \
|
||||||
@@ -42,5 +43,6 @@ struct mg_tcpip_driver_rw612_data {
|
|||||||
mg_tcpip_init(mgr, &mif_); \
|
mg_tcpip_init(mgr, &mif_); \
|
||||||
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
|
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,513 @@
|
|||||||
|
#include "net_builtin.h"
|
||||||
|
#include "l2_mesh.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
|
||||||
|
MG_ENABLE_DRIVER_RW612W
|
||||||
|
|
||||||
|
#include "sha256.h"
|
||||||
|
|
||||||
|
#ifdef ENET
|
||||||
|
#undef ENET
|
||||||
|
#endif
|
||||||
|
#include <fsl_ocotp.h>
|
||||||
|
|
||||||
|
// 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_SADDR = 0x35, // Short address, little endian
|
||||||
|
MGI_PROP_PANID = 0x36,
|
||||||
|
MGI_PROP_RAW_STREAM = 0x37,
|
||||||
|
MGI_PROP_PROMISCUOUS = 0x38,
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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
|
||||||
|
bool txing; // A send is on, see mg_rw612_start()
|
||||||
|
uint64_t txstart; // Its start, ms
|
||||||
|
struct mg_tcpip_if *ifp; // Our interface, for the L2 receive pool
|
||||||
|
};
|
||||||
|
static struct mg_rw612 s_rw612;
|
||||||
|
static struct mg_queue s_rw612w_rx;
|
||||||
|
static char s_rw612w_queue[2048];
|
||||||
|
|
||||||
|
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) {
|
||||||
|
{
|
||||||
|
char *dst;
|
||||||
|
if (mg_queue_book(&s_rw612w_rx, &dst, len) >= len) {
|
||||||
|
memcpy(dst, frame, len);
|
||||||
|
mg_queue_add(&s_rw612w_rx, len);
|
||||||
|
} else {
|
||||||
|
s_rw612.ifp->ndrop++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, with a new transaction ID. Its
|
||||||
|
// response sets rsp, see mg_rw612_spinel()
|
||||||
|
static bool mg_rw612_post(uint8_t *f, size_t len) {
|
||||||
|
struct mg_rw612 *s = &s_rw612;
|
||||||
|
uint8_t buf[2 * 160 + 6];
|
||||||
|
uint16_t fcs = 0xffff;
|
||||||
|
size_t i, n = 0;
|
||||||
|
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;
|
||||||
|
return mg_rw612_imumc_send(buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a Spinel frame and wait for its response. Return false on timeout
|
||||||
|
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
|
||||||
|
uint64_t expire = mg_millis() + timeout_ms;
|
||||||
|
if (!mg_rw612_post(f, len)) return false;
|
||||||
|
while (!s_rw612.rsp && mg_millis() < expire) mg_rw612_poll_imu();
|
||||||
|
return s_rw612.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start one raw transmission. The RCP adds the FCS and reports completion.
|
||||||
|
static size_t mgi_rw612w_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
uint8_t f[3 + 2 + 127 + 4];
|
||||||
|
size_t n = 3;
|
||||||
|
if (len > 125 || s_rw612.txing) return 0;
|
||||||
|
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, buf, len), n += len;
|
||||||
|
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio
|
||||||
|
(void) ifp;
|
||||||
|
f[n++] = MG_802154_CHANNEL;
|
||||||
|
f[n++] = 0; // Max CSMA backoffs
|
||||||
|
f[n++] = 0; // Max frame retries
|
||||||
|
f[n++] = 0; // CSMA disabled for scheduled turns
|
||||||
|
if (!mg_rw612_post(f, n)) return 0;
|
||||||
|
s_rw612.txing = true, s_rw612.txstart = mg_millis();
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_rw612w_radio_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_rw612 *s = &s_rw612;
|
||||||
|
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
uint8_t f[8], one = 1, zero = 0, pan[2], addr[2], channel = MG_802154_CHANNEL;
|
||||||
|
uint64_t expire;
|
||||||
|
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, s->ifp = ifp;
|
||||||
|
// 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) MG_802154_PAN_ID;
|
||||||
|
pan[1] = (uint8_t) (MG_802154_PAN_ID >> 8);
|
||||||
|
addr[0] = (uint8_t) node->radio_address;
|
||||||
|
addr[1] = (uint8_t) (node->radio_address >> 8);
|
||||||
|
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &channel, 1) ||
|
||||||
|
!mg_rw612_set(MGI_PROP_PANID, pan, 2) ||
|
||||||
|
!mg_rw612_set(MGI_PROP_SADDR, addr, 2) ||
|
||||||
|
!mg_rw612_set(MGI_PROP_PROMISCUOUS, &zero, 1)) {
|
||||||
|
return mg_rw612_fail("Channel, PAN ID or address set failed");
|
||||||
|
}
|
||||||
|
if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
|
||||||
|
return mg_rw612_fail("Raw stream enable failed");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_rw612w_tx(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||||
|
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_rw612w_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||||
|
size_t len = data->mesh->rx_len;
|
||||||
|
if (len > cap) return 0;
|
||||||
|
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||||
|
memcpy(buf, ifp->recv_queue.buf, len);
|
||||||
|
data->mesh->rx_len = 0;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_rw612w_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||||
|
struct mg_mesh_node *node = data->mesh;
|
||||||
|
uint64_t now = mg_micros();
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
(void) s1;
|
||||||
|
bool was_sending = s_rw612.txing;
|
||||||
|
mg_rw612_poll_imu();
|
||||||
|
if (s_rw612.txing &&
|
||||||
|
(s_rw612.rsp || mg_millis() > s_rw612.txstart + MGI_TX_TIMEOUT_MS)) {
|
||||||
|
if (!s_rw612.rsp) MG_ERROR(("TX timeout"));
|
||||||
|
s_rw612.txing = false;
|
||||||
|
}
|
||||||
|
if (was_sending && !s_rw612.txing)
|
||||||
|
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||||
|
node->now = now;
|
||||||
|
while (node->rx_len == 0 && (len = mg_queue_next(&s_rw612w_rx, &buf)) != 0) {
|
||||||
|
node->strategy->rx(node, buf, len);
|
||||||
|
mg_queue_del(&s_rw612w_rx, len);
|
||||||
|
}
|
||||||
|
node->strategy->tick(node, now);
|
||||||
|
return node->joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_rw612w_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||||
|
const struct mg_mesh *strategy = data->strategy;
|
||||||
|
uint8_t uid[FSL_OCOTP_UID_LENGTH] = {0}, digest[32];
|
||||||
|
uint32_t uid_len = sizeof(uid);
|
||||||
|
uint16_t address;
|
||||||
|
OCOTP_ReadUniqueID(uid, &uid_len);
|
||||||
|
mg_sha256(digest, uid, sizeof(uid));
|
||||||
|
memcpy(ifp->mac, digest, 6);
|
||||||
|
ifp->mac[0] = 0x02;
|
||||||
|
address =
|
||||||
|
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||||
|
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||||
|
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||||
|
if (data->mesh == NULL) return false;
|
||||||
|
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data->mesh->strategy = strategy;
|
||||||
|
data->mesh->tx = mgi_rw612w_radio_tx;
|
||||||
|
mg_queue_init(&s_rw612w_rx, s_rw612w_queue, sizeof(s_rw612w_queue));
|
||||||
|
if (mgi_rw612w_radio_init(ifp)) return true;
|
||||||
|
strategy->free(data->mesh);
|
||||||
|
free(data->mesh);
|
||||||
|
data->mesh = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mg_tcpip_driver mg_tcpip_driver_rw612w = {
|
||||||
|
mgi_rw612w_init, mgi_rw612w_tx, mgi_rw612w_rx, mgi_rw612w_poll};
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
|
||||||
|
MG_ENABLE_DRIVER_RW612W
|
||||||
|
|
||||||
|
struct mg_tcpip_driver_rw612w_data {
|
||||||
|
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||||
|
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mg_tcpip_driver mg_tcpip_driver_rw612w;
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_RW612W_INIT(manager) \
|
||||||
|
do { \
|
||||||
|
static struct mg_tcpip_driver_rw612w_data driver_data_; \
|
||||||
|
static struct mg_tcpip_if mif_; \
|
||||||
|
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||||
|
mif_.ip = MG_TCPIP_IP; \
|
||||||
|
mif_.mask = MG_TCPIP_MASK; \
|
||||||
|
mif_.gw = MG_TCPIP_GW; \
|
||||||
|
mif_.mgr = (manager); \
|
||||||
|
mif_.driver = &mg_tcpip_driver_rw612w; \
|
||||||
|
mif_.driver_data = &driver_data_; \
|
||||||
|
mg_tcpip_init((manager), &mif_); \
|
||||||
|
MG_INFO(("Driver: rw612w, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_DRIVER_INIT
|
||||||
|
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -365,7 +365,7 @@ static bool st67w6_init(uint8_t *mac) {
|
|||||||
}
|
}
|
||||||
// set clock, who cares ???
|
// set clock, who cares ???
|
||||||
if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false;
|
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
|
// BT-ENABLED DEPENDENCY
|
||||||
|
|
||||||
// MODULE DEPENDENCY
|
// MODULE DEPENDENCY
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \
|
#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \
|
||||||
MG_ENABLE_DRIVER_STM32N)
|
MG_ENABLE_DRIVER_STM32N)
|
||||||
|
// STM32C: vendor modded single-queue Synopsys v5.4, same settings as STM32H
|
||||||
// STM32H: vendor modded single-queue Synopsys v4.2
|
// STM32H: vendor modded single-queue Synopsys v4.2
|
||||||
// STM32N: dual-queue GbE Synopsys v5.2 with no hash table option, 64-bit AXI
|
// 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
|
// 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
|
#if MG_ENABLE_DRIVER_MCXN
|
||||||
void ETHERNET_IRQHandler(void);
|
void ETHERNET_IRQHandler(void);
|
||||||
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);
|
||||||
void ETH_IRQHandler(void) {
|
void ETH_IRQHandler(void) {
|
||||||
#else
|
#else
|
||||||
@@ -339,8 +340,10 @@ void ETH1_IRQHandler(void) {
|
|||||||
}
|
}
|
||||||
ETH->DMACSR =
|
ETH->DMACSR =
|
||||||
MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
|
MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
|
||||||
ETH->DMACRDTPR =
|
// Resume RX: tail is the last descriptor given back to DMA. It must differ
|
||||||
(uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX
|
// 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 = {
|
struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
|
||||||
|
|||||||
+11
-2
@@ -4,6 +4,13 @@
|
|||||||
#if !defined(MG_ENABLE_DRIVER_STM32H)
|
#if !defined(MG_ENABLE_DRIVER_STM32H)
|
||||||
#define MG_ENABLE_DRIVER_STM32H 0
|
#define MG_ENABLE_DRIVER_STM32H 0
|
||||||
#endif
|
#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)
|
#if !defined(MG_ENABLE_DRIVER_MCXN)
|
||||||
#define MG_ENABLE_DRIVER_MCXN 0
|
#define MG_ENABLE_DRIVER_MCXN 0
|
||||||
#endif
|
#endif
|
||||||
@@ -43,9 +50,11 @@ struct mg_tcpip_driver_stm32h_data {
|
|||||||
#define MG_DRIVER_MDC_CR 4
|
#define MG_DRIVER_MDC_CR 4
|
||||||
#endif
|
#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)
|
#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)
|
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
|
||||||
#elif !defined(MG_ENABLE_ETH_IRQ)
|
#elif !defined(MG_ENABLE_ETH_IRQ)
|
||||||
#define MG_ENABLE_ETH_IRQ()
|
#define MG_ENABLE_ETH_IRQ()
|
||||||
|
|||||||
+3
-4
@@ -4,7 +4,7 @@
|
|||||||
#include "ota.h"
|
#include "ota.h"
|
||||||
#ifdef MG_OTA_PUBLIC_KEY
|
#ifdef MG_OTA_PUBLIC_KEY
|
||||||
#include "sha256.h"
|
#include "sha256.h"
|
||||||
#include "tls_uecc.h"
|
#include "tls_p256.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM
|
#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];
|
uint8_t hash[32];
|
||||||
size_t fw_size = s_size - 68; // strip 64-byte sig + 4-byte magic
|
size_t fw_size = s_size - 68; // strip 64-byte sig + 4-byte magic
|
||||||
mg_sha256(hash, (uint8_t *) base, fw_size);
|
mg_sha256(hash, (uint8_t *) base, fw_size);
|
||||||
ok = mg_uecc_verify(s_pubkey, hash, sizeof(hash),
|
ok = mg_p256_verify(s_pubkey, hash, sizeof(hash),
|
||||||
(uint8_t *) base + fw_size,
|
(uint8_t *) base + fw_size) == 1;
|
||||||
mg_uecc_secp256r1()) == 1;
|
|
||||||
MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
|
MG_INFO(("Signature: %s", ok ? "ok" : "fail"));
|
||||||
} else {
|
} else {
|
||||||
ok = false;
|
ok = false;
|
||||||
|
|||||||
@@ -214,7 +214,12 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
|
|||||||
n++;
|
n++;
|
||||||
} else if (c == 's') {
|
} else if (c == 's') {
|
||||||
char *p = va_arg(*ap, char *);
|
char *p = va_arg(*ap, char *);
|
||||||
if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
|
if (p != NULL && pr != ~0U) {
|
||||||
|
for (j = 0; j < pr && p[j] != '\0'; j++) ; // portable strnlen()
|
||||||
|
pr = j;
|
||||||
|
} else {
|
||||||
|
pr = p == NULL ? 0 : strlen(p);
|
||||||
|
}
|
||||||
for (j = 0; !minus && pr < w && j + pr < w; j++)
|
for (j = 0; !minus && pr < w && j + pr < w; j++)
|
||||||
n += scpy(out, param, &pad, 1);
|
n += scpy(out, param, &pad, 1);
|
||||||
n += scpy(out, param, p, pr);
|
n += scpy(out, param, p, pr);
|
||||||
|
|||||||
+203
-8
@@ -1,24 +1,159 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "health.h"
|
#include "health.h"
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#if MG_ENABLE_HEALTH
|
||||||
|
#if !MG_ENABLE_CUSTOM_USAGE
|
||||||
|
size_t mg_usage(int task, int metric) {
|
||||||
|
// FreeRTOS: call NUM_TASKS first to snapshot tasks and copy their names
|
||||||
|
// Indices and names stay valid until the next refresh, preventing duplicate
|
||||||
|
// or mismatched entries. STACK_FREE is minimum-ever free; STACK_TOTAL is 0
|
||||||
|
#if MG_ENABLE_FREERTOS
|
||||||
|
if (metric == MG_USAGE_HEAP_FREE) return xPortGetFreeHeapSize();
|
||||||
|
#ifdef configTOTAL_HEAP_SIZE
|
||||||
|
if (metric == MG_USAGE_HEAP_TOTAL) return configTOTAL_HEAP_SIZE;
|
||||||
|
#endif
|
||||||
|
#if configUSE_TRACE_FACILITY == 1
|
||||||
|
static TaskStatus_t *tasks;
|
||||||
|
static UBaseType_t count;
|
||||||
|
if (metric == MG_USAGE_NUM_TASKS) {
|
||||||
|
size_t size = sizeof(*tasks) + configMAX_TASK_NAME_LEN;
|
||||||
|
vTaskSuspendAll();
|
||||||
|
if (tasks != NULL) {
|
||||||
|
mg_free(tasks);
|
||||||
|
tasks = NULL;
|
||||||
|
}
|
||||||
|
count = 0;
|
||||||
|
UBaseType_t n = uxTaskGetNumberOfTasks();
|
||||||
|
if (n > 0 && (size_t) n <= INT_MAX && (size_t) n <= SIZE_MAX / size) {
|
||||||
|
tasks = (TaskStatus_t *) mg_calloc((size_t) n, size);
|
||||||
|
if (tasks != NULL) {
|
||||||
|
count = uxTaskGetSystemState(tasks, n, NULL);
|
||||||
|
if (count == n) {
|
||||||
|
for (UBaseType_t i = 0; i < count; i++) {
|
||||||
|
char *name = (char *) (tasks + n) + (size_t) i * configMAX_TASK_NAME_LEN;
|
||||||
|
mg_snprintf(name, configMAX_TASK_NAME_LEN, "%s", tasks[i].pcTaskName);
|
||||||
|
tasks[i].pcTaskName = name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mg_free(tasks);
|
||||||
|
tasks = NULL;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(void) xTaskResumeAll();
|
||||||
|
return (size_t) count;
|
||||||
|
}
|
||||||
|
if (task >= 0 && (size_t) task < (size_t) count) {
|
||||||
|
if (metric == MG_USAGE_TASK_NAME) return (size_t) tasks[task].pcTaskName;
|
||||||
|
if (metric == MG_USAGE_STACK_FREE)
|
||||||
|
return (size_t) tasks[task].usStackHighWaterMark * sizeof(StackType_t);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
(void) task;
|
||||||
|
(void) metric;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static size_t print_stacks(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
|
int i;
|
||||||
|
int count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
|
||||||
|
size_t len = mg_xprintf(fn, arg, "[");
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
|
||||||
|
len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%zu,%m:%zu}",
|
||||||
|
i == 0 ? "" : ",", MG_ESC("name"),
|
||||||
|
MG_ESC(name == NULL ? "" : name), MG_ESC("free"),
|
||||||
|
(size_t) mg_usage(i, MG_USAGE_STACK_FREE), MG_ESC("total"),
|
||||||
|
(size_t) mg_usage(i, MG_USAGE_STACK_TOTAL));
|
||||||
|
}
|
||||||
|
(void) ap;
|
||||||
|
return len + mg_xprintf(fn, arg, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
|
size_t heap_total = (size_t) mg_usage(0, MG_USAGE_HEAP_TOTAL);
|
||||||
|
size_t heap_free = (size_t) mg_usage(0, MG_USAGE_HEAP_FREE);
|
||||||
|
(void) ap;
|
||||||
|
return mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}",
|
||||||
|
MG_ESC("heap_total"), heap_total, MG_ESC("heap_free"),
|
||||||
|
heap_free, MG_ESC("stacks"), print_stacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
|
// reusable for other platforms, currently ESP32-only
|
||||||
|
struct mg_health_coredump {
|
||||||
|
const void *ptr;
|
||||||
|
size_t poffset;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mg_health_coredump s_coredump;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool mg_health_coredump_begin(size_t *size) {
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
|
const esp_partition_t *p = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
|
||||||
|
size_t address = 0, len = 0;
|
||||||
|
esp_err_t err = esp_core_dump_image_get(&address, &len);
|
||||||
|
if (size == NULL || s_coredump.ptr != NULL || err != ESP_OK || p == NULL)
|
||||||
|
return false;
|
||||||
|
s_coredump.ptr = p;
|
||||||
|
s_coredump.poffset = address - p->address;
|
||||||
|
s_coredump.size = len;
|
||||||
|
*size = len;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
(void) size;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
|
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
||||||
|
if (p == NULL || offset > s_coredump.size)
|
||||||
|
return false;
|
||||||
|
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
||||||
|
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
|
||||||
|
#else
|
||||||
|
(void) offset, (void) buf, (void) len;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mg_health_coredump_end(void) {
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
|
bool ok = s_coredump.ptr != NULL;
|
||||||
|
memset(&s_coredump, 0, sizeof(s_coredump));
|
||||||
|
return ok;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// The one health record. Lives in RAM that survives a warm reset, see
|
// 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
|
// MG_HEALTH_RAM and the .mg_health region in the linker script
|
||||||
struct mg_health mg_health_record MG_HEALTH_RAM;
|
struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||||
|
|
||||||
// STM32 H723 support. Needs the _estack symbol from the linker script
|
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
|
||||||
#if MG_ENABLE_HEALTH && defined(STM32H723xx_H)
|
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
|
||||||
// Fault handler body. Runs in exception context: no printf, no malloc, no
|
// Fault handler body. Runs in exception context: no printf, no malloc, no
|
||||||
// blocking calls. Copies the unwind registers and raw stack into the health
|
// blocking calls. Copies the unwind registers and raw stack into the health
|
||||||
// record, then resets.
|
// record, then resets.
|
||||||
// "used" keeps the linker from garbage-collecting this section: the only
|
// "used" keeps the linker from garbage-collecting this section: the only
|
||||||
// reference is the "b fault_c" branch in the naked handler below
|
// reference is the "b fault_c" branch in the naked handler below
|
||||||
__attribute__((used, noinline)) static void fault_c(uint32_t *frame,
|
__attribute__((used, noinline)) static void fault_c(uint32_t *frame,
|
||||||
uint32_t exc_return) {
|
uint32_t exc_return) {
|
||||||
extern uint32_t _estack;
|
extern uint32_t _estack;
|
||||||
uint32_t *stack = frame + 8 + ((exc_return & (1U << 4)) ? 0 : 18);
|
uint32_t *stack = frame + 8 + ((exc_return & (1U << 4)) ? 0 : 18);
|
||||||
uint32_t *r = mg_health_record.data;
|
uint32_t *r = mg_health_record.data;
|
||||||
|
|
||||||
if (frame[7] & (1U << 9)) stack++; // Eight-byte stack alignment padding
|
if (frame[7] & (1U << 9)) stack++; // Eight-byte stack alignment padding
|
||||||
r[0] = (uint32_t) (uintptr_t) stack; // SP
|
r[0] = (uint32_t) (uintptr_t) stack; // SP
|
||||||
r[1] = frame[5]; // LR
|
r[1] = frame[5]; // LR
|
||||||
r[2] = frame[6]; // PC
|
r[2] = frame[6]; // PC
|
||||||
@@ -48,6 +183,59 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
|||||||
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Start a new boot: validate the record. Called by MG_HEALTH_INIT()
|
||||||
|
void mg_health_init(void) {
|
||||||
|
char magic[] = MG_HEALTH_MAGIC;
|
||||||
|
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 MG_ARCH == MG_ARCH_ESP32
|
||||||
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
|
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
|
||||||
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
|
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
|
||||||
|
#endif
|
||||||
|
esp_reset_reason_t rr = esp_reset_reason();
|
||||||
|
mg_health_record.reset_reason = esp2mg(rr);
|
||||||
|
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
||||||
|
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
||||||
|
#else
|
||||||
|
// 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
|
||||||
|
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
|
||||||
|
uint32_t *out = mg_health_record.data;
|
||||||
|
size_t n = 0;
|
||||||
|
memset(out, 0, sizeof(mg_health_record.data));
|
||||||
|
if (s == NULL) return;
|
||||||
|
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
||||||
|
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|
||||||
|
|| esp_core_dump_get_summary(s) != ESP_OK) {
|
||||||
|
free(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out[n++] = s->exc_pc;
|
||||||
|
memcpy(&out[n], s->exc_task, 16), n += 4;
|
||||||
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
|
out[n++] = s->ex_info.exc_a[0];
|
||||||
|
out[n++] = s->ex_info.exc_a[1];
|
||||||
|
size_t len = s->exc_bt_info.depth;
|
||||||
|
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
||||||
|
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
|
||||||
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
|
out[n++] = s->ex_info.exc_ra;
|
||||||
|
out[n++] = s->ex_info.exc_sp;
|
||||||
|
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
|
||||||
|
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
||||||
|
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
|
||||||
|
#endif
|
||||||
|
free(s);
|
||||||
|
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
|
#endif // MG_ARCH_ESP32
|
||||||
|
}
|
||||||
|
|
||||||
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||||
size_t n = va_arg(*ap, size_t);
|
size_t n = va_arg(*ap, size_t);
|
||||||
uint32_t *p = va_arg(*ap, uint32_t *);
|
uint32_t *p = va_arg(*ap, uint32_t *);
|
||||||
@@ -59,11 +247,17 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
|||||||
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
if (mg_health_valid() &&
|
if (mg_health_valid() &&
|
||||||
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
|
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
|
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
|
||||||
|
#endif
|
||||||
|
)) {
|
||||||
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
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]}", //
|
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
|
||||||
MG_ESC("version"), mg_health_record.magic[3], //
|
MG_ESC("version"), mg_health_record.magic[3], //
|
||||||
MG_ESC("cpuid"), mg_health_record.cpuid, //
|
MG_ESC("arch"), mg_health_record.arch, //
|
||||||
|
MG_ESC("reason"), mg_health_record.reset_reason, //
|
||||||
|
MG_ESC("cpuid"), mg_health_record.cpuid, //
|
||||||
MG_ESC("data"), print_uint32, n, mg_health_record.data);
|
MG_ESC("data"), print_uint32, n, mg_health_record.data);
|
||||||
} else {
|
} else {
|
||||||
len += mg_xprintf(fn, arg, "{}");
|
len += mg_xprintf(fn, arg, "{}");
|
||||||
@@ -71,3 +265,4 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
|||||||
(void) ap;
|
(void) ap;
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
+111
-14
@@ -5,16 +5,39 @@
|
|||||||
#include "arch.h"
|
#include "arch.h"
|
||||||
#include "printf.h"
|
#include "printf.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_HEALTH
|
||||||
|
|
||||||
#ifndef MG_HEALTH_MAGIC
|
#ifndef MG_HEALTH_MAGIC
|
||||||
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
|
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||||
|
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_HEALTH_DATA_SIZE
|
||||||
|
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
||||||
|
#define MG_HEALTH_DATA_SIZE 250
|
||||||
|
#elif MG_ARCH == MG_ARCH_ESP32
|
||||||
|
#define MG_HEALTH_DATA_SIZE 23
|
||||||
|
#else
|
||||||
|
#define MG_HEALTH_DATA_SIZE 10
|
||||||
|
#endif
|
||||||
|
#endif // MG_HEALTH_DATA_SIZE
|
||||||
|
|
||||||
// Places the record in RAM that survives a reset. Define in mongoose_config.h
|
// 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")))
|
// to match the linker script, e.g. __attribute__((section(".mg_health")))
|
||||||
#ifndef MG_HEALTH_RAM
|
#ifndef MG_HEALTH_RAM
|
||||||
#define MG_HEALTH_RAM
|
#define MG_HEALTH_RAM
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// CPU core architecture
|
||||||
|
enum mg_health_arch {
|
||||||
|
MG_HEALTH_ARCH_ARM = 0,
|
||||||
|
MG_HEALTH_ARCH_XTENSA,
|
||||||
|
MG_HEALTH_ARCH_RISCV
|
||||||
|
};
|
||||||
|
|
||||||
// Why the device restarted. Hardware usually reports several flags at once,
|
// 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
|
// so these are ordered by how much they tell you: the most specific cause a
|
||||||
// device reports wins
|
// device reports wins
|
||||||
@@ -29,13 +52,10 @@ enum mg_health_reason {
|
|||||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef MG_HEALTH_DATA_SIZE
|
|
||||||
#define MG_HEALTH_DATA_SIZE 20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct mg_health {
|
struct mg_health {
|
||||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||||
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
uint16_t reset_reason; // enum mg_health_reason that started this boot
|
||||||
|
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
||||||
uint32_t cpuid; // ARM CPU ID
|
uint32_t cpuid; // ARM CPU ID
|
||||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||||
};
|
};
|
||||||
@@ -68,17 +88,94 @@ static inline const char *mg_health_reason_str(int reason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a new boot: validate the record, count the boot, store the reset
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
// reason. The current boot moves to prev_* slots first, so the previous
|
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
|
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
|
||||||
static inline void mg_health_init(void) {
|
#endif
|
||||||
char magic[] = MG_HEALTH_MAGIC;
|
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
|
||||||
// struct mg_health *h = mg_health_get();
|
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
|
||||||
if (!mg_health_valid()) { // First boot, or RAM lost its contents
|
#endif
|
||||||
memset(&mg_health_record, 0, sizeof(mg_health_record));
|
// clang-format off
|
||||||
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
|
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
|
||||||
|
switch(rr) {
|
||||||
|
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||||
|
case ESP_RST_PWR_GLITCH:
|
||||||
|
#endif
|
||||||
|
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
|
||||||
|
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
|
||||||
|
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
|
||||||
|
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
|
||||||
|
case ESP_RST_INT_WDT:
|
||||||
|
case ESP_RST_TASK_WDT:
|
||||||
|
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||||
|
case ESP_RST_CPU_LOCKUP:
|
||||||
|
#endif
|
||||||
|
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
|
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
// clang-format on
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum mg_usage_metric {
|
||||||
|
MG_USAGE_NUM_TASKS,
|
||||||
|
MG_USAGE_TASK_NAME,
|
||||||
|
MG_USAGE_STACK_TOTAL,
|
||||||
|
MG_USAGE_STACK_FREE,
|
||||||
|
MG_USAGE_HEAP_TOTAL,
|
||||||
|
MG_USAGE_HEAP_FREE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
|
||||||
|
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
|
||||||
|
// size_t
|
||||||
|
// Unsupported metrics and unknown totals return 0
|
||||||
|
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
|
||||||
|
// provide custom measurements
|
||||||
|
size_t mg_usage(int task, int metric);
|
||||||
|
|
||||||
#define MG_HEALTH_INIT() mg_health_init()
|
#define MG_HEALTH_INIT() mg_health_init()
|
||||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||||
|
// JSON memory statistics via mg_usage(), suitable for %M: heap_total, heap_free,
|
||||||
|
// and a stacks array of {name, free, total}
|
||||||
|
// A zero total means unknown
|
||||||
|
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
|
||||||
|
extern void mg_health_init(void);
|
||||||
|
extern bool mg_health_coredump_begin(size_t *size);
|
||||||
|
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
|
||||||
|
extern bool mg_health_coredump_end(void);
|
||||||
|
#else
|
||||||
|
#define MG_HEALTH_INIT()
|
||||||
|
static inline int mg_health_reason(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static inline const char *mg_health_reason_str(int reason) {
|
||||||
|
(void) reason;
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
|
||||||
|
va_list *ap) {
|
||||||
|
(void) ap;
|
||||||
|
return mg_xprintf(fn, arg, "{}");
|
||||||
|
}
|
||||||
|
static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
|
(void) ap;
|
||||||
|
return mg_xprintf(fn, arg, "{}");
|
||||||
|
}
|
||||||
|
static inline bool mg_health_coredump_begin(size_t *size) {
|
||||||
|
(void) size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static inline bool mg_health_coredump_read(size_t offset, void *buf,
|
||||||
|
size_t *len) {
|
||||||
|
(void) offset, (void) buf, (void) len;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static inline bool mg_health_coredump_end(void) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
+46
-34
@@ -734,13 +734,15 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
|||||||
static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) {
|
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;
|
struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u;
|
||||||
size_t n;
|
size_t n;
|
||||||
|
bool matched = false;
|
||||||
while (mg_span(s, &part, &s, ',')) {
|
while (mg_span(s, &part, &s, ',')) {
|
||||||
if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
|
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 (v.len == 0) v = k, k = mg_str("/"), u = k, d = v;
|
||||||
if (uri.len < k.len) continue;
|
if (uri.len < k.len) continue;
|
||||||
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue;
|
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue;
|
||||||
u = k, d = v;
|
u = k, d = v, matched = true;
|
||||||
}
|
}
|
||||||
|
if (!matched) return false;
|
||||||
n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
|
n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
|
||||||
if (len == 0 || n + 2 >= len) return false; // Path overflow
|
if (len == 0 || n + 2 >= len) return false; // Path overflow
|
||||||
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
|
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
|
||||||
@@ -879,8 +881,8 @@ void mg_http_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
|
|||||||
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
|
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
|
||||||
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
|
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
|
||||||
MG_ERROR(("Cannot open %s for upload", path));
|
MG_ERROR(("Cannot open %s for upload", path));
|
||||||
} else if (!mg_http_stream_body(c, hm, serve_upload_cb, fd)) {
|
} else {
|
||||||
mg_fs_close(fd);
|
mg_http_stream_body(c, hm, serve_upload_cb, fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1014,6 +1016,7 @@ struct mg_upload_state {
|
|||||||
size_t received; // Number of body bytes received so far
|
size_t received; // Number of body bytes received so far
|
||||||
void *user_data; // Passed to cb, free for cb to use
|
void *user_data; // Passed to cb, free for cb to use
|
||||||
bool failed; // Set when cb returned false
|
bool failed; // Set when cb returned false
|
||||||
|
bool finished; // End callback has been called
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
||||||
@@ -1021,18 +1024,18 @@ struct mg_upload_state {
|
|||||||
// handler: the connection is closed after the reply.
|
// handler: the connection is closed after the reply.
|
||||||
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
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;
|
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||||
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
||||||
(ptrdiff_t) c->fn_data;
|
(ptrdiff_t) c->fn_data;
|
||||||
(void) ev_data;
|
(void) ev_data;
|
||||||
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
||||||
if (us->received < us->expected) {
|
if (!us->finished) {
|
||||||
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
||||||
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
us->finished = true; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||||
fn(NULL, NULL, &us->user_data);
|
fn(NULL, NULL, &us->user_data);
|
||||||
}
|
}
|
||||||
} else if (ev == MG_EV_READ && !c->is_draining) {
|
} else if (ev == MG_EV_READ && !c->is_draining && !us->finished) {
|
||||||
if (c->recv.len > 0 && us->received < us->expected) {
|
if (!us->failed && c->recv.len > 0 && us->received < us->expected) {
|
||||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
// 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 left = us->expected - us->received, r = c->recv.len;
|
||||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||||
@@ -1043,10 +1046,13 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
us->received += aligned;
|
us->received += aligned;
|
||||||
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
||||||
}
|
}
|
||||||
if (us->received >= us->expected) {
|
if (!us->finished && (us->failed || us->received >= us->expected)) {
|
||||||
// Finished upload. Always call cb, even after error, so it can clean up
|
// Finished upload. Always call cb, even after error, so it can clean up
|
||||||
|
us->finished = true;
|
||||||
us->failed |= !fn(NULL, NULL, &us->user_data);
|
us->failed |= !fn(NULL, NULL, &us->user_data);
|
||||||
if (!c->is_client) {
|
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",
|
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
|
||||||
us->received, us->failed ? "fail" : "ok");
|
us->received, us->failed ? "fail" : "ok");
|
||||||
}
|
}
|
||||||
@@ -1055,48 +1061,54 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||||
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
||||||
void **user_data),
|
void **),
|
||||||
void *user_data) {
|
void *user_data) {
|
||||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||||
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
|
int code = 0;
|
||||||
bool handled = false;
|
size_t head_len = hm->head.len;
|
||||||
|
|
||||||
// Catch upload requests early, without buffering whole body. Called on the
|
// 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
|
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
|
||||||
// the full HTTP body
|
// the full HTTP body
|
||||||
if (hm->body.len == (size_t) ~0 ||
|
if (!c->is_client && mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
|
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||||
// Unknown length or chunked, cannot stream. Leave it to the caller
|
code = 405;
|
||||||
} else if (!c->is_client &&
|
} else if (mg_http_get_header(hm, "Transfer-Encoding") != NULL) {
|
||||||
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
code = 400;
|
||||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
} else if (hm->body.len == (size_t) ~0 ||
|
||||||
// Not an upload request. Leave it to the caller
|
(!c->is_client && mg_http_get_header(hm, "Content-Length") == NULL)) {
|
||||||
|
code = 411;
|
||||||
} else if (sizeof(*us) > sizeof(c->data)) {
|
} else if (sizeof(*us) > sizeof(c->data)) {
|
||||||
if (!c->is_client) { // Nobody to reply to on a client connection
|
code = 500;
|
||||||
c->pfn = NULL;
|
}
|
||||||
mg_http_reply(c, 500, NULL, "data too small\n");
|
c->pfn = NULL; // Take ownership even when validation fails
|
||||||
c->is_draining = 1;
|
if (code != 0) {
|
||||||
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
|
c->fn = NULL;
|
||||||
handled = true;
|
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 {
|
} else {
|
||||||
c->pfn = NULL; // Silence HTTP protocol handler
|
|
||||||
memset(us, 0, sizeof(*us));
|
memset(us, 0, sizeof(*us));
|
||||||
us->expected = hm->body.len; // Store number of bytes we expect
|
us->expected = hm->body.len; // Store number of bytes we expect
|
||||||
us->user_data = user_data;
|
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
|
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
|
||||||
if (mg_http_get_header(hm, "Expect") != NULL) {
|
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_http_reply(c, 100, NULL, ""); // If curl expects, we continue
|
||||||
}
|
}
|
||||||
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
|
mg_iobuf_del(&c->recv, 0, head_len); // Delete HTTP headers
|
||||||
c->fn = mg_stream_handler; // We own the connection from now
|
|
||||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
|
||||||
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
||||||
handled = true;
|
|
||||||
}
|
}
|
||||||
return handled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_hex_digit(int c) {
|
static bool is_hex_digit(int c) {
|
||||||
|
|||||||
+14
-11
@@ -205,29 +205,32 @@ int mg_http_status(const struct mg_http_message *hm);
|
|||||||
|
|
||||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
// 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.
|
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||||
// with mg_match(). Returns true if it took over the connection, false if the
|
// with mg_match(). Always takes over the connection. Invalid requests receive
|
||||||
// request is not POST/PUT, is chunked, or has no Content-Length.
|
// 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:
|
// Callback upload_fn(hm, data, user_data) is called:
|
||||||
// - at start: hm is set. Open the file, set *user_data.
|
// - at start: hm is set. Open the file, set *user_data.
|
||||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||||
// again after a failure.
|
// again after a failure.
|
||||||
// - at end: both NULL. Always called once, also when the connection drops
|
// - at end: both NULL. Always called exactly once, including validation
|
||||||
// early, so release resources here. A truncated body looks like a complete
|
// failures before start and connections that drop early. Release resources
|
||||||
// one: validate before committing.
|
// here. A truncated body looks like a complete one: validate before
|
||||||
// Return true on success, false on error.
|
// committing.
|
||||||
|
// The callback returns true on success, false on error. A failure stops
|
||||||
|
// streaming immediately and calls the end callback before closing.
|
||||||
// Example:
|
// Example:
|
||||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
|
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
|
||||||
// mg_http_stream_body(c, hm, upload, NULL));
|
// mg_http_stream_body(c, hm, upload, NULL);
|
||||||
// }
|
// }
|
||||||
// Full example:
|
// Full example:
|
||||||
// tutorials/http/file-upload-single-post
|
// tutorials/http/file-upload-single-post
|
||||||
// Related APIs:
|
// Related APIs:
|
||||||
// mg_http_listen(), mg_http_next_multipart()
|
// mg_http_listen(), mg_http_next_multipart()
|
||||||
// Notes:
|
// Notes:
|
||||||
// After it returns true, this function hijacks the connection. Existing event
|
// The existing event handler is no longer called. hm is invalid after this
|
||||||
// handler not called, hm is invalid, c->data and c->fn_data are modified
|
// call; c->data and c->fn_data are reserved for streaming state.
|
||||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||||
bool (*upload_fn)(struct mg_http_message *,
|
bool (*upload_fn)(struct mg_http_message *,
|
||||||
struct mg_str *, void **user_data),
|
struct mg_str *, void **user_data),
|
||||||
void *user_data);
|
void *user_data);
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#if MG_TLS == MG_TLS_BUILTIN
|
#if MG_TLS == MG_TLS_BUILTIN
|
||||||
#include "tls_uecc.h"
|
#include "tls_p256.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static size_t jwt_split(struct mg_str jwt, struct mg_str *h, struct mg_str *p,
|
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
|
#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 mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
|
||||||
size_t len) {
|
size_t len) {
|
||||||
uint8_t hash[32], sig[64], tmp[2 * 32 + 64];
|
uint8_t hash[32], sig[64];
|
||||||
jwt_hash_ctx ctx = {
|
|
||||||
{jwt_hash_init, jwt_hash_update, jwt_hash_finish, 64, 32, tmp},
|
|
||||||
{{0}, 0, 0, {0}}};
|
|
||||||
size_t n = jwt_header_payload("ES256", opts, buf, len);
|
size_t n = jwt_header_payload("ES256", opts, buf, len);
|
||||||
size_t m;
|
size_t m;
|
||||||
if (n == 0 || n + 1 >= len) return 0;
|
if (n == 0 || n + 1 >= len) return 0;
|
||||||
mg_sha256(hash, (uint8_t *) buf, n);
|
mg_sha256(hash, (uint8_t *) buf, n);
|
||||||
if (mg_uecc_sign_deterministic(opts->private_key, hash, sizeof(hash),
|
if (mg_p256_sign(opts->private_key, hash, sizeof(hash), sig) != 1)
|
||||||
&ctx.uECC, sig, mg_uecc_secp256r1()) != 1)
|
|
||||||
return 0;
|
return 0;
|
||||||
buf[n++] = '.';
|
buf[n++] = '.';
|
||||||
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - 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)
|
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) != 64)
|
||||||
return 0;
|
return 0;
|
||||||
mg_sha256(hash, (uint8_t *) jwt.buf, n);
|
mg_sha256(hash, (uint8_t *) jwt.buf, n);
|
||||||
if (mg_uecc_verify(opts->public_key, hash, sizeof(hash), sig,
|
if (mg_p256_verify(opts->public_key, hash, sizeof(hash), sig) != 1)
|
||||||
mg_uecc_secp256r1()) != 1)
|
|
||||||
return 0;
|
return 0;
|
||||||
return mg_base64url_decode(p.buf, p.len, buf, len);
|
return mg_base64url_decode(p.buf, p.len, buf, len);
|
||||||
}
|
}
|
||||||
|
|||||||
+616
@@ -0,0 +1,616 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#include "l2_mesh.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "printf.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP
|
||||||
|
|
||||||
|
#define MGI_ETH_MAX 1514
|
||||||
|
#define MGI_TX_DST_LEN 2
|
||||||
|
|
||||||
|
// Mesh header, after the radio header: type, then burst: the fragment's
|
||||||
|
// index << 4 | the last fragment's index. Common to every radio
|
||||||
|
#define MG_MESH_HDR_LEN 2
|
||||||
|
|
||||||
|
#define MG_MESH_FRAME_HDR_LEN (MG_802154_HEADER_LEN + MG_MESH_HDR_LEN)
|
||||||
|
|
||||||
|
// MAC header, 9 bytes: frame control 0x8841, a data frame, PAN ID
|
||||||
|
// compression, short destination and source addresses; sequence number, 0,
|
||||||
|
// as nothing uses it yet; PAN ID; destination; source. Little endian, as
|
||||||
|
// the standard says
|
||||||
|
static void mgi_802154_print_hdr(const struct mg_radio *radio, uint8_t *buf,
|
||||||
|
uint16_t dst, uint16_t src) {
|
||||||
|
buf[0] = 0x41, buf[1] = 0x88, buf[2] = 0;
|
||||||
|
buf[3] = (uint8_t) MG_802154_PAN_ID;
|
||||||
|
buf[4] = (uint8_t) (MG_802154_PAN_ID >> 8);
|
||||||
|
buf[5] = (uint8_t) dst, buf[6] = (uint8_t) (dst >> 8);
|
||||||
|
buf[7] = (uint8_t) src, buf[8] = (uint8_t) (src >> 8);
|
||||||
|
(void) radio;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_802154_parse_hdr(const struct mg_radio *radio,
|
||||||
|
const uint8_t *buf, size_t len,
|
||||||
|
uint16_t *dst, uint16_t *src) {
|
||||||
|
(void) radio;
|
||||||
|
if (len < MG_802154_HEADER_LEN || buf[0] != 0x41 || buf[1] != 0x88 ||
|
||||||
|
buf[3] != (uint8_t) MG_802154_PAN_ID ||
|
||||||
|
buf[4] != (uint8_t) (MG_802154_PAN_ID >> 8)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*dst = (uint16_t) (buf[5] | buf[6] << 8);
|
||||||
|
*src = (uint16_t) (buf[7] | buf[8] << 8);
|
||||||
|
return MG_802154_HEADER_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct mg_radio mg_radio_802154 = {
|
||||||
|
"802.15.4", // name
|
||||||
|
125, // max_frame_size: 127 minus FCS
|
||||||
|
250000, // bitrate
|
||||||
|
8, // overhead
|
||||||
|
320, // slot_us
|
||||||
|
128, // cca_us
|
||||||
|
100, // turnaround_us
|
||||||
|
640, // ifs_us
|
||||||
|
3, // backoff_min
|
||||||
|
5, // backoff_max
|
||||||
|
true, // csma
|
||||||
|
MG_802154_HEADER_LEN, // header_len
|
||||||
|
mgi_802154_print_hdr, // print_hdr
|
||||||
|
mgi_802154_parse_hdr // parse_hdr
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint64_t mgi_mesh_b_airtime(size_t len) {
|
||||||
|
const struct mg_radio *radio = &mg_radio_802154;
|
||||||
|
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||||
|
radio->bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each queue record holds a destination and a complete burst.
|
||||||
|
static size_t mgi_mesh_b_queue(struct mg_mesh_node *node, struct mg_queue *q,
|
||||||
|
const void *buf, size_t len, uint16_t dst,
|
||||||
|
uint8_t type) {
|
||||||
|
const struct mg_radio *radio = &mg_radio_802154;
|
||||||
|
const uint8_t *eth = (const uint8_t *) buf;
|
||||||
|
size_t hdr = MG_MESH_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
|
||||||
|
size_t frames, size, i;
|
||||||
|
char *record;
|
||||||
|
uint8_t *out;
|
||||||
|
if (len < 14 || len > MGI_ETH_MAX) return 0;
|
||||||
|
frames = (len + cap - 1) / cap;
|
||||||
|
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
|
||||||
|
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
|
||||||
|
if (mg_queue_book(q, &record, size) < size) return 0;
|
||||||
|
out = (uint8_t *) record;
|
||||||
|
*out++ = (uint8_t) (dst >> 8);
|
||||||
|
*out++ = (uint8_t) dst;
|
||||||
|
for (i = 0; i < frames; i++) {
|
||||||
|
size_t chunk = len - i * cap;
|
||||||
|
if (chunk > cap) chunk = cap;
|
||||||
|
*out++ = (uint8_t) (hdr + chunk);
|
||||||
|
radio->print_hdr(radio, out, dst, node->radio_address);
|
||||||
|
out[radio->header_len] = type;
|
||||||
|
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
|
||||||
|
memcpy(out + hdr, eth + i * cap, chunk);
|
||||||
|
out += hdr + chunk;
|
||||||
|
}
|
||||||
|
mg_queue_add(q, size);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#define MGI_JOIN_WINDOW_US 3000
|
||||||
|
#define MGI_JOIN_TURN_US 192
|
||||||
|
#define MGI_JOIN_JITTER_US 640
|
||||||
|
#define MGI_DATA_QUEUE_SIZE 2048
|
||||||
|
#define MGI_TX_RETRY_US 32
|
||||||
|
// Our frames: the radio header, with the radio addresses, then the mesh
|
||||||
|
// header MG_MESH_HDR_LEN, then the payload
|
||||||
|
#define MGI_JOIN_UP_LEN \
|
||||||
|
(MG_MESH_FRAME_HDR_LEN + 6) // MGI_JOIN_UP's payload: MAC addr
|
||||||
|
#define MGI_MAX_FRAGMENTS 14
|
||||||
|
|
||||||
|
enum { MGI_JOIN_DOWN = 1, MGI_JOIN_UP, MGI_DATA_DOWN, MGI_DATA_UP };
|
||||||
|
|
||||||
|
enum { MGI_TURN_IDLE, MGI_TURN_JOIN, MGI_TURN_DATA };
|
||||||
|
|
||||||
|
struct mgi_mesh_b_data {
|
||||||
|
uint8_t rx_buf[MGI_ETH_MAX]; // Ethernet frame being reassembled
|
||||||
|
uint8_t rx_expected; // Expected index | last index, 0xff if idle
|
||||||
|
struct mg_mesh_node *node; // Owner of the TCP/IP interface
|
||||||
|
struct mg_queue tx; // One record per burst of RF fragments
|
||||||
|
size_t tx_offset; // Next queued fragment; zero for an empty frame
|
||||||
|
uint64_t tx_at; // Next fragment or JOIN transmission time
|
||||||
|
uint64_t deadline; // End of the current JOIN or DATA turn
|
||||||
|
struct mgi_mesh_b_peer *fanout; // Next broadcast recipient
|
||||||
|
char tx_buf[MGI_DATA_QUEUE_SIZE]; // Transmit queue storage
|
||||||
|
uint32_t random; // PRNG state for join timing
|
||||||
|
struct mgi_mesh_b_peer *members; // Bridge membership list
|
||||||
|
struct mgi_mesh_b_peer *current; // Member whose turn is active or next
|
||||||
|
int8_t missed_rounds; // -1 after a grant; otherwise completed rounds missed
|
||||||
|
uint16_t coordinator; // Coordinator radio address
|
||||||
|
uint8_t phase; // MGI_TURN_IDLE, MGI_TURN_JOIN, or MGI_TURN_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mgi_mesh_b_peer {
|
||||||
|
struct mgi_mesh_b_peer *next; // Next member in round-robin order
|
||||||
|
uint16_t radio_address; // Source address supplied in MGI_JOIN_UP
|
||||||
|
uint8_t mac[6]; // Ethernet MAC supplied in MGI_JOIN_UP
|
||||||
|
uint8_t misses; // Turns in a row it did not answer
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t mgi_mesh_b_random(struct mg_mesh_node *node,
|
||||||
|
struct mgi_mesh_b_data *data) {
|
||||||
|
uint32_t *random = &data->random;
|
||||||
|
if (*random == 0) {
|
||||||
|
size_t i;
|
||||||
|
*random = (uint32_t) mg_now();
|
||||||
|
for (i = 0; i < 6; i++) *random = *random * 33u + node->radio->mac[i];
|
||||||
|
if (*random == 0) *random = 1;
|
||||||
|
}
|
||||||
|
*random ^= *random << 13;
|
||||||
|
*random ^= *random >> 17;
|
||||||
|
*random ^= *random << 5;
|
||||||
|
return *random;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mg_radio *const s_mesh_default_radio = &mg_radio_802154;
|
||||||
|
|
||||||
|
// Read a frame's radio addresses to dst and src, see mg_radio::parse_hdr(), and
|
||||||
|
// return its type, from the mesh header. 0: not a frame of ours
|
||||||
|
static unsigned mgi_mesh_b_frame_parse(const uint8_t *frame, size_t len,
|
||||||
|
uint16_t *dst, uint16_t *src) {
|
||||||
|
if (len < MG_MESH_FRAME_HDR_LEN ||
|
||||||
|
s_mesh_default_radio->parse_hdr(s_mesh_default_radio, frame, len, dst,
|
||||||
|
src) !=
|
||||||
|
s_mesh_default_radio->header_len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frame[s_mesh_default_radio->header_len];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a frame's headers: the radio's, to dst from src, then ours: type,
|
||||||
|
// and burst: high nibble is the index, low nibble is the last index
|
||||||
|
static void mgi_mesh_b_frame_header(uint8_t *frame, uint16_t dst, uint16_t src,
|
||||||
|
uint8_t type, uint8_t burst) {
|
||||||
|
size_t h = s_mesh_default_radio->header_len;
|
||||||
|
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst, src);
|
||||||
|
frame[h] = type, frame[h + 1] = burst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mesh_b_control_send(struct mg_mesh_node *node, uint16_t dst,
|
||||||
|
uint8_t command) {
|
||||||
|
struct mg_tcpip_if *ifp = node->radio;
|
||||||
|
uint8_t frame[MGI_JOIN_UP_LEN] = {0};
|
||||||
|
size_t len = MG_MESH_FRAME_HDR_LEN;
|
||||||
|
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
||||||
|
if (command == MGI_JOIN_UP) {
|
||||||
|
memcpy(frame + MG_MESH_FRAME_HDR_LEN, node->radio->mac, 6);
|
||||||
|
len = MGI_JOIN_UP_LEN;
|
||||||
|
}
|
||||||
|
return node->now >= node->tx_at && node->tx(ifp, frame, len) == len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mesh_b_fragment_capacity(void) {
|
||||||
|
return s_mesh_default_radio->max_frame_size - MG_MESH_FRAME_HDR_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t mgi_mesh_b_burst_time(size_t len) {
|
||||||
|
size_t cap = mgi_mesh_b_fragment_capacity();
|
||||||
|
size_t frames = len == 0 ? 1 : (len + cap - 1) / cap;
|
||||||
|
return (frames - 1) *
|
||||||
|
(mgi_mesh_b_airtime(s_mesh_default_radio->max_frame_size) +
|
||||||
|
s_mesh_default_radio->ifs_us) +
|
||||||
|
mgi_mesh_b_airtime(MG_MESH_FRAME_HDR_LEN + len - (frames - 1) * cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_tx_finish(struct mgi_mesh_b_data *data) {
|
||||||
|
if (data->tx_offset != 0) {
|
||||||
|
if (data->fanout != NULL) data->fanout = data->fanout->next;
|
||||||
|
if (data->fanout == NULL) {
|
||||||
|
size_t len = mg_queue_next(&data->tx, NULL);
|
||||||
|
mg_queue_del(&data->tx, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->tx_offset = 0;
|
||||||
|
data->tx_at = (uint64_t) -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_tx_step(struct mgi_mesh_b_data *data, uint64_t now) {
|
||||||
|
struct mg_mesh_node *node = data->node;
|
||||||
|
struct mg_tcpip_if *radio = node->radio;
|
||||||
|
uint8_t empty[MG_MESH_FRAME_HDR_LEN], *frame = empty;
|
||||||
|
size_t len = MG_MESH_FRAME_HDR_LEN, record_len = 0;
|
||||||
|
uint16_t dst;
|
||||||
|
uint8_t command;
|
||||||
|
if (now < data->tx_at) return;
|
||||||
|
if (now >= data->deadline) {
|
||||||
|
mgi_mesh_b_tx_finish(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dst = node->lan == NULL ? data->coordinator : data->current->radio_address;
|
||||||
|
command = node->lan == NULL ? MGI_DATA_UP : MGI_DATA_DOWN;
|
||||||
|
if (data->tx_offset != 0) {
|
||||||
|
char *record;
|
||||||
|
uint8_t *part;
|
||||||
|
record_len = mg_queue_next(&data->tx, &record);
|
||||||
|
part = (uint8_t *) record + data->tx_offset;
|
||||||
|
len = part[0];
|
||||||
|
frame = part + 1;
|
||||||
|
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst,
|
||||||
|
node->radio_address);
|
||||||
|
} else {
|
||||||
|
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
||||||
|
}
|
||||||
|
if (now < data->node->tx_at || data->node->tx(radio, frame, len) != len) {
|
||||||
|
data->tx_at = now + MGI_TX_RETRY_US;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data->tx_offset == 0 || (data->tx_offset += len + 1) == record_len) {
|
||||||
|
mgi_mesh_b_tx_finish(data);
|
||||||
|
} else {
|
||||||
|
data->tx_at = now + mgi_mesh_b_airtime(len) + s_mesh_default_radio->ifs_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_tx_begin(struct mgi_mesh_b_data *data, bool burst,
|
||||||
|
uint64_t now, uint64_t deadline) {
|
||||||
|
data->phase = MGI_TURN_DATA;
|
||||||
|
data->tx_offset = burst ? MGI_TX_DST_LEN : 0;
|
||||||
|
data->tx_at = now;
|
||||||
|
data->deadline = deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the assembled length, zero for an empty frame, or -1 if incomplete
|
||||||
|
// or invalid. Full non-final fragments make the write offset derivable.
|
||||||
|
static int mgi_mesh_b_data_receive(struct mgi_mesh_b_data *data,
|
||||||
|
const uint8_t *frame, size_t len,
|
||||||
|
uint64_t now) {
|
||||||
|
unsigned burst = frame[MG_MESH_FRAME_HDR_LEN - 1], index = burst >> 4,
|
||||||
|
last = burst & 15;
|
||||||
|
size_t chunk = len - MG_MESH_FRAME_HDR_LEN,
|
||||||
|
cap = mgi_mesh_b_fragment_capacity();
|
||||||
|
size_t offset = index * cap;
|
||||||
|
if (index == 0) {
|
||||||
|
data->rx_expected = (uint8_t) burst;
|
||||||
|
if (data->node->lan == NULL)
|
||||||
|
data->deadline = now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||||
|
2 * s_mesh_default_radio->ifs_us + MGI_JOIN_TURN_US;
|
||||||
|
}
|
||||||
|
if (last >= MGI_MAX_FRAGMENTS || index > last || burst != data->rx_expected ||
|
||||||
|
chunk > cap || now > data->deadline || (index < last && chunk != cap) ||
|
||||||
|
(last != 0 && chunk == 0) || offset + chunk > sizeof(data->rx_buf)) {
|
||||||
|
data->rx_expected = 0xff;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(data->rx_buf + offset, frame + MG_MESH_FRAME_HDR_LEN, chunk);
|
||||||
|
if (index < last) {
|
||||||
|
data->rx_expected = (uint8_t) (burst + 0x10);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data->rx_expected = 0xff;
|
||||||
|
return offset + chunk == 0 || offset + chunk >= 14 ? (int) (offset + chunk)
|
||||||
|
: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge scheduling and Ethernet forwarding.
|
||||||
|
|
||||||
|
#define MGI_MAX_MISSES 3 // Turns in a row without an answer: remove the member
|
||||||
|
|
||||||
|
static uint16_t mgi_mesh_b_join_field(const uint8_t *p) {
|
||||||
|
return (uint16_t) ((unsigned) p[0] * 256 + p[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return chaddr for an Ethernet DHCP server reply, even when broadcast.
|
||||||
|
static const uint8_t *mgi_mesh_b_dhcp_client(const uint8_t *frame, size_t len) {
|
||||||
|
size_t ip_len, ihl, udp_len;
|
||||||
|
const uint8_t *udp, *dhcp;
|
||||||
|
if (len < 34 || mgi_mesh_b_join_field(frame + 12) != 0x0800 ||
|
||||||
|
(frame[14] >> 4) != 4 || frame[23] != 17 ||
|
||||||
|
(mgi_mesh_b_join_field(frame + 20) & 0x3fff) != 0)
|
||||||
|
return NULL;
|
||||||
|
ip_len = mgi_mesh_b_join_field(frame + 16);
|
||||||
|
ihl = (size_t) (frame[14] & 15) * 4;
|
||||||
|
if (ihl < 20 || ip_len > len - 14 || ip_len < ihl + 8 + 240) return NULL;
|
||||||
|
udp = frame + 14 + ihl;
|
||||||
|
udp_len = mgi_mesh_b_join_field(udp + 4);
|
||||||
|
if (mgi_mesh_b_join_field(udp) != 67 ||
|
||||||
|
mgi_mesh_b_join_field(udp + 2) != 68 || udp_len < 8 + 240 ||
|
||||||
|
udp_len > ip_len - ihl)
|
||||||
|
return NULL;
|
||||||
|
dhcp = udp + 8;
|
||||||
|
if (dhcp[0] != 2 || dhcp[1] != 1 || dhcp[2] != 6 ||
|
||||||
|
memcmp(dhcp + 236, "\x63\x82\x53\x63", 4) != 0)
|
||||||
|
return NULL;
|
||||||
|
return dhcp + 28;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mesh_b_enqueue(struct mg_mesh_node *node, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||||
|
const uint8_t *frame = (const uint8_t *) buf;
|
||||||
|
if (node->lan == NULL)
|
||||||
|
return mgi_mesh_b_queue(node, &data->tx, buf, len, data->coordinator,
|
||||||
|
MGI_DATA_UP);
|
||||||
|
if (len >= 14 && len <= MGI_ETH_MAX) {
|
||||||
|
const uint8_t *client = mgi_mesh_b_dhcp_client(frame, len);
|
||||||
|
const uint8_t *dst = client == NULL ? frame : client;
|
||||||
|
bool multicast = client == NULL && (frame[0] & 1) != 0;
|
||||||
|
struct mgi_mesh_b_peer *peer;
|
||||||
|
for (peer = data->members; peer != NULL; peer = peer->next) {
|
||||||
|
if (multicast || memcmp(dst, peer->mac, 6) == 0) {
|
||||||
|
uint16_t addr = multicast ? 0xffffu : peer->radio_address;
|
||||||
|
return mgi_mesh_b_queue(node, &data->tx, buf, len, addr, MGI_DATA_DOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_bridge_queue_lan(struct mg_mesh_node *bridge) {
|
||||||
|
struct mg_queue *rx = &bridge->lan->recv_queue;
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
while ((len = mg_queue_next(rx, &buf)) > 0) {
|
||||||
|
bridge->strategy->tx(bridge, buf, len);
|
||||||
|
mg_queue_del(rx, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_bridge_rx(uint64_t now, struct mg_mesh_node *b,
|
||||||
|
const uint8_t *frame, size_t len) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
||||||
|
struct mgi_mesh_b_peer *peer;
|
||||||
|
uint16_t dst = 0, src = 0;
|
||||||
|
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
||||||
|
if (command == MGI_DATA_UP && dst == b->radio_address &&
|
||||||
|
data->phase == MGI_TURN_DATA && data->current != NULL &&
|
||||||
|
src == data->current->radio_address && now <= data->deadline) {
|
||||||
|
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
||||||
|
if (received < 0) return;
|
||||||
|
if (received != 0) {
|
||||||
|
b->lan->driver->tx(data->rx_buf, (size_t) received, b->lan);
|
||||||
|
}
|
||||||
|
data->current->misses = 0;
|
||||||
|
data->current = data->current->next;
|
||||||
|
data->phase = MGI_TURN_IDLE;
|
||||||
|
data->deadline = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (len != MGI_JOIN_UP_LEN || command != MGI_JOIN_UP ||
|
||||||
|
dst != b->radio_address || data->phase != MGI_TURN_JOIN ||
|
||||||
|
now >= data->deadline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (src == 0 || src == 0xffffu) return;
|
||||||
|
for (peer = data->members; peer != NULL; peer = peer->next) {
|
||||||
|
if (peer->radio_address == src) break;
|
||||||
|
}
|
||||||
|
if (peer == NULL) {
|
||||||
|
peer = (struct mgi_mesh_b_peer *) mg_calloc(1, sizeof(*peer));
|
||||||
|
if (peer == NULL) return;
|
||||||
|
peer->radio_address = src;
|
||||||
|
peer->next = data->members;
|
||||||
|
data->members = peer;
|
||||||
|
}
|
||||||
|
memcpy(peer->mac, frame + MG_MESH_FRAME_HDR_LEN, sizeof(peer->mac));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member peer did not answer its turn. After MGI_MAX_MISSES in a row, it is
|
||||||
|
// gone: remove it, and its queued downlink frames
|
||||||
|
static void mgi_mesh_b_member_missed(struct mgi_mesh_b_data *data,
|
||||||
|
struct mgi_mesh_b_peer *peer) {
|
||||||
|
if (++peer->misses < MGI_MAX_MISSES) return;
|
||||||
|
MG_INFO(("Member %M removed: no answer %d times", mg_print_mac, peer->mac,
|
||||||
|
MGI_MAX_MISSES));
|
||||||
|
if (data->fanout == peer) {
|
||||||
|
data->fanout = peer->next;
|
||||||
|
if (data->fanout == NULL) {
|
||||||
|
size_t len = mg_queue_next(&data->tx, NULL);
|
||||||
|
mg_queue_del(&data->tx, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LIST_DELETE(struct mgi_mesh_b_peer, &data->members, peer);
|
||||||
|
mg_free(peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A broadcast occupies one record and is sent once to each member in list
|
||||||
|
// order.
|
||||||
|
static size_t mgi_mesh_b_bridge_burst(struct mgi_mesh_b_data *data,
|
||||||
|
struct mgi_mesh_b_peer *peer) {
|
||||||
|
char *record;
|
||||||
|
size_t len;
|
||||||
|
while ((len = mg_queue_next(&data->tx, &record)) != 0) {
|
||||||
|
uint16_t dst = mgi_mesh_b_join_field((const uint8_t *) record);
|
||||||
|
struct mgi_mesh_b_peer *member;
|
||||||
|
if (dst == 0xffffu) {
|
||||||
|
if (data->fanout == NULL) data->fanout = data->members;
|
||||||
|
return data->fanout == peer ? len : 0;
|
||||||
|
}
|
||||||
|
if (dst == peer->radio_address) return len;
|
||||||
|
for (member = data->members; member != NULL; member = member->next) {
|
||||||
|
if (member->radio_address == dst) return 0;
|
||||||
|
}
|
||||||
|
mg_queue_del(&data->tx, len); // Its destination has left.
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t mgi_mesh_b_queued_burst_time(struct mgi_mesh_b_data *data,
|
||||||
|
size_t len) {
|
||||||
|
char *record;
|
||||||
|
size_t frames;
|
||||||
|
if (len == 0) return mgi_mesh_b_burst_time(0);
|
||||||
|
mg_queue_next(&data->tx, &record);
|
||||||
|
// Fragment zero carries the final index. Count overhead and IFS per frame.
|
||||||
|
frames =
|
||||||
|
((uint8_t) record[MGI_TX_DST_LEN + MG_MESH_FRAME_HDR_LEN] & 15u) + 1u;
|
||||||
|
return mgi_mesh_b_airtime(len - MGI_TX_DST_LEN - frames +
|
||||||
|
(frames - 1) * s_mesh_default_radio->overhead) +
|
||||||
|
(frames - 1) * s_mesh_default_radio->ifs_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_bridge_io(uint64_t now, struct mg_mesh_node *b) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
||||||
|
mgi_mesh_b_bridge_queue_lan(b);
|
||||||
|
if (now >= data->deadline) {
|
||||||
|
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
||||||
|
if (data->phase == MGI_TURN_DATA && data->current != NULL) {
|
||||||
|
struct mgi_mesh_b_peer *peer = data->current;
|
||||||
|
data->current = peer->next;
|
||||||
|
mgi_mesh_b_member_missed(data, peer);
|
||||||
|
} else if (data->phase == MGI_TURN_JOIN) {
|
||||||
|
data->current = data->members;
|
||||||
|
}
|
||||||
|
data->phase = MGI_TURN_IDLE;
|
||||||
|
if (data->current != NULL) {
|
||||||
|
size_t burst = mgi_mesh_b_bridge_burst(data, data->current);
|
||||||
|
uint64_t down = mgi_mesh_b_queued_burst_time(data, burst);
|
||||||
|
data->rx_expected = 0xff;
|
||||||
|
mgi_mesh_b_tx_begin(data, burst != 0, now,
|
||||||
|
now + down + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||||
|
2 * s_mesh_default_radio->ifs_us +
|
||||||
|
MGI_JOIN_TURN_US);
|
||||||
|
} else if (mgi_mesh_b_control_send(b, 0xffffu, MGI_JOIN_DOWN)) {
|
||||||
|
data->phase = MGI_TURN_JOIN;
|
||||||
|
data->deadline = now + MGI_JOIN_WINDOW_US;
|
||||||
|
}
|
||||||
|
if (data->deadline <= now) data->deadline = now + MGI_JOIN_TURN_US;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_bridge_free(struct mgi_mesh_b_data *data) {
|
||||||
|
while (data->members != NULL) {
|
||||||
|
struct mgi_mesh_b_peer *peer = data->members;
|
||||||
|
data->members = peer->next;
|
||||||
|
mg_free(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_node_rx(struct mg_mesh_node *n, uint64_t now,
|
||||||
|
uint8_t *frame, size_t len) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) n->data;
|
||||||
|
uint16_t dst = 0, src = 0;
|
||||||
|
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
||||||
|
if (command == MGI_JOIN_DOWN && len == MG_MESH_FRAME_HDR_LEN &&
|
||||||
|
dst == 0xffffu) {
|
||||||
|
if (n->joined && src == data->coordinator &&
|
||||||
|
++data->missed_rounds >= MGI_MAX_MISSES) {
|
||||||
|
n->joined = false;
|
||||||
|
data->phase = MGI_TURN_IDLE;
|
||||||
|
data->tx_at = (uint64_t) -1;
|
||||||
|
data->tx_offset = 0;
|
||||||
|
data->rx_expected = 0xff;
|
||||||
|
}
|
||||||
|
if (!n->joined && (mgi_mesh_b_random(n, data) % 64) == 0) {
|
||||||
|
// Spread contenders across windows as well as within the reply window.
|
||||||
|
data->coordinator = src;
|
||||||
|
data->phase = MGI_TURN_JOIN;
|
||||||
|
data->tx_at = now + MGI_JOIN_TURN_US +
|
||||||
|
mgi_mesh_b_random(n, data) % MGI_JOIN_JITTER_US;
|
||||||
|
}
|
||||||
|
} else if (command == MGI_DATA_DOWN && data->coordinator != 0 &&
|
||||||
|
dst == n->radio_address && src == data->coordinator) {
|
||||||
|
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
||||||
|
if (received < 0) return;
|
||||||
|
n->joined = true;
|
||||||
|
data->missed_rounds =
|
||||||
|
-1; // This round had a grant; do not count it as missed.
|
||||||
|
if (received != 0) {
|
||||||
|
struct mg_tcpip_if *ifp = n->radio;
|
||||||
|
if (n->rx_len != 0 || (size_t) received > ifp->recv_queue.size) {
|
||||||
|
ifp->ndrop++;
|
||||||
|
} else {
|
||||||
|
memcpy(ifp->recv_queue.buf, data->rx_buf, (size_t) received);
|
||||||
|
n->rx_len = (size_t) received;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
||||||
|
mgi_mesh_b_tx_begin(data, mg_queue_next(&data->tx, NULL) != 0, now,
|
||||||
|
now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||||
|
2 * s_mesh_default_radio->ifs_us +
|
||||||
|
MGI_JOIN_TURN_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_input(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||||
|
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
|
||||||
|
if (node->tx_at < ready) node->tx_at = ready;
|
||||||
|
node->next_tick = node->now;
|
||||||
|
if (node->lan != NULL)
|
||||||
|
mgi_mesh_b_bridge_rx(node->now, node, (const uint8_t *) buf, len);
|
||||||
|
else
|
||||||
|
mgi_mesh_b_node_rx(node, node->now, (uint8_t *) buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_tick(struct mg_mesh_node *node, uint64_t now) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||||
|
node->now = now;
|
||||||
|
if (node->lan != NULL) {
|
||||||
|
mgi_mesh_b_bridge_io(now, node);
|
||||||
|
mgi_mesh_b_tx_step(data, now);
|
||||||
|
node->next_tick = data->deadline;
|
||||||
|
if (data->tx_at < node->next_tick) node->next_tick = data->tx_at;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data->phase == MGI_TURN_JOIN) {
|
||||||
|
if (data->tx_at <= now) {
|
||||||
|
if (now - data->tx_at <= MGI_JOIN_TURN_US && !node->joined)
|
||||||
|
mgi_mesh_b_control_send(node, data->coordinator, MGI_JOIN_UP);
|
||||||
|
data->tx_at = (uint64_t) -1;
|
||||||
|
data->phase = MGI_TURN_IDLE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mgi_mesh_b_tx_step(data, now);
|
||||||
|
}
|
||||||
|
node->next_tick = data->tx_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mesh_b_init(struct mg_mesh_node *node,
|
||||||
|
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
|
||||||
|
uint16_t radio_address) {
|
||||||
|
struct mgi_mesh_b_data *data =
|
||||||
|
(struct mgi_mesh_b_data *) mg_calloc(1, sizeof(*data));
|
||||||
|
memset(node, 0, sizeof(*node));
|
||||||
|
node->radio = radio;
|
||||||
|
node->lan = lan;
|
||||||
|
node->radio_address = radio_address;
|
||||||
|
node->next_tick = (uint64_t) -1;
|
||||||
|
node->ram_used = node->ram_peak = sizeof(*node);
|
||||||
|
if (data == NULL) return false;
|
||||||
|
node->ram_used += sizeof(*data);
|
||||||
|
node->ram_peak = node->ram_used;
|
||||||
|
data->rx_expected = 0xff;
|
||||||
|
node->data = data;
|
||||||
|
data->node = node;
|
||||||
|
data->tx_at = (uint64_t) -1;
|
||||||
|
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
|
||||||
|
if (lan != NULL) node->next_tick = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_b_free(struct mg_mesh_node *node) {
|
||||||
|
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||||
|
if (data == NULL) return;
|
||||||
|
mgi_mesh_b_bridge_free(data);
|
||||||
|
mg_free(data);
|
||||||
|
node->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct mg_mesh mg_mesh_default = {
|
||||||
|
"default",
|
||||||
|
mgi_mesh_b_init,
|
||||||
|
mgi_mesh_b_tick,
|
||||||
|
mgi_mesh_b_free,
|
||||||
|
mgi_mesh_b_enqueue,
|
||||||
|
mgi_mesh_b_input,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
&mg_radio_802154,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
|
||||||
|
// the mesh splits Ethernet frames into radio frames. Two parts plug in:
|
||||||
|
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
|
||||||
|
// - struct mg_mesh: how the mesh is built and routed
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "net_builtin.h"
|
||||||
|
|
||||||
|
#if MG_ENABLE_TCPIP
|
||||||
|
|
||||||
|
// Monotonic time in microseconds.
|
||||||
|
uint64_t mg_micros(void);
|
||||||
|
|
||||||
|
// A radio standard
|
||||||
|
struct mg_radio {
|
||||||
|
const char *name; // For logs
|
||||||
|
size_t max_frame_size; // Largest radio frame, bytes
|
||||||
|
uint32_t bitrate; // Bits per second on air
|
||||||
|
size_t overhead; // Bytes on air besides the frame: preamble, FCS
|
||||||
|
uint32_t slot_us; // Random backoff slot duration, us
|
||||||
|
uint32_t cca_us; // Channel sensing duration, us
|
||||||
|
uint32_t turnaround_us; // Idle check to transmission start, us
|
||||||
|
uint32_t ifs_us; // Inter-frame spacing in us: gap between transmits
|
||||||
|
uint8_t backoff_min; // Initial backoff exponent
|
||||||
|
uint8_t backoff_max; // Maximum backoff exponent
|
||||||
|
bool csma; // Use channel sensing and backoff before TX
|
||||||
|
size_t header_len; // Radio hardware header length
|
||||||
|
// Write the radio header, with 0xffff as the broadcast address.
|
||||||
|
void (*print_hdr)(const struct mg_radio *radio, uint8_t *buf, uint16_t dst,
|
||||||
|
uint16_t src);
|
||||||
|
// Return the parsed header length, or zero for an invalid header.
|
||||||
|
size_t (*parse_hdr)(const struct mg_radio *radio, const uint8_t *buf,
|
||||||
|
size_t len, uint16_t *dst, uint16_t *src);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MG_802154_HEADER_LEN 9 // MAC header, short addresses
|
||||||
|
#define MG_802154_PAN_ID 0xabcd // Our network's PAN ID
|
||||||
|
|
||||||
|
#ifndef MG_802154_CHANNEL
|
||||||
|
#define MG_802154_CHANNEL 15
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern const struct mg_radio mg_radio_802154;
|
||||||
|
|
||||||
|
struct mg_mesh_node {
|
||||||
|
struct mg_tcpip_if *radio; // Owning radio driver interface
|
||||||
|
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a client
|
||||||
|
const struct mg_mesh *strategy; // Selected strategy
|
||||||
|
size_t (*tx)(struct mg_tcpip_if *, const void *, size_t); // Radio frame TX
|
||||||
|
size_t rx_len; // Complete Ethernet frame in the interface RX buffer
|
||||||
|
uint64_t now; // Current driver poll time, us
|
||||||
|
uint64_t tx_at; // Earliest next transmission, us
|
||||||
|
void *data; // Private state owned by the strategy
|
||||||
|
size_t ram_used; // Live mesh state bytes
|
||||||
|
size_t ram_peak; // Peak live mesh state bytes
|
||||||
|
uint64_t next_tick; // Next deadline in us, or UINT64_MAX
|
||||||
|
uint16_t radio_address; // Local address supplied by the radio
|
||||||
|
bool joined; // Client is a member of the mesh
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mg_mesh {
|
||||||
|
const char *name;
|
||||||
|
bool (*init)(struct mg_mesh_node *node, struct mg_tcpip_if *radio,
|
||||||
|
struct mg_tcpip_if *lan, uint16_t radio_address);
|
||||||
|
void (*tick)(struct mg_mesh_node *node, uint64_t now_microseconds);
|
||||||
|
void (*free)(struct mg_mesh_node *node);
|
||||||
|
size_t (*tx)(struct mg_mesh_node *, const void *, size_t); // ETH frame TX
|
||||||
|
void (*rx)(struct mg_mesh_node *, void *, size_t); // Radio frame RX
|
||||||
|
bool single_bridge; // Require exactly one bridge
|
||||||
|
bool scheduled_tx; // The mesh schedules transmission turns
|
||||||
|
const struct mg_radio *radio; // Radio configuration for this strategy
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct mg_mesh mg_mesh_default;
|
||||||
|
|
||||||
|
#endif
|
||||||
+46
-1
@@ -89,6 +89,7 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
}
|
}
|
||||||
mg_iobuf_free(&io);
|
mg_iobuf_free(&io);
|
||||||
} else if (ev == MG_EV_CLOSE) {
|
} else if (ev == MG_EV_CLOSE) {
|
||||||
|
mg_health_coredump_end();
|
||||||
mg_mdash_conn = NULL;
|
mg_mdash_conn = NULL;
|
||||||
MG_INFO(("disconnected, ram %lu", 0));
|
MG_INFO(("disconnected, ram %lu", 0));
|
||||||
}
|
}
|
||||||
@@ -105,6 +106,10 @@ static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
|
|||||||
MG_ESC("crash"), mg_print_crash_record);
|
MG_ESC("crash"), mg_print_crash_record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mg_mdash_rpc_mem_stats(struct mg_rpc_req *r) {
|
||||||
|
mg_rpc_ok(r, "%M", mg_print_mem_stats);
|
||||||
|
}
|
||||||
|
|
||||||
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
|
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);
|
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
|
||||||
if (mg_ota_begin(total)) {
|
if (mg_ota_begin(total)) {
|
||||||
@@ -146,24 +151,64 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
|
|||||||
// MG_ESC("percent"));
|
// MG_ESC("percent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rpc_coredump_begin(struct mg_rpc_req *r) {
|
||||||
|
size_t size = 0;
|
||||||
|
if (mg_health_coredump_begin(&size)) {
|
||||||
|
mg_rpc_ok(r, "{%m:%llu}", MG_ESC("size"), (uint64_t) size);
|
||||||
|
} else {
|
||||||
|
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rpc_coredump_read(struct mg_rpc_req *r) {
|
||||||
|
uint8_t buf[768];
|
||||||
|
size_t len = sizeof(buf);
|
||||||
|
size_t offset = (size_t) mg_json_get_long(r->frame, "$.params.offset", -1);
|
||||||
|
if (mg_health_coredump_read(offset, buf, &len)) {
|
||||||
|
mg_rpc_ok(r, "{%m:\"%M\"}", MG_ESC("data"), mg_print_base64,
|
||||||
|
(int) len, buf);
|
||||||
|
} else {
|
||||||
|
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rpc_coredump_end(struct mg_rpc_req *r) {
|
||||||
|
if (mg_health_coredump_end()) {
|
||||||
|
mg_rpc_ok(r, "true");
|
||||||
|
} else {
|
||||||
|
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void mg_mdash_init(struct mg_mgr *mgr) {
|
void mg_mdash_init(struct mg_mgr *mgr) {
|
||||||
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
|
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("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("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
|
||||||
|
mg_rpc_add(&mgr->rpcs, mg_str("Sys.MemStats"), mg_mdash_rpc_mem_stats, NULL);
|
||||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, 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.Write"), mg_mdash_rpc_ota_write, NULL);
|
||||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
|
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
|
||||||
|
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Begin"), rpc_coredump_begin, NULL);
|
||||||
|
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Read"), rpc_coredump_read, NULL);
|
||||||
|
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.End"), rpc_coredump_end, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_mdash_poll(struct mg_mgr *mgr) {
|
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;
|
static uint64_t timer = 0;
|
||||||
if (mg_mdash_conn == NULL && mg_timer_expired(&timer, 1000, mg_millis())) {
|
// 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_INFO(("Connecting to %s", MG_MDASH_URL));
|
||||||
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
|
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
|
||||||
"Origin: %s\r\n"
|
"Origin: %s\r\n"
|
||||||
"Authorization: Bearer %s\r\n",
|
"Authorization: Bearer %s\r\n",
|
||||||
MG_MDASH_URL, MG_MDASH_KEY);
|
MG_MDASH_URL, MG_MDASH_KEY);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void mg_mdash_free(struct mg_mgr *mgr) {
|
void mg_mdash_free(struct mg_mgr *mgr) {
|
||||||
|
|||||||
+10
@@ -438,6 +438,16 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
|
|||||||
case MQTT_CMD_CONNACK:
|
case MQTT_CMD_CONNACK:
|
||||||
if (end - p < 2) return MQTT_MALFORMED;
|
if (end - p < 2) return MQTT_MALFORMED;
|
||||||
m->ack = p[1];
|
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;
|
break;
|
||||||
case MQTT_CMD_PUBACK:
|
case MQTT_CMD_PUBACK:
|
||||||
case MQTT_CMD_PUBREC:
|
case MQTT_CMD_PUBREC:
|
||||||
|
|||||||
@@ -299,10 +299,10 @@ void mg_mgr_free(struct mg_mgr *mgr) {
|
|||||||
while (t != NULL) tmp = t->next, mg_free(t), t = tmp;
|
while (t != NULL) tmp = t->next, mg_free(t), t = tmp;
|
||||||
mgr->timers = NULL; // Important. Next call to poll won't touch timers
|
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;
|
for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
|
||||||
mg_mgr_poll(mgr, 0);
|
|
||||||
#if MG_ENABLE_MDASH
|
#if MG_ENABLE_MDASH
|
||||||
mg_mdash_free(mgr);
|
mg_mdash_free(mgr); // Before the final poll, so mdash does not reconnect
|
||||||
#endif
|
#endif
|
||||||
|
mg_mgr_poll(mgr, 0);
|
||||||
#if MG_ENABLE_FREERTOS_TCP
|
#if MG_ENABLE_FREERTOS_TCP
|
||||||
FreeRTOS_DeleteSocketSet(mgr->ss);
|
FreeRTOS_DeleteSocketSet(mgr->ss);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -92,11 +92,12 @@ struct mg_connection {
|
|||||||
unsigned is_mqtt5 : 1; // MQTT5 connection (set by mg_mqtt_login)
|
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_hexdumping : 1; // Log hex dump of all in/out traffic
|
||||||
unsigned is_draining : 1; // Flush send buffer, then close
|
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_full : 1; // Pause incoming reads until cleared
|
||||||
unsigned is_tls_throttled : 1; // TLS write was throttled; retry pending
|
unsigned is_tls_throttled : 1; // TLS write was throttled; retry pending
|
||||||
unsigned is_readable : 1; // Socket is ready to read (epoll/select)
|
unsigned is_readable : 1; // Socket is ready to read (epoll/select)
|
||||||
unsigned is_writable : 1; // Socket is ready to write (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.
|
// Runs one iteration of the event loop.
|
||||||
|
|||||||
+166
-104
@@ -1,4 +1,5 @@
|
|||||||
#include "net_builtin.h"
|
#include "net_builtin.h"
|
||||||
|
#include "bsd.h"
|
||||||
#include "profile.h"
|
#include "profile.h"
|
||||||
|
|
||||||
#if MG_ENABLE_TCPIP
|
#if MG_ENABLE_TCPIP
|
||||||
@@ -9,8 +10,14 @@
|
|||||||
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
|
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_ACK_MS
|
||||||
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing
|
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MG_TCPIP_ARP_MS
|
||||||
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
|
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
|
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
|
||||||
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
|
#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_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1)
|
||||||
@@ -31,16 +38,16 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct connstate {
|
struct connstate {
|
||||||
uint32_t seq, ack; // TCP seq/ack counters
|
uint32_t seq, ack; // TCP seq/ack counters
|
||||||
uint64_t timer; // TCP timer (see 'ttype' below)
|
uint64_t timer; // TCP timer (see 'ttype' below)
|
||||||
uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5)
|
uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5)
|
||||||
uint32_t acked; // Last ACK-ed number
|
uint32_t acked; // Last ACK-ed number
|
||||||
size_t unacked; // Not acked bytes
|
size_t unacked; // Not acked bytes
|
||||||
uint32_t maxseq; // Max send seq (ack + window)
|
uint32_t maxseq; // Max send seq (ack + window)
|
||||||
uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8)
|
uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8)
|
||||||
uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4)
|
uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4)
|
||||||
uint16_t win; // destination current window size
|
uint16_t win; // destination current window size
|
||||||
uint16_t dmss; // destination MSS (from TCP opts)
|
uint16_t dmss; // destination MSS (from TCP opts)
|
||||||
uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address
|
uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address
|
||||||
uint8_t ttype; // Timer type:
|
uint8_t ttype; // Timer type:
|
||||||
#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive
|
#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive
|
||||||
@@ -54,6 +61,8 @@ struct connstate {
|
|||||||
bool twclosure; // 3-way closure done
|
bool twclosure; // 3-way closure done
|
||||||
bool retransmit; // Retain sent data until acknowledged
|
bool retransmit; // Retain sent data until acknowledged
|
||||||
bool is_full; // Advertise a zero receive window
|
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)
|
struct mg_iobuf txq; // Length-prefixed sent TCP segments (RFC-9293, 3.8)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -310,6 +319,8 @@ static bool tcpcsum_ok(void *d, void *t) {
|
|||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MG_IPISLOCAL(i, a) ((a & i->mask) == (i->ip & i->mask))
|
||||||
|
|
||||||
#if MG_ENABLE_IPV6
|
#if MG_ENABLE_IPV6
|
||||||
static uint16_t p6csum(void *d, void *p, uint8_t proto, size_t plen) {
|
static uint16_t p6csum(void *d, void *p, uint8_t proto, size_t plen) {
|
||||||
uint32_t sum;
|
uint32_t sum;
|
||||||
@@ -359,6 +370,7 @@ static const struct mg_addr ip6_allrouters = {
|
|||||||
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true};
|
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true};
|
||||||
static const struct mg_addr ip6_allnodes = {
|
static const struct mg_addr ip6_allnodes = {
|
||||||
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true};
|
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true};
|
||||||
|
static const uint8_t locpx[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
|
#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
|
||||||
#endif
|
#endif
|
||||||
@@ -413,17 +425,18 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
|
|||||||
if (ifp->is_ip_changed) {
|
if (ifp->is_ip_changed) {
|
||||||
struct mg_connection *c;
|
struct mg_connection *c;
|
||||||
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
||||||
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
|
if (!c->is_listening && !c->is_udp && !c->loc.is_ip6) c->is_closing = 1;
|
||||||
}
|
}
|
||||||
ifp->is_ip_changed = false;
|
ifp->is_ip_changed = false;
|
||||||
}
|
}
|
||||||
} else if (ifp->state == MG_TCPIP_STATE_IP) {
|
} else if (ifp->state == MG_TCPIP_STATE_IP) {
|
||||||
if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH)
|
if (ifp->l2type == MG_TCPIP_L2_ETH) {
|
||||||
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request
|
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) {
|
} else if (ifp->state == MG_TCPIP_STATE_UP) {
|
||||||
srand((unsigned int) mg_millis());
|
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);
|
mg_tcpip_call(ifp, MG_TCPIP_EV_STATE_CHANGE, &ifp->state);
|
||||||
}
|
}
|
||||||
@@ -672,6 +685,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 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) {
|
static bool dhcp_opt_len_ok(uint8_t len, uint8_t *p, uint8_t *end) {
|
||||||
return (len >= 4 && (len & 3) == 0 && p + 6 < end);
|
return (len >= 4 && (len & 3) == 0 && p + 6 < end);
|
||||||
@@ -727,21 +741,21 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr
|
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr
|
||||||
: pkt->ip->src);
|
: pkt->ip->src);
|
||||||
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
|
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
|
||||||
|
ifp->lease_expire = ifp->now + 5000; // No ACK by then: start over
|
||||||
} else if (msgtype == 5) { // DHCPACK
|
} else if (msgtype == 5) { // DHCPACK
|
||||||
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
|
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
|
||||||
uint64_t rand;
|
uint64_t rand;
|
||||||
ifp->lease_expire = ifp->now + (uint64_t) lease * 1000;
|
ifp->lease_expire = ifp->now + (uint64_t) lease * 1000;
|
||||||
MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 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;
|
if (ifp->ip != ip) ifp->is_ip_changed = true;
|
||||||
ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
|
ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
|
||||||
ifp->state = MG_TCPIP_STATE_IP; // BOUND state
|
ifp->state = MG_TCPIP_STATE_IP; // BOUND state
|
||||||
mg_random(&rand, sizeof(rand));
|
mg_random(&rand, sizeof(rand));
|
||||||
srand((unsigned int) (rand + mg_millis()));
|
srand((unsigned int) (rand + mg_millis()));
|
||||||
if (ifp->enable_req_dns && dns != 0) {
|
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);
|
mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns);
|
||||||
}
|
}
|
||||||
if (ifp->enable_req_sntp && sntp != 0)
|
if (ifp->enable_req_sntp && sntp != 0)
|
||||||
@@ -792,7 +806,7 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
res.xid = pkt->dhcp->xid;
|
res.xid = pkt->dhcp->xid;
|
||||||
mac = mg_l2_getaddr(ifp, pkt->l2);
|
mac = mg_l2_getaddr(ifp, pkt->l2);
|
||||||
if (ifp->enable_get_gateway) {
|
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));
|
memcpy(ifp->gwmac, mac, sizeof(ifp->gwmac));
|
||||||
}
|
}
|
||||||
tx_udp4(ifp, mac, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
|
tx_udp4(ifp, mac, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
|
||||||
@@ -809,7 +823,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,
|
struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6,
|
||||||
ifp->mac, l2_dst, l2p);
|
ifp->mac, l2_dst, l2p);
|
||||||
memset(ip6, 0, sizeof(*ip6));
|
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->label[0] = (uint8_t) (dscp << 6);
|
||||||
ip6->plen = mg_htons((uint16_t) plen);
|
ip6->plen = mg_htons((uint16_t) plen);
|
||||||
ip6->next = next;
|
ip6->next = next;
|
||||||
@@ -1008,12 +1022,13 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
|
|
||||||
if (pkt->pay.len < sizeof(*ra)) return;
|
if (pkt->pay.len < sizeof(*ra)) return;
|
||||||
if (ifp->state6 == MG_TCPIP_STATE_UP) {
|
if (ifp->state6 == MG_TCPIP_STATE_UP) {
|
||||||
MG_DEBUG(("Received NDP RA")); // fill gw6 address
|
MG_DEBUG(("Received NDP RA")); // fill gw6 address if RFC 4861 6.1.2
|
||||||
|
if (memcmp(pkt->ip6->src, locpx, 8) != 0 || pkt->ip6->hops != 255) return;
|
||||||
// parse options
|
// parse options
|
||||||
while (opt_left >= 2) {
|
while (opt_left >= 2) {
|
||||||
uint8_t type = opts[0], len = opts[1];
|
uint8_t type = opts[0], len = opts[1];
|
||||||
size_t length = (size_t) len * 8;
|
size_t length = (size_t) len * 8;
|
||||||
if (length == 0 || length > opt_left) break; // malformed
|
if (length == 0 || length > opt_left) return; // RFC 4861 6.1.2
|
||||||
if (type == 1 && length >= 8) {
|
if (type == 1 && length >= 8) {
|
||||||
// Received router's L2 address
|
// Received router's L2 address
|
||||||
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
|
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
|
||||||
@@ -1049,6 +1064,7 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return;
|
if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return;
|
||||||
if (changed) ifp->is_ip6_changed = true;
|
if (changed) ifp->is_ip6_changed = true;
|
||||||
ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1];
|
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) memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac));
|
||||||
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP ||
|
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP ||
|
||||||
ifp->l2type == MG_TCPIP_L2_PPPoE) {
|
ifp->l2type == MG_TCPIP_L2_PPPoE) {
|
||||||
@@ -1063,10 +1079,8 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
mtu));
|
mtu));
|
||||||
ifp->mtu = (uint16_t) mtu;
|
ifp->mtu = (uint16_t) mtu;
|
||||||
}
|
}
|
||||||
if (ifp->state6 != MG_TCPIP_STATE_READY) {
|
if (ifp->state6 != MG_TCPIP_STATE_READY)
|
||||||
tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution
|
|
||||||
ifp->state6 = MG_TCPIP_STATE_IP;
|
ifp->state6 = MG_TCPIP_STATE_IP;
|
||||||
}
|
|
||||||
onstate6change(ifp);
|
onstate6change(ifp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1095,7 +1109,7 @@ static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 134: // Router Advertisement
|
case 134: // Router Advertisement
|
||||||
rx_ndp_ra(ifp, pkt);
|
if (pkt->icmp6->code == 0) rx_ndp_ra(ifp, pkt); // RFC 4861 6.1.2
|
||||||
break;
|
break;
|
||||||
case 135: // Neighbor Solicitation
|
case 135: // Neighbor Solicitation
|
||||||
rx_ndp_ns(ifp, pkt);
|
rx_ndp_ns(ifp, pkt);
|
||||||
@@ -1132,7 +1146,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) {
|
|||||||
if (ifp->is_ip6_changed) {
|
if (ifp->is_ip6_changed) {
|
||||||
struct mg_connection *c;
|
struct mg_connection *c;
|
||||||
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
||||||
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
|
if (!c->is_listening && !c->is_udp && c->loc.is_ip6) c->is_closing = 1;
|
||||||
}
|
}
|
||||||
ifp->is_ip6_changed = false;
|
ifp->is_ip6_changed = false;
|
||||||
}
|
}
|
||||||
@@ -1182,7 +1196,7 @@ static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
|
|||||||
} else
|
} else
|
||||||
#endif
|
#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
|
return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame
|
||||||
if (ifp->gw_ready) // use the router
|
if (ifp->gw_ready) // use the router
|
||||||
return ifp->gwmac; // ignore source address in frame
|
return ifp->gwmac; // ignore source address in frame
|
||||||
@@ -1192,6 +1206,20 @@ static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
|
|||||||
return NULL;
|
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) {
|
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||||
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
|
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
|
||||||
struct connstate *s;
|
struct connstate *s;
|
||||||
@@ -1214,10 +1242,10 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
|||||||
return false; // safety net for lousy networks
|
return false; // safety net for lousy networks
|
||||||
memcpy(s->mac, l2addr, sizeof(s->mac));
|
memcpy(s->mac, l2addr, sizeof(s->mac));
|
||||||
if (c->recv.len >= MG_MAX_RECV_SIZE) {
|
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 &&
|
} else if (c->recv.size - c->recv.len < pkt->pay.len &&
|
||||||
!mg_iobuf_resize(&c->recv, 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 {
|
} else {
|
||||||
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
|
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
|
||||||
c->recv.len += pkt->pay.len;
|
c->recv.len += pkt->pay.len;
|
||||||
@@ -1278,7 +1306,7 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
|
|||||||
tcp->off = (uint8_t) (hlen / 4 << 4);
|
tcp->off = (uint8_t) (hlen / 4 << 4);
|
||||||
#if MG_ENABLE_IPV6
|
#if MG_ENABLE_IPV6
|
||||||
if (ip_dst->is_ip6) {
|
if (ip_dst->is_ip6) {
|
||||||
tcp->csum = p6csum(ip6, tcp, 6, hlen + len);
|
tcp->csum = p6csum(ip6, tcp, 6, hlen + len);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@@ -1491,7 +1519,7 @@ static void handle_tls_recv(struct mg_connection *c) {
|
|||||||
size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail;
|
size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail;
|
||||||
struct mg_iobuf *io = &c->recv; // allocated on first avail > 0
|
struct mg_iobuf *io = &c->recv; // allocated on first avail > 0
|
||||||
if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) {
|
if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) {
|
||||||
mg_error(c, "oom");
|
proto_error(c, true, "oom");
|
||||||
} else {
|
} else {
|
||||||
// Decrypt data directly into c->recv. If io->buf = NULL or
|
// 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
|
// io->len = io->size (no room), there can be outstanding data that can't be
|
||||||
@@ -1500,7 +1528,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,
|
long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf,
|
||||||
io->size - io->len);
|
io->size - io->len);
|
||||||
if (n == MG_IO_ERR) {
|
if (n == MG_IO_ERR) {
|
||||||
mg_error(c, "TLS recv error");
|
proto_error(c, true, "TLS recv error");
|
||||||
} else if (n > 0) {
|
} else if (n > 0) {
|
||||||
// Decrypted successfully - trigger MG_EV_READ
|
// Decrypted successfully - trigger MG_EV_READ
|
||||||
io->len += (size_t) n;
|
io->len += (size_t) n;
|
||||||
@@ -1544,7 +1572,7 @@ static void retransmit(struct mg_connection *c) {
|
|||||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
|
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
|
||||||
mg_htonl(s->txq_seq), mg_htonl(s->ack),
|
mg_htonl(s->txq_seq), mg_htonl(s->ack),
|
||||||
s->is_full ? 0 : MG_TCPIP_WIN, buf, len) == (size_t) -1) {
|
s->is_full ? 0 : MG_TCPIP_WIN, buf, len) == (size_t) -1) {
|
||||||
mg_error(c, "retransmit");
|
proto_error(c, false, "retransmit"); // should RST, but sending just failed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Back off RTO after every timeout (RFC-6298, 5.5)
|
// Back off RTO after every timeout (RFC-6298, 5.5)
|
||||||
@@ -1577,15 +1605,11 @@ 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->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
|
||||||
s->fin_rcvd = true;
|
s->fin_rcvd = true;
|
||||||
if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
|
if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
|
||||||
if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
|
// Simultaneous closure ?
|
||||||
s->seq++; // Yes. Increment our SEQ
|
|
||||||
} else { // Otherwise,
|
|
||||||
s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
|
|
||||||
}
|
|
||||||
s->twclosure = true;
|
s->twclosure = true;
|
||||||
} else {
|
} else {
|
||||||
// Peer closed first: send ACK only, enter CLOSE_WAIT.
|
// 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.
|
// is flushed, then send our FIN.
|
||||||
c->is_draining = 1;
|
c->is_draining = 1;
|
||||||
}
|
}
|
||||||
@@ -1619,18 +1643,11 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
|
|||||||
s->twclosure = true;
|
s->twclosure = true;
|
||||||
}
|
}
|
||||||
if (pkt->pay.len == 0) return;
|
if (pkt->pay.len == 0) return;
|
||||||
if (io->size - io->len < pkt->pay.len &&
|
if (!c->is_closing && io->size - io->len < pkt->pay.len &&
|
||||||
!mg_iobuf_resize(io, io->len + pkt->pay.len)) {
|
!mg_iobuf_resize(io, io->len + pkt->pay.len)) {
|
||||||
mg_error(c, "oom");
|
proto_error(c, true, "oom");
|
||||||
return; // drop it
|
return; // drop it
|
||||||
}
|
}
|
||||||
// Copy TCP payload into the IO buffer. If the connection is plain text,
|
|
||||||
// we copy to c->recv. If the connection is TLS, this data is encrypted,
|
|
||||||
// therefore we copy that encrypted data to the c->rtls iobuffer instead,
|
|
||||||
// and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
|
|
||||||
// call back mg_io_recv() which grabs raw data from c->rtls
|
|
||||||
memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
|
|
||||||
io->len += pkt->pay.len;
|
|
||||||
MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
|
MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
|
||||||
if (!(pkt->tcp->flags & TH_FIN)) { // FIN already advanced s->ack
|
if (!(pkt->tcp->flags & TH_FIN)) { // FIN already advanced s->ack
|
||||||
// Advance ACK counter
|
// Advance ACK counter
|
||||||
@@ -1638,7 +1655,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
|
|||||||
s->unacked += pkt->pay.len;
|
s->unacked += pkt->pay.len;
|
||||||
}
|
}
|
||||||
// size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
|
// 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
|
// Send ACK immediately
|
||||||
MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
|
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,
|
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
||||||
@@ -1651,6 +1669,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 not already running, setup a timer to send an ACK later
|
||||||
if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
|
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) {
|
if (c->is_tls) {
|
||||||
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
|
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
|
||||||
} else {
|
} else {
|
||||||
@@ -1759,11 +1785,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) mg_tls_handshake(c);
|
||||||
if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init()
|
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) {
|
} 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) {
|
} else if (c != NULL && pkt->tcp->flags & TH_RST) {
|
||||||
uint32_t seqno = mg_ntohl(pkt->tcp->seq);
|
uint32_t seqno = mg_ntohl(pkt->tcp->seq);
|
||||||
if (seqno >= s->ack && seqno < (s->ack + MG_TCPIP_WIN)) // RFC-9293 3.5.3
|
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) {
|
} else if (c != NULL) {
|
||||||
// process segment
|
// process segment
|
||||||
s->tmiss = 0; // Reset missed keep-alive counter
|
s->tmiss = 0; // Reset missed keep-alive counter
|
||||||
@@ -1845,7 +1871,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 == 17) pkt->udp = (struct udp *) (pkt->pay.buf);
|
||||||
if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
|
if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
|
||||||
c = getpeer(ifp->mgr, pkt, false);
|
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) {
|
} else if (pkt->ip->proto == 1) {
|
||||||
pkt->icmp = (struct icmp *) (pkt->pay.buf);
|
pkt->icmp = (struct icmp *) (pkt->pay.buf);
|
||||||
if (pkt->pay.len < sizeof(*pkt->icmp)) return;
|
if (pkt->pay.len < sizeof(*pkt->icmp)) return;
|
||||||
@@ -1928,7 +1954,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] == 17) pkt->udp = (struct udp *) (pkt->pay.buf);
|
||||||
if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
|
if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
|
||||||
c = getpeer(ifp->mgr, pkt, false);
|
c = getpeer(ifp->mgr, pkt, false);
|
||||||
if (c) mg_error(c, "Received fragmented packet");
|
if (c) proto_error(c, false, "Received fragmented packet");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 59: // No Next Header 4.7
|
case 59: // No Next Header 4.7
|
||||||
@@ -2024,6 +2050,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
|
|||||||
if (ifp->enable_dhcp_client && s1) {
|
if (ifp->enable_dhcp_client && s1) {
|
||||||
if (ifp->state == MG_TCPIP_STATE_UP) {
|
if (ifp->state == MG_TCPIP_STATE_UP) {
|
||||||
tx_dhcp_discover(ifp); // INIT (4.4.1)
|
tx_dhcp_discover(ifp); // INIT (4.4.1)
|
||||||
|
} else if (ifp->state == MG_TCPIP_STATE_REQ &&
|
||||||
|
ifp->now >= ifp->lease_expire) {
|
||||||
|
ifp->state = MG_TCPIP_STATE_UP; // REQUEST or ACK lost: DISCOVER again
|
||||||
} else if (ifp->state == MG_TCPIP_STATE_READY &&
|
} else if (ifp->state == MG_TCPIP_STATE_READY &&
|
||||||
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
|
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
|
||||||
if (ifp->now >= ifp->lease_expire) {
|
if (ifp->now >= ifp->lease_expire) {
|
||||||
@@ -2065,12 +2094,11 @@ static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
|
|||||||
static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
|
static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
|
||||||
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
|
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
|
||||||
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP);
|
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP);
|
||||||
const uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6
|
|
||||||
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
|
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
|
||||||
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
|
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
|
||||||
ifp->ip6ll[1] == 0) { // gen ll address
|
ifp->ip6ll[1] == 0) { // gen ll address
|
||||||
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
|
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
|
||||||
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
|
memcpy(ifp->ip6ll, locpx, 8); // RFC-4291 2.5.6; RFC-2464 5
|
||||||
} // just got our link local address if we didn't have one.
|
} // just got our link local address if we didn't have one.
|
||||||
// If static configuration is used, global addresses,
|
// If static configuration is used, global addresses,
|
||||||
// prefix length, and gw are already filled at this point.
|
// prefix length, and gw are already filled at this point.
|
||||||
@@ -2090,7 +2118,7 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
|
|||||||
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
|
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
|
||||||
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
|
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
|
||||||
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
|
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
|
||||||
memcpy(ifp->ip6ll, px, 8); // RFC-5072 5
|
memcpy(ifp->ip6ll, locpx, 8); // RFC-5072 5
|
||||||
onstate6change(ifp);
|
onstate6change(ifp);
|
||||||
} // just got our link local address if we didn't have one.
|
} // just got our link local address if we didn't have one.
|
||||||
}
|
}
|
||||||
@@ -2122,7 +2150,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
|||||||
}
|
}
|
||||||
// Handle gw ARP request timeout, order is important
|
// Handle gw ARP request timeout, order is important
|
||||||
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) {
|
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);
|
onstatechange(ifp);
|
||||||
}
|
}
|
||||||
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready &&
|
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready &&
|
||||||
@@ -2131,10 +2159,10 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
|||||||
#if MG_ENABLE_IPV6
|
#if MG_ENABLE_IPV6
|
||||||
// Handle gw NS/NA req/resp timeout, order is important
|
// Handle gw NS/NA req/resp timeout, order is important
|
||||||
if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) {
|
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);
|
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))
|
(ifp->gw6[0] != 0 || ifp->gw6[1] != 0))
|
||||||
tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution
|
tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution
|
||||||
#endif
|
#endif
|
||||||
@@ -2193,7 +2221,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
|||||||
}
|
}
|
||||||
if (ifp->now > s->timer) {
|
if (ifp->now > s->timer) {
|
||||||
if (s->ttype == MIP_TTYPE_ARP) {
|
if (s->ttype == MIP_TTYPE_ARP) {
|
||||||
mg_error(c, "ARP timeout");
|
proto_error(c, false, "ARP timeout");
|
||||||
} else if (c->is_udp) {
|
} else if (c->is_udp) {
|
||||||
continue;
|
continue;
|
||||||
} else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
|
} else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
|
||||||
@@ -2202,13 +2230,13 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
|
|||||||
mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
|
mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
|
||||||
s->acked = s->ack;
|
s->acked = s->ack;
|
||||||
} else if (s->ttype == MIP_TTYPE_SYN) {
|
} 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) {
|
} else if (s->ttype == MIP_TTYPE_FIN) {
|
||||||
c->is_closing = 1;
|
s->is_closing = 1;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
if (s->tmiss++ > 2) {
|
if (s->tmiss++ > 2) {
|
||||||
mg_error(c, "keepalive");
|
proto_error(c, false, "keepalive");
|
||||||
} else {
|
} else {
|
||||||
MG_VERBOSE(("%lu keepalive", c->id));
|
MG_VERBOSE(("%lu keepalive", c->id));
|
||||||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
|
||||||
@@ -2268,9 +2296,17 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
|
|||||||
MG_DEBUG(("Builtin TCP/IP init done. Queue size: %zu, extra conn size: %zu",
|
MG_DEBUG(("Builtin TCP/IP init done. Queue size: %zu, extra conn size: %zu",
|
||||||
ifp->recv_queue.size, mgr->extraconnsize));
|
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) {
|
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->recv_queue.buf);
|
||||||
mg_free(ifp->tx.buf);
|
mg_free(ifp->tx.buf);
|
||||||
mg_free(ifp->dns4_url);
|
mg_free(ifp->dns4_url);
|
||||||
@@ -2337,12 +2373,12 @@ void mg_connect_resolved(struct mg_connection *c) {
|
|||||||
memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
|
memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
|
||||||
l2addr_resolved(c);
|
l2addr_resolved(c);
|
||||||
} else {
|
} else {
|
||||||
MG_ERROR(("No IPv6 gateway, can't connect"));
|
mg_error(c, "No IPv6 gateway, can't connect");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
uint32_t rem_ip = c->rem.addr.ip4;
|
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)
|
rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP)
|
||||||
// If we're in the same LAN, fire an ARP lookup.
|
// If we're in the same LAN, fire an ARP lookup.
|
||||||
MG_DEBUG(("%lu ARP lookup...", c->id));
|
MG_DEBUG(("%lu ARP lookup...", c->id));
|
||||||
@@ -2353,7 +2389,7 @@ void mg_connect_resolved(struct mg_connection *c) {
|
|||||||
memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
|
memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
|
||||||
l2addr_resolved(c);
|
l2addr_resolved(c);
|
||||||
} else {
|
} else {
|
||||||
MG_ERROR(("No gateway, can't connect"));
|
mg_error(c, "No gateway, can't connect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2372,29 +2408,13 @@ static void write_conn(struct mg_connection *c) {
|
|||||||
: mg_io_send(c, c->send.buf, c->send.len);
|
: mg_io_send(c, c->send.buf, c->send.len);
|
||||||
// TODO(): mg_tls_send() may return 0 forever on steady OOM
|
// TODO(): mg_tls_send() may return 0 forever on steady OOM
|
||||||
if (len == MG_IO_ERR) {
|
if (len == MG_IO_ERR) {
|
||||||
mg_error(c, "tx err");
|
proto_error(c, false, "tx err");
|
||||||
} else if (len > 0) {
|
} else if (len > 0) {
|
||||||
mg_iobuf_del(&c->send, 0, (size_t) len);
|
mg_iobuf_del(&c->send, 0, (size_t) len);
|
||||||
mg_call(c, MG_EV_WRITE, &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_listening == false && c->is_connecting == false) {
|
|
||||||
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);
|
|
||||||
settmout(c, MIP_TTYPE_FIN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void close_conn(struct mg_connection *c) {
|
|
||||||
struct connstate *s = (struct connstate *) (c + 1);
|
|
||||||
mg_iobuf_free(&s->txq);
|
|
||||||
mg_close_conn(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool can_write(struct mg_connection *c) {
|
static bool can_write(struct mg_connection *c) {
|
||||||
return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
|
return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
|
||||||
c->is_tls_hs == 0 && c->is_arplooking == 0;
|
c->is_tls_hs == 0 && c->is_arplooking == 0;
|
||||||
@@ -2415,29 +2435,50 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
|||||||
!c->is_listening && !c->is_connecting;
|
!c->is_listening && !c->is_connecting;
|
||||||
tmp = c->next;
|
tmp = c->next;
|
||||||
mg_call(c, MG_EV_POLL, &now);
|
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_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
|
||||||
c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c',
|
c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c',
|
||||||
mg_tls_pending(c), c->rtls.len));
|
s->is_closing ? 'F' : 'f', mg_tls_pending(c), c->rtls.len));
|
||||||
// order is important, TLS conn close with > 1 record in buffer (below)
|
if (!c->is_closing) {
|
||||||
if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0))
|
// 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);
|
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
|
||||||
if (can_write(c)) write_conn(c);
|
if (can_write(c)) write_conn(c);
|
||||||
if (is_tls && c->send.len == 0) flush = mg_tls_flush(c);
|
if (is_tls && c->send.len == 0) flush = mg_tls_flush(c);
|
||||||
if (flush == MG_IO_ERR) mg_error(c, "tx err");
|
|
||||||
if (c->is_draining && c->send.len == 0) {
|
|
||||||
if (c->is_udp) c->is_closing = 1;
|
|
||||||
if (!c->is_udp && flush == 0 && s->txq.len == 0 &&
|
|
||||||
s->ttype != MIP_TTYPE_FIN)
|
|
||||||
init_closure(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 non-TLS, close immediately upon completing the 3-way closure
|
||||||
// For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires
|
// For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires
|
||||||
if (s->twclosure &&
|
if (s->twclosure &&
|
||||||
(!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0)))
|
(!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0))) {
|
||||||
c->is_closing = 1;
|
s->is_closing = 1;
|
||||||
if (c->is_closing) close_conn(c);
|
} 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;
|
(void) ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2445,10 +2486,10 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
|
|||||||
struct mg_tcpip_if *ifp = c->mgr->ifp;
|
struct mg_tcpip_if *ifp = c->mgr->ifp;
|
||||||
bool res = false;
|
bool res = false;
|
||||||
if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) {
|
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
|
#if MG_ENABLE_IPV6
|
||||||
} else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
|
} else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
|
||||||
mg_error(c, "net down");
|
proto_error(c, false, "net down");
|
||||||
#endif
|
#endif
|
||||||
} else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
|
} else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
|
||||||
// Fail to send, no target MAC or IP
|
// Fail to send, no target MAC or IP
|
||||||
@@ -2482,10 +2523,31 @@ static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) {
|
|||||||
MG_DEBUG(("Set DNS URL to %s", dnsc->url));
|
MG_DEBUG(("Set DNS URL to %s", dnsc->url));
|
||||||
if (ifp->mgr->use_dns6) return;
|
if (ifp->mgr->use_dns6) return;
|
||||||
if (dnsc->c != NULL) mg_close_conn(dnsc->c);
|
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) {
|
void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) {
|
||||||
struct connstate *s = (struct connstate *) (c + 1);
|
struct connstate *s = (struct connstate *) (c + 1);
|
||||||
uint8_t *l2addr = tcpip_mapip(c->mgr->ifp, ip);
|
uint8_t *l2addr = tcpip_mapip(c->mgr->ifp, ip);
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ 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_atcmd;
|
||||||
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||||
|
|
||||||
|
#include "drivers/mg24.h"
|
||||||
|
#include "drivers/mcxw71.h"
|
||||||
|
#include "drivers/nrf52.h"
|
||||||
|
#include "drivers/rw612w.h"
|
||||||
|
|
||||||
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
|
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
|
||||||
// Populate and assign to the driver's driver_data field.
|
// Populate and assign to the driver's driver_data field.
|
||||||
struct mg_tcpip_spi {
|
struct mg_tcpip_spi {
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
} else if (ev == MG_EV_HTTP_MSG) {
|
} else if (ev == MG_EV_HTTP_MSG) {
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
char version[MG_OTA_MAX_VERSION_LEN];
|
char version[MG_OTA_MAX_VERSION_LEN];
|
||||||
double result;
|
|
||||||
MG_DEBUG(("Got metadata: %.*s", hm->body.len, hm->body.buf));
|
MG_DEBUG(("Got metadata: %.*s", hm->body.len, hm->body.buf));
|
||||||
if (mg_http_status(hm) != 200 || mg_json_get(hm->body, "$", NULL) != 0 ||
|
if (mg_http_status(hm) != 200 || mg_json_get(hm->body, "$", NULL) != 0 ||
|
||||||
!mg_json_unescape(hm->body, "$.version", version, sizeof(version)) ||
|
!mg_json_unescape(hm->body, "$.version", version, sizeof(version)) ||
|
||||||
!mg_json_unescape(hm->body, "$.url", s_ota->url, sizeof(s_ota->url)) ||
|
!mg_json_unescape(hm->body, "$.url", s_ota->url, sizeof(s_ota->url)) ||
|
||||||
!mg_json_get_num(hm->body, "$.size", &result) || result <= 0) {
|
!mg_str_to_num(mg_json_get_tok(hm->body, "$.size"), 10, &s_ota->size,
|
||||||
|
sizeof(s_ota->size)) || s_ota->size == 0) {
|
||||||
char buf[100];
|
char buf[100];
|
||||||
mg_snprintf(buf, sizeof(buf), "Bad metadata: %.*s", hm->body.len,
|
mg_snprintf(buf, sizeof(buf), "Bad metadata: %.*s", hm->body.len,
|
||||||
hm->body.buf);
|
hm->body.buf);
|
||||||
@@ -121,7 +121,6 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
s_ota = NULL;
|
s_ota = NULL;
|
||||||
} else {
|
} else {
|
||||||
struct mg_connection *fc;
|
struct mg_connection *fc;
|
||||||
s_ota->size = (size_t) result;
|
|
||||||
// TODO (robertc2000): parse and validate sha256
|
// TODO (robertc2000): parse and validate sha256
|
||||||
MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", version, s_ota->url,
|
MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", version, s_ota->url,
|
||||||
s_ota->size));
|
s_ota->size));
|
||||||
@@ -208,9 +207,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);
|
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
|
||||||
} else {
|
} else {
|
||||||
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
||||||
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
|
mg_http_stream_body(c, hm, s_firmware_cb, NULL);
|
||||||
mg_error(c, "Cannot stream firmware");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (ev == MG_EV_ERROR) {
|
} else if (ev == MG_EV_ERROR) {
|
||||||
s_ota->fn((char *) ev_data);
|
s_ota->fn((char *) ev_data);
|
||||||
@@ -243,6 +240,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) {
|
void mg_ota_poll(struct mg_mgr *mgr) {
|
||||||
static uint64_t t = 5000; // Fire first time 5 sec after boot
|
static uint64_t t = 5000; // Fire first time 5 sec after boot
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "arch.h"
|
#include "arch.h"
|
||||||
|
#include "http.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
// Supported OTA targets
|
// Supported OTA targets
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
#define MG_OTA_STM32H7 2 // STM32 H7
|
#define MG_OTA_STM32H7 2 // STM32 H7
|
||||||
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
|
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
|
||||||
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
|
#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_CH32V307 100 // WCH CH32V307
|
||||||
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
|
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
|
||||||
#define MG_OTA_RT1020 300 // IMXRT1020
|
#define MG_OTA_RT1020 300 // IMXRT1020
|
||||||
@@ -43,6 +45,7 @@
|
|||||||
#define MG_OTA_RT1170 304 // IMXRT1170
|
#define MG_OTA_RT1170 304 // IMXRT1170
|
||||||
#define MG_OTA_RT1180 305 // IMXRT1180
|
#define MG_OTA_RT1180 305 // IMXRT1180
|
||||||
#define MG_OTA_MCXN 310 // MCXN947
|
#define MG_OTA_MCXN 310 // MCXN947
|
||||||
|
#define MG_OTA_MCXW71 311 // MCXW71
|
||||||
#define MG_OTA_RW612 320 // FRDM-RW612
|
#define MG_OTA_RW612 320 // FRDM-RW612
|
||||||
#define MG_OTA_FLASH 900 // OTA via internal flash
|
#define MG_OTA_FLASH 900 // OTA via internal flash
|
||||||
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
|
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
|
||||||
@@ -72,6 +75,18 @@ bool mg_ota_write(const void *buf, size_t len);
|
|||||||
// Ends the OTA process and commits the written firmware.
|
// Ends the OTA process and commits the written firmware.
|
||||||
bool mg_ota_end(void);
|
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
|
// 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
|
// default calls swap_fn() (no-op on single-bank) then resets. Override in
|
||||||
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
|
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
|
||||||
|
|||||||
@@ -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
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "ota.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_write(void *, const void *, size_t);
|
||||||
static bool mg_stm32h5_swap(void);
|
static bool mg_stm32h5_swap(void);
|
||||||
|
|||||||
+3
-1
@@ -71,8 +71,10 @@ char *mg_mprintf(const char *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mg_pfn_stdout(char c, void *param) {
|
void mg_pfn_stdout(char c, void *param) {
|
||||||
|
#if MG_ENABLE_POSIX_FS
|
||||||
putchar(c);
|
putchar(c);
|
||||||
(void) param;
|
#endif
|
||||||
|
(void) param, (void) c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
|
static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ uint64_t mg_now(void);
|
|||||||
// MG_INFO(("every second"));
|
// MG_INFO(("every second"));
|
||||||
// }
|
// }
|
||||||
// Full examples:
|
// Full examples:
|
||||||
// tutorials/http/http-server/arduino/teensy41-http,
|
|
||||||
// tutorials/mqtt/mqtt-client
|
// tutorials/mqtt/mqtt-client
|
||||||
// Related APIs:
|
// Related APIs:
|
||||||
// mg_millis(), mg_now()
|
// mg_millis(), mg_now()
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ void mg_tls_ctx_free(struct mg_mgr *);
|
|||||||
#if MG_TLS == MG_TLS_BUILTIN
|
#if MG_TLS == MG_TLS_BUILTIN
|
||||||
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
|
// 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.
|
// 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
|
#endif
|
||||||
|
|
||||||
// Low-level IO primitives used by TLS layer
|
// Low-level IO primitives used by TLS layer
|
||||||
|
|||||||
+227
-194
@@ -6,8 +6,8 @@
|
|||||||
#include "tls_aes128.h"
|
#include "tls_aes128.h"
|
||||||
#include "tls_chacha20.h"
|
#include "tls_chacha20.h"
|
||||||
#include "tls_rsa.h"
|
#include "tls_rsa.h"
|
||||||
#include "tls_uecc.h"
|
#include "tls_p256.h"
|
||||||
#include "tls_x25519.h"
|
#include "tls_p384.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#if MG_TLS == MG_TLS_BUILTIN
|
#if MG_TLS == MG_TLS_BUILTIN
|
||||||
@@ -61,6 +61,7 @@ enum mg_tls_hs_state {
|
|||||||
|
|
||||||
// Server state machine:
|
// Server state machine:
|
||||||
MG_TLS_STATE_SERVER_START, // Wait for ClientHello
|
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_CERT, // Wait for Certificate
|
||||||
MG_TLS_STATE_SERVER_WAIT_CV, // Wait for CertificateVerify
|
MG_TLS_STATE_SERVER_WAIT_CV, // Wait for CertificateVerify
|
||||||
MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finish
|
MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finish
|
||||||
@@ -106,9 +107,9 @@ struct tls_data {
|
|||||||
|
|
||||||
uint8_t random[32]; // client random from ClientHello
|
uint8_t random[32]; // client random from ClientHello
|
||||||
uint8_t session_id[32]; // client session ID between the handshake states
|
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 p256_cli[32]; // client P-256 key between the handshake states
|
uint8_t p256_cli[32]; // client P-256 key between the handshake states
|
||||||
uint8_t x25519_sec[32]; // ECDHE secret (X25519 or P-256) between the 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 skip_verification; // do not perform checks on server certificate
|
||||||
bool cert_requested; // client received a CertificateRequest
|
bool cert_requested; // client received a CertificateRequest
|
||||||
@@ -132,6 +133,8 @@ struct tls_data {
|
|||||||
size_t pubkeysz; // size of the server public key
|
size_t pubkeysz; // size of the server public key
|
||||||
uint8_t sighash[32]; // calculated signature verification hash
|
uint8_t sighash[32]; // calculated signature verification hash
|
||||||
uint8_t sighash384[48]; // same, SHA-384, for ecdsa_secp384r1_sha384
|
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 enc; // actual keys in use at this time
|
||||||
struct tls_enc app_keys; // storage during two-way auth handshake
|
struct tls_enc app_keys; // storage during two-way auth handshake
|
||||||
@@ -323,7 +326,7 @@ static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz,
|
|||||||
memmove(hash, secret, hashsz);
|
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
|
// handshake encryption keys
|
||||||
static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
|
static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
@@ -344,12 +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,
|
mg_tls_derive_secret("tls13 derived", early_secret, 32, zeros_sha256_digest,
|
||||||
32, pre_extract_secret, 32);
|
32, pre_extract_secret, 32);
|
||||||
mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
|
mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
|
||||||
sizeof(pre_extract_secret), tls->x25519_sec,
|
sizeof(pre_extract_secret), tls->ecdhe_sec,
|
||||||
sizeof(tls->x25519_sec));
|
sizeof(tls->ecdhe_sec));
|
||||||
// ECDHE keys and secret are not needed anymore, wipe for forward secrecy
|
// ECDHE keys and secret are not needed anymore, wipe for forward secrecy
|
||||||
mg_bzero(tls->x25519_cli, sizeof(tls->x25519_cli));
|
|
||||||
mg_bzero(tls->p256_cli, sizeof(tls->p256_cli));
|
mg_bzero(tls->p256_cli, sizeof(tls->p256_cli));
|
||||||
mg_bzero(tls->x25519_sec, sizeof(tls->x25519_sec));
|
mg_bzero(tls->ecdhe_sec, sizeof(tls->ecdhe_sec));
|
||||||
mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);
|
mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);
|
||||||
|
|
||||||
// mg_sha256_final is not idempotent, need to copy sha256 context to calculate
|
// mg_sha256_final is not idempotent, need to copy sha256 context to calculate
|
||||||
@@ -467,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;
|
c->is_client ? tls->enc.client_write_iv : tls->enc.server_write_iv;
|
||||||
|
|
||||||
if (msgsz > 16384) {
|
if (msgsz > 16384) {
|
||||||
MG_ERROR(("msg longer than recordsz"));
|
mg_error(c, "msg longer than recordsz");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,19 +514,20 @@ static bool mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
|
|||||||
|
|
||||||
static void verbose_alert(uint8_t *buf) {
|
static void verbose_alert(uint8_t *buf) {
|
||||||
uint8_t level = buf[0], desc = buf[1];
|
uint8_t level = buf[0], desc = buf[1];
|
||||||
MG_INFO(("TLS ALERT received: level=%d, desc=%d (%s)", level, desc,
|
MG_LOG(desc == 0 ? MG_LL_VERBOSE : MG_LL_INFO, // close_notify is normal
|
||||||
desc == 0 ? "close_notify"
|
("TLS ALERT received: level=%d, desc=%d (%s)", level, desc,
|
||||||
: desc == 10 ? "unexpected_message"
|
desc == 0 ? "close_notify"
|
||||||
: desc == 20 ? "bad_record_mac"
|
: desc == 10 ? "unexpected_message"
|
||||||
: desc == 21 ? "decryption_failed"
|
: desc == 20 ? "bad_record_mac"
|
||||||
: desc == 40 ? "handshake_failure"
|
: desc == 21 ? "decryption_failed"
|
||||||
: desc == 42 ? "bad_certificate"
|
: desc == 40 ? "handshake_failure"
|
||||||
: desc == 43 ? "unsupported_certificate"
|
: desc == 42 ? "bad_certificate"
|
||||||
: desc == 44 ? "certificate_revoked"
|
: desc == 43 ? "unsupported_certificate"
|
||||||
: desc == 45 ? "certificate_expired"
|
: desc == 44 ? "certificate_revoked"
|
||||||
: desc == 46 ? "certificate_unknown"
|
: desc == 45 ? "certificate_expired"
|
||||||
: desc == 48 ? "unknown_ca"
|
: desc == 46 ? "certificate_unknown"
|
||||||
: "unknown"));
|
: desc == 48 ? "unknown_ca"
|
||||||
|
: "unknown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// read an encrypted record, decrypt it in place
|
// read an encrypted record, decrypt it in place
|
||||||
@@ -659,7 +662,8 @@ static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
|
|||||||
if (hash384 != NULL) mg_sha384(hash384, sig_content, sizeof(sig_content));
|
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) {
|
static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
struct mg_iobuf *rio = &c->rtls;
|
struct mg_iobuf *rio = &c->rtls;
|
||||||
@@ -669,9 +673,13 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
|||||||
uint16_t ext_len;
|
uint16_t ext_len;
|
||||||
uint8_t *ext;
|
uint8_t *ext;
|
||||||
uint16_t msgsz;
|
uint16_t msgsz;
|
||||||
|
bool have_p256 = false;
|
||||||
|
bool is_retry = tls->state == MG_TLS_STATE_SERVER_RETRY;
|
||||||
|
|
||||||
if (!mg_tls_got_record(c)) {
|
for (;;) {
|
||||||
return MG_IO_WAIT;
|
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) {
|
if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
|
||||||
mg_error(c, "not a client hello packet");
|
mg_error(c, "not a client hello packet");
|
||||||
@@ -715,16 +723,21 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
for (k = 0; k < key_exchange_len;) {
|
for (k = 0; k < key_exchange_len;) {
|
||||||
uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
|
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 (((uint32_t) m + k + 4) > key_exchange_len) goto fail;
|
||||||
if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) {
|
// secp256r1, uncompressed point 0x04 || X || Y
|
||||||
memmove(tls->x25519_cli, key_exchange + k + 4, m);
|
if (group == 0x0017 && m == 65 && key_exchange[k + 4] == 0x04) {
|
||||||
mg_tls_drop_record(c);
|
memmove(tls->p256_peer, key_exchange + k + 5, 64);
|
||||||
return 0;
|
have_p256 = true;
|
||||||
}
|
}
|
||||||
k += (uint16_t) (m + 4);
|
k += (uint16_t) (m + 4);
|
||||||
}
|
}
|
||||||
j += (uint16_t) (n + 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:
|
fail:
|
||||||
mg_error(c, "bad client hello");
|
mg_error(c, "bad client hello");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -738,58 +751,98 @@ fail:
|
|||||||
static bool mg_tls_server_send_hello(struct mg_connection *c) {
|
static bool mg_tls_server_send_hello(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
struct mg_iobuf *wio = &tls->send;
|
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
|
// calculate P-256 keyshare and the shared secret
|
||||||
uint8_t msg_server_hello[122] = {
|
pub[0] = 0x04; // uncompressed point 0x04 || X || Y
|
||||||
// server hello, tls 1.2
|
if (!mg_p256_make_key(pub + 1, prv) ||
|
||||||
0x02, 0x00, 0x00, 0x76, 0x03, 0x03,
|
!mg_p256_shared_secret(tls->p256_peer, prv, tls->ecdhe_sec)) {
|
||||||
// random (32 bytes)
|
mg_bzero(prv, sizeof(prv));
|
||||||
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) {
|
|
||||||
mg_error(c, "bad key");
|
mg_error(c, "bad key");
|
||||||
return false;
|
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
|
hello[0] = 0x02; // server hello, tls 1.2
|
||||||
memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
|
MG_STORE_BE24(hello + 1, n - 4);
|
||||||
memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id));
|
hello[4] = 0x03, hello[5] = 0x03;
|
||||||
memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
|
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
|
// server hello message
|
||||||
if (mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5) == 0 ||
|
if (mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr)) == 0 ||
|
||||||
mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello)) ==
|
mg_iobuf_add(wio, wio->len, hello, n) == 0)
|
||||||
0)
|
|
||||||
return false;
|
return false;
|
||||||
mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello));
|
mg_sha256_update(&tls->sha256, hello, n);
|
||||||
|
|
||||||
// change cipher message
|
// change cipher message, unless it was already sent after HelloRetryRequest
|
||||||
if (mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6) == 0)
|
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 false;
|
||||||
return true;
|
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) {
|
static bool mg_tls_server_send_ext(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
// server extensions
|
// server extensions
|
||||||
@@ -799,14 +852,10 @@ static bool mg_tls_server_send_ext(struct mg_connection *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// signature algorithms we actually support: ecdsa_secp256r1_sha256,
|
// signature algorithms we actually support: ecdsa_secp256r1_sha256,
|
||||||
// ecdsa_secp384r1_sha384 (if enabled), rsa_pss_rsae_sha256, rsa_pkcs1_sha256
|
// ecdsa_secp384r1_sha384, rsa_pss_rsae_sha256, rsa_pkcs1_sha256
|
||||||
static const uint8_t secp256r1_sig_algs[] = {
|
static const uint8_t secp256r1_sig_algs[] = {
|
||||||
#if MG_UECC_SUPPORTS_secp384r1
|
|
||||||
0x00, 0x0d, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03,
|
0x00, 0x0d, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03,
|
||||||
0x05, 0x03, 0x08, 0x04, 0x04, 0x01};
|
0x05, 0x03, 0x08, 0x04, 0x04, 0x01};
|
||||||
#else
|
|
||||||
0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
|
static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
@@ -878,28 +927,6 @@ static bool mg_tls_send_cert(struct mg_connection *c, bool is_client) {
|
|||||||
return res;
|
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,
|
static void mg_tls_mgf1(uint8_t *mask, size_t mask_len, const uint8_t *seed,
|
||||||
size_t seed_len) {
|
size_t seed_len) {
|
||||||
uint32_t counter = 0;
|
uint32_t counter = 0;
|
||||||
@@ -1103,19 +1130,19 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
|||||||
|
|
||||||
// Check bounds
|
// Check bounds
|
||||||
if (emlen > sizeof(em) || verifysz > sizeof(verify)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mg_tls_pss_encode(hash, sizeof(hash), &rn, em)) {
|
if (!mg_tls_pss_encode(hash, sizeof(hash), &rn, em)) {
|
||||||
MG_ERROR(("Failed PSS encode"));
|
mg_error(c, "Failed PSS encode");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate PSS encoded message format
|
// Validate PSS encoded message format
|
||||||
if (em[emlen - 1] != 0xbc) {
|
if (em[emlen - 1] != 0xbc) {
|
||||||
MG_ERROR(("Invalid PSS encoding: last byte is 0x%02x, expected 0xbc",
|
mg_error(c, "Invalid PSS encoding: last byte is 0x%02x, expected 0xbc",
|
||||||
em[emlen - 1]));
|
em[emlen - 1]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1128,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)
|
// Sign directly into the verify buffer (verify + 8 = signature location)
|
||||||
memset(verify + 8, 0, emlen); // Initialize signature area
|
memset(verify + 8, 0, emlen); // Initialize signature area
|
||||||
if (!mg_tls_rsa_sign(tls, em, emlen, verify + 8)) {
|
if (!mg_tls_rsa_sign(tls, em, emlen, verify + 8)) {
|
||||||
MG_ERROR(("Failed RSA sign"));
|
mg_error(c, "Failed RSA sign");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,15 +1169,13 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
|
|||||||
} else {
|
} else {
|
||||||
// EC certificate verify packet
|
// EC certificate verify packet
|
||||||
uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
|
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;
|
size_t sigsz, verifysz = 0;
|
||||||
int neg1, neg2;
|
int neg1, neg2;
|
||||||
uint8_t sig[64] = {0};
|
uint8_t sig[64] = {0};
|
||||||
mg_uecc_sign_deterministic(tls->ec_key, hash, sizeof(hash), &ctx.uECC, sig,
|
if (!mg_p256_sign(tls->ec_key, hash, sizeof(hash), sig)) {
|
||||||
mg_uecc_secp256r1());
|
mg_error(c, "P-256 sign");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
neg1 = !!(sig[0] & 0x80);
|
neg1 = !!(sig[0] & 0x80);
|
||||||
neg2 = !!(sig[32] & 0x80);
|
neg2 = !!(sig[32] & 0x80);
|
||||||
@@ -1213,9 +1238,6 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
|||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
struct mg_iobuf *wio = &tls->send;
|
struct mg_iobuf *wio = &tls->send;
|
||||||
|
|
||||||
uint8_t x25519_pub[X25519_BYTES];
|
|
||||||
uint8_t p256_pub[64]; // X || Y, sent as an uncompressed point 0x04 || X || Y
|
|
||||||
|
|
||||||
// - "signature algorithms we actually support", see above
|
// - "signature algorithms we actually support", see above
|
||||||
// uint8_t secp256r1_sig_algs[]
|
// uint8_t secp256r1_sig_algs[]
|
||||||
// - all popular signature algorithms (if we don't care about verification)
|
// - all popular signature algorithms (if we don't care about verification)
|
||||||
@@ -1227,11 +1249,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
|||||||
0xfe, 0x00, 0x00, 0xfe};
|
0xfe, 0x00, 0x00, 0xfe};
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
uint8_t msg_client_hello[216] = {
|
uint8_t msg_client_hello[178] = {
|
||||||
// TLS Client Hello header reported as TLS1.2 (5)
|
// 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)
|
// client hello, tls 1.2 (6)
|
||||||
0x01, 0x00, 0x00, 0x8c, 0x03, 0x03,
|
0x01, 0x00, 0x00, 0xd4, 0x03, 0x03,
|
||||||
// random (32 bytes)
|
// random (32 bytes)
|
||||||
PLACEHOLDER_32B,
|
PLACEHOLDER_32B,
|
||||||
// session ID length + session ID (32 bytes)
|
// session ID length + session ID (32 bytes)
|
||||||
@@ -1248,17 +1270,15 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
|||||||
0x01, 0x00,
|
0x01, 0x00,
|
||||||
// extensions + keyshare
|
// extensions + keyshare
|
||||||
0x00, 0xfe,
|
0x00, 0xfe,
|
||||||
// keyshare: x25519
|
|
||||||
0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20,
|
|
||||||
PLACEHOLDER_32B,
|
|
||||||
// keyshare: secp256r1, uncompressed point
|
// keyshare: secp256r1, uncompressed point
|
||||||
|
0x00, 0x33, 0x00, 0x47, 0x00, 0x45,
|
||||||
0x00, 0x17, 0x00, 0x41, 0x04, PLACEHOLDER_32B, PLACEHOLDER_32B,
|
0x00, 0x17, 0x00, 0x41, 0x04, PLACEHOLDER_32B, PLACEHOLDER_32B,
|
||||||
// supported groups (x25519, secp256r1)
|
// supported groups (secp256r1)
|
||||||
0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1d, 0x00, 0x17,
|
0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17,
|
||||||
// supported versions (tls1.3 == 0x304)
|
// supported versions (tls1.3 == 0x304)
|
||||||
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
|
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
|
||||||
// session ticket (none)
|
// session ticket (none)
|
||||||
0x00, 0x23, 0x00, 0x00, // 215 bytes till here
|
0x00, 0x23, 0x00, 0x00, // 177 bytes till here
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
const char *hostname = tls->hostname;
|
const char *hostname = tls->hostname;
|
||||||
@@ -1271,11 +1291,11 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
|||||||
|
|
||||||
// patch ClientHello with correct hostname ext length (if any)
|
// patch ClientHello with correct hostname ext length (if any)
|
||||||
MG_STORE_BE16(msg_client_hello + 3,
|
MG_STORE_BE16(msg_client_hello + 3,
|
||||||
hostname_extsz + 254 - 9 - 34 + sig_alg_sz);
|
hostname_extsz + 216 - 9 - 34 + sig_alg_sz);
|
||||||
MG_STORE_BE16(msg_client_hello + 7,
|
MG_STORE_BE16(msg_client_hello + 7,
|
||||||
hostname_extsz + 250 - 9 - 34 + sig_alg_sz);
|
hostname_extsz + 212 - 9 - 34 + sig_alg_sz);
|
||||||
MG_STORE_BE16(msg_client_hello + 82,
|
MG_STORE_BE16(msg_client_hello + 82,
|
||||||
hostname_extsz + 175 - 9 - 34 + sig_alg_sz);
|
hostname_extsz + 137 - 9 - 34 + sig_alg_sz);
|
||||||
|
|
||||||
if (hostnamesz > 0) {
|
if (hostnamesz > 0) {
|
||||||
MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
|
MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
|
||||||
@@ -1283,21 +1303,20 @@ static bool mg_tls_client_send_hello(struct mg_connection *c) {
|
|||||||
MG_STORE_BE16(server_name_ext + 7, hostnamesz);
|
MG_STORE_BE16(server_name_ext + 7, hostnamesz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate keyshares
|
// calc+fill keyshare; X || Y, sent as an uncompressed point 0x04 || X || Y
|
||||||
if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG");
|
if (!mg_p256_make_key(msg_client_hello + 95, tls->p256_cli)) {
|
||||||
mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
|
|
||||||
if (!mg_uecc_make_key(p256_pub, tls->p256_cli, mg_uecc_secp256r1())) {
|
|
||||||
mg_error(c, "P-256 key");
|
mg_error(c, "P-256 key");
|
||||||
return false; // Do not send uninitialised p256_pub
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill in the gaps: random + session ID + keyshares
|
// fill in the gaps: random + session ID
|
||||||
if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG");
|
if (!mg_random(tls->session_id, sizeof(tls->session_id)) ||
|
||||||
if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG");
|
!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 + 11, tls->random, sizeof(tls->random));
|
||||||
memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
|
memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
|
||||||
memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
|
|
||||||
memmove(msg_client_hello + 131, p256_pub, sizeof(p256_pub));
|
|
||||||
|
|
||||||
// client hello message
|
// client hello message
|
||||||
if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
|
if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
|
||||||
@@ -1370,26 +1389,17 @@ static int mg_tls_client_recv_hello(struct mg_connection *c) {
|
|||||||
key_exchange_len = MG_LOAD_BE16(ext + j + 6);
|
key_exchange_len = MG_LOAD_BE16(ext + j + 6);
|
||||||
key_exchange = ext + j + 8;
|
key_exchange = ext + j + 8;
|
||||||
if (key_exchange_len > ext_len2 - 4) goto fail;
|
if (key_exchange_len > ext_len2 - 4) goto fail;
|
||||||
if (group == 0x001d) { // x25519
|
if (group != 0x0017) { // secp256r1, uncompressed point 0x04 || X || Y
|
||||||
if (key_exchange_len != 32 ||
|
|
||||||
mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) <
|
|
||||||
0) {
|
|
||||||
mg_error(c, "bad key");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else if (group == 0x0017) { // secp256r1, uncompressed point 0x04 || X || Y
|
|
||||||
if (key_exchange_len != 65 || key_exchange[0] != 0x04 ||
|
|
||||||
!mg_uecc_valid_public_key(key_exchange + 1, mg_uecc_secp256r1()) ||
|
|
||||||
!mg_uecc_shared_secret(key_exchange + 1, tls->p256_cli,
|
|
||||||
tls->x25519_sec, mg_uecc_secp256r1())) {
|
|
||||||
mg_error(c, "bad key");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mg_error(c, "bad key exchange group");
|
mg_error(c, "bad key exchange group");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// mg_tls_hexdump("c ecdhe sec", tls->x25519_sec, 32);
|
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 ecdhe sec", tls->ecdhe_sec, 32);
|
||||||
mg_tls_drop_record(c);
|
mg_tls_drop_record(c);
|
||||||
/* generate handshake keys */
|
/* generate handshake keys */
|
||||||
mg_tls_generate_handshake_keys(c);
|
mg_tls_generate_handshake_keys(c);
|
||||||
@@ -1542,8 +1552,8 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) return -1;
|
if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) return -1;
|
||||||
if (memcmp(after.value, "99991231235959Z", 15) == 0) { // RFC-5280 4.1.2.5
|
if (after.len == 15 && memcmp(after.value, "99991231235959Z", 15) == 0) {
|
||||||
MG_ERROR(("No well-defined expiration date"));
|
MG_ERROR(("No well-defined expiration date")); // RFC-5280 4.1.2.5
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (now > (t = asnt2t(after.value, after.type))) {
|
if (now > (t = asnt2t(after.value, after.type))) {
|
||||||
@@ -1631,7 +1641,7 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
|
|||||||
}
|
}
|
||||||
if (i.type != 0x04) return -1;
|
if (i.type != 0x04) return -1;
|
||||||
if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) return -1;
|
if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) return -1;
|
||||||
if (!(b.value[1] & MG_BIT(2))) certsign = false;
|
if (b.len < 2 || !(b.value[1] & MG_BIT(2))) certsign = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ca && certsign) cert->is_ca = true;
|
if (ca && certsign) cert->is_ca = true;
|
||||||
@@ -1714,7 +1724,6 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
|
|||||||
MG_ERROR(("cert verification error"));
|
MG_ERROR(("cert verification error"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#if MG_UECC_SUPPORTS_secp256r1
|
|
||||||
if (issuer->pubkey.len == 64) {
|
if (issuer->pubkey.len == 64) {
|
||||||
const uint32_t N = 32;
|
const uint32_t N = 32;
|
||||||
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
|
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
|
||||||
@@ -1723,13 +1732,9 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
|
|||||||
memmove(sig + (N - a.len), a.value, a.len);
|
memmove(sig + (N - a.len), a.value, a.len);
|
||||||
memset(sig + N, 0, N - b.len);
|
memset(sig + N, 0, N - b.len);
|
||||||
memmove(sig + N + (N - b.len), b.value, b.len);
|
memmove(sig + N + (N - b.len), b.value, b.len);
|
||||||
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
return mg_p256_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||||
(unsigned) cert->tbshashsz, sig,
|
cert->tbshashsz, sig);
|
||||||
mg_uecc_secp256r1());
|
} else if (issuer->pubkey.len == 96) {
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
#if MG_UECC_SUPPORTS_secp384r1
|
|
||||||
if (issuer->pubkey.len == 96) {
|
|
||||||
const uint32_t N = 48;
|
const uint32_t N = 48;
|
||||||
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
|
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;
|
if (b.len > N) b.value += (b.len - N), b.len = N;
|
||||||
@@ -1737,12 +1742,9 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
|
|||||||
memmove(sig + (N - a.len), a.value, a.len);
|
memmove(sig + (N - a.len), a.value, a.len);
|
||||||
memset(sig + N, 0, N - b.len);
|
memset(sig + N, 0, N - b.len);
|
||||||
memmove(sig + N + (N - b.len), b.value, b.len);
|
memmove(sig + N + (N - b.len), b.value, b.len);
|
||||||
return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
return mg_p384_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
|
||||||
(unsigned) cert->tbshashsz, sig,
|
cert->tbshashsz, sig);
|
||||||
mg_uecc_secp384r1());
|
} else {
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len));
|
MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1931,6 +1933,18 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
|
|||||||
} // else r = 0 or -2 => 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) {
|
if (certnum == sizeof(certs) / sizeof(certs[0]) - 1) {
|
||||||
mg_error(c, "too many certificates in the chain");
|
mg_error(c, "too many certificates in the chain");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -2028,8 +2042,7 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
MG_VERBOSE(("certificate verification successful (RSA)"));
|
MG_VERBOSE(("certificate verification successful (RSA)"));
|
||||||
} else if (sigalg == 0x0403 ||
|
} else if (sigalg == 0x0403 || sigalg == 0x0503) {
|
||||||
(sigalg == 0x0503 && MG_UECC_SUPPORTS_secp384r1)) {
|
|
||||||
// ecdsa_secp256r1_sha256 or ecdsa_secp384r1_sha384. Extract certificate
|
// ecdsa_secp256r1_sha256 or ecdsa_secp384r1_sha384. Extract certificate
|
||||||
// signature and verify it using pubkey and sighash
|
// signature and verify it using pubkey and sighash
|
||||||
bool is384 = sigalg == 0x0503;
|
bool is384 = sigalg == 0x0503;
|
||||||
@@ -2042,15 +2055,15 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
|||||||
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
|
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
|
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0 || seq.type != 0x30) {
|
||||||
mg_error(c, "verification message is not an ASN.1 DER sequence");
|
mg_error(c, "verification message is not an ASN.1 DER sequence");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (mg_der_to_tlv(seq.value, seq.len, &r) < 0) {
|
if (mg_der_next(&seq, &r) <= 0 || r.type != 2) {
|
||||||
mg_error(c, "missing first part of the signature");
|
mg_error(c, "missing first part of the signature");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (mg_der_to_tlv(r.value + r.len, seq.len - r.len, &s) < 0) {
|
if (mg_der_next(&seq, &s) <= 0 || s.type != 2) {
|
||||||
mg_error(c, "missing second part of the signature");
|
mg_error(c, "missing second part of the signature");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -2062,15 +2075,12 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
|||||||
memmove(sig + (N - r.len), r.value, r.len);
|
memmove(sig + (N - r.len), r.value, r.len);
|
||||||
memmove(sig + N + (N - s.len), s.value, s.len);
|
memmove(sig + N + (N - s.len), s.value, s.len);
|
||||||
|
|
||||||
#if MG_UECC_SUPPORTS_secp384r1
|
|
||||||
if (is384) {
|
if (is384) {
|
||||||
ok = mg_uecc_verify(tls->pubkey, tls->sighash384,
|
ok = mg_p384_verify(tls->pubkey, tls->sighash384,
|
||||||
sizeof(tls->sighash384), sig, mg_uecc_secp384r1());
|
sizeof(tls->sighash384), sig);
|
||||||
} else
|
} else {
|
||||||
#endif
|
ok = mg_p256_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash),
|
||||||
{
|
sig);
|
||||||
ok = mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash),
|
|
||||||
sig, mg_uecc_secp256r1());
|
|
||||||
}
|
}
|
||||||
if (ok != 1) {
|
if (ok != 1) {
|
||||||
mg_error(c, "failed to verify EC certificate (certverify)");
|
mg_error(c, "failed to verify EC certificate (certverify)");
|
||||||
@@ -2087,8 +2097,8 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
|||||||
// 0809 = rsa_pss_pss_sha256
|
// 0809 = rsa_pss_pss_sha256
|
||||||
// 080A = rsa_pss_pss_sha384
|
// 080A = rsa_pss_pss_sha384
|
||||||
// 080B = rsa_pss_pss_sha512
|
// 080B = rsa_pss_pss_sha512
|
||||||
MG_ERROR(("unsupported certverify signature scheme: %x of %d bytes",
|
mg_error(c, "unsupported certverify signature scheme: %x of %d bytes",
|
||||||
sigalg, siglen));
|
sigalg, siglen);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2174,16 +2184,23 @@ static bool mg_tls_client_handshake(struct mg_connection *c) {
|
|||||||
|
|
||||||
static bool mg_tls_server_handshake(struct mg_connection *c) {
|
static bool mg_tls_server_handshake(struct mg_connection *c) {
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
|
int r;
|
||||||
switch (tls->state) {
|
switch (tls->state) {
|
||||||
case MG_TLS_STATE_SERVER_START:
|
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;
|
if (!mg_tls_server_send_hello(c)) return false;
|
||||||
mg_tls_generate_handshake_keys(c);
|
mg_tls_generate_handshake_keys(c);
|
||||||
if (!mg_tls_server_send_ext(c)) return false;
|
if (!mg_tls_server_send_ext(c)) return false;
|
||||||
if (tls->is_twoway && !mg_tls_server_send_cert_request(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) ||
|
if (!mg_tls_send_cert(c, false)) return false;
|
||||||
!mg_tls_server_send_finish(c))
|
if (!mg_tls_send_cert_verify(c, false)) return false;
|
||||||
return false;
|
if (!mg_tls_server_send_finish(c)) return false;
|
||||||
if (tls->is_twoway) {
|
if (tls->is_twoway) {
|
||||||
// generate application keys at this point, keep using handshake keys
|
// generate application keys at this point, keep using handshake keys
|
||||||
struct tls_enc hs_keys = tls->enc;
|
struct tls_enc hs_keys = tls->enc;
|
||||||
@@ -2224,16 +2241,27 @@ void mg_tls_handshake(struct mg_connection *c) {
|
|||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
struct tls_data *tls = (struct tls_data *) c->tls;
|
||||||
long n;
|
long n;
|
||||||
bool res;
|
bool res;
|
||||||
|
uint64_t t;
|
||||||
if (c->is_closing) return; // we don't clear rx buf, so ignore what's left
|
if (c->is_closing) return; // we don't clear rx buf, so ignore what's left
|
||||||
if (tls->is_sntp_pending) return;
|
if (tls->is_sntp_pending) return;
|
||||||
|
t = mg_millis();
|
||||||
if (c->is_client) {
|
if (c->is_client) {
|
||||||
// will clear is_hs when sending last chunk
|
// will clear is_hs when sending last chunk
|
||||||
res = mg_tls_client_handshake(c);
|
res = mg_tls_client_handshake(c);
|
||||||
} else {
|
} else {
|
||||||
res = mg_tls_server_handshake(c);
|
res = mg_tls_server_handshake(c);
|
||||||
}
|
}
|
||||||
if (!res) {
|
tls->hs_proc += mg_millis() - t;
|
||||||
mg_error(c, "TLS OOM");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
while (tls->send.len > 0 &&
|
while (tls->send.len > 0 &&
|
||||||
@@ -2650,7 +2678,7 @@ static int mg_parse_pem_certs(const struct mg_str pem, struct mg_str **ders) {
|
|||||||
return count;
|
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);
|
struct mg_str der = mg_str_n(NULL, 0);
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
if (buf == NULL || len < 32) return 0;
|
if (buf == NULL || len < 32) return 0;
|
||||||
@@ -2690,6 +2718,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
|||||||
mg_error(c, "tls oom");
|
mg_error(c, "tls oom");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
tls->hs_start = mg_millis();
|
||||||
|
|
||||||
tls->state =
|
tls->state =
|
||||||
c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
|
c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
|
||||||
@@ -2733,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 (!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"));
|
MG_VERBOSE(("No certificate provided"));
|
||||||
goto xit;
|
goto xit;
|
||||||
}
|
}
|
||||||
@@ -2762,7 +2791,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse PEM or DER EC key
|
// 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");
|
mg_error(c, "Certificate provided without a private key");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2875,11 +2904,15 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
|
|||||||
long n = MG_IO_WAIT;
|
long n = MG_IO_WAIT;
|
||||||
bool was_throttled = c->is_tls_throttled; // see #3074
|
bool was_throttled = c->is_tls_throttled; // see #3074
|
||||||
if (!was_throttled) { // encrypt new data
|
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 > MG_IO_SIZE) len = MG_IO_SIZE;
|
||||||
if (len > 16384) len = 16384;
|
for (i = 0; i < len; i += rs) { // Split into records, send them at once
|
||||||
if (!mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA))
|
size_t m = len - i < rs ? len - i : rs;
|
||||||
return 0; // returning 0 means an OOM condition (iobuf couldn't resize),
|
if (!mg_tls_encrypt(c, (const uint8_t *) buf + i, m, MG_TLS_APP_DATA))
|
||||||
// yet this is so far recoverable, let the caller decide
|
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
|
} // else, resend outstanding encrypted data in tls->send
|
||||||
while (tls->send.len > 0 &&
|
while (tls->send.len > 0 &&
|
||||||
(n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
|
(n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ static PORTABLE_8439_DECL void poly1305_finish(poly1305_context *ctx,
|
|||||||
// ******* END: poly1305-donna/poly1305-donna.h ********
|
// ******* END: poly1305-donna/poly1305-donna.h ********
|
||||||
// ******* BEGIN: chacha-portable.c ********
|
// ******* BEGIN: chacha-portable.c ********
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// this is a fresh implementation of chacha20, based on the description in
|
// 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
|
// rfc8349 it's such a nice compact algorithm that it is easy to do. In
|
||||||
// relationship to other c implementation this implementation:
|
// relationship to other c implementation this implementation:
|
||||||
|
|||||||
+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)
|
||||||
+577
-603
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
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
/**
|
|
||||||
* Adapted from STROBE: https://strobe.sourceforge.io/
|
|
||||||
* Copyright (c) 2015-2016 Cryptography Research, Inc.
|
|
||||||
* Author: Mike Hamburg
|
|
||||||
* License: MIT License
|
|
||||||
*/
|
|
||||||
#include "tls.h"
|
|
||||||
#include "tls_x25519.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#if MG_TLS == MG_TLS_BUILTIN
|
|
||||||
|
|
||||||
const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
|
|
||||||
|
|
||||||
#define X25519_WBITS 32
|
|
||||||
|
|
||||||
typedef uint32_t limb_t;
|
|
||||||
typedef uint64_t dlimb_t;
|
|
||||||
typedef int64_t sdlimb_t;
|
|
||||||
|
|
||||||
#define NLIMBS (256 / X25519_WBITS)
|
|
||||||
typedef limb_t mg_fe[NLIMBS];
|
|
||||||
|
|
||||||
static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) {
|
|
||||||
dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
|
|
||||||
*carry = (limb_t) (tmp >> X25519_WBITS);
|
|
||||||
return (limb_t) tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// These functions are implemented in terms of umaal on ARM
|
|
||||||
static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
|
|
||||||
dlimb_t total = (dlimb_t) *carry + acc + mand;
|
|
||||||
*carry = (limb_t) (total >> X25519_WBITS);
|
|
||||||
return (limb_t) total;
|
|
||||||
}
|
|
||||||
|
|
||||||
static limb_t adc0(limb_t *carry, limb_t acc) {
|
|
||||||
dlimb_t total = (dlimb_t) *carry + acc;
|
|
||||||
*carry = (limb_t) (total >> X25519_WBITS);
|
|
||||||
return (limb_t) total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - Precondition: carry is small.
|
|
||||||
// - Invariant: result of propagate is < 2^255 + 1 word
|
|
||||||
// - In particular, always less than 2p.
|
|
||||||
// - Also, output x >= min(x,19)
|
|
||||||
static void propagate(mg_fe x, limb_t over) {
|
|
||||||
unsigned i;
|
|
||||||
limb_t carry;
|
|
||||||
over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
|
|
||||||
x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
|
|
||||||
|
|
||||||
carry = over * 19;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
x[i] = adc0(&carry, x[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add(mg_fe out, const mg_fe a, const mg_fe b) {
|
|
||||||
unsigned i;
|
|
||||||
limb_t carry = 0;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
out[i] = adc(&carry, a[i], b[i]);
|
|
||||||
}
|
|
||||||
propagate(out, carry);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
|
|
||||||
unsigned i;
|
|
||||||
sdlimb_t carry = -38;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
carry = carry + a[i] - b[i];
|
|
||||||
out[i] = (limb_t) carry;
|
|
||||||
carry >>= X25519_WBITS;
|
|
||||||
}
|
|
||||||
propagate(out, (limb_t) (1 + carry));
|
|
||||||
}
|
|
||||||
|
|
||||||
// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of `mg_fe`
|
|
||||||
// to avoid build warnings
|
|
||||||
static void mul(mg_fe out, const mg_fe a, const limb_t *b, unsigned nb) {
|
|
||||||
limb_t accum[2 * NLIMBS] = {0};
|
|
||||||
unsigned i, j;
|
|
||||||
|
|
||||||
limb_t carry2;
|
|
||||||
for (i = 0; i < nb; i++) {
|
|
||||||
limb_t mand = b[i];
|
|
||||||
carry2 = 0;
|
|
||||||
for (j = 0; j < NLIMBS; j++) {
|
|
||||||
limb_t tmp; // "a" may be misaligned
|
|
||||||
memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy
|
|
||||||
accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
|
|
||||||
}
|
|
||||||
accum[i + j] = carry2;
|
|
||||||
}
|
|
||||||
|
|
||||||
carry2 = 0;
|
|
||||||
for (j = 0; j < NLIMBS; j++) {
|
|
||||||
out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
|
|
||||||
}
|
|
||||||
propagate(out, carry2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sqr(mg_fe out, const mg_fe a) {
|
|
||||||
mul(out, a, a, NLIMBS);
|
|
||||||
}
|
|
||||||
static void mul1(mg_fe out, const mg_fe a) {
|
|
||||||
mul(out, a, out, NLIMBS);
|
|
||||||
}
|
|
||||||
static void sqr1(mg_fe a) {
|
|
||||||
mul1(a, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
|
|
||||||
limb_t doswap) {
|
|
||||||
unsigned i;
|
|
||||||
for (i = 0; i < 2 * NLIMBS; i++) {
|
|
||||||
limb_t xor_ab = (a[i] ^ b[i]) & doswap;
|
|
||||||
a[i] ^= xor_ab;
|
|
||||||
b[i] ^= xor_ab;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize a field element x, reducing it to the least residue which is
|
|
||||||
// congruent to it mod 2^255-19
|
|
||||||
// - Precondition: x < 2^255 + 1 word
|
|
||||||
static limb_t canon(mg_fe x) {
|
|
||||||
// First, add 19.
|
|
||||||
unsigned i;
|
|
||||||
limb_t carry0 = 19;
|
|
||||||
limb_t res;
|
|
||||||
sdlimb_t carry;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
x[i] = adc0(&carry0, x[i]);
|
|
||||||
}
|
|
||||||
propagate(x, carry0);
|
|
||||||
|
|
||||||
// Here, 19 <= x2 < 2^255
|
|
||||||
// - This is because we added 19, so before propagate it can't be less
|
|
||||||
// than 19. After propagate, it still can't be less than 19, because if
|
|
||||||
// propagate does anything it adds 19.
|
|
||||||
// - We know that the high bit must be clear, because either the input was ~
|
|
||||||
// 2^255 + one word + 19 (in which case it propagates to at most 2 words) or
|
|
||||||
// it was < 2^255. So now, if we subtract 19, we will get back to something in
|
|
||||||
// [0,2^255-19).
|
|
||||||
carry = -19;
|
|
||||||
res = 0;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
carry += x[i];
|
|
||||||
res |= x[i] = (limb_t) carry;
|
|
||||||
carry >>= X25519_WBITS;
|
|
||||||
}
|
|
||||||
return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const limb_t a24[1] = {121665};
|
|
||||||
|
|
||||||
static void ladder_part1(mg_fe xs[5]) {
|
|
||||||
limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
|
|
||||||
add(t1, x2, z2); // t1 = A
|
|
||||||
sub(z2, x2, z2); // z2 = B
|
|
||||||
add(x2, x3, z3); // x2 = C
|
|
||||||
sub(z3, x3, z3); // z3 = D
|
|
||||||
mul1(z3, t1); // z3 = DA
|
|
||||||
mul1(x2, z2); // x3 = BC
|
|
||||||
add(x3, z3, x2); // x3 = DA+CB
|
|
||||||
sub(z3, z3, x2); // z3 = DA-CB
|
|
||||||
sqr1(t1); // t1 = AA
|
|
||||||
sqr1(z2); // z2 = BB
|
|
||||||
sub(x2, t1, z2); // x2 = E = AA-BB
|
|
||||||
mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24
|
|
||||||
add(z2, z2, t1); // z2 = E*a24 + AA
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
|
|
||||||
limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
|
|
||||||
sqr1(z3); // z3 = (DA-CB)^2
|
|
||||||
mul1(z3, x1); // z3 = x1 * (DA-CB)^2
|
|
||||||
sqr1(x3); // x3 = (DA+CB)^2
|
|
||||||
mul1(z2, x2); // z2 = AA*(E*a24+AA)
|
|
||||||
sub(x2, t1, x2); // x2 = BB again
|
|
||||||
mul1(x2, t1); // x2 = AA*BB
|
|
||||||
}
|
|
||||||
|
|
||||||
static void x25519_core(mg_fe xs[5], const uint8_t scalar[X25519_BYTES],
|
|
||||||
const uint8_t *x1, int clamp) {
|
|
||||||
int i;
|
|
||||||
mg_fe x1_limbs;
|
|
||||||
limb_t swap = 0;
|
|
||||||
limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
|
|
||||||
memset(xs, 0, 4 * sizeof(mg_fe));
|
|
||||||
x2[0] = z3[0] = 1;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
x3[i] = x1_limbs[i] =
|
|
||||||
MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 255; i >= 0; i--) {
|
|
||||||
uint8_t bytei = scalar[i / 8];
|
|
||||||
limb_t doswap;
|
|
||||||
if (clamp) {
|
|
||||||
if (i / 8 == 0) {
|
|
||||||
bytei &= (uint8_t) ~7U;
|
|
||||||
} else if (i / 8 == X25519_BYTES - 1) {
|
|
||||||
bytei &= 0x7F;
|
|
||||||
bytei |= 0x40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
|
|
||||||
condswap(x2, x3, swap ^ doswap);
|
|
||||||
swap = doswap;
|
|
||||||
|
|
||||||
ladder_part1(xs);
|
|
||||||
ladder_part2(xs, (const limb_t *) x1_limbs);
|
|
||||||
}
|
|
||||||
condswap(x2, x3, swap);
|
|
||||||
}
|
|
||||||
|
|
||||||
int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
|
|
||||||
const uint8_t x1[X25519_BYTES], int clamp) {
|
|
||||||
int i, ret;
|
|
||||||
mg_fe xs[5], out_limbs;
|
|
||||||
limb_t *x2, *z2, *z3, *prev;
|
|
||||||
static const struct {
|
|
||||||
uint8_t a, c, n;
|
|
||||||
} steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3, 1, 1},
|
|
||||||
{3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
|
|
||||||
{3, 1, 2}, {3, 1, 2}, {3, 1, 1}};
|
|
||||||
x25519_core(xs, scalar, x1, clamp);
|
|
||||||
|
|
||||||
// Precomputed inversion chain
|
|
||||||
x2 = xs[0];
|
|
||||||
z2 = xs[1];
|
|
||||||
z3 = xs[3];
|
|
||||||
|
|
||||||
prev = z2;
|
|
||||||
for (i = 0; i < 13; i++) {
|
|
||||||
int j;
|
|
||||||
limb_t *a = xs[steps[i].a];
|
|
||||||
for (j = steps[i].n; j > 0; j--) {
|
|
||||||
sqr(a, prev);
|
|
||||||
prev = a;
|
|
||||||
}
|
|
||||||
mul1(a, xs[steps[i].c]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here prev = z3
|
|
||||||
// x2 /= z2
|
|
||||||
mul(out_limbs, x2, z3, NLIMBS);
|
|
||||||
ret = (int) canon(out_limbs);
|
|
||||||
if (!clamp) ret = 0;
|
|
||||||
for (i = 0; i < NLIMBS; i++) {
|
|
||||||
uint32_t n = out_limbs[i];
|
|
||||||
out[i * 4] = (uint8_t) (n & 0xff);
|
|
||||||
out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
|
|
||||||
out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
|
|
||||||
out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#ifndef TLS_X15519_H
|
|
||||||
#define TLS_X15519_H
|
|
||||||
|
|
||||||
#include "arch.h"
|
|
||||||
|
|
||||||
#define X25519_BYTES 32
|
|
||||||
extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
|
|
||||||
|
|
||||||
int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
|
|
||||||
const uint8_t x1[X25519_BYTES], int clamp);
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* TLS_X15519_H */
|
|
||||||
+25
-21
@@ -1,5 +1,6 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
// Not using memset for zeroing memory, cause it can be dropped by compiler
|
// Not using memset for zeroing memory, cause it can be dropped by compiler
|
||||||
// See https://github.com/cesanta/mongoose/pull/1265
|
// See https://github.com/cesanta/mongoose/pull/1265
|
||||||
@@ -43,11 +44,12 @@ bool mg_random(void *buf, size_t len) {
|
|||||||
success = true;
|
success = true;
|
||||||
#elif MG_ARCH == MG_ARCH_CUBE && defined(HAL_RNG_MODULE_ENABLED)
|
#elif MG_ARCH == MG_ARCH_CUBE && defined(HAL_RNG_MODULE_ENABLED)
|
||||||
extern RNG_HandleTypeDef hrng;
|
extern RNG_HandleTypeDef hrng;
|
||||||
|
success = true;
|
||||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||||
uint32_t r = HAL_RNG_ReadLastRandomNumber(&hrng);
|
uint32_t r = 0;
|
||||||
|
if (HAL_RNG_GenerateRandomNumber(&hrng, &r) != HAL_OK) success = false;
|
||||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||||
}
|
}
|
||||||
success = true;
|
|
||||||
#elif MG_ARCH == MG_ARCH_PICOSDK
|
#elif MG_ARCH == MG_ARCH_PICOSDK
|
||||||
while (len--) *p++ = (unsigned char) (get_rand_32() & 255);
|
while (len--) *p++ = (unsigned char) (get_rand_32() & 255);
|
||||||
success = true;
|
success = true;
|
||||||
@@ -142,22 +144,21 @@ uint16_t mg_crc16(uint16_t crc, const char *buf, size_t len) {
|
|||||||
return (uint16_t) ~c;
|
return (uint16_t) ~c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int isbyte(int n) {
|
// Parse "IP" or "IP/BITS" into net and mask. The whole spec must be valid
|
||||||
return n >= 0 && n <= 255;
|
static bool parse_net(struct mg_str spec, uint32_t *net, uint32_t *mask) {
|
||||||
}
|
struct mg_str caps[3], ip = spec, bits = mg_str("32");
|
||||||
|
struct mg_addr addr;
|
||||||
static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
|
uint8_t n = 0;
|
||||||
int n, a, b, c, d, slash = 32, len = 0;
|
if (mg_match(spec, mg_str("*/*"), caps)) ip = caps[0], bits = caps[1];
|
||||||
if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
|
// ip.len == 0 check: mg_aton() parses an empty string as 0.0.0.0
|
||||||
sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
|
if (ip.len == 0 || !mg_aton(ip, &addr) || addr.is_ip6 ||
|
||||||
isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 &&
|
!mg_str_to_num(bits, 10, &n, sizeof(n)) || n > 32) {
|
||||||
slash < 33) {
|
return false;
|
||||||
len = n;
|
|
||||||
*net = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) |
|
|
||||||
(uint32_t) d;
|
|
||||||
*mask = slash ? (uint32_t) (0xffffffffU << (32 - slash)) : (uint32_t) 0;
|
|
||||||
}
|
}
|
||||||
return len;
|
memcpy(net, addr.addr.ip, sizeof(*net));
|
||||||
|
*net = mg_ntohl(*net);
|
||||||
|
*mask = n ? (uint32_t) (0xffffffffU << (32 - n)) : (uint32_t) 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
|
int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
|
||||||
@@ -171,7 +172,9 @@ int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
|
|||||||
while (mg_span(acl, &entry, &acl, ',')) {
|
while (mg_span(acl, &entry, &acl, ',')) {
|
||||||
uint32_t net, mask;
|
uint32_t net, mask;
|
||||||
if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
|
if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
|
||||||
if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
|
if (!parse_net(mg_str_n(entry.buf + 1, entry.len - 1), &net, &mask)) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
|
if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +200,7 @@ bool mg_path_is_sane(const struct mg_str path) {
|
|||||||
#if MG_ENABLE_CUSTOM_MILLIS
|
#if MG_ENABLE_CUSTOM_MILLIS
|
||||||
#else
|
#else
|
||||||
uint64_t mg_millis(void) {
|
uint64_t mg_millis(void) {
|
||||||
#if MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || MG_ENABLE_FREERTOS
|
#if MG_ARCH == MG_ARCH_ESP32 || MG_ENABLE_FREERTOS
|
||||||
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||||
#elif MG_ARCH == MG_ARCH_THREADX
|
#elif MG_ARCH == MG_ARCH_THREADX
|
||||||
return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
|
return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
|
||||||
@@ -258,8 +261,9 @@ uint64_t mg_ntohll(uint64_t net) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mg_delayms(unsigned int ms) {
|
void mg_delayms(unsigned int ms) {
|
||||||
uint64_t to = mg_millis() + ms + 1;
|
uint64_t now, to = mg_millis() + ms + 1;
|
||||||
while (mg_millis() < to) (void) 0;
|
while ((now = mg_millis()) < to)
|
||||||
|
if (now + ms + 1 < to) to = now + ms + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MG_ENABLE_CUSTOM_CALLOC
|
#if MG_ENABLE_CUSTOM_CALLOC
|
||||||
|
|||||||
+10
-19
@@ -16,9 +16,9 @@ IPV6 ?= 1
|
|||||||
MDASH ?= 1
|
MDASH ?= 1
|
||||||
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
|
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
|
||||||
ASAN_OPTIONS ?= detect_leaks=1
|
ASAN_OPTIONS ?= detect_leaks=1
|
||||||
# This does not include ESP32, ESP8266, Microchip; all examples that are directly placed under an OS tutorial. Also, it is needed that way...
|
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/esp32/*/Makefile)) $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/microchip/*/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/*/Makefile)) $(dir $(wildcard ../tutorials/pico-sdk/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/silabs/*/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
|
||||||
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
|
TUTORIALS_ZEPHYR := $(dir $(wildcard ../tutorials/zephyr/*/Makefile))
|
||||||
TUTORIALS := ../tutorials/micropython/ $(filter-out $(TUTORIALS_EMBEDDED), $(dir $(wildcard ../tutorials/*/*/Makefile)))
|
TUTORIALS := ../tutorials/micropython/ $(filter-out $(TUTORIALS_EMBEDDED) $(TUTORIALS_ZEPHYR), $(dir $(wildcard ../tutorials/*/*/Makefile)))
|
||||||
TUTORIALS_QUICK := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/, $(TUTORIALS))
|
TUTORIALS_QUICK := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/, $(TUTORIALS))
|
||||||
TUTORIALS_MAC := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/ ../tutorials/tcpip/tap-driver/, $(TUTORIALS))
|
TUTORIALS_MAC := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/ ../tutorials/tcpip/tap-driver/, $(TUTORIALS))
|
||||||
TUTORIALS_WIN := $(dir $(wildcard ../tutorials/http/file-*/Makefile) $(wildcard ../tutorials/http/http-*/Makefile) $(wildcard ../tutorials/mqtt/*/Makefile) $(wildcard ../tutorials/websocket/*/Makefile) $(wildcard ../tutorials/webui/*/Makefile))
|
TUTORIALS_WIN := $(dir $(wildcard ../tutorials/http/file-*/Makefile) $(wildcard ../tutorials/http/http-*/Makefile) $(wildcard ../tutorials/mqtt/*/Makefile) $(wildcard ../tutorials/websocket/*/Makefile) $(wildcard ../tutorials/webui/*/Makefile))
|
||||||
@@ -76,19 +76,10 @@ LDFLAGS += -L$(WOLFSSL)/lib -lwolfssl
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq "$(SSL)" "BUILTIN"
|
ifeq "$(SSL)" "BUILTIN"
|
||||||
DEFS += -DMG_TLS=MG_TLS_BUILTIN
|
# Small TLS records: several records per TCP segment, tests record handling
|
||||||
|
DEFS += -DMG_TLS=MG_TLS_BUILTIN -DMG_TLS_RECORD_SIZE=256
|
||||||
endif
|
endif
|
||||||
|
|
||||||
tls_multirec/server: FORCE
|
|
||||||
ifneq "$(SSL)" ""
|
|
||||||
ifeq "$(MULTIREC)" ""
|
|
||||||
$(MAKE) -C tls_multirec CC=gcc
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
true
|
|
||||||
|
|
||||||
FORCE:
|
|
||||||
true
|
|
||||||
|
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@@ -107,13 +98,13 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
|
|||||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
||||||
|
|
||||||
mongoose.h: $(HDRS) Makefile
|
mongoose.h: $(HDRS) Makefile
|
||||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_aes128.h src/tls_p256.h src/tls_p384.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/l2_mesh.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||||
|
|
||||||
# Check that all external (exported) symbols have "mg_" prefix
|
# Check that all external (exported) symbols have "mg_" prefix
|
||||||
mg_prefix: mongoose.c mongoose.h
|
mg_prefix: mongoose.c mongoose.h
|
||||||
$(CC) mongoose.c $(CFLAGS) -c -o /tmp/x.o && nm /tmp/x.o | grep ' T ' | grep -v 'mg_' ; test $$? = 1
|
$(CC) mongoose.c $(CFLAGS) -c -o /tmp/x.o && nm /tmp/x.o | grep ' T ' | grep -v 'mg_' ; test $$? = 1
|
||||||
|
|
||||||
test: Makefile mongoose.h $(SRCS) tls_multirec/server
|
test: Makefile mongoose.h $(SRCS)
|
||||||
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test
|
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test
|
||||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
|
||||||
|
|
||||||
@@ -263,7 +254,6 @@ packed_fs.c: Makefile data/ssi.h fuzz.c data/a.txt data/range.txt data/ca.pem ce
|
|||||||
|
|
||||||
clean: clean_tutorials clean_tutorials_embedded
|
clean: clean_tutorials clean_tutorials_embedded
|
||||||
rm -rf $(PROG) *.exe *.o *.dSYM *_test ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* packed_fs.c pack
|
rm -rf $(PROG) *.exe *.o *.dSYM *_test ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* packed_fs.c pack
|
||||||
$(MAKE) -C tls_multirec $@
|
|
||||||
$(MAKE) -C ../resources/I-CUBE-Mongoose $@
|
$(MAKE) -C ../resources/I-CUBE-Mongoose $@
|
||||||
#find tutorials -maxdepth 3 -name zephyr -prune -o -name Makefile -print | xargs dirname | xargs -n1 make clean -C
|
#find tutorials -maxdepth 3 -name zephyr -prune -o -name Makefile -print | xargs dirname | xargs -n1 make clean -C
|
||||||
|
|
||||||
@@ -287,7 +277,7 @@ atcmd_test: atcmd_test.c mongoose.c mongoose.h Makefile
|
|||||||
$(CC) atcmd_test.c $(CFLAGS) $(LDFLAGS) -o $@
|
$(CC) atcmd_test.c $(CFLAGS) $(LDFLAGS) -o $@
|
||||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
||||||
|
|
||||||
mip_tap_test: mip_x_test.c mip_tap_test.c mongoose.c mongoose.h packed_fs.c Makefile tls_multirec/server
|
mip_tap_test: mip_x_test.c mip_tap_test.c mongoose.c mongoose.h packed_fs.c Makefile
|
||||||
$(CC) mip_tap_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
|
$(CC) mip_tap_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
|
||||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
||||||
|
|
||||||
@@ -300,7 +290,7 @@ port_tap_bridge: port_tap_bridge.c
|
|||||||
cc port_tap_bridge.c -o $@
|
cc port_tap_bridge.c -o $@
|
||||||
|
|
||||||
# requires port_tap_bridge to be running
|
# requires port_tap_bridge to be running
|
||||||
mip_port_test: mip_x_test.c mip_port_test.c mongoose.c mongoose.h packed_fs.c Makefile tls_multirec/server
|
mip_port_test: mip_x_test.c mip_port_test.c mongoose.c mongoose.h packed_fs.c Makefile
|
||||||
$(CC) mip_port_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
|
$(CC) mip_port_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
|
||||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
|
||||||
|
|
||||||
@@ -309,6 +299,7 @@ mip_diabish: CC = gcc
|
|||||||
mip_diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
|
mip_diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
|
||||||
mip_diabish: mip_test
|
mip_diabish: mip_test
|
||||||
|
|
||||||
|
mip_vc98: MDASH = 0
|
||||||
mip_vc98: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile
|
mip_vc98: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile
|
||||||
$(DOCKER) mdashnet/vc98 wine cl mip_test.c packed_fs.c $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
|
$(DOCKER) mdashnet/vc98 wine cl mip_test.c packed_fs.c $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
|
||||||
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
|
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
|
||||||
|
|||||||
@@ -29,20 +29,20 @@ teensy-board:
|
|||||||
touch teensy-board
|
touch teensy-board
|
||||||
|
|
||||||
seeeduino-xiao: arduino seeeduino-xiao-board
|
seeeduino-xiao: arduino seeeduino-xiao-board
|
||||||
cd $(ROOT)/tutorials/mqtt/mqtt-client/arduino/sim800-mqtt && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 sim800-mqtt.ino
|
cd $(ROOT)/tutorials/arduino/sim800-mqtt && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 sim800-mqtt.ino
|
||||||
cd $(ROOT)/tutorials/http/http-server/arduino/w5500-http && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 w5500-http.ino
|
cd $(ROOT)/tutorials/arduino/w5500-http && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 w5500-http.ino
|
||||||
cd $(ROOT)/tutorials/mqtt/mqtt-client/arduino/w5500-mqtt && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 w5500-mqtt.ino
|
cd $(ROOT)/tutorials/arduino/w5500-mqtt && $(ARD)/arduino/arduino --verbose --verify --board Seeeduino:samd:seeed_XIAO_m0 w5500-mqtt.ino
|
||||||
|
|
||||||
arduino-nano: arduino
|
arduino-nano: arduino
|
||||||
cd $(ROOT)/tutorials/mqtt/mqtt-client/arduino/nano-w5500-mqtt && $(ARD)/arduino/arduino --pref compiler.cpp.extra_flags=-DMG_ENABLE_LOG=0 --verbose --verify --board arduino:avr:nano nano-w5500-mqtt.ino
|
cd $(ROOT)/tutorials/arduino/nano-w5500-mqtt && $(ARD)/arduino/arduino --pref compiler.cpp.extra_flags=-DMG_ENABLE_LOG=0 --verbose --verify --board arduino:avr:nano nano-w5500-mqtt.ino
|
||||||
|
|
||||||
esp32: arduino esp32-board
|
esp32: arduino esp32-board
|
||||||
cd $(ROOT)/tutorials/mqtt/mqtt-client/arduino/esp32-mqtt && $(ARD)/arduino/arduino --verbose --verify --board esp32:esp32:node32s esp32-mqtt.ino
|
cd $(ROOT)/tutorials/arduino/esp32-mqtt && $(ARD)/arduino/arduino --verbose --verify --board esp32:esp32:node32s esp32-mqtt.ino
|
||||||
# Needs a board with 'LED_BUILTIN', yet somehow invocation is missing that definition (lack of include somehow amended by the IDE)
|
# Needs a board with 'LED_BUILTIN', yet somehow invocation is missing that definition (lack of include somehow amended by the IDE)
|
||||||
#cd $(ROOT)/tutorials/http/http-server/arduino/esp32-http && $(ARD)/arduino/arduino --verbose --verify --board esp32:esp32:esp32 esp32-http.ino
|
#cd $(ROOT)/tutorials/arduino/esp32-http && $(ARD)/arduino/arduino --verbose --verify --board esp32:esp32:esp32 esp32-http.ino
|
||||||
|
|
||||||
teensy41: arduino teensy-board
|
teensy41: arduino teensy-board
|
||||||
cd $(ROOT)/tutorials/http/http-server/arduino/teensy41-http && $(ARD)/arduino/arduino --verbose --verify --board teensy41-http.ino
|
cd $(ROOT)/tutorials/arduino/teensy41-http && $(ARD)/arduino/arduino --verbose --verify --board teensy41-http.ino
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+28
-5
@@ -13,18 +13,21 @@ else
|
|||||||
C_WARN ?= -Wno-deprecated
|
C_WARN ?= -Wno-deprecated
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all bsd_test bsd_client shm_tap_bridge shm_port_bridge bsd_s390 clean
|
.PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \
|
||||||
|
shm_port_bridge bsd_s390 bsd_nortos_s390 clean
|
||||||
|
|
||||||
TEST_SOURCES = bsd_shm_test.c
|
TEST_SOURCES = bsd_shm_test.c
|
||||||
# A C++ application compiles its sources as C++, linking Mongoose and FreeRTOS as C.
|
# A C++ application compiles its sources as C++, linking Mongoose and FreeRTOS as C.
|
||||||
# FreeRTOS does not build as C++, so C sources are compiled separately.
|
# FreeRTOS does not build as C++, so C sources are compiled separately.
|
||||||
C_SOURCES = ../../mongoose.c
|
C_SOURCES = ../../mongoose.c
|
||||||
C_SOURCES += FreeRTOS-Kernel/portable/MemMang/heap_4.c
|
FREERTOS_SOURCES = FreeRTOS-Kernel/portable/MemMang/heap_4.c
|
||||||
C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c
|
FREERTOS_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c
|
||||||
C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c
|
FREERTOS_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c
|
||||||
C_SOURCES += $(wildcard FreeRTOS-Kernel/*.c)
|
FREERTOS_SOURCES += $(wildcard FreeRTOS-Kernel/*.c)
|
||||||
C_OBJECTS = $(notdir $(C_SOURCES:.c=.o))
|
C_OBJECTS = $(notdir $(C_SOURCES:.c=.o))
|
||||||
|
FREERTOS_OBJECTS = $(notdir $(FREERTOS_SOURCES:.c=.o))
|
||||||
C_INPUTS = $(foreach S,$(C_SOURCES),-x c $(S))
|
C_INPUTS = $(foreach S,$(C_SOURCES),-x c $(S))
|
||||||
|
FREERTOS_INPUTS = $(foreach S,$(FREERTOS_SOURCES),-x c $(S))
|
||||||
CFLAGS += -IFreeRTOS-Kernel/include
|
CFLAGS += -IFreeRTOS-Kernel/include
|
||||||
CFLAGS += -IFreeRTOS-Kernel/portable/ThirdParty/GCC/Posix -Wno-conversion -Wno-undef
|
CFLAGS += -IFreeRTOS-Kernel/portable/ThirdParty/GCC/Posix -Wno-conversion -Wno-undef
|
||||||
|
|
||||||
@@ -47,8 +50,19 @@ CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM
|
|||||||
all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
|
all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
|
||||||
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
|
||||||
|
|
||||||
|
# also
|
||||||
|
bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test
|
||||||
|
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS)
|
||||||
|
|
||||||
# requires shm_tap_bridge to be running
|
# requires shm_tap_bridge to be running
|
||||||
|
bsd_test: CFLAGS_MONGOOSE += -DMG_ENABLE_FREERTOS=1
|
||||||
bsd_test: FreeRTOS-Kernel
|
bsd_test: FreeRTOS-Kernel
|
||||||
|
$(CC) $(C_INPUTS) $(FREERTOS_INPUTS) -c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
||||||
|
$(CC) -x $(TEST_LANGUAGE) -c $(TEST_SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
||||||
|
$(CC) $(C_OBJECTS) $(FREERTOS_OBJECTS) $(TEST_SOURCES:.c=.o) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@
|
||||||
|
|
||||||
|
bsd_nortos_test: CFLAGS_MONGOOSE += -DMG_ENABLE_FREERTOS=0
|
||||||
|
bsd_nortos_test:
|
||||||
$(CC) $(C_INPUTS) -c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
$(CC) $(C_INPUTS) -c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
||||||
$(CC) -x $(TEST_LANGUAGE) -c $(TEST_SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
$(CC) -x $(TEST_LANGUAGE) -c $(TEST_SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)
|
||||||
$(CC) $(C_OBJECTS) $(TEST_SOURCES:.c=.o) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@
|
$(CC) $(C_OBJECTS) $(TEST_SOURCES:.c=.o) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@
|
||||||
@@ -61,12 +75,21 @@ shm_tap_bridge: shm_tap_bridge.c shm_queue.h
|
|||||||
$(CC) shm_tap_bridge.c -pthread -o $@
|
$(CC) shm_tap_bridge.c -pthread -o $@
|
||||||
|
|
||||||
# requires test/port_tap_bridge to be running
|
# requires test/port_tap_bridge to be running
|
||||||
|
bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
|
||||||
bsd_s390: ASAN=
|
bsd_s390: ASAN=
|
||||||
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
|
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
|
||||||
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
|
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
|
||||||
bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test
|
bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test
|
||||||
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
|
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
|
||||||
|
|
||||||
|
#also
|
||||||
|
bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
|
||||||
|
bsd_nortos_s390: ASAN=
|
||||||
|
bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc
|
||||||
|
bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390
|
||||||
|
bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test
|
||||||
|
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
|
||||||
|
|
||||||
shm_port_bridge: shm_port_bridge.c shm_queue.h
|
shm_port_bridge: shm_port_bridge.c shm_queue.h
|
||||||
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
|
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
|
||||||
|
|
||||||
|
|||||||
+73
-5
@@ -1,4 +1,5 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -91,22 +92,89 @@ fail:
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE, WAIT_SEND };
|
||||||
|
|
||||||
|
// Wait for output, optionally draining through the final marker.
|
||||||
|
static int wait_data(const char *host, enum wait_mode mode) {
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
struct timeval timeout;
|
||||||
|
char buf[512];
|
||||||
|
ssize_t n;
|
||||||
|
size_t i;
|
||||||
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
|
||||||
|
if (fd < 0) return 1;
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_port = htons(PORT);
|
||||||
|
if (inet_pton(AF_INET, host, &sa.sin_addr) != 1) goto fail;
|
||||||
|
timeout.tv_sec = 5, timeout.tv_usec = 0;
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0)
|
||||||
|
goto fail;
|
||||||
|
if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) goto fail;
|
||||||
|
if (mode == WAIT_HOLD) {
|
||||||
|
usleep(100000); // Keep the peer connected while the server fails TX
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (mode == WAIT_SEND) {
|
||||||
|
usleep(100000); // Let the server shut down its read side
|
||||||
|
n = send(fd, "x", 1, 0);
|
||||||
|
usleep(500000); // Keep peer connected for server SHUT_WR
|
||||||
|
close(fd);
|
||||||
|
return n == 1 ? 0 : 1;
|
||||||
|
}
|
||||||
|
if (mode == WAIT_CLOSE) {
|
||||||
|
n = recv(fd, buf, sizeof(buf), 0);
|
||||||
|
close(fd);
|
||||||
|
return n == 0 || (n < 0 && errno == ECONNRESET) ? 0 : 1;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
n = recv(fd, buf, sizeof(buf), 0);
|
||||||
|
if (n <= 0) goto fail;
|
||||||
|
if (mode == WAIT_ABORT) break;
|
||||||
|
for (i = 0; i < (size_t) n; i++) {
|
||||||
|
if (buf[i] == '\1') break;
|
||||||
|
}
|
||||||
|
if (i < (size_t) n) break;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
pthread_t threads[NUM_CONNECTIONS];
|
pthread_t threads[NUM_CONNECTIONS];
|
||||||
int i;
|
int connections = NUM_CONNECTIONS, i;
|
||||||
|
|
||||||
if (argc != 2) {
|
if (argc == 3 && strcmp(argv[2], "waitandabort") == 0)
|
||||||
fprintf(stderr, "usage: %s IP\n", argv[0]);
|
return wait_data(argv[1], WAIT_ABORT);
|
||||||
|
if (argc == 3 && strcmp(argv[2], "waitanddrain") == 0)
|
||||||
|
return wait_data(argv[1], WAIT_DRAIN);
|
||||||
|
if (argc == 3 && strcmp(argv[2], "hold") == 0)
|
||||||
|
return wait_data(argv[1], WAIT_HOLD);
|
||||||
|
if (argc == 3 && strcmp(argv[2], "expectclose") == 0)
|
||||||
|
return wait_data(argv[1], WAIT_CLOSE);
|
||||||
|
if (argc == 3 && strcmp(argv[2], "sendlater") == 0)
|
||||||
|
return wait_data(argv[1], WAIT_SEND);
|
||||||
|
if (argc == 3) connections = atoi(argv[2]);
|
||||||
|
if ((argc != 2 && argc != 3) || connections < 1 ||
|
||||||
|
connections > NUM_CONNECTIONS) {
|
||||||
|
fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose|sendlater]\n",
|
||||||
|
argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
srandom(1);
|
srandom(1);
|
||||||
|
|
||||||
for (i = 0; i < NUM_CONNECTIONS; i++) {
|
for (i = 0; i < connections; i++) {
|
||||||
if (pthread_create(&threads[i], NULL, worker, argv[1]) != 0) return 1;
|
if (pthread_create(&threads[i], NULL, worker, argv[1]) != 0) return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < NUM_CONNECTIONS; i++) {
|
for (i = 0; i < connections; i++) {
|
||||||
if (pthread_join(threads[i], NULL) != 0) return 1;
|
if (pthread_join(threads[i], NULL) != 0) return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+939
-71
File diff suppressed because it is too large.
Load diff
@@ -1,18 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// See https://mongoose.ws/documentation/#build-options
|
// See https://mongoose.ws/documentation/#build-options
|
||||||
//#undef MG_ARCH
|
|
||||||
//#define MG_ARCH MG_ARCH_UNIX
|
|
||||||
#define MG_ENABLE_FREERTOS 1
|
|
||||||
|
|
||||||
#define MG_ENABLE_TCPIP 1
|
#define MG_ENABLE_TCPIP 1
|
||||||
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
|
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
|
||||||
#define MG_ENABLE_BSD_SOCKETS 1
|
#define MG_ENABLE_BSD_SOCKETS 1
|
||||||
#define MG_ENABLE_BSD_LOG 1
|
|
||||||
#define MG_ENABLE_LINES 1
|
#define MG_ENABLE_LINES 1
|
||||||
#define MG_ENABLE_CUSTOM_RANDOM 1
|
#define MG_ENABLE_CUSTOM_RANDOM 1
|
||||||
|
|
||||||
//#include <ctype.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
@@ -24,7 +19,16 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if MG_ENABLE_FREERTOS
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define MG_ENABLE_CUSTOM_CALLOC 1
|
||||||
|
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
ROOT ?= $(realpath $(CURDIR)/../..)
|
ROOT ?= $(realpath $(CURDIR)/../..)
|
||||||
PROJECTS ?= ../../tutorials/http/device-dashboard/esp32/ ../../tutorials/micropython/esp32/ ../../tutorials/http/uart-bridge/esp32/
|
PROJECTS ?= ../../tutorials/esp32/device-dashboard/ ../../tutorials/micropython/esp32/ ../../tutorials/esp32/uart-bridge/
|
||||||
TARGET ?= Debug
|
TARGET ?= Debug
|
||||||
DOCKER = docker run --rm -v $(ROOT):$(ROOT) -v $(CURDIR):/root -w $(CURDIR)
|
DOCKER = docker run --rm -v $(ROOT):$(ROOT) -v $(CURDIR):/root -w $(CURDIR)
|
||||||
IMAGE ?= espressif/idf
|
IMAGE ?= espressif/idf
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Mesh simulator. `make` builds and runs an example
|
||||||
|
ROOT = ../..
|
||||||
|
PROG ?= simulator
|
||||||
|
SRCS = simulator.c mesh_a.c $(ROOT)/mongoose.c
|
||||||
|
HDRS = simulator.h $(ROOT)/mongoose.h
|
||||||
|
WARN = -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common \
|
||||||
|
-Wconversion -Wundef -Wmissing-prototypes -Wstrict-prototypes
|
||||||
|
DEFS = -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ENABLE_IPV6=0 \
|
||||||
|
-DMG_ENABLE_CUSTOM_MILLIS=1 -DMG_ENABLE_TCPIP_TCPRTX=1 \
|
||||||
|
-DMG_IO_SIZE=512 \
|
||||||
|
-DMG_TCPIP_ARP_MS=1000 -DMG_TCPIP_ACK_MS=500 # Slow radio: wait longer
|
||||||
|
CFLAGS ?= -O2 -g0 $(WARN)
|
||||||
|
X ?= 100
|
||||||
|
Y ?= 100
|
||||||
|
ROWS ?= 10
|
||||||
|
COLS ?= 10
|
||||||
|
|
||||||
|
# Nodes at every corner of a ROWS x COLS grid over X,Y, edges included
|
||||||
|
GRID = $(shell for r in $$(seq 0 $(ROWS)); do \
|
||||||
|
for c in $$(seq 0 $(COLS)); do \
|
||||||
|
echo -node $$((c * $(X) / $(COLS))),$$((r * $(Y) / $(ROWS))); \
|
||||||
|
done; done)
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
$(RUN) ./$(PROG) -area $(X),$(Y) \
|
||||||
|
-bridge $$(($(X) / 2)),$$(($(Y) / 2)) \
|
||||||
|
$(GRID) $(ARGS)
|
||||||
|
|
||||||
|
$(PROG): $(SRCS) $(HDRS) Makefile
|
||||||
|
$(CC) $(SRCS) $(CFLAGS) $(DEFS) -I. -I$(ROOT) -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(PROG) $(PROG).dSYM *.exe
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
# Ethernet over a radio mesh
|
||||||
|
|
||||||
|
## The idea
|
||||||
|
|
||||||
|
Small radio devices, e.g. IEEE 802.15.4, sub-GHz or LoRa boards, should run
|
||||||
|
Mongoose as if they were on an ordinary Ethernet LAN: get an IP address over
|
||||||
|
DHCP, answer HTTP, talk MQTT. No 6LoWPAN, no Thread, no special stack.
|
||||||
|
|
||||||
|
To do that, the radio mesh looks like an Ethernet switch to Mongoose.
|
||||||
|
One or more bridges connect the radio network to the wired LAN: a bridge has
|
||||||
|
an Ethernet port and a radio. A node in range of a bridge talks to it
|
||||||
|
directly; a node out of range reaches a bridge through other nodes, which
|
||||||
|
forward its frames. That makes the radio network a mesh. With several
|
||||||
|
bridges, each node uses one of them, e.g. the nearest; the mesh covers a
|
||||||
|
bigger area, and keeps working if a bridge goes down. A PC on the LAN sees
|
||||||
|
the radio nodes as regular hosts on the same LAN.
|
||||||
|
|
||||||
|
```
|
||||||
|
PC --- LAN --- bridge ~~~ radio nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Two parts plug in
|
||||||
|
|
||||||
|
- `struct mg_radio` describes frame size, bitrate, airtime overhead, signal
|
||||||
|
parameters and channel-access timings.
|
||||||
|
- `struct mg_mesh` has `init`, `tick`, `free`, `tx` and `rx` callbacks. The most important
|
||||||
|
one is the `tick()` callback which is called with a microsecond timestamp,
|
||||||
|
obtained from `mg_micros()`, and handles routing and mesh management.
|
||||||
|
The mesh queues and fragments Ethernet frames and reassembles incoming radio
|
||||||
|
frames. Driver `tx()` queues Ethernet into the mesh; `rx()` returns a
|
||||||
|
reassembled Ethernet frame. A private callback transmits one radio frame.
|
||||||
|
|
||||||
|
The default slotted mesh in `../../src/l2_mesh.c` uses the bridge as a coordinator.
|
||||||
|
It opens windows for new nodes to join and gives joined nodes DATA turns
|
||||||
|
in round-robin order.
|
||||||
|
|
||||||
|
Mongoose calls driver's `poll()`. Radio driver calls `mg_micros()` to get
|
||||||
|
the current timestamp and calls `mg_mesh :: tick()`.
|
||||||
|
|
||||||
|
The goal is to swap radios and mesh strategies without changing application
|
||||||
|
code. The slotted star deliberately omits radio ACKs to avoid
|
||||||
|
the extra airtime and radio turnaround. Other strategies may use them.
|
||||||
|
|
||||||
|
## Why a simulator
|
||||||
|
|
||||||
|
Real boards are slow to test with, and you can't put 500 of them on a desk.
|
||||||
|
The simulator runs a whole network in one process: every node is a real
|
||||||
|
Mongoose instance, only the radio and the air between nodes are emulated.
|
||||||
|
That lets us try mesh strategies with many nodes, and measure how long it
|
||||||
|
takes nodes to join, get an address, and answer a request.
|
||||||
|
|
||||||
|
## Virtual time
|
||||||
|
|
||||||
|
The simulator exposes its virtual clock through `mg_micros()`.
|
||||||
|
TCP/IP reads the same clock through `mg_millis()`, rounded down to milliseconds.
|
||||||
|
Nothing waits for real time. `-t SEC` sets the duration, 10 minutes by default.
|
||||||
|
|
||||||
|
Radio channel checks, transmission starts and ends run at their scheduled
|
||||||
|
microsecond timestamps. The clock jumps to the earliest radio event, mesh
|
||||||
|
deadline, or periodic stack poll.
|
||||||
|
|
||||||
|
`STEP_MICROSECONDS` is 1000: periodic polling services Mongoose's internal
|
||||||
|
stack timers every millisecond. Packet delivery triggers immediate work:
|
||||||
|
the simulator repeatedly polls DHCP, the tester, bridges and nodes at the
|
||||||
|
same virtual timestamp until that work drains. Ethernet forwarding and
|
||||||
|
radio receive processing therefore do not wait for the periodic tick.
|
||||||
|
Internal timer handling can still be delayed by up to about 1 ms.
|
||||||
|
|
||||||
|
## The setup
|
||||||
|
|
||||||
|
```
|
||||||
|
DHCP server ---------+
|
||||||
|
10.0.0.1 |
|
||||||
|
02:01:00:00:00:00 +-- LAN ---- bridge ~~~ nodes
|
||||||
|
|
|
||||||
|
test node --------+
|
||||||
|
10.0.0.2
|
||||||
|
02:02:00:00:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
The LAN is assumed infinitely fast, see "How frames move on the LAN" below.
|
||||||
|
Two nodes are on it:
|
||||||
|
|
||||||
|
- The DHCP server, 10.0.0.1, MAC 02:01:00:00:00:00, gives each node an
|
||||||
|
address made from its MAC: 02:00:00:X:Y:Z gets 10.X+1.Y.Z, so node
|
||||||
|
addresses never clash with the LAN nodes' 10.0.0.x. It also answers
|
||||||
|
ARP requests for its advertised gateway IP, 10.0.0.1, so nodes can resolve
|
||||||
|
the gateway MAC and stop retrying. It does not implement IP routing,
|
||||||
|
and does not have a full Mongoose instance
|
||||||
|
- The test node, 10.0.0.2, MAC 02:02:00:00:00:00, is a Mongoose
|
||||||
|
instance. It waits for the mesh to settle, 60 seconds by default
|
||||||
|
(`-w SEC`), then sends `GET /` to each node in turn, one at a time, at the
|
||||||
|
node's own IP address. It wraps back to the first node and keeps querying
|
||||||
|
until five seconds before the end, when it stops starting new requests.
|
||||||
|
An in-flight request may still complete or time out during the remaining
|
||||||
|
time. Runs lasting five seconds or less start no requests. A node with
|
||||||
|
no address, a connection error, or a request exceeding 10 seconds counts
|
||||||
|
as failed. The summary reports successful and failed attempts, plus
|
||||||
|
minimum, mean and maximum response times for successful requests. A
|
||||||
|
request still pending at the end is not included in those counts.
|
||||||
|
|
||||||
|
Bridges are on the LAN too, and get every frame, as if in promiscuous mode:
|
||||||
|
a frame for one of their radio nodes isn't addressed to the bridge itself.
|
||||||
|
A bridge forwards every frame from the LAN to the radio and every frame
|
||||||
|
from the radio to the LAN.
|
||||||
|
|
||||||
|
Bridges and radio nodes get MACs 02:00:00:xx:xx:xx, numbered from 1 in
|
||||||
|
command-line order: 02:00:00:00:00:01, 02:00:00:00:00:02, and so on.
|
||||||
|
|
||||||
|
### How frames move on the LAN
|
||||||
|
|
||||||
|
Every LAN node, the DHCP server, the test node and each bridge, has a
|
||||||
|
LAN interface, `lif`: a `struct mg_tcpip_if` with a receive queue. All of
|
||||||
|
them use the same driver, `s_lan_driver`. There are no cables and no switch:
|
||||||
|
|
||||||
|
- Sending. A node sends a frame with its driver's `tx`, which is
|
||||||
|
`lan_tx()`. It looks at the destination MAC and copies the frame into the
|
||||||
|
receive queues of the nodes it is for:
|
||||||
|
- a broadcast or multicast goes to every node but the sender;
|
||||||
|
- a unicast goes to the node with that MAC;
|
||||||
|
- bridges get every frame, as a real bridge in promiscuous mode would:
|
||||||
|
a frame for a radio node is addressed to the node, not to its bridge.
|
||||||
|
- Receiving. Each node takes the frames from its own queue when
|
||||||
|
the main loop polls it:
|
||||||
|
- the test node runs Mongoose, which reads the queue by itself, as it
|
||||||
|
would with a real Ethernet driver that queues frames from an interrupt;
|
||||||
|
- the DHCP server reads it in `dhcp_poll()`, and replies by sending
|
||||||
|
through its own `lif`;
|
||||||
|
- bridges read it in `bridges_poll()`, and forward it to the radio.
|
||||||
|
|
||||||
|
Enqueuing marks immediate work pending. The main loop drains that work
|
||||||
|
without advancing virtual time, so the LAN has no simulated propagation,
|
||||||
|
serialization or polling delay. Sending never calls receiver code directly;
|
||||||
|
replies are processed by subsequent polls at the same timestamp.
|
||||||
|
|
||||||
|
For example, a node asking for an address:
|
||||||
|
|
||||||
|
1. The node's DHCP DISCOVER crosses the radio to a bridge, which sends it
|
||||||
|
out of its `lif`. It is a broadcast, so `lan_tx()` puts it into the
|
||||||
|
queues of the DHCP server, the test node and the other bridges.
|
||||||
|
2. On its next poll, the DHCP server reads it and sends an OFFER, also a
|
||||||
|
broadcast, which lands in the test node's and the bridges' queues.
|
||||||
|
3. On their next poll, the bridges read the OFFER and send it over the
|
||||||
|
radio to the node. The test node's Mongoose drops it: the test node
|
||||||
|
has a fixed address and no DHCP client.
|
||||||
|
|
||||||
|
### How frames move on the radio
|
||||||
|
|
||||||
|
The simulator keeps one accepted transmission per sender in a singly linked
|
||||||
|
list. A busy sender's driver returns zero, so the mesh retains the fragment
|
||||||
|
until the driver accepts it. Different senders can still overlap on air.
|
||||||
|
|
||||||
|
1. A sender waits a random number of backoff slots, then listens for
|
||||||
|
`cca_us` to check whether the channel was idle throughout that interval.
|
||||||
|
2. If busy, it increases its backoff exponent up to `backoff_max`, waits
|
||||||
|
again, and repeats. There is no channel-access attempt limit yet.
|
||||||
|
3. If idle, it schedules transmission after `turnaround_us`. Another sender
|
||||||
|
can also pass its check before either starts: the channel check does not
|
||||||
|
reserve the air.
|
||||||
|
4. Transmission takes `(len + overhead) * 8 / bitrate` seconds, rounded up
|
||||||
|
to a microsecond. Overlapping transmissions are both marked collided.
|
||||||
|
Channel checks at the same timestamp run before transmission starts,
|
||||||
|
so list order cannot make one simultaneous check win.
|
||||||
|
5. At the end, a collided frame is dropped. Otherwise it is queued for all
|
||||||
|
other nodes for broadcast/multicast, or the matching node for unicast.
|
||||||
|
Every other bridge receives it too. Receive processing happens at the
|
||||||
|
same virtual timestamp.
|
||||||
|
|
||||||
|
A receiver drops a frame if any part of that frame overlaps its own channel
|
||||||
|
backoff, sensing, turnaround or transmission interval, even if it is
|
||||||
|
available again when the frame ends.
|
||||||
|
The radio summary reports total sent and collided frames, and lost receptions.
|
||||||
|
Losses include range or random loss, plus missed unicast receptions when the
|
||||||
|
destination was transmitting.
|
||||||
|
|
||||||
|
The 802.15.4 definition uses 250 kbit/s, 8 bytes overhead, 320 us backoff
|
||||||
|
slots, 128 us channel sensing and 100 us turnaround. The initial backoff
|
||||||
|
exponent is 3 (0–7 slots), increasing to 5 (0–31 slots) on busy checks.
|
||||||
|
After transmitting, a sender waits the radio's inter-frame spacing,
|
||||||
|
`mg_radio::ifs_us`, 640 us for 802.15.4, before starting its next frame's
|
||||||
|
backoff. This wait applies even if the frame collided or the sender's
|
||||||
|
queue was temporarily empty. Reception remains enabled during the
|
||||||
|
inter-frame wait.
|
||||||
|
The backoff generator is seeded from the wall clock.
|
||||||
|
|
||||||
|
The mesh limits each transmission to 125 bytes plus the 2-byte FCS.
|
||||||
|
Overhead and inter-frame spacing apply to each fragment.
|
||||||
|
A receiver must be within `-range METRES` of the sender (default 185 m).
|
||||||
|
`-loss PERCENT` adds independent random loss for each in-range reception
|
||||||
|
(default 0%). The radio summary counts range and random losses together.
|
||||||
|
Collisions and channel sensing still apply across the whole simulated air.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | What |
|
||||||
|
|------|------|
|
||||||
|
| `../../src/l2_mesh.h`, `../../src/l2_mesh.c` | `struct mg_radio`, `struct mg_mesh`, shared node initialization, and IEEE 802.15.4 radio |
|
||||||
|
| `mesh_a.c` | Alternative unslotted star strategy |
|
||||||
|
| `simulator.h`, `simulator.c` | The simulator |
|
||||||
|
| `Makefile` | Builds and runs the simulator |
|
||||||
|
|
||||||
|
## Where we are
|
||||||
|
|
||||||
|
The slotted strategy uses direct radio hops between nodes and a bridge.
|
||||||
|
The simulator models airtime, half-duplex reception and collisions.
|
||||||
|
|
||||||
|
- Frames outside reception range are not received, so nodes out of a bridge's
|
||||||
|
range never join. But collisions and channel sensing are still global:
|
||||||
|
every overlap is a collision, wherever the receiver is, and a busy
|
||||||
|
channel is busy for everyone.
|
||||||
|
- Mesh routing remains unimplemented: bridge selection, forwarding through other
|
||||||
|
nodes and recovery are not implemented.
|
||||||
|
|
||||||
|
Next steps include distance-aware collision and channel sensing, mesh
|
||||||
|
routing and bridge filtering.
|
||||||
|
|
||||||
|
## Strategy A — unslotted (simulator default)
|
||||||
|
|
||||||
|
Each sender contends for the channel using CSMA. The mesh queues radio
|
||||||
|
fragments and submits them one at a time. The receiver reassembles bursts
|
||||||
|
per sender, and the bridge forwards Ethernet frames between LAN and radio.
|
||||||
|
|
||||||
|
## Default strategy — slotted mesh
|
||||||
|
|
||||||
|
One bridge coordinates the network and starts with an empty member list.
|
||||||
|
It broadcasts a JOIN beacon to open a 3 ms window. Unjoined nodes choose
|
||||||
|
windows at random and add up to 640 µs of jitter before sending their JOIN
|
||||||
|
request with their Ethernet MAC to the bridge. The bridge adds each new
|
||||||
|
radio address and its Ethernet MAC to the head of its member list.
|
||||||
|
|
||||||
|
After the JOIN window, the bridge grants each member a DATA turn in list
|
||||||
|
order, then opens another JOIN window. The bridge advances as soon as it
|
||||||
|
reassembles a valid uplink. If the reply is missing, the turn times out
|
||||||
|
after allowing for the downlink and a maximum-size uplink burst, including
|
||||||
|
inter-frame spacing. A member must answer every turn,
|
||||||
|
with an empty `DATA_UP` if it has nothing to send. A member that misses 3
|
||||||
|
turns in a row is removed. With no
|
||||||
|
members, the bridge opens JOIN windows back to back. A node recognizes its
|
||||||
|
first `DATA_DOWN` as
|
||||||
|
confirmation that it joined.
|
||||||
|
|
||||||
|
Slotted nodes have a separate TCP/IP interface that reports link up after
|
||||||
|
the first grant. Each mesh interface has one 2 KiB TX queue of split
|
||||||
|
radio bursts. The bridge shares that queue among members and sends the
|
||||||
|
head burst in its destination's turn. It directs DHCP replies by the
|
||||||
|
client MAC in `chaddr` and keeps one copy of other multicast and broadcast
|
||||||
|
bursts until each member has had its turn. A frame that does not fit is
|
||||||
|
rejected in full. The hardware and simulator drivers accept one raw frame
|
||||||
|
at a time. JOIN messages fit in a single radio frame. A DATA
|
||||||
|
burst carries one Ethernet frame across up to 14 radio frames. Each
|
||||||
|
fragment has a 9-byte short-address MAC header, a one-byte type, and a
|
||||||
|
byte containing the fragment index and final index in the high and low
|
||||||
|
nibbles. Empty DATA messages use a single header-only frame. The node
|
||||||
|
starts its uplink after receiving the whole downlink, and the bridge
|
||||||
|
forwards only complete uplinks to the LAN.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make # 100x100 m, a bridge, 11x11 nodes
|
||||||
|
make X=200 Y=50 ROWS=2 COLS=8 # Another area and grid
|
||||||
|
```
|
||||||
|
|
||||||
|
`make` puts a bridge in the middle of the area, and a node at every corner
|
||||||
|
of a `ROWS` x `COLS` grid over it. The simulator itself takes:
|
||||||
|
|
||||||
|
```
|
||||||
|
./mesh_sim -area X,Y,Z -bridge X,Y,Z -node X,Y,Z ... -t SEC -w SEC -m A|default -range METRES -loss PERCENT
|
||||||
|
```
|
||||||
|
|
||||||
|
Positions are in metres; `X,Y` means `Z` is 0. Repeat `-bridge` and `-node`
|
||||||
|
for more.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
The FRDM-RW612 example is the Ethernet bridge. The XIAO MG24 and
|
||||||
|
FRDM-MCXW71 examples are radio clients. All three use the slotted mesh.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -C tutorials/nxp/frdm-rw612/bridge
|
||||||
|
make -C tutorials/silabs/xiao-mg24/minimal
|
||||||
|
make -C tutorials/nxp/frdm-mcxw71/minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
Use each tutorial's `flash` target. Connect the
|
||||||
|
bridge to an Ethernet LAN with a DHCP server. Find the client's assigned
|
||||||
|
IP in its serial log, then send an HTTP GET to that IP from the LAN.
|
||||||
|
|
||||||
|
All three boards use channel 15 and PAN `0xabcd` by default. Each board derives its radio address from its Ethernet MAC.
|
||||||
|
MG24 is a node only; its radio address is derived from its MAC.
|
||||||
|
|
||||||
|
The drivers in `src/drivers` send and receive raw 802.15.4 frames.
|
||||||
|
They use the same slotted mesh implementation as the simulator.
|
||||||
|
The board driver is initialized by `mg_mgr_init()`. The RW612 bridge
|
||||||
|
has its own `bridge/main.c`, which initializes and polls the two drivers
|
||||||
|
directly, without an event manager or local IP stack.
|
||||||
|
|
||||||
|
## Hardware integration
|
||||||
|
|
||||||
|
The default mesh and radio drivers are included in `mongoose.c` and `mongoose.h`.
|
||||||
|
Radio drivers live in `src/drivers/`: `mg24`, `mcxw71`, `rw612w`,
|
||||||
|
and `nrf52`. MG24 driver data contains its mesh state and `strategy`; it has
|
||||||
|
no bridge LAN interface.
|
||||||
|
A NULL strategy selects `mg_mesh_default`, named `"default"`; assigning another
|
||||||
|
`const struct mg_mesh *` overrides it. Hardware callbacks transmit and receive
|
||||||
|
raw radio frames; the selected strategy owns joining, scheduling and Ethernet
|
||||||
|
fragmentation. Driver initialization goes through `mg_tcpip_init()`.
|
||||||
|
|
||||||
|
Board examples are under `tutorials/nxp/frdm-rw612/bridge`,
|
||||||
|
`tutorials/nxp/frdm-mcxw71/minimal`, `tutorials/silabs/xiao-mg24/minimal`, and
|
||||||
|
`tutorials/nordic/xiao-52840/minimal`. Their HALs supply `mg_micros()`.
|
||||||
|
|
||||||
|
The simulator and all radio drivers keep the hardware driver installed throughout the
|
||||||
|
interface lifetime. Mesh state belongs to `driver_data`. Driver `tx()` queues
|
||||||
|
Ethernet frames; `poll()` feeds raw received frames to the mesh and calls
|
||||||
|
strategy `tick()`. The strategy sends scheduled fragments through the driver's
|
||||||
|
private `radio_tx` callback. Driver `rx()` returns a reassembled Ethernet frame
|
||||||
|
using the stack's existing receive buffer. There is no mesh polling timer,
|
||||||
|
copied radio interface, or mesh-specific change in `net_builtin.*`.
|
||||||
|
The simulator explicitly frees its mesh state after `mg_mgr_free()`; board
|
||||||
|
drivers keep their state for the firmware lifetime. MG24, MCXW71 and nRF52 are
|
||||||
|
radio nodes. The RW612 bridge application owns Ethernet initialization, its RX
|
||||||
|
queue, promiscuous mode and link polling, and attaches the LAN to the mesh.
|
||||||
|
The RW612W driver owns only the radio interface.
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
// Radio header followed by type and fragment index/last index.
|
||||||
|
#define MGI_FRAME_HDR_LEN (MG_802154_HEADER_LEN + 2)
|
||||||
|
|
||||||
|
#define MGI_ETH_MAX 1514
|
||||||
|
#define MGI_TX_DST_LEN 2
|
||||||
|
|
||||||
|
#define MGI_RX_TIMEOUT_US 1000000
|
||||||
|
#define MGI_TX_QUEUE_SIZE 4096
|
||||||
|
#define MGI_DATA_TAG \
|
||||||
|
0x80u // High bit identifies DATA, low seven bits tag a burst
|
||||||
|
|
||||||
|
struct mgi_mesh_a_rx {
|
||||||
|
struct mgi_mesh_a_rx *next;
|
||||||
|
uint64_t expires;
|
||||||
|
uint16_t src;
|
||||||
|
uint8_t tag, index, last;
|
||||||
|
size_t len;
|
||||||
|
uint8_t buf[MGI_ETH_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mgi_mesh_a_data {
|
||||||
|
struct mg_mesh_node *node;
|
||||||
|
struct mg_tcpip_if *ethernet;
|
||||||
|
struct mgi_mesh_a_rx *rx;
|
||||||
|
uint8_t tag;
|
||||||
|
struct mg_queue tx;
|
||||||
|
char tx_buf[MGI_TX_QUEUE_SIZE];
|
||||||
|
size_t tx_offset;
|
||||||
|
uint64_t tx_at;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct mg_radio *const s_radio = &mg_radio_802154;
|
||||||
|
|
||||||
|
static uint64_t mgi_mesh_a_airtime(size_t len) {
|
||||||
|
const struct mg_radio *radio = &mg_radio_802154;
|
||||||
|
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||||
|
radio->bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each queue record holds a destination and a complete burst.
|
||||||
|
static size_t mgi_mesh_a_queue(struct mg_mesh_node *node, struct mg_queue *q,
|
||||||
|
const void *buf, size_t len, uint16_t dst, uint8_t type) {
|
||||||
|
const struct mg_radio *radio = &mg_radio_802154;
|
||||||
|
const uint8_t *eth = (const uint8_t *) buf;
|
||||||
|
size_t hdr = MGI_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
|
||||||
|
size_t frames, size, i;
|
||||||
|
char *record;
|
||||||
|
uint8_t *out;
|
||||||
|
if (len < 14 || len > MGI_ETH_MAX) return 0;
|
||||||
|
frames = (len + cap - 1) / cap;
|
||||||
|
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
|
||||||
|
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
|
||||||
|
if (mg_queue_book(q, &record, size) < size) return 0;
|
||||||
|
out = (uint8_t *) record;
|
||||||
|
*out++ = (uint8_t) (dst >> 8);
|
||||||
|
*out++ = (uint8_t) dst;
|
||||||
|
for (i = 0; i < frames; i++) {
|
||||||
|
size_t chunk = len - i * cap;
|
||||||
|
if (chunk > cap) chunk = cap;
|
||||||
|
*out++ = (uint8_t) (hdr + chunk);
|
||||||
|
radio->print_hdr(radio, out, dst, node->radio_address);
|
||||||
|
out[radio->header_len] = type;
|
||||||
|
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
|
||||||
|
memcpy(out + hdr, eth + i * cap, chunk);
|
||||||
|
out += hdr + chunk;
|
||||||
|
}
|
||||||
|
mg_queue_add(q, size);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mesh_a_send(struct mgi_mesh_a_data *data, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
uint8_t tag = (uint8_t) ((data->tag + 1) & 0x7f);
|
||||||
|
size_t queued = mgi_mesh_a_queue(data->node, &data->tx, buf, len, 0xffffu,
|
||||||
|
(uint8_t) (MGI_DATA_TAG | tag));
|
||||||
|
if (queued != 0) data->tag = tag;
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_tx(struct mgi_mesh_a_data *data, uint64_t now) {
|
||||||
|
struct mg_tcpip_if *radio = data->node->radio;
|
||||||
|
char *record;
|
||||||
|
size_t size = mg_queue_next(&data->tx, &record);
|
||||||
|
if (size == 0) return;
|
||||||
|
if (now >= data->tx_at) {
|
||||||
|
const uint8_t *part = (const uint8_t *) record + data->tx_offset;
|
||||||
|
size_t len = part[0];
|
||||||
|
if (now >= data->node->tx_at &&
|
||||||
|
data->node->tx(radio, part + 1, len) == len) {
|
||||||
|
data->tx_offset += len + 1;
|
||||||
|
if (data->tx_offset == size) {
|
||||||
|
mg_queue_del(&data->tx, size);
|
||||||
|
data->tx_offset = MGI_TX_DST_LEN;
|
||||||
|
}
|
||||||
|
data->tx_at = now + mgi_mesh_a_airtime(len) + s_radio->ifs_us;
|
||||||
|
} else {
|
||||||
|
data->tx_at = now + s_radio->slot_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mg_queue_next(&data->tx, NULL) != 0) data->node->next_tick = data->tx_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t mgi_mesh_a_enqueue(struct mg_mesh_node *node, const void *buf,
|
||||||
|
size_t len) {
|
||||||
|
return mgi_mesh_a_send(node->data, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_deliver(struct mgi_mesh_a_data *data, uint8_t *buf,
|
||||||
|
size_t len) {
|
||||||
|
struct mg_tcpip_if *lan = data->node->lan;
|
||||||
|
if (len < 14) return;
|
||||||
|
if (lan != NULL) {
|
||||||
|
lan->driver->tx(buf, len, lan);
|
||||||
|
} else {
|
||||||
|
struct mg_tcpip_if *ifp = data->node->radio;
|
||||||
|
if (data->node->rx_len != 0 || len > ifp->recv_queue.size) {
|
||||||
|
ifp->ndrop++;
|
||||||
|
} else {
|
||||||
|
memcpy(ifp->recv_queue.buf, buf, len);
|
||||||
|
data->node->rx_len = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_rx_free(struct mgi_mesh_a_data *data,
|
||||||
|
struct mgi_mesh_a_rx **p) {
|
||||||
|
struct mgi_mesh_a_rx *r = *p;
|
||||||
|
*p = r->next;
|
||||||
|
data->node->ram_used -= sizeof(*r);
|
||||||
|
free(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_receive(struct mgi_mesh_a_data *data, uint8_t *frame,
|
||||||
|
size_t len, uint64_t now) {
|
||||||
|
struct mgi_mesh_a_rx **p = &data->rx, *r;
|
||||||
|
size_t cap = s_radio->max_frame_size - MGI_FRAME_HDR_LEN, chunk;
|
||||||
|
uint16_t dst, src;
|
||||||
|
uint8_t tag, index, last;
|
||||||
|
if (len <= MGI_FRAME_HDR_LEN || len > s_radio->max_frame_size ||
|
||||||
|
s_radio->parse_hdr(s_radio, frame, len, &dst, &src) !=
|
||||||
|
s_radio->header_len ||
|
||||||
|
(dst != 0xffffu && dst != data->node->radio_address))
|
||||||
|
return;
|
||||||
|
tag = frame[MGI_FRAME_HDR_LEN - 2];
|
||||||
|
index = frame[MGI_FRAME_HDR_LEN - 1] >> 4;
|
||||||
|
last = frame[MGI_FRAME_HDR_LEN - 1] & 15;
|
||||||
|
chunk = len - MGI_FRAME_HDR_LEN;
|
||||||
|
if ((tag & MGI_DATA_TAG) == 0 || index > last ||
|
||||||
|
last > (MGI_ETH_MAX - 1) / cap || (index < last && chunk != cap))
|
||||||
|
return;
|
||||||
|
if (index == 0 &&
|
||||||
|
(chunk < 14 ||
|
||||||
|
(data->node->lan == NULL && (frame[MGI_FRAME_HDR_LEN] & 1) == 0 &&
|
||||||
|
memcmp(frame + MGI_FRAME_HDR_LEN, data->ethernet->mac, 6) != 0)))
|
||||||
|
return;
|
||||||
|
while (*p != NULL && (*p)->src != src) p = &(*p)->next;
|
||||||
|
if (index == 0) {
|
||||||
|
if (*p != NULL) mgi_mesh_a_rx_free(data, p);
|
||||||
|
if (last == 0) {
|
||||||
|
mgi_mesh_a_deliver(data, frame + MGI_FRAME_HDR_LEN, chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r = calloc(1, sizeof(*r));
|
||||||
|
if (r == NULL) return;
|
||||||
|
data->node->ram_used += sizeof(*r);
|
||||||
|
if (data->node->ram_peak < data->node->ram_used)
|
||||||
|
data->node->ram_peak = data->node->ram_used;
|
||||||
|
r->next = *p;
|
||||||
|
*p = r;
|
||||||
|
r->src = src;
|
||||||
|
r->tag = tag;
|
||||||
|
r->last = last;
|
||||||
|
}
|
||||||
|
r = *p;
|
||||||
|
if (r == NULL || r->tag != tag) return;
|
||||||
|
if (r->index != index || r->last != last || chunk > sizeof(r->buf) - r->len) {
|
||||||
|
mgi_mesh_a_rx_free(data, p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(r->buf + r->len, frame + MGI_FRAME_HDR_LEN, chunk);
|
||||||
|
r->len += chunk;
|
||||||
|
r->index++;
|
||||||
|
r->expires = now + MGI_RX_TIMEOUT_US;
|
||||||
|
if (index == last) {
|
||||||
|
mgi_mesh_a_deliver(data, r->buf, r->len);
|
||||||
|
mgi_mesh_a_rx_free(data, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_input(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||||
|
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
|
||||||
|
if (node->tx_at < ready) node->tx_at = ready;
|
||||||
|
node->next_tick = node->now;
|
||||||
|
mgi_mesh_a_receive(node->data, buf, len, node->now);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_tick(struct mg_mesh_node *node, uint64_t now) {
|
||||||
|
struct mgi_mesh_a_data *data = node->data;
|
||||||
|
struct mgi_mesh_a_rx **p = &data->rx;
|
||||||
|
size_t len;
|
||||||
|
node->now = now;
|
||||||
|
node->next_tick = (uint64_t) -1;
|
||||||
|
while (*p != NULL) {
|
||||||
|
if ((*p)->expires <= now)
|
||||||
|
mgi_mesh_a_rx_free(data, p);
|
||||||
|
else
|
||||||
|
p = &(*p)->next;
|
||||||
|
}
|
||||||
|
if (node->lan != NULL) {
|
||||||
|
struct mg_queue *q = &node->lan->recv_queue;
|
||||||
|
char *buf;
|
||||||
|
while ((len = mg_queue_next(q, &buf)) > 0) {
|
||||||
|
mgi_mesh_a_send(data, buf, len);
|
||||||
|
mg_queue_del(q, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mgi_mesh_a_tx(data, now);
|
||||||
|
for (p = &data->rx; *p != NULL; p = &(*p)->next) {
|
||||||
|
if ((*p)->expires < node->next_tick) node->next_tick = (*p)->expires;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mgi_mesh_a_free(struct mg_mesh_node *node) {
|
||||||
|
struct mgi_mesh_a_data *data = node->data;
|
||||||
|
if (data == NULL) return;
|
||||||
|
while (data->rx != NULL) mgi_mesh_a_rx_free(data, &data->rx);
|
||||||
|
free(data);
|
||||||
|
node->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mgi_mesh_a_init(struct mg_mesh_node *node,
|
||||||
|
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
|
||||||
|
uint16_t radio_address) {
|
||||||
|
struct mgi_mesh_a_data *data = calloc(1, sizeof(*data));
|
||||||
|
memset(node, 0, sizeof(*node));
|
||||||
|
node->radio = radio;
|
||||||
|
node->lan = lan;
|
||||||
|
node->radio_address = radio_address;
|
||||||
|
node->next_tick = (uint64_t) -1;
|
||||||
|
node->ram_used = node->ram_peak = sizeof(*node);
|
||||||
|
if (data == NULL) return false;
|
||||||
|
node->ram_used += sizeof(*data);
|
||||||
|
node->ram_peak = node->ram_used;
|
||||||
|
data->node = node;
|
||||||
|
data->tx_offset = MGI_TX_DST_LEN;
|
||||||
|
node->data = data;
|
||||||
|
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
|
||||||
|
data->ethernet = radio;
|
||||||
|
node->joined = lan == NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct mg_mesh mg_mesh_a = {
|
||||||
|
"A", mgi_mesh_a_init, mgi_mesh_a_tick, mgi_mesh_a_free,
|
||||||
|
mgi_mesh_a_enqueue, mgi_mesh_a_input, false, false, &mg_radio_802154,
|
||||||
|
};
|
||||||
@@ -0,0 +1,776 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// Radio mesh simulator. Bridges and radio nodes have positions in an area. Time
|
||||||
|
// is virtual: see mg_millis(). On the LAN: a DHCP server, and a test node that
|
||||||
|
// sends HTTP requests to the nodes
|
||||||
|
|
||||||
|
#include "simulator.h"
|
||||||
|
|
||||||
|
#define STEP_MICROSECONDS 1000 // Periodic stack timer polling only
|
||||||
|
|
||||||
|
static const struct mg_mesh *s_mesh = &mg_mesh_default;
|
||||||
|
static struct simulator s_sim; // The simulator
|
||||||
|
|
||||||
|
// Mongoose's clock: virtual time. A minute of network traffic takes far less
|
||||||
|
// than a minute to simulate
|
||||||
|
uint64_t mg_micros(void) {
|
||||||
|
return s_sim.now;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t mg_millis(void) {
|
||||||
|
return mg_micros() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse "X,Y,Z", or "X,Y" with Z 0. Return false if it is not that
|
||||||
|
static bool parse_pos(const char *s, struct pos *p) {
|
||||||
|
int n = 0; // Characters parsed
|
||||||
|
p->x = p->y = p->z = 0;
|
||||||
|
return (sscanf(s, "%lf,%lf,%lf%n", &p->x, &p->y, &p->z, &n) == 3 ||
|
||||||
|
sscanf(s, "%lf,%lf%n", &p->x, &p->y, &n) == 2) &&
|
||||||
|
s[n] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is position p inside the area of sim
|
||||||
|
static bool inside(const struct simulator *sim, const struct pos *p) {
|
||||||
|
return p->x >= 0 && p->x <= sim->area.x && p->y >= 0 && p->y <= sim->area.y &&
|
||||||
|
p->z >= 0 && p->z <= sim->area.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const struct simulator *sim, const char *prog) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: %s OPTIONS\n"
|
||||||
|
"Positions: X,Y,Z in metres, or X,Y with Z 0\n"
|
||||||
|
" -area X,Y,Z - area, a box, default: %g,%g,%g\n"
|
||||||
|
" -bridge X,Y,Z - a bridge; repeat for more\n"
|
||||||
|
" -node X,Y,Z - a radio node; repeat for more\n"
|
||||||
|
" -t SEC - simulated time, seconds, default: %g\n"
|
||||||
|
" -w SEC - mesh settle time before requests, default: %g\n"
|
||||||
|
" -range METRES - radio range, default: %g\n"
|
||||||
|
" -loss PERCENT - random reception loss, default: %g\n"
|
||||||
|
" -m A|default - mesh strategy, default: default\n",
|
||||||
|
prog, sim->area.x, sim->area.y, sim->area.z,
|
||||||
|
(double) (int64_t) sim->duration / 1e6,
|
||||||
|
(double) (int64_t) sim->tester.wait / 1e6, sim->radio_range,
|
||||||
|
sim->radio_loss);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next radio MAC, to mac: 02:00:00:xx:xx:xx, numbered from 1
|
||||||
|
static void next_mac(struct simulator *sim, uint8_t *mac) {
|
||||||
|
uint32_t n = ++sim->last_mac;
|
||||||
|
mac[0] = 2, mac[1] = mac[2] = 0, mac[3] = (uint8_t) (n >> 16);
|
||||||
|
mac[4] = (uint8_t) (n >> 8), mac[5] = (uint8_t) n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a bridge at position s. Return false if s is not a position
|
||||||
|
static bool add_bridge(struct simulator *sim, const char *s) {
|
||||||
|
struct bridge *b = (struct bridge *) calloc(1, sizeof(*b));
|
||||||
|
if (b == NULL || !parse_pos(s, &b->pos)) return free(b), false;
|
||||||
|
if (sim->last_mac >= 0xffffu - 1u) return free(b), false;
|
||||||
|
next_mac(sim, b->rif.mac);
|
||||||
|
b->radio.address = (uint16_t) sim->last_mac;
|
||||||
|
LIST_ADD_TAIL(struct bridge, &sim->bridges, b);
|
||||||
|
sim->num_bridges++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a radio node at position s. Return false if s is not a position
|
||||||
|
static bool add_node(struct simulator *sim, const char *s) {
|
||||||
|
struct radio_node *n = (struct radio_node *) calloc(1, sizeof(*n));
|
||||||
|
if (n == NULL || !parse_pos(s, &n->pos)) return free(n), false;
|
||||||
|
if (sim->last_mac >= 0xffffu - 1u) return free(n), false;
|
||||||
|
next_mac(sim, n->rif.mac);
|
||||||
|
n->radio.address = (uint16_t) sim->last_mac;
|
||||||
|
LIST_ADD_TAIL(struct radio_node, &sim->nodes, n);
|
||||||
|
sim->num_nodes++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if position x is outside the area of sim
|
||||||
|
static void check_inside(const struct simulator *sim, const struct pos *x) {
|
||||||
|
if (!inside(sim, x)) {
|
||||||
|
fprintf(stderr, "%g,%g,%g is outside the area\n", x->x, x->y, x->z);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all bridges and nodes are inside the area of sim
|
||||||
|
static void check_area(const struct simulator *sim) {
|
||||||
|
struct bridge *b;
|
||||||
|
struct radio_node *n;
|
||||||
|
for (b = sim->bridges; b != NULL; b = b->next) check_inside(sim, &b->pos);
|
||||||
|
for (n = sim->nodes; n != NULL; n = n->next) check_inside(sim, &n->pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LAN_QUEUE_SIZE (64 * 1024) // Receive queue of a LAN interface
|
||||||
|
|
||||||
|
// Put frame buf, len bytes, into the receive queue of LAN interface to, if
|
||||||
|
// it is not the sender, from
|
||||||
|
static void lan_put(struct mg_tcpip_if *to, struct mg_tcpip_if *from,
|
||||||
|
const void *buf, size_t len) {
|
||||||
|
if (to != from) {
|
||||||
|
mg_tcpip_qwrite((void *) buf, len, to);
|
||||||
|
s_sim.work_pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LAN driver, all LAN interfaces share it. The LAN is infinitely fast: tx
|
||||||
|
// puts the frame straight into the receive queues of the interfaces it is
|
||||||
|
// for: all, if broadcast; else the one with the destination MAC. Bridges get
|
||||||
|
// every frame: some are for their radio nodes. Each node takes frames
|
||||||
|
// from its queue when polled, see dhcp_poll() and the mesh bridge callback;
|
||||||
|
// Mongoose does it for the test node
|
||||||
|
static size_t lan_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||||
|
const uint8_t *dst = (const uint8_t *) buf;
|
||||||
|
bool all = dst[0] & 1; // Broadcast, multicast
|
||||||
|
struct bridge *b;
|
||||||
|
if (all || memcmp(dst, s_sim.dhcp.lif.mac, 6) == 0) {
|
||||||
|
lan_put(&s_sim.dhcp.lif, ifp, buf, len);
|
||||||
|
}
|
||||||
|
if (all || memcmp(dst, s_sim.tester.lif.mac, 6) == 0) {
|
||||||
|
lan_put(&s_sim.tester.lif, ifp, buf, len);
|
||||||
|
}
|
||||||
|
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||||
|
lan_put(&b->lif, ifp, buf, len);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lan_init(struct mg_tcpip_if *ifp) {
|
||||||
|
(void) ifp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lan_link(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
(void) ifp, (void) s1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mg_tcpip_driver s_lan_driver = {lan_init, lan_tx, NULL, lan_link};
|
||||||
|
|
||||||
|
// Internet checksum of len bytes
|
||||||
|
static uint16_t ip_csum(const uint8_t *buf, size_t len) {
|
||||||
|
uint32_t sum = 0;
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i + 1 < len; i += 2) sum += (uint32_t) (buf[i] << 8 | buf[i + 1]);
|
||||||
|
if (len & 1) sum += (uint32_t) (buf[len - 1] << 8);
|
||||||
|
while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
|
||||||
|
return (uint16_t) ~sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A node's address, from its MAC 02:00:00:X:Y:Z: 10.X+1.Y.Z, so it never
|
||||||
|
// clashes with the LAN nodes' 10.0.0.x
|
||||||
|
static void node_ip(const uint8_t *mac, uint8_t *ip) {
|
||||||
|
ip[0] = 10, ip[1] = (uint8_t) (mac[3] + 1), ip[2] = mac[4], ip[3] = mac[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHCP server, a frame from its queue: a DISCOVER gets an OFFER, a REQUEST
|
||||||
|
// an ACK. Replies are broadcast. The address comes from the client's MAC
|
||||||
|
static void dhcp_rx(struct simulator *sim, const uint8_t *buf, size_t len) {
|
||||||
|
static const uint8_t opts[] = {53, 1, 0, // Message type, set below
|
||||||
|
1, 4, 255, 0, 0, 0, // Mask
|
||||||
|
3, 4, 10, 0, 0, 1, // Router
|
||||||
|
54, 4, 10, 0, 0, 1, // Server
|
||||||
|
51, 4, 0, 0, 14, 16, // Lease, 3600 s
|
||||||
|
255};
|
||||||
|
uint8_t r[14 + 20 + 8 + 240 + sizeof(opts)], type = 0;
|
||||||
|
const uint8_t *dh, *p, *end = buf + len;
|
||||||
|
size_t ihl, rlen = sizeof(r);
|
||||||
|
if ((buf[0] & 1) == 0) return; // Bcast
|
||||||
|
if (len < 34 || buf[12] != 8 || buf[13] != 0 || buf[23] != 17) return; // UDP
|
||||||
|
ihl = (size_t) (buf[14] & 15) * 4, dh = buf + 14 + ihl + 8; // DHCP
|
||||||
|
if (dh + 240 > end || buf[14 + ihl + 2] != 0 || buf[14 + ihl + 3] != 67) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (p = dh + 240; p + 2 <= end && p[0] != 255; p += p[0] ? p[1] + 2 : 1) {
|
||||||
|
if (p[0] == 53 && p[1] == 1 && p + 3 <= end) type = p[2];
|
||||||
|
}
|
||||||
|
if (type != 1 && type != 3) return; // DISCOVER, REQUEST
|
||||||
|
memset(r, 0, sizeof(r));
|
||||||
|
memset(r, 0xff, 6), memcpy(r + 6, sim->dhcp.lif.mac, 6),
|
||||||
|
r[12] = 8; // Ethernet
|
||||||
|
r[14] = 0x45, r[16] = (uint8_t) ((rlen - 14) >> 8); // IPv4
|
||||||
|
r[17] = (uint8_t) (rlen - 14), r[22] = 64, r[23] = 17;
|
||||||
|
r[26] = 10, r[29] = 1, memset(r + 30, 0xff, 4); // 10.0.0.1 -> bcast
|
||||||
|
r[24] = (uint8_t) (ip_csum(r + 14, 20) >> 8);
|
||||||
|
r[25] = (uint8_t) ip_csum(r + 14, 20);
|
||||||
|
r[35] = 67, r[37] = 68; // UDP, csum 0: none
|
||||||
|
r[38] = (uint8_t) ((rlen - 34) >> 8), r[39] = (uint8_t) (rlen - 34);
|
||||||
|
r[42] = 2, r[43] = 1, r[44] = 6; // DHCP: reply, Ethernet
|
||||||
|
memcpy(r + 46, dh + 4, 4); // xid
|
||||||
|
memcpy(r + 52, dh + 10, 2); // flags
|
||||||
|
node_ip(dh + 28, r + 58); // yiaddr
|
||||||
|
r[62] = 10, r[65] = 1; // siaddr
|
||||||
|
memcpy(r + 70, dh + 28, 16); // chaddr
|
||||||
|
r[278] = 99, r[279] = 130, r[280] = 83, r[281] = 99; // Magic
|
||||||
|
memcpy(r + 282, opts, sizeof(opts));
|
||||||
|
r[284] = type == 1 ? 2 : 5; // OFFER, ACK
|
||||||
|
sim->dhcp.lif.driver->tx(r, rlen, &sim->dhcp.lif);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DHCP node also owns the advertised gateway IP, 10.0.0.1.
|
||||||
|
static bool dhcp_arp(struct simulator *sim, const uint8_t *f, size_t n) {
|
||||||
|
static const uint8_t header[] = {0, 1, 8, 0, 6, 4, 0, 1};
|
||||||
|
static const uint8_t ip[] = {10, 0, 0, 1};
|
||||||
|
uint8_t reply[60];
|
||||||
|
if (n < 42 || f[12] != 8 || f[13] != 6 ||
|
||||||
|
memcmp(f + 14, header, sizeof(header)) != 0 ||
|
||||||
|
memcmp(f + 38, ip, sizeof(ip)) != 0)
|
||||||
|
return false;
|
||||||
|
memset(reply, 0, sizeof(reply));
|
||||||
|
memcpy(reply, f + 6, 6);
|
||||||
|
memcpy(reply + 6, sim->dhcp.lif.mac, 6);
|
||||||
|
memcpy(reply + 12, f + 12, 8);
|
||||||
|
reply[21] = 2;
|
||||||
|
memcpy(reply + 22, sim->dhcp.lif.mac, 6);
|
||||||
|
memcpy(reply + 28, ip, sizeof(ip));
|
||||||
|
memcpy(reply + 32, f + 22, 10);
|
||||||
|
sim->dhcp.lif.driver->tx(reply, sizeof(reply), &sim->dhcp.lif);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHCP server's work: take the frames from its queue
|
||||||
|
static void dhcp_poll(struct simulator *sim) {
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
while ((len = mg_queue_next(&sim->dhcp.lif.recv_queue, &buf)) > 0) {
|
||||||
|
if (!dhcp_arp(sim, (const uint8_t *) buf, len)) {
|
||||||
|
dhcp_rx(sim, (const uint8_t *) buf, len);
|
||||||
|
}
|
||||||
|
mg_queue_del(&sim->dhcp.lif.recv_queue, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request on the way finished: ok, or failed
|
||||||
|
static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
|
||||||
|
double ms = (double) (int64_t) (now - t->start) / 1000;
|
||||||
|
if (ok) {
|
||||||
|
t->ok++, t->sum += ms;
|
||||||
|
if (t->ok == 1 || ms < t->min) t->min = ms;
|
||||||
|
if (ms > t->max) t->max = ms;
|
||||||
|
} else {
|
||||||
|
t->failed++;
|
||||||
|
}
|
||||||
|
if (t->c != NULL) t->c->is_closing = 1, t->c->fn_data = NULL;
|
||||||
|
t->c = NULL;
|
||||||
|
s_sim.work_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP client: GET /, the reply ends the request
|
||||||
|
static void tester_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
|
struct lan_tester *t = (struct lan_tester *) c->fn_data;
|
||||||
|
if (t == NULL) return; // Done with
|
||||||
|
if (ev == MG_EV_CONNECT) {
|
||||||
|
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
||||||
|
} else if (ev == MG_EV_HTTP_MSG) {
|
||||||
|
tester_done(t, s_sim.now, true);
|
||||||
|
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||||
|
tester_done(t, s_sim.now, false);
|
||||||
|
}
|
||||||
|
(void) ev_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test node's work: Mongoose, then requests, one at a time, once the mesh
|
||||||
|
// had time to settle. Cycle through nodes until five seconds before the end.
|
||||||
|
static void tester_poll(struct simulator *sim) {
|
||||||
|
struct radio_node *n;
|
||||||
|
char url[64];
|
||||||
|
mg_mgr_poll(&sim->tester.mgr, 0);
|
||||||
|
if (sim->tester.c != NULL && sim->now - sim->tester.start >
|
||||||
|
(uint64_t) sim->request_timeout_ms * 1000) {
|
||||||
|
tester_done(&sim->tester, sim->now, false);
|
||||||
|
}
|
||||||
|
if (sim->now < sim->tester.wait || sim->tester.c != NULL ||
|
||||||
|
sim->tester.next == NULL || sim->duration <= 5000000U ||
|
||||||
|
sim->now >= sim->duration - 5000000U)
|
||||||
|
return;
|
||||||
|
n = sim->tester.next;
|
||||||
|
sim->tester.next = n->next == NULL ? sim->nodes : n->next;
|
||||||
|
if (n->mgr.ifp == NULL) return;
|
||||||
|
sim->tester.start = sim->now;
|
||||||
|
if (n->mgr.ifp->ip == 0) {
|
||||||
|
// Wait for the next poll rather than endlessly cycling at this time.
|
||||||
|
sim->tester.failed++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->mgr.ifp->ip);
|
||||||
|
if ((sim->tester.c = mg_http_connect(&sim->tester.mgr, url, tester_fn,
|
||||||
|
&sim->tester)) == NULL) {
|
||||||
|
tester_done(&sim->tester, sim->now, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up sim: defaults, LAN nodes
|
||||||
|
static void init(struct simulator *sim) {
|
||||||
|
static const uint8_t dmac[6] = {2, 1, 0, 0, 0, 0}, // DHCP server MAC
|
||||||
|
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test node MAC
|
||||||
|
memset(sim, 0, sizeof(*sim));
|
||||||
|
sim->area.x = sim->area.y = 100;
|
||||||
|
sim->radio_range = 185;
|
||||||
|
sim->loss_random = 1;
|
||||||
|
sim->duration = 600 * 1000000U, sim->tester.wait = 60 * 1000000U;
|
||||||
|
sim->request_timeout_ms = 10000;
|
||||||
|
memcpy(sim->dhcp.lif.mac, dmac, 6), sim->dhcp.lif.driver = &s_lan_driver;
|
||||||
|
mg_queue_init(&sim->dhcp.lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||||
|
LAN_QUEUE_SIZE);
|
||||||
|
memcpy(sim->tester.lif.mac, tmac, 6);
|
||||||
|
sim->tester.lif.ip = MG_IPV4(10, 0, 0, 2),
|
||||||
|
sim->tester.lif.mask = MG_IPV4(255, 0, 0, 0);
|
||||||
|
sim->tester.lif.driver = &s_lan_driver,
|
||||||
|
sim->tester.lif.enable_mac_check = true;
|
||||||
|
sim->tester.lif.recv_queue.size = 128 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The driver queues each submitted frame for transmission over the air.
|
||||||
|
// All nodes hear one channel. CCA and turnaround are separate events:
|
||||||
|
// two idle checks can succeed before either node starts transmitting.
|
||||||
|
// Inter-frame spacing is per sender, not a reservation of the shared air.
|
||||||
|
uint64_t radio_airtime(size_t len) {
|
||||||
|
const struct mg_radio *radio = s_mesh->radio;
|
||||||
|
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||||
|
radio->bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void radio_schedule(struct simulator *sim, struct radio_frame *f,
|
||||||
|
bool busy) {
|
||||||
|
struct radio_port *port = (struct radio_port *) f->from->driver_data;
|
||||||
|
const struct mg_radio *radio = s_mesh->radio;
|
||||||
|
bool checked = f->state == RADIO_CCA;
|
||||||
|
if (s_mesh->scheduled_tx || !radio->csma || (checked && !busy)) {
|
||||||
|
f->state = RADIO_START;
|
||||||
|
f->due = sim->now + (checked ? radio->turnaround_us : 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!checked) {
|
||||||
|
f->backoff = radio->backoff_min;
|
||||||
|
} else if (f->backoff < radio->backoff_max) {
|
||||||
|
f->backoff++;
|
||||||
|
}
|
||||||
|
if (port->random == 0) {
|
||||||
|
size_t i;
|
||||||
|
port->random = (uint32_t) mg_now();
|
||||||
|
for (i = 0; i < 6; i++) {
|
||||||
|
port->random = port->random * 33u + f->from->mac[i];
|
||||||
|
}
|
||||||
|
if (port->random == 0) port->random = 1;
|
||||||
|
}
|
||||||
|
port->random ^= port->random << 13;
|
||||||
|
port->random ^= port->random >> 17;
|
||||||
|
port->random ^= port->random << 5;
|
||||||
|
f->state = RADIO_BACKOFF;
|
||||||
|
f->due = sim->now + (port->random % (1U << f->backoff)) * radio->slot_us;
|
||||||
|
if (f->due > sim->now) port->rx_off_until = f->due;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t radio_send(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
|
||||||
|
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||||
|
struct simulator *sim = port->sim;
|
||||||
|
struct radio_frame *f, **p = &sim->air_head;
|
||||||
|
const struct mg_radio *radio = s_mesh->radio;
|
||||||
|
if (len < radio->header_len || len > sizeof(f->buf) ||
|
||||||
|
len > radio->max_frame_size || port->tx_ready_at > sim->now ||
|
||||||
|
radio->bitrate == 0)
|
||||||
|
return 0;
|
||||||
|
while (*p != NULL) {
|
||||||
|
if ((*p)->from == ifp) return 0;
|
||||||
|
p = &(*p)->next;
|
||||||
|
}
|
||||||
|
if ((f = (struct radio_frame *) calloc(1, sizeof(*f))) == NULL) return 0;
|
||||||
|
f->from = ifp, f->len = len;
|
||||||
|
memcpy(f->buf, buf, len);
|
||||||
|
*p = f;
|
||||||
|
sim->work_pending = true;
|
||||||
|
radio_schedule(sim, f, false);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void radio_receive(struct radio_port *port,
|
||||||
|
const struct radio_frame *f) {
|
||||||
|
char *buf;
|
||||||
|
if (mg_queue_book(&port->rx, &buf, f->len) >= f->len) {
|
||||||
|
memcpy(buf, f->buf, f->len);
|
||||||
|
mg_queue_add(&port->rx, f->len);
|
||||||
|
port->sim->work_pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test range and sample independent interference loss for this reception.
|
||||||
|
static bool visible(struct simulator *sim, const struct pos *a,
|
||||||
|
const struct pos *b) {
|
||||||
|
double dx = a->x - b->x, dy = a->y - b->y, dz = a->z - b->z;
|
||||||
|
double range = sim->radio_range;
|
||||||
|
if (dx * dx + dy * dy + dz * dz > range * range) return false;
|
||||||
|
if (sim->radio_loss == 0) return true;
|
||||||
|
sim->loss_random ^= sim->loss_random << 13;
|
||||||
|
sim->loss_random ^= sim->loss_random >> 17;
|
||||||
|
sim->loss_random ^= sim->loss_random << 5;
|
||||||
|
return (double) sim->loss_random * (100.0 / 4294967296.0) >= sim->radio_loss;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void radio_deliver_to(struct radio_port *port,
|
||||||
|
const struct radio_port *from,
|
||||||
|
const struct radio_frame *f, uint16_t dst) {
|
||||||
|
bool all = dst == 0xffffu;
|
||||||
|
if (port == from || (!all && port->address != dst)) return;
|
||||||
|
if (!visible(port->sim, from->pos, port->pos)) {
|
||||||
|
port->sim->air_lost++;
|
||||||
|
} else if (port->tx_until > f->start) {
|
||||||
|
if (!all) port->sim->air_lost++;
|
||||||
|
} else if (!f->collided && port->rx_off_until <= f->start) {
|
||||||
|
radio_receive(port, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void radio_deliver(struct simulator *sim, const struct radio_frame *f) {
|
||||||
|
const struct mg_radio *radio = s_mesh->radio;
|
||||||
|
const struct radio_port *from = f->from->driver_data;
|
||||||
|
uint16_t dst, src;
|
||||||
|
struct bridge *b;
|
||||||
|
struct radio_node *n;
|
||||||
|
if (radio->parse_hdr(radio, f->buf, f->len, &dst, &src) == 0) return;
|
||||||
|
for (b = sim->bridges; b != NULL; b = b->next)
|
||||||
|
radio_deliver_to(&b->radio, from, f, dst);
|
||||||
|
for (n = sim->nodes; n != NULL; n = n->next)
|
||||||
|
radio_deliver_to(&n->radio, from, f, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process ends, then all channel checks, then starts at this timestamp.
|
||||||
|
// Checks therefore see the same air state regardless of list order.
|
||||||
|
static void radio_poll(struct simulator *sim) {
|
||||||
|
struct radio_frame **p = &sim->air_head, *f, *g;
|
||||||
|
while ((f = *p) != NULL) {
|
||||||
|
if (f->state != RADIO_END || f->due > sim->now) {
|
||||||
|
p = &f->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (f->collided) {
|
||||||
|
sim->air_collisions++;
|
||||||
|
}
|
||||||
|
radio_deliver(sim, f);
|
||||||
|
*p = f->next;
|
||||||
|
free(f);
|
||||||
|
}
|
||||||
|
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||||
|
if (f->state == RADIO_BACKOFF && f->due <= sim->now) {
|
||||||
|
f->state = RADIO_CCA;
|
||||||
|
f->due = sim->now + s_mesh->radio->cca_us;
|
||||||
|
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||||
|
if (f->state != RADIO_CCA || f->due > sim->now) continue;
|
||||||
|
radio_schedule(sim, f,
|
||||||
|
sim->air_busy_until > sim->now - s_mesh->radio->cca_us);
|
||||||
|
if (f->state == RADIO_START) {
|
||||||
|
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||||
|
struct radio_port *port = f->from->driver_data;
|
||||||
|
if (f->state != RADIO_START || f->due > sim->now) continue;
|
||||||
|
f->state = RADIO_END;
|
||||||
|
f->start = sim->now;
|
||||||
|
f->due = sim->now + radio_airtime(f->len);
|
||||||
|
port->rx_off_until = port->tx_until = f->due;
|
||||||
|
port->tx_ready_at = f->due + s_mesh->radio->ifs_us;
|
||||||
|
for (g = sim->air_head; g != NULL; g = g->next) {
|
||||||
|
if (g != f && g->state == RADIO_END && g->due > sim->now) {
|
||||||
|
f->collided = g->collided = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f->due > sim->air_busy_until) sim->air_busy_until = f->due;
|
||||||
|
sim->air_sent++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radio events keep microsecond precision between periodic timer polls.
|
||||||
|
static uint64_t radio_next(const struct simulator *sim, uint64_t next) {
|
||||||
|
const struct radio_frame *f;
|
||||||
|
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||||
|
if (f->due < next) next = f->due;
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t radio_receive_frame(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||||
|
char *src;
|
||||||
|
size_t len;
|
||||||
|
while ((len = mg_queue_next(&port->rx, &src)) > 0) {
|
||||||
|
if (len <= cap) memcpy(buf, src, len);
|
||||||
|
mg_queue_del(&port->rx, len);
|
||||||
|
if (len <= cap) {
|
||||||
|
if (mg_queue_next(&port->rx, &src) > 0) port->sim->work_pending = true;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||||
|
struct radio_port *port = ifp->driver_data;
|
||||||
|
return port->mesh->strategy->tx(port->mesh, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t radio_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||||
|
struct radio_port *port = ifp->driver_data;
|
||||||
|
size_t len = port->mesh->rx_len;
|
||||||
|
if (len > cap) return 0;
|
||||||
|
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||||
|
memcpy(buf, ifp->recv_queue.buf, len);
|
||||||
|
port->mesh->rx_len = 0;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool radio_driver_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||||
|
struct radio_port *port = ifp->driver_data;
|
||||||
|
uint8_t frame[128];
|
||||||
|
size_t len;
|
||||||
|
(void) s1;
|
||||||
|
port->mesh->now = mg_micros();
|
||||||
|
while (port->mesh->rx_len == 0 &&
|
||||||
|
(len = radio_receive_frame(frame, sizeof(frame), ifp)) != 0)
|
||||||
|
port->mesh->strategy->rx(port->mesh, frame, len);
|
||||||
|
s_mesh->tick(port->mesh, mg_micros());
|
||||||
|
return port->mesh->joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool radio_init(struct mg_tcpip_if *ifp) {
|
||||||
|
struct radio_port *port = ifp->driver_data;
|
||||||
|
char *buf = malloc(LAN_QUEUE_SIZE);
|
||||||
|
if (buf == NULL) return false;
|
||||||
|
mg_queue_init(&port->rx, buf, LAN_QUEUE_SIZE);
|
||||||
|
port->mesh = calloc(1, sizeof(*port->mesh));
|
||||||
|
if (port->mesh == NULL ||
|
||||||
|
!s_mesh->init(port->mesh, ifp, port->lan, port->address)) {
|
||||||
|
free(port->mesh);
|
||||||
|
port->mesh = NULL;
|
||||||
|
free(buf);
|
||||||
|
port->rx.buf = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
port->mesh->strategy = s_mesh;
|
||||||
|
port->mesh->tx = radio_send;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mg_tcpip_driver s_radio_driver = {
|
||||||
|
radio_init, radio_tx, radio_rx, radio_driver_poll};
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_SIM_RADIO_INIT(manager, interface) \
|
||||||
|
do { \
|
||||||
|
struct mg_tcpip_if *mif_ = (interface); \
|
||||||
|
mif_->mgr = (manager); \
|
||||||
|
mg_tcpip_init((manager), mif_); \
|
||||||
|
MG_INFO(("Driver: sim_radio, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define MG_TCPIP_DRIVER_SIM_LAN_INIT(manager, interface) \
|
||||||
|
do { \
|
||||||
|
struct mg_tcpip_if *mif_ = (interface); \
|
||||||
|
mg_tcpip_init((manager), mif_); \
|
||||||
|
MG_INFO(("Driver: sim_lan, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// A node's HTTP server: the test node requests GET /
|
||||||
|
static void node_http_ev_handler(struct mg_connection *c, int ev,
|
||||||
|
void *ev_data) {
|
||||||
|
if (ev == MG_EV_HTTP_MSG) {
|
||||||
|
mg_http_reply(c, 200, "", "Hi from Mongoose, my MAC is %M\n", mg_print_mac,
|
||||||
|
c->fn_data);
|
||||||
|
}
|
||||||
|
(void) ev_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start each node's Mongoose: its radio interface, no IP address, so DHCP,
|
||||||
|
// and an HTTP server
|
||||||
|
static void nodes_init(struct simulator *sim) {
|
||||||
|
struct radio_node *n;
|
||||||
|
for (n = sim->nodes; n != NULL; n = n->next) {
|
||||||
|
n->rif.driver = &s_radio_driver;
|
||||||
|
n->rif.driver_data = &n->radio;
|
||||||
|
n->radio.sim = sim;
|
||||||
|
n->radio.pos = &n->pos;
|
||||||
|
mg_mgr_init(&n->mgr);
|
||||||
|
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&n->mgr, &n->rif);
|
||||||
|
if (n->mgr.ifp != NULL) {
|
||||||
|
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
|
||||||
|
n->mgr.ifp->mac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start each bridge: its LAN and radio drivers, with no IP stack.
|
||||||
|
static void bridges_init(struct simulator *sim) {
|
||||||
|
struct bridge *b;
|
||||||
|
for (b = sim->bridges; b != NULL; b = b->next) {
|
||||||
|
b->lif.driver = &s_lan_driver, b->rif.driver = &s_radio_driver;
|
||||||
|
b->rif.driver_data = &b->radio;
|
||||||
|
b->radio.sim = sim;
|
||||||
|
b->radio.pos = &b->pos;
|
||||||
|
mg_mgr_init(&b->mgr);
|
||||||
|
b->radio.lan = &b->lif;
|
||||||
|
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&b->mgr, &b->rif);
|
||||||
|
mg_queue_init(&b->lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||||
|
LAN_QUEUE_SIZE);
|
||||||
|
b->lif.driver->init(&b->lif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
uint64_t poll_at = 0, mesh_at = (uint64_t) -1;
|
||||||
|
char *pending;
|
||||||
|
clock_t started; // CPU time, to show how fast the simulation runs
|
||||||
|
int i;
|
||||||
|
|
||||||
|
init(&s_sim);
|
||||||
|
for (i = 1; i < argc; i++) {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
usage(&s_sim, argv[0]);
|
||||||
|
} else if (strcmp(argv[i], "-area") == 0) {
|
||||||
|
if (!parse_pos(argv[++i], &s_sim.area)) usage(&s_sim, argv[0]);
|
||||||
|
} else if (strcmp(argv[i], "-bridge") == 0) {
|
||||||
|
if (!add_bridge(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
|
||||||
|
} else if (strcmp(argv[i], "-node") == 0) {
|
||||||
|
if (!add_node(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
|
||||||
|
} else if (strcmp(argv[i], "-t") == 0) {
|
||||||
|
s_sim.duration = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||||
|
} else if (strcmp(argv[i], "-w") == 0) {
|
||||||
|
s_sim.tester.wait = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||||
|
} else if (strcmp(argv[i], "-range") == 0 ||
|
||||||
|
strcmp(argv[i], "-loss") == 0) {
|
||||||
|
bool loss = strcmp(argv[i], "-loss") == 0;
|
||||||
|
const char *arg = argv[++i];
|
||||||
|
char *end;
|
||||||
|
double value = strtod(arg, &end);
|
||||||
|
if (end == arg || *end != '\0' ||
|
||||||
|
!(value >= 0 && value <= (loss ? 100 : 1e100)))
|
||||||
|
usage(&s_sim, argv[0]);
|
||||||
|
if (loss)
|
||||||
|
s_sim.radio_loss = value;
|
||||||
|
else
|
||||||
|
s_sim.radio_range = value;
|
||||||
|
} else if (strcmp(argv[i], "-m") == 0) {
|
||||||
|
const char *name = argv[++i];
|
||||||
|
if (strcmp(name, mg_mesh_a.name) == 0)
|
||||||
|
s_mesh = &mg_mesh_a;
|
||||||
|
else if (strcmp(name, mg_mesh_default.name) == 0)
|
||||||
|
s_mesh = &mg_mesh_default;
|
||||||
|
else
|
||||||
|
usage(&s_sim, argv[0]);
|
||||||
|
} else {
|
||||||
|
usage(&s_sim, argv[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Area %gx%gx%g m, radio %s, mesh %s, bridges: %d, nodes: %d\n",
|
||||||
|
s_sim.area.x, s_sim.area.y, s_sim.area.z, s_mesh->radio->name,
|
||||||
|
s_mesh->name, s_sim.num_bridges, s_sim.num_nodes);
|
||||||
|
check_area(&s_sim);
|
||||||
|
if (s_mesh->single_bridge && s_sim.num_bridges != 1) {
|
||||||
|
fprintf(stderr, "%s requires exactly one bridge\n", s_mesh->name);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mg_log_set(MG_LL_INFO);
|
||||||
|
|
||||||
|
mg_mgr_init(&s_sim.tester.mgr);
|
||||||
|
MG_TCPIP_DRIVER_SIM_LAN_INIT(&s_sim.tester.mgr, &s_sim.tester.lif);
|
||||||
|
s_sim.tester.next = s_sim.nodes;
|
||||||
|
bridges_init(&s_sim);
|
||||||
|
nodes_init(&s_sim);
|
||||||
|
|
||||||
|
// Drain packet processing at the current time. Only idle timer polling
|
||||||
|
// waits for STEP_MICROSECONDS; radio deadlines remain exact.
|
||||||
|
started = clock();
|
||||||
|
while (s_sim.now < s_sim.duration) {
|
||||||
|
radio_poll(&s_sim);
|
||||||
|
if (s_sim.now == poll_at || s_sim.now == mesh_at || s_sim.work_pending) {
|
||||||
|
do {
|
||||||
|
s_sim.work_pending = false;
|
||||||
|
dhcp_poll(&s_sim);
|
||||||
|
tester_poll(&s_sim);
|
||||||
|
{
|
||||||
|
struct bridge *b;
|
||||||
|
struct radio_node *n;
|
||||||
|
mesh_at = (uint64_t) -1;
|
||||||
|
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||||
|
mg_mgr_poll(&b->mgr, 0);
|
||||||
|
if (b->radio.mesh->next_tick < mesh_at)
|
||||||
|
mesh_at = b->radio.mesh->next_tick;
|
||||||
|
}
|
||||||
|
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||||
|
mg_mgr_poll(&n->mgr, 0);
|
||||||
|
if (n->radio.mesh->next_tick < mesh_at)
|
||||||
|
mesh_at = n->radio.mesh->next_tick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mg_queue_next(&s_sim.tester.lif.recv_queue, &pending) > 0) {
|
||||||
|
s_sim.work_pending = true;
|
||||||
|
}
|
||||||
|
} while (s_sim.work_pending);
|
||||||
|
if (s_sim.now == poll_at) poll_at += STEP_MICROSECONDS;
|
||||||
|
}
|
||||||
|
s_sim.now = radio_next(&s_sim, mesh_at < poll_at ? mesh_at : poll_at);
|
||||||
|
if (s_sim.now > s_sim.duration) s_sim.now = s_sim.duration;
|
||||||
|
}
|
||||||
|
printf("Requests: %d ok, %d failed, of %d nodes", s_sim.tester.ok,
|
||||||
|
s_sim.tester.failed, s_sim.num_nodes);
|
||||||
|
if (s_sim.tester.ok > 0) {
|
||||||
|
printf("; ms: min %.1f, mean %.1f, max %.1f", s_sim.tester.min,
|
||||||
|
s_sim.tester.sum / s_sim.tester.ok, s_sim.tester.max);
|
||||||
|
}
|
||||||
|
printf("\nSimulated %g s in %.3f s", (double) (int64_t) s_sim.now / 1e6,
|
||||||
|
(double) (clock() - started) / CLOCKS_PER_SEC);
|
||||||
|
if (s_sim.nodes != NULL) {
|
||||||
|
const struct radio_node *n;
|
||||||
|
size_t min = (size_t) -1, max = 0;
|
||||||
|
double total = 0;
|
||||||
|
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||||
|
size_t peak = n->radio.mesh->ram_peak;
|
||||||
|
if (peak < min) min = peak;
|
||||||
|
if (peak > max) max = peak;
|
||||||
|
total += (double) peak;
|
||||||
|
}
|
||||||
|
printf("; peak mesh RAM per node (bytes): min %zu, max %zu, avg %.1f", min,
|
||||||
|
max, total / s_sim.num_nodes);
|
||||||
|
}
|
||||||
|
putchar('\n');
|
||||||
|
|
||||||
|
while (s_sim.air_head != NULL) {
|
||||||
|
struct radio_frame *f = s_sim.air_head;
|
||||||
|
s_sim.air_head = f->next;
|
||||||
|
free(f);
|
||||||
|
}
|
||||||
|
printf("Radio: %lu sent, %lu collided, %lu lost\n",
|
||||||
|
s_sim.air_sent, s_sim.air_collisions, s_sim.air_lost);
|
||||||
|
{
|
||||||
|
struct radio_node *n;
|
||||||
|
int count = 0, joined = 0;
|
||||||
|
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||||
|
if (n->mgr.ifp != NULL && n->mgr.ifp->ip != 0) count++;
|
||||||
|
if (n->radio.mesh->joined) joined++;
|
||||||
|
}
|
||||||
|
printf("Nodes with IP: %d of %d, joined: %d of %d\n", count,
|
||||||
|
s_sim.num_nodes, joined, s_sim.num_nodes);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct bridge *b;
|
||||||
|
struct radio_node *n;
|
||||||
|
mg_log_set(MG_LL_NONE);
|
||||||
|
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||||
|
mg_mgr_free(&b->mgr);
|
||||||
|
s_mesh->free(b->radio.mesh);
|
||||||
|
free(b->radio.mesh);
|
||||||
|
free(b->radio.rx.buf);
|
||||||
|
}
|
||||||
|
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||||
|
mg_mgr_free(&n->mgr);
|
||||||
|
s_mesh->free(n->radio.mesh);
|
||||||
|
free(n->radio.mesh);
|
||||||
|
free(n->radio.rx.buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) 2026 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MG_ENABLE_TCPIP 1
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
extern const struct mg_mesh mg_mesh_a;
|
||||||
|
|
||||||
|
// A position in metres
|
||||||
|
struct pos {
|
||||||
|
double x, y, z; // Coordinates
|
||||||
|
};
|
||||||
|
|
||||||
|
struct simulator;
|
||||||
|
|
||||||
|
// Private state of the simulated radio driver.
|
||||||
|
struct radio_port {
|
||||||
|
struct simulator *sim;
|
||||||
|
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a node
|
||||||
|
const struct pos *pos; // Location of the radio
|
||||||
|
uint64_t tx_until; // Latest transmission end, us
|
||||||
|
uint64_t tx_ready_at; // Inter-frame wait ends, us
|
||||||
|
uint64_t rx_off_until; // Latest backoff/sensing/turnaround/TX end, us
|
||||||
|
struct mg_mesh_node *mesh; // Mesh state owned by the driver
|
||||||
|
struct mg_queue rx;
|
||||||
|
uint32_t random; // Driver backoff PRNG
|
||||||
|
uint16_t address; // Preassigned radio address
|
||||||
|
};
|
||||||
|
|
||||||
|
// A bridge: forwards frames between the LAN and the radio, no IP stack
|
||||||
|
struct bridge {
|
||||||
|
struct bridge *next; // Next in simulator::bridges
|
||||||
|
struct pos pos; // Where it is
|
||||||
|
struct mg_mgr mgr; // Polls the raw radio driver
|
||||||
|
struct mg_tcpip_if lif; // LAN side: LAN driver, gets every LAN frame
|
||||||
|
struct mg_tcpip_if rif; // Radio side: radio driver, gets every frame
|
||||||
|
struct radio_port radio;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A radio node: Mongoose on a radio interface
|
||||||
|
struct radio_node {
|
||||||
|
struct radio_node *next; // Next in simulator::nodes
|
||||||
|
struct pos pos; // Where it is
|
||||||
|
struct mg_mgr mgr; // Mongoose event manager
|
||||||
|
struct mg_tcpip_if rif; // Radio interface. rif.ip from DHCP, 0: none yet
|
||||||
|
struct radio_port radio;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct simulator;
|
||||||
|
|
||||||
|
// LAN node: a DHCP server, 10.0.0.1. Gives each client an address made
|
||||||
|
// from its MAC, see node_ip()
|
||||||
|
struct lan_dhcp_server {
|
||||||
|
struct mg_tcpip_if lif; // Its LAN interface, no Mongoose on it
|
||||||
|
};
|
||||||
|
|
||||||
|
// LAN node: the test node, 10.0.0.2, a Mongoose instance. Waits for
|
||||||
|
// the mesh to settle, then cycles GET / through nodes until 5 s before the end
|
||||||
|
struct lan_tester {
|
||||||
|
struct mg_mgr mgr; // Its Mongoose
|
||||||
|
struct mg_tcpip_if lif; // Its LAN interface
|
||||||
|
uint64_t wait; // Mesh settle time: start requests then, us
|
||||||
|
struct radio_node *next; // Node to request next, wraps at the end
|
||||||
|
struct mg_connection *c; // Request on the way, NULL: none
|
||||||
|
uint64_t start; // When it started, us
|
||||||
|
int ok, failed; // Requests done, by result
|
||||||
|
double min, max, sum; // Times of successful requests, ms
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { RADIO_BACKOFF, RADIO_CCA, RADIO_START, RADIO_END };
|
||||||
|
|
||||||
|
// One radio frame waiting on the simulated radio channel
|
||||||
|
struct radio_frame {
|
||||||
|
struct radio_frame *next;
|
||||||
|
struct mg_tcpip_if *from;
|
||||||
|
uint64_t due; // Next radio event, us
|
||||||
|
uint64_t start; // Transmission start, for receive overlap checks
|
||||||
|
uint8_t state, backoff;
|
||||||
|
bool collided;
|
||||||
|
size_t len;
|
||||||
|
uint8_t buf[1540];
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t radio_airtime(size_t len);
|
||||||
|
|
||||||
|
// Simulator state
|
||||||
|
struct simulator {
|
||||||
|
struct radio_frame *air_head; // Pending and active frames, singly linked
|
||||||
|
uint64_t air_busy_until; // Last transmission end, for channel sensing
|
||||||
|
unsigned long air_sent, air_collisions;
|
||||||
|
unsigned long air_lost; // Receptions lost to range, random loss or TX overlap
|
||||||
|
double radio_range; // Maximum reception distance, metres
|
||||||
|
double radio_loss; // In-range reception loss probability, percent
|
||||||
|
uint32_t loss_random; // Reception-loss PRNG state
|
||||||
|
bool work_pending; // Drain immediate work before advancing time
|
||||||
|
struct pos area; // A box: all positions inside
|
||||||
|
struct bridge *bridges; // Bridges, a list
|
||||||
|
struct radio_node *nodes; // Radio nodes, a list
|
||||||
|
int num_bridges, num_nodes; // How many
|
||||||
|
uint64_t now; // Virtual time, us since the start
|
||||||
|
uint64_t duration; // Run this long, us
|
||||||
|
uint32_t last_mac; // Low 3 bytes of the last MAC given
|
||||||
|
int request_timeout_ms; // A request that takes longer failed
|
||||||
|
struct lan_dhcp_server dhcp; // DHCP server on the LAN
|
||||||
|
struct lan_tester tester; // Test node on the LAN
|
||||||
|
};
|
||||||
+18
-8
@@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
#include "mip_x_test.c"
|
#include "mip_x_test.c"
|
||||||
|
|
||||||
|
// drop TCP in data phase, testing retransmit queue (RTX). It also validates
|
||||||
|
// duplicate-peer-data handling and “re-ACK the current sequence”.
|
||||||
|
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp, const char *dir);
|
||||||
|
|
||||||
struct port_driver_data {
|
struct port_driver_data {
|
||||||
int sockfd;
|
int sockfd;
|
||||||
struct sockaddr_in port_addr;
|
struct sockaddr_in port_addr;
|
||||||
@@ -16,6 +21,7 @@ static size_t sock_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
|||||||
ssize_t received = recv(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
ssize_t received = recv(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
||||||
usleep(1); // This is to avoid 100% CPU
|
usleep(1); // This is to avoid 100% CPU
|
||||||
if (received < 0) return 0;
|
if (received < 0) return 0;
|
||||||
|
if (drop_tcp_segment(buf, (size_t) received, ifp, "RX")) return 0;
|
||||||
return (size_t) received;
|
return (size_t) received;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,15 +43,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
|
|||||||
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp, const char *dir) {
|
||||||
uint32_t random;
|
uint32_t random;
|
||||||
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
|
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
|
||||||
mg_random(&random, sizeof(random));
|
// Introduce a 10% segment loss
|
||||||
if (random % 10 < 3) {
|
mg_random(&random, sizeof(random));
|
||||||
MG_DEBUG(("dropping TCP segment"));
|
if (random % 10 != 0) return false;
|
||||||
return len;
|
MG_DEBUG(("dropping TCP %s segment", dir));
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||||
|
if (drop_tcp_segment(buf, len, ifp, "TX")) return len;
|
||||||
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
MG_ERROR(("sock_tx failed: %d", errno));
|
MG_ERROR(("sock_tx failed: %d", errno));
|
||||||
|
|||||||
+17
-8
@@ -13,12 +13,17 @@
|
|||||||
|
|
||||||
#include "mip_x_test.c"
|
#include "mip_x_test.c"
|
||||||
|
|
||||||
|
// drop TCP in data phase, testing retransmit queue (RTX). It also validates
|
||||||
|
// duplicate-peer-data handling and “re-ACK the current sequence”.
|
||||||
|
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp, const char *dir);
|
||||||
|
|
||||||
// MIP TUNTAP driver
|
// MIP TUNTAP driver
|
||||||
static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||||
ssize_t received = read(*(int *) ifp->driver_data, buf, len);
|
ssize_t received = read(*(int *) ifp->driver_data, buf, len);
|
||||||
usleep(1); // This is to avoid 100% CPU
|
usleep(1); // This is to avoid 100% CPU
|
||||||
if (received < 0) return 0;
|
if (received < 0) return 0;
|
||||||
|
if (drop_tcp_segment(buf, (size_t) received, ifp, "RX")) return 0;
|
||||||
return (size_t) received;
|
return (size_t) received;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,15 +45,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
|
|||||||
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||||
|
struct mg_tcpip_if *ifp, const char *dir) {
|
||||||
uint32_t random;
|
uint32_t random;
|
||||||
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
|
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
|
||||||
mg_random(&random, sizeof(random));
|
// Introduce a 10% segment loss
|
||||||
if (random % 10 < 3) {
|
mg_random(&random, sizeof(random));
|
||||||
MG_DEBUG(("dropping TCP segment"));
|
if (random % 10 != 0) return false;
|
||||||
return len;
|
MG_DEBUG(("dropping TCP %s segment", dir));
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||||
|
if (drop_tcp_segment(buf, len, ifp, "TX")) return len;
|
||||||
ssize_t res = write(*(int *) ifp->driver_data, buf, len);
|
ssize_t res = write(*(int *) ifp->driver_data, buf, len);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
MG_ERROR(("tap_tx failed: %d", errno));
|
MG_ERROR(("tap_tx failed: %d", errno));
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ static void test_statechange(void) {
|
|||||||
onstatechange(&iface);
|
onstatechange(&iface);
|
||||||
ASSERT(executed == true);
|
ASSERT(executed == true);
|
||||||
executed = false;
|
executed = false;
|
||||||
|
iface.enable_dhcp_client = true; // DHCP REQUEST unanswered: start over
|
||||||
|
iface.state = MG_TCPIP_STATE_REQ, iface.lease_expire = 0;
|
||||||
|
mg_ip_poll(&iface, true);
|
||||||
|
ASSERT(iface.state == MG_TCPIP_STATE_UP);
|
||||||
}
|
}
|
||||||
#if MG_ENABLE_IPV6
|
#if MG_ENABLE_IPV6
|
||||||
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
|
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
|
||||||
|
|||||||
+80
-56
@@ -76,6 +76,9 @@ static int s_num_tests = 0;
|
|||||||
static bool s_error = false;
|
static bool s_error = false;
|
||||||
|
|
||||||
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
|
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
|
||||||
|
#define RTX_ATTEMPTS(mgr) ((mgr)->ifp->enable_tcp_retransmit ? 3 : 1)
|
||||||
|
#define SERVER_POLL_LIMIT(mgr) \
|
||||||
|
((mgr)->ifp->enable_tcp_retransmit ? 18000 : 300)
|
||||||
|
|
||||||
#ifdef NO_ABORT
|
#ifdef NO_ABORT
|
||||||
static int s_abort = 0;
|
static int s_abort = 0;
|
||||||
@@ -151,13 +154,15 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
struct fetch_data {
|
struct fetch_data {
|
||||||
char *buf;
|
char *buf;
|
||||||
const char *url;
|
const char *url;
|
||||||
int code, closed;
|
int code;
|
||||||
|
bool connected, received, closed;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
struct fetch_data *fd = (struct fetch_data *) c->fn_data;
|
struct fetch_data *fd = (struct fetch_data *) c->fn_data;
|
||||||
if (ev == MG_EV_CONNECT) {
|
if (ev == MG_EV_CONNECT) {
|
||||||
MG_DEBUG(("CONNECT"));
|
MG_DEBUG(("CONNECT"));
|
||||||
|
fd->connected = true;
|
||||||
if (mg_url_is_ssl(fd->url)) {
|
if (mg_url_is_ssl(fd->url)) {
|
||||||
struct mg_tls_opts opts;
|
struct mg_tls_opts opts;
|
||||||
memset(&opts, 0, sizeof(opts)); // read CA from packed_fs
|
memset(&opts, 0, sizeof(opts)); // read CA from packed_fs
|
||||||
@@ -173,10 +178,10 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
}
|
}
|
||||||
} else if (ev == MG_EV_HTTP_MSG) {
|
} else if (ev == MG_EV_HTTP_MSG) {
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
|
fd->received = true;
|
||||||
snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len,
|
snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len,
|
||||||
hm->message.buf);
|
hm->message.buf);
|
||||||
fd->code = atoi(hm->uri.buf);
|
fd->code = atoi(hm->uri.buf);
|
||||||
fd->closed = 1;
|
|
||||||
c->is_closing = 1;
|
c->is_closing = 1;
|
||||||
MG_DEBUG(("CODE: %d, MSG: %.*s", fd->code, (int) hm->message.len,
|
MG_DEBUG(("CODE: %d, MSG: %.*s", fd->code, (int) hm->message.len,
|
||||||
hm->message.buf));
|
hm->message.buf));
|
||||||
@@ -192,31 +197,38 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
|
|
||||||
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
||||||
const char *fmt, ...) {
|
const char *fmt, ...) {
|
||||||
struct fetch_data fd = {buf, url, 0, 0};
|
int i, attempt;
|
||||||
int i;
|
|
||||||
struct mg_connection *c = NULL;
|
struct mg_connection *c = NULL;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
|
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||||
// timestamp (from an ancient call in other test)
|
struct fetch_data fd = {buf, url, 0, false, false, false};
|
||||||
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
|
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
|
||||||
c = mg_http_connect(mgr, url, fcb, &fd);
|
// timestamp (from an ancient call in other test)
|
||||||
ASSERT(c != NULL);
|
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
|
||||||
va_start(ap, fmt);
|
c = mg_http_connect(mgr, url, fcb, &fd);
|
||||||
mg_vprintf(c, fmt, &ap);
|
ASSERT(c != NULL);
|
||||||
va_end(ap);
|
va_start(ap, fmt);
|
||||||
buf[0] = '\0';
|
mg_vprintf(c, fmt, &ap);
|
||||||
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
|
va_end(ap);
|
||||||
// process buffered records when no more frames are coming in
|
buf[0] = '\0';
|
||||||
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
|
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
|
||||||
|
// process buffered records when no more frames are coming in
|
||||||
|
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
|
||||||
|
mg_mgr_poll(mgr, 0);
|
||||||
|
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
|
||||||
|
// allow enough loops to get the ARP response, otherwise,
|
||||||
|
// given enough traffic, the timer expires before we get a
|
||||||
|
// chance to see the response
|
||||||
|
}
|
||||||
|
if (!fd.closed) // failed, force closure and conn removal, and loop end
|
||||||
|
((struct connstate *) (c + 1))->is_closing = 1;
|
||||||
mg_mgr_poll(mgr, 0);
|
mg_mgr_poll(mgr, 0);
|
||||||
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
|
if (!(fd.connected && fd.closed && !fd.received) ||
|
||||||
// allow enough loops to get the ARP response, otherwise,
|
attempt + 1 == RTX_ATTEMPTS(mgr))
|
||||||
// given enough traffic, the timer expires before we get a
|
return fd.code;
|
||||||
// chance to see the response
|
MG_INFO(("HTTP retry %d", attempt + 2));
|
||||||
}
|
}
|
||||||
if (!fd.closed) c->is_closing = 1;
|
return 0;
|
||||||
mg_mgr_poll(mgr, 0);
|
|
||||||
return fd.code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_http_client(struct mg_mgr *mgr) {
|
static void test_http_client(struct mg_mgr *mgr) {
|
||||||
@@ -256,13 +268,14 @@ static char s_topic[16];
|
|||||||
|
|
||||||
struct mqtt_data {
|
struct mqtt_data {
|
||||||
char *url;
|
char *url;
|
||||||
bool passed;
|
bool connected, opened, passed;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
struct mqtt_data *data = (struct mqtt_data *) c->fn_data;
|
struct mqtt_data *data = (struct mqtt_data *) c->fn_data;
|
||||||
if (ev == MG_EV_CONNECT) {
|
if (ev == MG_EV_CONNECT) {
|
||||||
MG_DEBUG(("CONNECT"));
|
MG_DEBUG(("CONNECT"));
|
||||||
|
data->connected = true;
|
||||||
#if MG_TLS
|
#if MG_TLS
|
||||||
struct mg_tls_opts opts;
|
struct mg_tls_opts opts;
|
||||||
memset(&opts, 0, sizeof(opts));
|
memset(&opts, 0, sizeof(opts));
|
||||||
@@ -277,6 +290,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
#endif
|
#endif
|
||||||
} else if (ev == MG_EV_MQTT_OPEN) {
|
} else if (ev == MG_EV_MQTT_OPEN) {
|
||||||
MG_DEBUG(("MQTT CONNECT"));
|
MG_DEBUG(("MQTT CONNECT"));
|
||||||
|
data->opened = true;
|
||||||
struct mg_mqtt_opts sub_opts;
|
struct mg_mqtt_opts sub_opts;
|
||||||
memset(&sub_opts, 0, sizeof(sub_opts));
|
memset(&sub_opts, 0, sizeof(sub_opts));
|
||||||
sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic)));
|
sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic)));
|
||||||
@@ -326,12 +340,30 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool mqtt_attempt(struct mg_mgr *mgr, struct mqtt_data *data,
|
||||||
|
struct mg_mqtt_opts *opts) {
|
||||||
|
int i;
|
||||||
|
data->connected = data->opened = data->passed = false;
|
||||||
|
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
||||||
|
mg_mgr_poll(mgr, 0);
|
||||||
|
s_conn = mg_mqtt_connect(mgr, data->url, opts, mqtt_fn, data);
|
||||||
|
ASSERT(s_conn != NULL);
|
||||||
|
for (i = 0; i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
||||||
|
i++) {
|
||||||
|
mg_mgr_poll(mgr, 0);
|
||||||
|
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
||||||
|
}
|
||||||
|
mg_mgr_poll(mgr, 0);
|
||||||
|
return data->connected && !data->opened && s_conn == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
||||||
struct mqtt_data data;
|
struct mqtt_data data;
|
||||||
struct mg_mqtt_opts opts;
|
struct mg_mqtt_opts opts;
|
||||||
|
bool retry;
|
||||||
|
int attempt;
|
||||||
memset(&opts, 0, sizeof(opts));
|
memset(&opts, 0, sizeof(opts));
|
||||||
opts.clean = true, opts.version = 4;
|
opts.clean = true, opts.version = 4;
|
||||||
data.passed = false;
|
|
||||||
#if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN
|
#if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN
|
||||||
if (host_ip == NULL) {
|
if (host_ip == NULL) {
|
||||||
printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n");
|
printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n");
|
||||||
@@ -359,15 +391,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
|||||||
data.url = strdup(MQTT_URL);
|
data.url = strdup(MQTT_URL);
|
||||||
#endif
|
#endif
|
||||||
#endif // MG_TLS
|
#endif // MG_TLS
|
||||||
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||||
mg_mgr_poll(mgr, 0);
|
retry = mqtt_attempt(mgr, &data, &opts);
|
||||||
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
|
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
|
||||||
ASSERT(s_conn != NULL);
|
MG_INFO(("MQTT retry %d", attempt + 2));
|
||||||
for (int i = 0;
|
|
||||||
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
|
||||||
i++) {
|
|
||||||
mg_mgr_poll(mgr, 0);
|
|
||||||
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
|
||||||
}
|
}
|
||||||
ASSERT(data.passed);
|
ASSERT(data.passed);
|
||||||
mg_mgr_poll(mgr, 0);
|
mg_mgr_poll(mgr, 0);
|
||||||
@@ -406,16 +433,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
|||||||
//data.url = strdup(MQTT_URL);
|
//data.url = strdup(MQTT_URL);
|
||||||
#endif
|
#endif
|
||||||
#endif // MG_TLS
|
#endif // MG_TLS
|
||||||
data.passed = false;
|
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||||
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
retry = mqtt_attempt(mgr, &data, &opts);
|
||||||
mg_mgr_poll(mgr, 0);
|
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
|
||||||
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
|
MG_INFO(("MQTT retry %d", attempt + 2));
|
||||||
ASSERT(s_conn != NULL);
|
|
||||||
for (int i = 0;
|
|
||||||
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
|
||||||
i++) {
|
|
||||||
mg_mgr_poll(mgr, 0);
|
|
||||||
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
|
||||||
}
|
}
|
||||||
ASSERT(data.passed);
|
ASSERT(data.passed);
|
||||||
mg_mgr_poll(mgr, 0);
|
mg_mgr_poll(mgr, 0);
|
||||||
@@ -430,7 +451,7 @@ static volatile bool s_poll_done;
|
|||||||
static void *poll_thread(void *p) {
|
static void *poll_thread(void *p) {
|
||||||
struct mg_mgr *mgr = (struct mg_mgr *) p;
|
struct mg_mgr *mgr = (struct mg_mgr *) p;
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < POLL_LIMIT(300) && !s_poll_done; i++) {
|
for (i = 0; i < SERVER_POLL_LIMIT(mgr) && !s_poll_done; i++) {
|
||||||
mg_mgr_poll(mgr, 0);
|
mg_mgr_poll(mgr, 0);
|
||||||
usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit
|
usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit
|
||||||
}
|
}
|
||||||
@@ -445,7 +466,7 @@ static void test_http_server(struct mg_mgr *mgr) {
|
|||||||
struct mg_connection *c;
|
struct mg_connection *c;
|
||||||
char *cmd;
|
char *cmd;
|
||||||
pthread_t thread_id = (pthread_t) 0;
|
pthread_t thread_id = (pthread_t) 0;
|
||||||
int rc;
|
int attempt, rc;
|
||||||
#if MG_TLS
|
#if MG_TLS
|
||||||
struct mg_tls_opts opts;
|
struct mg_tls_opts opts;
|
||||||
memset(&opts, 0, sizeof(opts));
|
memset(&opts, 0, sizeof(opts));
|
||||||
@@ -456,20 +477,24 @@ static void test_http_server(struct mg_mgr *mgr) {
|
|||||||
mg_hexdump(opts.cert.buf, opts.cert.len);
|
mg_hexdump(opts.cert.buf, opts.cert.len);
|
||||||
mg_hexdump(opts.key.buf, opts.key.len);
|
mg_hexdump(opts.key.buf, opts.key.len);
|
||||||
c = mg_http_listen(mgr, "https://0.0.0.0:12347", eh1, &opts);
|
c = mg_http_listen(mgr, "https://0.0.0.0:12347", eh1, &opts);
|
||||||
cmd = mg_mprintf("./mip_curl.sh --insecure https://%M:12347", mg_print_ip4,
|
cmd = mg_mprintf("./mip_curl.sh --max-time 120 --insecure https://%M:12347",
|
||||||
&mgr->ifp->ip);
|
mg_print_ip4, &mgr->ifp->ip);
|
||||||
#else
|
#else
|
||||||
c = mg_http_listen(mgr, "http://0.0.0.0:12347", eh1, NULL);
|
c = mg_http_listen(mgr, "http://0.0.0.0:12347", eh1, NULL);
|
||||||
cmd =
|
cmd =
|
||||||
mg_mprintf("./mip_curl.sh http://%M:12347", mg_print_ip4, &mgr->ifp->ip);
|
mg_mprintf("./mip_curl.sh --max-time 120 http://%M:12347", mg_print_ip4,
|
||||||
|
&mgr->ifp->ip);
|
||||||
#endif
|
#endif
|
||||||
ASSERT(c != NULL);
|
ASSERT(c != NULL);
|
||||||
ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0
|
ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0
|
||||||
s_poll_done = false;
|
s_poll_done = false;
|
||||||
pthread_create(&thread_id, NULL, poll_thread,
|
pthread_create(&thread_id, NULL, poll_thread,
|
||||||
mgr); // simpler this way, no concurrency anyway
|
mgr); // simpler this way, no concurrency anyway
|
||||||
MG_DEBUG(("CURL"));
|
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||||
rc = system(cmd); // wait for curl
|
MG_DEBUG(("CURL attempt %d", attempt + 1));
|
||||||
|
rc = system(cmd); // wait for curl
|
||||||
|
if (rc == 0) break;
|
||||||
|
}
|
||||||
MG_DEBUG(("MONGOOSE"));
|
MG_DEBUG(("MONGOOSE"));
|
||||||
s_poll_done = true;
|
s_poll_done = true;
|
||||||
pthread_join(thread_id, NULL); // wait for Mongoose
|
pthread_join(thread_id, NULL); // wait for Mongoose
|
||||||
@@ -480,8 +505,7 @@ static void test_http_server(struct mg_mgr *mgr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void test_tls(struct mg_mgr *mgr) {
|
static void test_tls(struct mg_mgr *mgr) {
|
||||||
#if MG_TLS
|
#if MG_TLS && 0 // TODO(cpq): restore multi-record TLS test
|
||||||
char *url;
|
|
||||||
char buf[FETCH_BUF_SIZE]; // make sure it can hold Makefile
|
char buf[FETCH_BUF_SIZE]; // make sure it can hold Makefile
|
||||||
mg_mem_files = mg_packed_files; // Use generated packed filesystem
|
mg_mem_files = mg_packed_files; // Use generated packed filesystem
|
||||||
struct mg_str data = mg_unpacked("/Makefile");
|
struct mg_str data = mg_unpacked("/Makefile");
|
||||||
@@ -496,7 +520,8 @@ static void test_tls(struct mg_mgr *mgr) {
|
|||||||
// - Fire patched server, test multiple TLS records per TCP segment handling
|
// - Fire patched server, test multiple TLS records per TCP segment handling
|
||||||
// skip other TLS stacks to avoid "bad client hello", we are 1.3 only
|
// skip other TLS stacks to avoid "bad client hello", we are 1.3 only
|
||||||
if (access("tls_multirec/server", X_OK) == 0) {
|
if (access("tls_multirec/server", X_OK) == 0) {
|
||||||
url = mg_mprintf("https://%s:8443", host_ip);
|
char url[256];
|
||||||
|
mg_snrintf(url, sizeof(url), "https://%s:8443", host_ip);
|
||||||
ASSERT(system("tls_multirec/server -d tls_multirec &") == 0);
|
ASSERT(system("tls_multirec/server -d tls_multirec &") == 0);
|
||||||
sleep(1);
|
sleep(1);
|
||||||
// fetch() needs to loop enough times in order to process all TLS records;
|
// fetch() needs to loop enough times in order to process all TLS records;
|
||||||
@@ -504,7 +529,6 @@ static void test_tls(struct mg_mgr *mgr) {
|
|||||||
ASSERT(fetch(mgr, buf, url, "GET /thefile HTTP/1.0\n\n") == 200);
|
ASSERT(fetch(mgr, buf, url, "GET /thefile HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(cmpbody(buf, data.buf) == 0); // "thefile" links to Makefile
|
ASSERT(cmpbody(buf, data.buf) == 0); // "thefile" links to Makefile
|
||||||
ASSERT(system("killall tls_multirec/server") == 0);
|
ASSERT(system("killall tls_multirec/server") == 0);
|
||||||
free(url);
|
|
||||||
} else {
|
} else {
|
||||||
printf("SKIPPED TLS MULTIPLE RECORDS TEST, tls_multirec/server NOT PRESENT\n");
|
printf("SKIPPED TLS MULTIPLE RECORDS TEST, tls_multirec/server NOT PRESENT\n");
|
||||||
}
|
}
|
||||||
@@ -512,7 +536,6 @@ static void test_tls(struct mg_mgr *mgr) {
|
|||||||
printf("SKIPPED TLS MULTIPLE RECORDS TEST, not a known TLS 1.3 stack\n");
|
printf("SKIPPED TLS MULTIPLE RECORDS TEST, not a known TLS 1.3 stack\n");
|
||||||
(void) cmpbody("", "");
|
(void) cmpbody("", "");
|
||||||
(void) mgr;
|
(void) mgr;
|
||||||
(void) url; // these three: NO SIMPLE TLS TEST
|
|
||||||
(void) buf;
|
(void) buf;
|
||||||
(void) data;
|
(void) data;
|
||||||
#endif
|
#endif
|
||||||
@@ -593,6 +616,7 @@ bool mip_x_test(struct mg_mgr *mgr) {
|
|||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
s_error = false;
|
s_error = false;
|
||||||
|
mg_mgr_poll(mgr, 0); // prevent mDash from trashing mg_mgr_free()
|
||||||
mg_mgr_free(mgr);
|
mg_mgr_free(mgr);
|
||||||
ASSERT(mgr->conns == NULL); // Deconstruction OK
|
ASSERT(mgr->conns == NULL); // Deconstruction OK
|
||||||
printf("HEALTH_DASHBOARD\t\"cleanup\": %s\n", s_error ? "false":"true");
|
printf("HEALTH_DASHBOARD\t\"cleanup\": %s\n", s_error ? "false":"true");
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
CFLAGS = -g -I. # This is a known version, no need to check
|
|
||||||
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1 -DMG_ENABLE_IPV6=1
|
|
||||||
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
|
|
||||||
|
|
||||||
server: main.c patched_mongoose.c mongoose.h Makefile
|
|
||||||
$(CC) main.c patched_mongoose.c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf server *.exe *.o *.dSYM
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
// Copyright (c) 2020 Cesanta Software Limited
|
|
||||||
// All rights reserved
|
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
#include "mongoose.h"
|
|
||||||
|
|
||||||
static int s_debug_level = MG_LL_INFO;
|
|
||||||
static const char *s_root_dir = ".";
|
|
||||||
static const char *s_addr1 = "http://0.0.0.0:8000";
|
|
||||||
static const char *s_addr2 = "https://0.0.0.0:8443";
|
|
||||||
static const char *s_enable_hexdump = "no";
|
|
||||||
static const char *s_ssi_pattern = "#.html";
|
|
||||||
static const char *s_upload_dir = NULL; // File uploads disabled by default
|
|
||||||
|
|
||||||
// Self signed certificates, see
|
|
||||||
// https://github.com/cesanta/mongoose/blob/master/test/certs/generate.sh
|
|
||||||
#ifdef TLS_TWOWAY
|
|
||||||
static const char *s_tls_ca =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIIBFTCBvAIJAMNTFtpfcq8NMAoGCCqGSM49BAMCMBMxETAPBgNVBAMMCE1vbmdv\n"
|
|
||||||
"b3NlMB4XDTI0MDUwNzE0MzczNloXDTM0MDUwNTE0MzczNlowEzERMA8GA1UEAwwI\n"
|
|
||||||
"TW9uZ29vc2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASuP+86T/rOWnGpEVhl\n"
|
|
||||||
"fxYZ+pjMbCmDZ+vdnP0rjoxudwRMRQCv5slRlDK7Lxue761sdvqxWr0Ma6TFGTNg\n"
|
|
||||||
"epsRMAoGCCqGSM49BAMCA0gAMEUCIQCwb2CxuAKm51s81S6BIoy1IcandXSohnqs\n"
|
|
||||||
"us64BAA7QgIgGGtUrpkgFSS0oPBlCUG6YPHFVw42vTfpTC0ySwAS0M4=\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
#endif
|
|
||||||
static const char *s_tls_cert =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIIBMTCB2aADAgECAgkAluqkgeuV/zUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAwwI\n"
|
|
||||||
"TW9uZ29vc2UwHhcNMjQwNTA3MTQzNzM2WhcNMzQwNTA1MTQzNzM2WjARMQ8wDQYD\n"
|
|
||||||
"VQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASo3oEiG+BuTt5y\n"
|
|
||||||
"ZRyfwNr0C+SP+4M0RG2pYkb2v+ivbpfi72NHkmXiF/kbHXtgmSrn/PeTqiA8M+mg\n"
|
|
||||||
"BhYjDX+zoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAw\n"
|
|
||||||
"RAIgTXW9MITQSwzqbNTxUUdt9DcB+8pPUTbWZpiXcA26GMYCIBiYw+DSFMLHmkHF\n"
|
|
||||||
"+5U3NXW3gVCLN9ntD5DAx8LTG8sB\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
static const char *s_tls_key =
|
|
||||||
"-----BEGIN EC PRIVATE KEY-----\n"
|
|
||||||
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
|
|
||||||
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
|
|
||||||
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
|
|
||||||
"-----END EC PRIVATE KEY-----\n";
|
|
||||||
|
|
||||||
// Handle interrupts, like Ctrl-C
|
|
||||||
static int s_signo;
|
|
||||||
static void signal_handler(int signo) {
|
|
||||||
s_signo = signo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event handler for the listening connection.
|
|
||||||
// Simply serve static files from `s_root_dir`
|
|
||||||
static void cb(struct mg_connection *c, int ev, void *ev_data) {
|
|
||||||
if (ev == MG_EV_ACCEPT && c->fn_data != NULL) {
|
|
||||||
struct mg_tls_opts opts;
|
|
||||||
memset(&opts, 0, sizeof(opts));
|
|
||||||
#ifdef TLS_TWOWAY
|
|
||||||
opts.ca = mg_str(s_tls_ca);
|
|
||||||
#endif
|
|
||||||
opts.cert = mg_str(s_tls_cert);
|
|
||||||
opts.key = mg_str(s_tls_key);
|
|
||||||
mg_tls_init(c, &opts);
|
|
||||||
}
|
|
||||||
if (ev == MG_EV_HTTP_MSG) {
|
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
|
||||||
|
|
||||||
if (mg_match(hm->uri, mg_str("/upload"), NULL)) {
|
|
||||||
// Serve file upload
|
|
||||||
if (s_upload_dir == NULL) {
|
|
||||||
mg_http_reply(c, 403, "", "Denied: file upload directory not set\n");
|
|
||||||
} else {
|
|
||||||
struct mg_http_part part;
|
|
||||||
size_t pos = 0, total_bytes = 0, num_files = 0;
|
|
||||||
while ((pos = mg_http_next_multipart(hm->body, pos, &part)) > 0) {
|
|
||||||
char path[MG_PATH_MAX];
|
|
||||||
MG_INFO(("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
|
|
||||||
part.name.len, part.name.buf, part.filename.len,
|
|
||||||
part.filename.buf, part.body.len));
|
|
||||||
mg_snprintf(path, sizeof(path), "%s/%.*s", s_upload_dir,
|
|
||||||
part.filename.len, part.filename.buf);
|
|
||||||
if (mg_path_is_sane(mg_str(path))) {
|
|
||||||
mg_file_write(&mg_fs_posix, path, part.body.buf, part.body.len);
|
|
||||||
total_bytes += part.body.len;
|
|
||||||
num_files++;
|
|
||||||
} else {
|
|
||||||
MG_ERROR(("Rejecting dangerous path %s", path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mg_http_reply(c, 200, "", "Uploaded %lu files, %lu bytes\n", num_files,
|
|
||||||
total_bytes);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Serve web root directory
|
|
||||||
struct mg_http_serve_opts opts = {0};
|
|
||||||
opts.root_dir = s_root_dir;
|
|
||||||
opts.ssi_pattern = s_ssi_pattern;
|
|
||||||
mg_http_serve_dir(c, hm, &opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log request
|
|
||||||
MG_INFO(("%.*s %.*s %lu -> %.*s %lu", hm->method.len, hm->method.buf,
|
|
||||||
hm->uri.len, hm->uri.buf, hm->body.len, 3, c->send.buf + 9,
|
|
||||||
c->send.len));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usage(const char *prog) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Mongoose v.%s\n"
|
|
||||||
"Usage: %s OPTIONS\n"
|
|
||||||
" -H yes|no - enable traffic hexdump, default: '%s'\n"
|
|
||||||
" -S PAT - SSI filename pattern, default: '%s'\n"
|
|
||||||
" -d DIR - directory to serve, default: '%s'\n"
|
|
||||||
" -l ADDR - listening address, default: '%s'\n"
|
|
||||||
" -u DIR - file upload directory, default: unset\n"
|
|
||||||
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
|
|
||||||
MG_VERSION, prog, s_enable_hexdump, s_ssi_pattern, s_root_dir,
|
|
||||||
s_addr1, s_debug_level);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
char path[MG_PATH_MAX] = ".";
|
|
||||||
struct mg_mgr mgr;
|
|
||||||
struct mg_connection *c;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Parse command-line flags
|
|
||||||
for (i = 1; i < argc; i++) {
|
|
||||||
if (strcmp(argv[i], "-d") == 0) {
|
|
||||||
s_root_dir = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-H") == 0) {
|
|
||||||
s_enable_hexdump = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-S") == 0) {
|
|
||||||
s_ssi_pattern = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-l") == 0) {
|
|
||||||
s_addr1 = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-l2") == 0) {
|
|
||||||
s_addr2 = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-u") == 0) {
|
|
||||||
s_upload_dir = argv[++i];
|
|
||||||
} else if (strcmp(argv[i], "-v") == 0) {
|
|
||||||
s_debug_level = atoi(argv[++i]);
|
|
||||||
} else {
|
|
||||||
usage(argv[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root directory must not contain double dots. Make it absolute
|
|
||||||
// Do the conversion only if the root dir spec does not contain overrides
|
|
||||||
if (strchr(s_root_dir, ',') == NULL) {
|
|
||||||
realpath(s_root_dir, path);
|
|
||||||
s_root_dir = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise stuff
|
|
||||||
signal(SIGINT, signal_handler);
|
|
||||||
signal(SIGTERM, signal_handler);
|
|
||||||
mg_log_set(s_debug_level);
|
|
||||||
mg_mgr_init(&mgr);
|
|
||||||
if ((c = mg_http_listen(&mgr, s_addr1, cb, NULL)) == NULL) {
|
|
||||||
MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT",
|
|
||||||
s_addr1));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
if ((c = mg_http_listen(&mgr, s_addr2, cb, (void *) 1)) == NULL) {
|
|
||||||
MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT",
|
|
||||||
s_addr2));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1;
|
|
||||||
|
|
||||||
// Start infinite event loop
|
|
||||||
MG_INFO(("Mongoose version : v%s", MG_VERSION));
|
|
||||||
MG_INFO(("HTTP listener : %s", s_addr1));
|
|
||||||
MG_INFO(("HTTPS listener : %s", s_addr2));
|
|
||||||
MG_INFO(("Web root : [%s]", s_root_dir));
|
|
||||||
MG_INFO(("Upload dir : [%s]", s_upload_dir ? s_upload_dir : "unset"));
|
|
||||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000);
|
|
||||||
mg_mgr_free(&mgr);
|
|
||||||
MG_INFO(("Exiting on signal %d", s_signo));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1 +0,0 @@
|
|||||||
../Makefile
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
diff --git a/mongoose.c b/mongoose.c
|
|
||||||
index 44539e81..b3f1c83a 100644
|
|
||||||
--- a/mongoose.c
|
|
||||||
+++ b/mongoose.c
|
|
||||||
@@ -11300,9 +11300,16 @@ void mg_tls_free(struct mg_connection *c) {
|
|
||||||
long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
|
|
||||||
struct tls_data *tls = (struct tls_data *) c->tls;
|
|
||||||
long n = MG_IO_WAIT;
|
|
||||||
+ size_t maxsize = 256, encrypted = 0;
|
|
||||||
if (len > MG_IO_SIZE) len = MG_IO_SIZE;
|
|
||||||
if (len > 16384) len = 16384;
|
|
||||||
- mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
|
|
||||||
+ while (encrypted < len) {
|
|
||||||
+ const uint8_t *chunk = (const uint8_t *) buf + encrypted;
|
|
||||||
+ size_t chunksize = len - encrypted;
|
|
||||||
+ if (chunksize > maxsize) chunksize = maxsize;
|
|
||||||
+ mg_tls_encrypt(c, chunk, chunksize, MG_TLS_APP_DATA);
|
|
||||||
+ encrypted += chunksize;
|
|
||||||
+ }
|
|
||||||
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);
|
|
||||||
+176
-45
@@ -803,11 +803,18 @@ static void test_mqtt_parse(void) {
|
|||||||
struct mg_mqtt_prop prop;
|
struct mg_mqtt_prop prop;
|
||||||
// MQTT5 PUBLISH with Subscription Identifier = 5
|
// MQTT5 PUBLISH with Subscription Identifier = 5
|
||||||
const uint8_t sub_id_pkt[] = {0x30, 0x06, 0x00, 0x01, 'x', 0x02, 0x0B, 0x05};
|
const uint8_t sub_id_pkt[] = {0x30, 0x06, 0x00, 0x01, 'x', 0x02, 0x0B, 0x05};
|
||||||
|
// MQTT5 CONNACK with Server Keep Alive = 60
|
||||||
|
const uint8_t connack_pkt[] = {0x20, 0x06, 0x00, 0x00, 0x03, 0x13, 0x00, 0x3C};
|
||||||
|
|
||||||
memset(&mm, 0, sizeof(struct mg_mqtt_message));
|
memset(&mm, 0, sizeof(struct mg_mqtt_message));
|
||||||
memset(&prop, 0, sizeof(struct mg_mqtt_prop));
|
memset(&prop, 0, sizeof(struct mg_mqtt_prop));
|
||||||
ASSERT(mg_mqtt_parse(sub_id_pkt, sizeof(sub_id_pkt), 5, &mm) == MQTT_OK);
|
ASSERT(mg_mqtt_parse(sub_id_pkt, sizeof(sub_id_pkt), 5, &mm) == MQTT_OK);
|
||||||
mg_mqtt_next_prop(&mm, &prop, 0);
|
mg_mqtt_next_prop(&mm, &prop, 0);
|
||||||
|
|
||||||
|
ASSERT(mg_mqtt_parse(connack_pkt, sizeof(connack_pkt), 5, &mm) == MQTT_OK);
|
||||||
|
ASSERT(mm.ack == 0);
|
||||||
|
ASSERT(mg_mqtt_next_prop(&mm, &prop, 0) > 0);
|
||||||
|
ASSERT(prop.id == MQTT_PROP_SERVER_KEEP_ALIVE && prop.iv == 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_mqtt(void) {
|
static void test_mqtt(void) {
|
||||||
@@ -918,6 +925,10 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max number of mg_mgr_poll() calls fetch() waits for a response. Large
|
||||||
|
// transfers over builtin TLS with small records (MG_TLS_RECORD_SIZE) need more
|
||||||
|
static int s_fetch_polls = 100;
|
||||||
|
|
||||||
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
||||||
const char *fmt, ...) {
|
const char *fmt, ...) {
|
||||||
struct fetch_data fd = {buf, 0, 0};
|
struct fetch_data fd = {buf, 0, 0};
|
||||||
@@ -946,7 +957,7 @@ static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
|||||||
mg_vprintf(c, fmt, &ap);
|
mg_vprintf(c, fmt, &ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
for (i = 0; i < 100 && buf[0] == '\0'; i++) mg_mgr_poll(mgr, 1);
|
for (i = 0; i < s_fetch_polls && buf[0] == '\0'; i++) mg_mgr_poll(mgr, 1);
|
||||||
if (!fd.closed) c->is_closing = 1;
|
if (!fd.closed) c->is_closing = 1;
|
||||||
mg_mgr_poll(mgr, 1);
|
mg_mgr_poll(mgr, 1);
|
||||||
return fd.code;
|
return fd.code;
|
||||||
@@ -1464,6 +1475,11 @@ static void h4(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
opts.root_dir = "/a=./data";
|
opts.root_dir = "/a=./data";
|
||||||
opts.page404 = "./data/404.html"; // existing 404 page
|
opts.page404 = "./data/404.html"; // existing 404 page
|
||||||
mg_http_serve_dir(c, hm, &opts);
|
mg_http_serve_dir(c, hm, &opts);
|
||||||
|
} else if (mg_match(hm->uri, mg_str("/unmapped"), NULL)) {
|
||||||
|
struct mg_http_serve_opts opts;
|
||||||
|
memset(&opts, 0, sizeof(opts));
|
||||||
|
opts.root_dir = "/a=./data";
|
||||||
|
mg_http_serve_dir(c, hm, &opts);
|
||||||
} else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) {
|
||||||
struct mg_http_serve_opts opts;
|
struct mg_http_serve_opts opts;
|
||||||
memset(&opts, 0, sizeof(opts));
|
memset(&opts, 0, sizeof(opts));
|
||||||
@@ -1491,6 +1507,7 @@ static void test_http_404(void) {
|
|||||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||||
ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200);
|
ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||||
|
ASSERT(fetch(&mgr, buf, url, "GET /unmapped HTTP/1.0\n\n") == 400);
|
||||||
ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200);
|
ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200);
|
||||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||||
|
|
||||||
@@ -1515,7 +1532,6 @@ static void test_http_404(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void test_tls(void) {
|
static void test_tls(void) {
|
||||||
// return;
|
|
||||||
#if MG_TLS
|
#if MG_TLS
|
||||||
struct mg_mgr mgr;
|
struct mg_mgr mgr;
|
||||||
struct mg_connection *c;
|
struct mg_connection *c;
|
||||||
@@ -1538,6 +1554,7 @@ static void test_tls(void) {
|
|||||||
// POST a large file, several MSS but less than max TLS record length
|
// POST a large file, several MSS but less than max TLS record length
|
||||||
// make sure we drain TLS buffers and read all, #2619
|
// make sure we drain TLS buffers and read all, #2619
|
||||||
ASSERT(data.len > 3100 && data.len < 16384); // pick another file on failure
|
ASSERT(data.len > 3100 && data.len < 16384); // pick another file on failure
|
||||||
|
s_fetch_polls = 5000; // Many small TLS records, one is decrypted per poll
|
||||||
ASSERT(fetch(&mgr, buf, url,
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
"POST /body HTTP/1.0\n"
|
"POST /body HTTP/1.0\n"
|
||||||
"Content-Length: %lu\n\n"
|
"Content-Length: %lu\n\n"
|
||||||
@@ -1554,29 +1571,7 @@ static void test_tls(void) {
|
|||||||
"%s",
|
"%s",
|
||||||
bd.len, bd.buf) == 200);
|
bd.len, bd.buf) == 200);
|
||||||
ASSERT(cmpbody(buf, bd.buf) == 0);
|
ASSERT(cmpbody(buf, bd.buf) == 0);
|
||||||
#if MG_TLS == MG_TLS_BUILTIN && defined(__linux__) && \
|
s_fetch_polls = 100;
|
||||||
MG_ENABLE_CHACHA20 // skip for non-CHACHA tests
|
|
||||||
// fire patched server, test multiple TLS records per TCP segment handling
|
|
||||||
// skip other TLS stacks to avoid "bad client hello", we are 1.3 only
|
|
||||||
if (access("tls_multirec/server", X_OK) == 0) {
|
|
||||||
ASSERT(system("tls_multirec/server -d tls_multirec &") == 0);
|
|
||||||
sleep(1);
|
|
||||||
// fetch() needs to loop enough times in order to process all TLS records;
|
|
||||||
// otherwise it will end with 200 and shorter file contents
|
|
||||||
ASSERT(fetch(&mgr, buf, "https://localhost:8443",
|
|
||||||
"GET /thefile HTTP/1.0\n\n") == 200);
|
|
||||||
data = mg_unpacked("/Makefile");
|
|
||||||
ASSERT(cmpbody(buf, data.buf) == 0); // "thefile" links to Makefile
|
|
||||||
ASSERT(system("killall tls_multirec/server") == 0);
|
|
||||||
} else {
|
|
||||||
printf("\nSKIPPED TLS MULTIPLE RECORDS TEST, tls_multirec/server NOT PRESENT\n");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
printf(
|
|
||||||
"\n Skipping multiple TLS records per TCP segment handling test, server "
|
|
||||||
"is 1.3 only; re-enable when other stacks can be easily configured for "
|
|
||||||
"1.3\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Repeat the simplest test with two-way authentication
|
// Repeat the simplest test with two-way authentication
|
||||||
opts.ca = mg_unpacked("/certs/ca.crt"); // configure the server for two-way
|
opts.ca = mg_unpacked("/certs/ca.crt"); // configure the server for two-way
|
||||||
@@ -2410,10 +2405,15 @@ static void test_str(void) {
|
|||||||
ASSERT(sn("%.*s", 1, "ab"));
|
ASSERT(sn("%.*s", 1, "ab"));
|
||||||
ASSERT(sn("%.1s", "ab"));
|
ASSERT(sn("%.1s", "ab"));
|
||||||
ASSERT(sn("%.99s", "a"));
|
ASSERT(sn("%.99s", "a"));
|
||||||
|
ASSERT(sn("%10.8s", "abc"));
|
||||||
|
ASSERT(sn("%-6.4s", "ab"));
|
||||||
|
ASSERT(sn("%5.1s", ""));
|
||||||
ASSERT(sn("%11s", "a"));
|
ASSERT(sn("%11s", "a"));
|
||||||
ASSERT(sn("%s", "a\0b"));
|
ASSERT(sn("%s", "a\0b"));
|
||||||
ASSERT(sn("%2s", "a"));
|
ASSERT(sn("%2s", "a"));
|
||||||
ASSERT(sn("%.*s", 3, "a\0b"));
|
ASSERT(sn("%.*s", 3, "a\0b"));
|
||||||
|
ASSERT(mg_snprintf(NULL, 0, "%.8s", (char *) NULL) == 0);
|
||||||
|
ASSERT(mg_snprintf(NULL, 0, "%8.4s", (char *) NULL) == 8);
|
||||||
ASSERT(sn("%d", 7));
|
ASSERT(sn("%d", 7));
|
||||||
ASSERT(sn("%d", 123));
|
ASSERT(sn("%d", 123));
|
||||||
#if MG_ARCH == MG_ARCH_UNIX
|
#if MG_ARCH == MG_ARCH_UNIX
|
||||||
@@ -3169,8 +3169,6 @@ static bool su_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
|||||||
static void su(struct mg_connection *c, int ev, void *ev_data) {
|
static void su(struct mg_connection *c, int ev, void *ev_data) {
|
||||||
if (ev == MG_EV_HTTP_HDRS) {
|
if (ev == MG_EV_HTTP_HDRS) {
|
||||||
mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL);
|
mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL);
|
||||||
} else if (ev == MG_EV_HTTP_MSG) { // Only for requests that were not streamed
|
|
||||||
mg_http_reply(c, 400, "", "not streamed\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3205,8 +3203,7 @@ static void cr(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
if (ev == MG_EV_CONNECT) {
|
if (ev == MG_EV_CONNECT) {
|
||||||
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
||||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||||
ASSERT(mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb,
|
mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb, NULL);
|
||||||
NULL));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3219,12 +3216,103 @@ static void cs(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
(void) ev_data;
|
(void) ev_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct stream_test_state {
|
||||||
|
int fail_at, starts, chunks, ends;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool stream_test_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||||
|
void **p) {
|
||||||
|
struct stream_test_state *s = (struct stream_test_state *) *p;
|
||||||
|
ASSERT(s != NULL);
|
||||||
|
if (hm != NULL) {
|
||||||
|
s->starts++;
|
||||||
|
return s->fail_at != 1;
|
||||||
|
} else if (data != NULL) {
|
||||||
|
s->chunks++;
|
||||||
|
return s->fail_at != 2;
|
||||||
|
}
|
||||||
|
s->ends++;
|
||||||
|
*p = NULL;
|
||||||
|
return s->fail_at != 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stream_test_original(struct mg_connection *c, int ev, void *data) {
|
||||||
|
(void) c, (void) ev, (void) data;
|
||||||
|
ASSERT(false); // Streaming must take ownership even when validation fails
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_http_stream_errors(void) {
|
||||||
|
static const struct {
|
||||||
|
const char *request;
|
||||||
|
int client, fail_at, body_size, code, starts, chunks;
|
||||||
|
} cases[] = {
|
||||||
|
{"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 405, 0, 0},
|
||||||
|
{"POST / HTTP/1.1\r\n\r\n", 0, 0, 0, 411, 0, 0},
|
||||||
|
{"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||||
|
0, 0, 0, 400, 0, 0},
|
||||||
|
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 0, 0, 200, 1, 1},
|
||||||
|
{"PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 200, 1, 0},
|
||||||
|
// Start failure must reply without waiting for the body or sending 100.
|
||||||
|
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\nExpect: 100-continue\r\n\r\n",
|
||||||
|
0, 1, 0, 500, 1, 0},
|
||||||
|
// Write failure must also reply before the announced body is complete.
|
||||||
|
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\n\r\n",
|
||||||
|
0, 2, 512, 500, 1, 1},
|
||||||
|
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 3, 0, 500, 1, 1},
|
||||||
|
// Clients close on errors and never send an HTTP error response.
|
||||||
|
{"HTTP/1.1 200 OK\r\n\r\n", 1, 0, 0, 411, 0, 0},
|
||||||
|
{"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||||
|
1, 0, 0, 400, 0, 0},
|
||||||
|
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
|
||||||
|
1, 1, 0, 500, 1, 0},
|
||||||
|
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
|
||||||
|
1, 2, 512, 500, 1, 1},
|
||||||
|
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 3, 0, 500, 1, 1},
|
||||||
|
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 0, 0, 200, 1, 1},
|
||||||
|
{"HTTP/1.1 204 No Content\r\n\r\n", 1, 0, 0, 200, 1, 0},
|
||||||
|
};
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||||
|
struct mg_connection c;
|
||||||
|
struct mg_http_message hm;
|
||||||
|
struct stream_test_state state = {0, 0, 0, 0};
|
||||||
|
char body[512];
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
memset(body, 'x', sizeof(body));
|
||||||
|
state.fail_at = cases[i].fail_at;
|
||||||
|
c.is_client = cases[i].client != 0;
|
||||||
|
c.fn = c.pfn = stream_test_original;
|
||||||
|
mg_iobuf_add(&c.recv, 0, cases[i].request, strlen(cases[i].request));
|
||||||
|
mg_iobuf_add(&c.recv, c.recv.len, body, (size_t) cases[i].body_size);
|
||||||
|
ASSERT(mg_http_parse((char *) c.recv.buf, c.recv.len, &hm) > 0);
|
||||||
|
mg_http_stream_body(&c, &hm, stream_test_cb, &state);
|
||||||
|
ASSERT(c.pfn == NULL && c.is_draining);
|
||||||
|
ASSERT(state.starts == cases[i].starts && state.chunks == cases[i].chunks);
|
||||||
|
ASSERT(state.ends == 1);
|
||||||
|
if (c.is_client) {
|
||||||
|
ASSERT(c.send.len == 0);
|
||||||
|
ASSERT(c.is_closing == (cases[i].code != 200));
|
||||||
|
} else {
|
||||||
|
ASSERT(mg_http_parse((char *) c.send.buf, c.send.len, &hm) > 0);
|
||||||
|
ASSERT(mg_http_status(&hm) == cases[i].code);
|
||||||
|
ASSERT(hm.message.len == c.send.len); // Exactly one response
|
||||||
|
}
|
||||||
|
mg_call(&c, MG_EV_READ, NULL);
|
||||||
|
mg_error(&c, "stream cleanup test");
|
||||||
|
mg_call(&c, MG_EV_CLOSE, NULL);
|
||||||
|
ASSERT(state.ends == 1); // Error followed by close must not clean up twice
|
||||||
|
mg_iobuf_free(&c.recv);
|
||||||
|
mg_iobuf_free(&c.send);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void test_http_upload(void) {
|
static void test_http_upload(void) {
|
||||||
struct mg_mgr mgr;
|
struct mg_mgr mgr;
|
||||||
const char *url = "http://127.0.0.1:12352";
|
const char *url = "http://127.0.0.1:12352";
|
||||||
int i;
|
int i;
|
||||||
const char *s = "ok (8 foo\nbar\n)\n";
|
const char *s = "ok (8 foo\nbar\n)\n";
|
||||||
|
|
||||||
|
test_http_stream_errors();
|
||||||
mg_mgr_init(&mgr);
|
mg_mgr_init(&mgr);
|
||||||
mg_http_listen(&mgr, url, us, NULL);
|
mg_http_listen(&mgr, url, us, NULL);
|
||||||
mg_http_connect(&mgr, url, uc, (void *) &s);
|
mg_http_connect(&mgr, url, uc, (void *) &s);
|
||||||
@@ -3268,7 +3356,7 @@ static void test_http_upload(void) {
|
|||||||
ASSERT(mgr.conns == NULL);
|
ASSERT(mgr.conns == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mg_http_stream_body: chunked request is not streamed, caller replies "400"
|
// mg_http_stream_body: chunked request rejected with "400", cleanup still runs
|
||||||
{
|
{
|
||||||
char buf[FETCH_BUF_SIZE];
|
char buf[FETCH_BUF_SIZE];
|
||||||
s_su_done = s_su_end = 0;
|
s_su_done = s_su_end = 0;
|
||||||
@@ -3279,7 +3367,7 @@ static void test_http_upload(void) {
|
|||||||
"POST /su_chunked.txt HTTP/1.1\r\n"
|
"POST /su_chunked.txt HTTP/1.1\r\n"
|
||||||
"Transfer-Encoding: chunked\r\n\r\n"
|
"Transfer-Encoding: chunked\r\n\r\n"
|
||||||
"3\r\nfoo\r\n0\r\n\r\n") == 400);
|
"3\r\nfoo\r\n0\r\n\r\n") == 400);
|
||||||
ASSERT(s_su_done == 0 && s_su_end == 0); // The callback was never called
|
ASSERT(s_su_done == 1 && s_su_end == 1); // End called without start
|
||||||
ASSERT(fopen("su_chunked.txt", "r") == NULL);
|
ASSERT(fopen("su_chunked.txt", "r") == NULL);
|
||||||
mg_mgr_free(&mgr);
|
mg_mgr_free(&mgr);
|
||||||
ASSERT(mgr.conns == NULL);
|
ASSERT(mgr.conns == NULL);
|
||||||
@@ -3656,6 +3744,14 @@ static void test_check_ip_acl(void) {
|
|||||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/8"), &ip) == 1);
|
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/8"), &ip) == 1);
|
||||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.2.3.4"), &ip) == 1);
|
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.2.3.4"), &ip) == 1);
|
||||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/16"), &ip) == 0);
|
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/16"), &ip) == 0);
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+1.2.3.4xyz"), &ip) == -2); // junk
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+1.2.3.4/32abc"), &ip) == -2); // junk
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+1.2.3.4/"), &ip) == -2);
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+1.2.3.4/33"), &ip) == -2);
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+1.2.3.256"), &ip) == -2);
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str("+"), &ip) == -2);
|
||||||
|
// Not NUL-terminated: must not read past the end of the ACL
|
||||||
|
ASSERT(mg_check_ip_acl(mg_str_n("+1.2.3.4xyz", 8), &ip) == 1);
|
||||||
ip.is_ip6 = true;
|
ip.is_ip6 = true;
|
||||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) ==
|
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) ==
|
||||||
-1); // not yet supported
|
-1); // not yet supported
|
||||||
@@ -4409,18 +4505,39 @@ static void test_split(void) {
|
|||||||
ASSERT(mg_strcmp(b, mg_str("")) == 0);
|
ASSERT(mg_strcmp(b, mg_str("")) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_x25519(void) {
|
// RFC 6979 A.2.5: P-256, SHA-256, message "sample"
|
||||||
|
static void test_p256(void) {
|
||||||
#if MG_TLS == MG_TLS_BUILTIN
|
#if MG_TLS == MG_TLS_BUILTIN
|
||||||
uint8_t key[X25519_BYTES];
|
static const uint8_t x[32] = {
|
||||||
uint8_t buf[X25519_BYTES];
|
0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, 0x6b, 0x5c, 0x21,
|
||||||
char tmp[100];
|
0x57, 0x67, 0xb1, 0xd6, 0x93, 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8,
|
||||||
size_t i;
|
0x9b, 0x12, 0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f, 0x67, 0x21};
|
||||||
for (i = 0; i < sizeof(key); i++) key[i] = (uint8_t) i;
|
static const uint8_t ux[8] = {0x60, 0xfe, 0xd4, 0xba, 0x25, 0x5a, 0x9d, 0x31};
|
||||||
for (i = 0; i < sizeof(buf); i++) buf[i] = 0;
|
static const uint8_t uy[8] = {0x79, 0x03, 0xfe, 0x10, 0x08, 0xb8, 0xbc, 0x99};
|
||||||
mg_tls_x25519(buf, key, X25519_BASE_POINT, 1);
|
static const uint8_t rs[64] = {
|
||||||
mg_snprintf(tmp, sizeof(tmp), "%M", mg_print_hex, sizeof(buf), buf);
|
0xef, 0xd4, 0x8b, 0x2a, 0xac, 0xb6, 0xa8, 0xfd, 0x11, 0x40, 0xdd, 0x9c,
|
||||||
MG_INFO(("%s", tmp));
|
0xd4, 0x5e, 0x81, 0xd6, 0x9d, 0x2c, 0x87, 0x7b, 0x56, 0xaa, 0xf9, 0x91,
|
||||||
ASSERT(mg_strcmp(mg_str("8f40c5adb6"), mg_str_n(tmp, 10)) == 0);
|
0xc3, 0x4d, 0x0e, 0xa8, 0x4e, 0xaf, 0x37, 0x16, 0xf7, 0xcb, 0x1c, 0x94,
|
||||||
|
0x2d, 0x65, 0x7c, 0x41, 0xd4, 0x36, 0xc7, 0xa1, 0xb6, 0xe2, 0x9f, 0x65,
|
||||||
|
0xf3, 0xe9, 0x00, 0xdb, 0xb9, 0xaf, 0xf4, 0x06, 0x4d, 0xc4, 0xab, 0x2f,
|
||||||
|
0x84, 0x3a, 0xcd, 0xa8};
|
||||||
|
uint8_t pub[64], hash[32], sig[64], prv2[32], pub2[64], sec1[32], sec2[32];
|
||||||
|
mg_sha256(hash, (uint8_t *) "sample", 6);
|
||||||
|
ASSERT(mg_p256_compute_public_key(x, pub) == 1);
|
||||||
|
ASSERT(memcmp(pub, ux, sizeof(ux)) == 0);
|
||||||
|
ASSERT(memcmp(pub + 32, uy, sizeof(uy)) == 0);
|
||||||
|
ASSERT(mg_p256_sign(x, hash, sizeof(hash), sig) == 1);
|
||||||
|
ASSERT(memcmp(sig, rs, sizeof(rs)) == 0);
|
||||||
|
ASSERT(mg_p256_verify(pub, hash, sizeof(hash), sig) == 1);
|
||||||
|
sig[10] ^= 1;
|
||||||
|
ASSERT(mg_p256_verify(pub, hash, sizeof(hash), sig) == 0);
|
||||||
|
// ECDH: both sides compute the same secret
|
||||||
|
ASSERT(mg_p256_make_key(pub2, prv2) == 1);
|
||||||
|
ASSERT(mg_p256_shared_secret(pub2, x, sec1) == 1);
|
||||||
|
ASSERT(mg_p256_shared_secret(pub, prv2, sec2) == 1);
|
||||||
|
ASSERT(memcmp(sec1, sec2, sizeof(sec1)) == 0);
|
||||||
|
pub2[63] ^= 1; // Not on the curve anymore
|
||||||
|
ASSERT(mg_p256_shared_secret(pub2, x, sec1) == 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4643,8 +4760,8 @@ static void test_jwt(void) {
|
|||||||
opts.kid = mg_str("ec-key-1");
|
opts.kid = mg_str("ec-key-1");
|
||||||
opts.private_key = key;
|
opts.private_key = key;
|
||||||
opts.public_key = pub;
|
opts.public_key = pub;
|
||||||
ASSERT(mg_uecc_parse_private_key(mg_str(key_pem), key, sizeof(key)) == 32);
|
ASSERT(mg_p256_parse_private_key(mg_str(key_pem), key, sizeof(key)) == 32);
|
||||||
ASSERT(mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) == 1);
|
ASSERT(mg_p256_compute_public_key(key, pub) == 1);
|
||||||
n = mg_jwt_sign_es256(&opts, jwt, sizeof(jwt));
|
n = mg_jwt_sign_es256(&opts, jwt, sizeof(jwt));
|
||||||
ASSERT(n > 0 && n + 1 < sizeof(jwt));
|
ASSERT(n > 0 && n + 1 < sizeof(jwt));
|
||||||
jwt[n] = '\0';
|
jwt[n] = '\0';
|
||||||
@@ -4666,7 +4783,7 @@ static void test_crypto(void) {
|
|||||||
test_sha1();
|
test_sha1();
|
||||||
test_sha256();
|
test_sha256();
|
||||||
test_sha384();
|
test_sha384();
|
||||||
test_x25519();
|
test_p256();
|
||||||
test_rsa();
|
test_rsa();
|
||||||
test_jwt();
|
test_jwt();
|
||||||
}
|
}
|
||||||
@@ -4992,6 +5109,20 @@ static void test_dash(void) {
|
|||||||
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
|
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
|
||||||
ASSERT(cmpbody(buf, login_admin_expected) == 0);
|
ASSERT(cmpbody(buf, login_admin_expected) == 0);
|
||||||
|
|
||||||
|
// OTA uses streaming and enforces the dashboard's authentication.
|
||||||
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
|
"POST /api/ota HTTP/1.0\nContent-Length: 1\n\nx") == 403);
|
||||||
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
|
"GET /api/ota HTTP/1.0\nContent-Length: 0\n"
|
||||||
|
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 405);
|
||||||
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
|
"POST /ota HTTP/1.0\nContent-Length: 1\n"
|
||||||
|
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 404);
|
||||||
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
|
"POST /api/ota HTTP/1.0\nContent-Length: 1\n"
|
||||||
|
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 200);
|
||||||
|
ASSERT(cmpbody(buf, "1 ok\n") == 0);
|
||||||
|
|
||||||
// set3 is admin-only
|
// set3 is admin-only
|
||||||
ASSERT(fetch(&mgr, buf, url,
|
ASSERT(fetch(&mgr, buf, url,
|
||||||
"GET /api/get/set3 HTTP/1.0\n"
|
"GET /api/get/set3 HTTP/1.0\n"
|
||||||
|
|||||||
+5
-4
@@ -9,13 +9,13 @@ The fastest way to quickly get Mongoose running on your hardware is to follow ou
|
|||||||
|
|
||||||
Steps to get the most out of this code base:
|
Steps to get the most out of this code base:
|
||||||
|
|
||||||
- Most code here has a README.md file that points to a verbose tutorial describing how to get the intended functionality out of Mongoose Network Library. Code there runs as is in Windows, Ubuntu Linux, and Mac (very few cases don't run in Windows).
|
- Most workstation code here has a README.md file that points to a verbose tutorial describing how to get the intended functionality out of Mongoose Network Library. Code there runs as is in Windows, Ubuntu Linux, and Mac (very few cases don't run in Windows).
|
||||||
- Navigate your way to the desired protocol or function, read the README.md file
|
- Navigate your way to the desired protocol or function, read the README.md file
|
||||||
- Follow the linked tutorial
|
- Follow the linked tutorial
|
||||||
- In cases were there is no tutorial:
|
- In cases were there is no tutorial:
|
||||||
- Follow the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial to setup your development environment.
|
- Follow the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial to setup your development environment.
|
||||||
- run `make all`
|
- run `make all`
|
||||||
- Embedded hardware tutorials are conveniently linked from OS tutorials, or grouped by vendor and board.
|
- Embedded hardware tutorials are grouped by vendor and board.
|
||||||
- Navigate your way to the desired board or the closest one, read the README.md file. In most cases you'll find a link to our [Wizard](https://mongoose.ws/wizard/); for others, there is a link to a [Tutorial](https://mongoose.ws/documentation/#tutorials), as described at the top of this text. The Wizard will also link you to the tutorials you'll need.
|
- Navigate your way to the desired board or the closest one, read the README.md file. In most cases you'll find a link to our [Wizard](https://mongoose.ws/wizard/); for others, there is a link to a [Tutorial](https://mongoose.ws/documentation/#tutorials), as described at the top of this text. The Wizard will also link you to the tutorials you'll need.
|
||||||
- In cases were there you don't find a link to a tutorial:
|
- In cases were there you don't find a link to a tutorial:
|
||||||
- Follow the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial to setup your development environment.
|
- Follow the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial to setup your development environment.
|
||||||
@@ -28,7 +28,8 @@ Steps to get the most out of this code base:
|
|||||||
Rules for creating a new example:
|
Rules for creating a new example:
|
||||||
|
|
||||||
- Makefile golden reference for desktop/server examples: [http-server](http/http-server/)
|
- Makefile golden reference for desktop/server examples: [http-server](http/http-server/)
|
||||||
- Makefile golden reference for embedded examples: [stm32/nucleo-f746zg-baremetal-builtin-rndis](stm32/nucleo-f746zg-baremetal-builtin-rndis/)
|
- Makefile golden reference for embedded examples: [stm32/nucleo-f746zg/minimal](stm32/nucleo-f746zg/minimal/)
|
||||||
|
- Desktop/server examples compile `$(REPO_ROOT)/mongoose.c` and add `$(REPO_ROOT)` to their include path
|
||||||
- Examples must build on Windows, Mac and Ubuntu Linux systems
|
- Examples must build on Windows, Mac and Ubuntu Linux systems
|
||||||
- Assume that users installed their tools according to the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial
|
- Assume that users installed their tools according to the [Build Tools](https://mongoose.ws/documentation/tutorials/tools/) tutorial
|
||||||
- Makefiles must not include other Makefiles
|
- Makefiles must not include other Makefiles
|
||||||
@@ -37,5 +38,5 @@ Rules for creating a new example:
|
|||||||
- Use `$(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)` to compile
|
- Use `$(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA)` to compile
|
||||||
- If an external repository is required, download it on demand using git shallow clone. See the embedded examples golden reference
|
- If an external repository is required, download it on demand using git shallow clone. See the embedded examples golden reference
|
||||||
- Keep Makefiles as short as possible, but verbose to understand them easily
|
- Keep Makefiles as short as possible, but verbose to understand them easily
|
||||||
- Symlink files when required, like `mongoose.c`, `ca.pem`, etc. Make no copies
|
- Symlink files when required, like `ca.pem`, etc. Make no copies
|
||||||
- Examples README.md files should contain only the title and the link to the corresponding mongoose.ws tutorial page
|
- Examples README.md files should contain only the title and the link to the corresponding mongoose.ws tutorial page
|
||||||
@@ -1 +0,0 @@
|
|||||||
../http/http-server/arduino/esp32-http
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 743 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user