Compare commits

...
114 Commits
Author SHA1 Message Date
Sergio R. Caprile 02bdbb9ed6 update version 2026-08-12 09:41:29 -03:00
robertc2000 84553d06a0 Merge pull request #3662 from cesanta/flash-unlock
Flash unlock validation on STM32
2026-08-11 16:41:57 +03:00
Sergio R. Caprile de7cd48a79 fix typorenthesis 2026-08-11 10:03:03 -03:00
Sergio R. Caprile 5df2445bc9 Merge commit from fork
fix TCP opt parser
2026-08-11 09:20:36 -03:00
Sergio R. Caprile 90890e4c63 Merge commit from fork
strenghten len checks
2026-08-11 09:16:05 -03:00
Sergio R. Caprile a6deed5868 Merge commit from fork
fix missing check
2026-08-11 09:10:46 -03:00
Sergio R. Caprile 5cdb25f05a Merge commit from fork
fix too short length guard
2026-08-11 09:05:40 -03:00
Sergio R. Caprile d8764746a4 Merge commit from fork
verify chain in bundle
2026-08-11 09:00:21 -03:00
robert fa4a2563e5 Flash unlock validation on STM32 2026-08-11 11:48:19 +03:00
Sergio R. Caprile 9ccfc02a86 Merge pull request #3661 from cesanta/bsd++
make BSD C++ compatible,survive to FreeRTOS not being so
2026-08-10 11:45:02 -03:00
Sergio R. Caprile f58f29b9a0 make BSD C++ compatible,survive to FreeRTOS not being so 2026-08-10 11:40:10 -03:00
Sergio R. Caprile 282ae0d968 add compiler matrix, verbose log 2026-08-07 16:21:46 -03:00
robert 735038c98c fix tests 2026-08-07 17:35:39 +03:00
robert 2696c1ff94 OTA: strenghten checks and introduce HTTPS support 2026-08-07 17:20:26 +03:00
Sergio R. Caprile 9f16806a22 Merge pull request #3648 from cesanta/bsd
BSD layer: Fix handling of full queues, add error handling
2026-08-05 10:47:00 -03:00
Sergey Lyubka 8d1b9047b6 Merge pull request #3657 from cesanta/dscp
Add support for DSCP
2026-08-05 12:11:40 +01:00
Sergio R. Caprile 2988bc9df3 verify chain in bundle 2026-08-04 11:59:22 -03:00
robert 5e733a1461 Add scan timeout 2026-08-04 11:43:08 +03:00
robertc2000 34813493a4 Merge pull request #3658 from cesanta/claude
Update Claude scanning
2026-08-04 11:11:29 +03:00
robert f220223947 Update Claude scanning 2026-08-04 10:47:16 +03:00
Sergio R. Caprile 758e301f0c fix too short length guard 2026-08-03 14:19:13 -03:00
cpq 7fa830fc0c New project instructions 2026-08-03 14:12:38 +01:00
cpq fc699a0b0b Nits 2026-08-02 12:12:48 +01:00
Sergio R. Caprile 4e3e7ac4b2 update stale example 2026-07-31 13:48:15 -03:00
Sergio R. Caprile 2838c85342 move nightly test start time 2026-07-30 21:11:12 -03:00
Sergio R. Caprile bc17f1667b retry failed night tests 2026-07-30 21:03:40 -03:00
Sergio R. Caprile 309cb1006f ephemeral stuff 2026-07-30 18:36:29 -03:00
Sergio R. Caprile 501c5a4bcd ephemeral stuff 2026-07-30 18:01:34 -03:00
Sergio R. Caprile 765bb237b7 Add support for DSCP 2026-07-30 17:36:46 -03:00
Sergio R. Caprile b0f5e9cec4 Merge pull request #3656 from cesanta/pppfcs
Add mg_crc16(), make PPP use it
2026-07-30 09:53:53 -03:00
Sergio R. Caprile 125b79bd70 Merge pull request #3654 from cesanta/misc
remove redundant checks, clarify algorithm
2026-07-30 09:52:50 -03:00
Sergio R. Caprile 7821fb9ab4 Add mg_crc16(), make PPP use it 2026-07-29 17:57:30 -03:00
Sergio R. Caprile 03be16803c Merge pull request #3655 from cesanta/todo
misc todo's and related fixes
2026-07-29 17:18:53 -03:00
Sergio R. Caprile 3c11521446 misc todo's and related fixes 2026-07-29 16:06:29 -03:00
Sergio R. Caprile 4faa3d5bf1 remove redundant checks 2026-07-29 13:50:32 -03:00
Sergio R. Caprile bfdc14ce0a Merge pull request #3653 from cesanta/synconn
fix corner case
2026-07-29 09:58:14 -03:00
Sergio R. Caprile ac9ebdfb1a fix corner case 2026-07-28 20:21:28 -03:00
Sergio R. Caprile 85d77ec3ac group embedded tests 2026-07-28 16:29:31 -03:00
Sergio R. Caprile 9b7d318df5 Merge pull request #3651 from cesanta/aton6
improve mg_aton6 and tests
2026-07-28 14:48:06 -03:00
Sergio R. Caprile 6cbd810206 improve mg_aton6 and tests 2026-07-28 14:46:16 -03:00
Sergio R. Caprile 771f5f2ed9 Merge pull request #3650 from cesanta/wolf
fix WolfSSL not working on built-in TCP/IP
2026-07-28 13:08:45 -03:00
Sergio R. Caprile 6b8d8b5caa fix WolfSSL not working on built-in TCP/IP 2026-07-28 12:12:33 -03:00
Sergio R. Caprile ba942dbd7a Fix handling of full queues, add error handling 2026-07-27 17:27:03 -03:00
Sergio R. Caprile 2db7119080 Merge pull request #3649 from cesanta/frt
handle (more) FreeRTOS proprietary return codes
2026-07-27 17:13:33 -03:00
Sergio R. Caprile 6a3599682b handle (more) FreeRTOS proprietary return codes 2026-07-27 17:12:21 -03:00
Sergio R. Caprile 77e0faf020 Merge pull request #3647 from cesanta/jwt
fix warnings (and uncovered latent bug)
2026-07-27 11:34:47 -03:00
Sergio R. Caprile 08e01cb312 fix warnings (and uncovered latent bug) 2026-07-27 10:35:04 -03:00
Sergey Lyubka d8fa55e1d6 Merge pull request #3644 from cesanta/mcxupresso-ota-rollback
Add ARCH_MCUXPRESSO and OTA rollback for IMXRT boards
2026-07-27 11:32:56 +01:00
Sergio R. Caprile 61638caeae Merge pull request #3643 from cesanta/mdns
Add SD query and response to mDNS
2026-07-24 11:50:09 -03:00
Sergio R. Caprile f78cb1b02f Merge pull request #3642 from cesanta/jwt
Add JWT Bearer Authentication support and tutorial
2026-07-24 11:49:46 -03:00
Sergio R. Caprile f8e785f56b Merge pull request #3634 from 94xhn/fix/json-hex-validation
Reject malformed input in mg_json_get_hex
2026-07-24 11:36:31 -03:00
robert e473f2189c Add ARCH_MCUXPRESSO and OTA rollback for IMXRT boards 2026-07-24 14:27:49 +03:00
Sergio R. Caprile 66eac28c20 Add SD query and response to mDNS 2026-07-22 16:58:13 -03:00
Sergio R. Caprile cdd957572e Add JWT Bearer Authentication support and tutorial 2026-07-21 14:25:03 -03:00
robertc2000 af7eaaa9b1 Merge pull request #3641 from cesanta/esp32-ota
Add ESP32 OTA rollback support
2026-07-20 11:55:46 +03:00
robert 8d7bbda48d Add ESP32 OTA rollback support 2026-07-20 10:50:03 +03:00
Sergio R. Caprile 17a25373db fix fuzzer 2026-07-17 10:02:59 -03:00
Sergio R. Caprile b86f255407 Merge pull request #3637 from cesanta/misc
misc check strenghtens and debug guards
2026-07-16 15:16:43 -03:00
Sergio R. Caprile cb57182ee0 misc check strenghtens and debug guards 2026-07-16 15:10:19 -03:00
Sergey Lyubka 72ff0dfb07 Merge pull request #3633 from cesanta/len
harden some len checks
2026-07-16 12:00:21 +01:00
94xhn dc52d8b1dc fix(json): reject malformed hex input
Reject odd-length and non-hex strings before decoding them.

Tested: 1796 ASan/UBSan tests in amalgamated and unamalgamated builds
2026-07-16 08:33:06 +08:00
Sergio R. Caprile e2e2db8900 Merge pull request #3636 from cesanta/timegm
remove duplicated epoch handling, test it
2026-07-15 15:59:08 -03:00
Sergio R. Caprile b6a353cae9 remove duplicated epoch handling, test it 2026-07-15 15:53:38 -03:00
Sergio R. Caprile cc7c6d2d18 fix TCP opt parser 2026-07-14 10:29:43 -03:00
robert 7a2a3fc136 update Claude prompt 2026-07-14 10:44:56 +03:00
Sergio R. Caprile 2ec27994f4 harden some len checks 2026-07-13 17:51:50 -03:00
Sergio R. Caprile bc69625a5e unbreak tests 2026-07-13 17:04:46 -03:00
Sergio R. Caprile a8b4ae95db fix missing check 2026-07-13 14:52:26 -03:00
Sergio R. Caprile f52e4f4bb0 Merge pull request #3632 from cesanta/bsdbe
Add big-endian tests for our BSD layer
2026-07-10 18:30:52 -03:00
Sergio R. Caprile 0aa25dbd18 Add big-endian tests for our BSD layer 2026-07-10 18:28:40 -03:00
Sergio R. Caprile 72a0787161 Merge pull request #3628 from cesanta/tlssntp
make tls_init request boot time if it is 0
2026-07-10 11:47:30 -03:00
Sergio R. Caprile 37234ad26f make tls_init request boot time if it is 0 2026-07-10 11:40:39 -03:00
Sergio R. Caprile 96050c2587 Merge pull request #3629 from cesanta/bigendian
Add test targets for PowerPC and MIPS (bigendian)
2026-07-10 11:30:37 -03:00
Sergio R. Caprile 09254c9e64 Add PowerPC and MIPS (bigendian) 2026-07-10 11:26:46 -03:00
Sergey Lyubka a43599c081 Merge pull request #3631 from cesanta/c89diab
add more C89-like targets; fix related stuff
2026-07-10 10:29:32 +01:00
Sergio R. Caprile f391c4eea0 add more C89-like targets; fix related stuff 2026-07-09 16:26:30 -03:00
Sergio R. Caprile 6709c028aa Merge pull request #3630 from cesanta/nozutest
mdash/mingw does not support %zu
2026-07-09 12:02:46 -03:00
Sergio R. Caprile f84598f66a mdash/mingw does not support %zu 2026-07-09 11:57:22 -03:00
Sergio R. Caprile 02625d3bb6 minor nit 2026-07-09 11:54:41 -03:00
Sergio R. Caprile 957b9a991c Merge pull request #3627 from cesanta/z
Add %zu printf support
2026-07-08 10:56:32 -03:00
cpq 875c0fcd4f Add %zu printf support 2026-07-07 12:37:47 +01:00
Sergio R. Caprile ad269fa4d5 Merge pull request #3625 from cesanta/tests
add real life testing for PPPoE and VLANs
2026-07-03 14:45:50 -03:00
Sergio R. Caprile 9448625662 add real life testing for PPPoE and VLANs 2026-07-03 14:45:20 -03:00
Sergio R. Caprile ba2abbc444 Merge pull request #3614 from cesanta/bsd
fixes and additions for BSD
2026-07-03 09:52:47 -03:00
Sergio R. Caprile a865890a09 make bsd tests go live 2026-07-03 09:28:30 -03:00
Sergio R. Caprile ad2ffa8e45 fixes and additions for BSD 2026-07-03 09:26:24 -03:00
Sergio R. Caprile b424eb79cf fix typo 2026-07-02 18:43:04 -03:00
Sergio R. Caprile ac8fe85478 Merge pull request #3624 from cesanta/fuzz
improve fuzzing
2026-07-02 15:58:19 -03:00
Sergio R. Caprile 38b6b13547 improve fuzzing 2026-07-02 15:57:11 -03:00
Sergio R. Caprile 9bc4d9741e Merge pull request #3621 from cesanta/tests
add tests for ATcmd driver, fix some warnings
2026-07-02 10:19:13 -03:00
Sergio R. Caprile cee80a8391 add tests for ATcmd driver, fix some warnings 2026-07-02 10:15:50 -03:00
robert 9d3926a25a Improve OTA boundary handling 2026-07-02 12:22:39 +03:00
robert 28cd3439dd Update false-positive filtering 2026-07-02 11:12:43 +03:00
robertc2000 6fcf32f203 Merge pull request #3618 from cesanta/freertos-tcp-sock
Handle no pending connections in FreeRTOS TCP
2026-07-02 09:38:36 +03:00
robert 9879f4d715 Handle no pending connections in FreeRTOS TCP 2026-07-02 09:36:58 +03:00
Sergio R. Caprile 8fb3aa26cd Merge pull request #3620 from cesanta/tests
simplify eth and ppp tests, add IPV6 testing for VC targets
2026-07-01 16:20:48 -03:00
Sergio R. Caprile dfbbe5fa8b simplify eth and ppp tests, add IPV6 testing for VC targets 2026-07-01 16:19:38 -03:00
Sergio R. Caprile 751f14742d strenghten len checks 2026-07-01 14:13:31 -03:00
Sergio R. Caprile 8bb65c6492 Merge pull request #3619 from cesanta/rsaverify
delegate RSA verify to the RSA module
2026-07-01 13:01:57 -03:00
Sergio R. Caprile f8816d67c3 delegate RSA verify to the RSA module 2026-07-01 12:31:11 -03:00
Sergio R. Caprile d897b32c64 fix order of amalgamated headers 2026-07-01 11:53:42 -03:00
Sergio R. Caprile f055e45755 Merge pull request #3617 from cesanta/tests
add C89 and bigendian tests for L2, be for MIP
2026-06-30 15:56:46 -03:00
Sergio R. Caprile 5bf953cb4a add C89 and bigendian tests for L2, be for MIP 2026-06-30 14:37:58 -03:00
Sergio R. Caprile 8e7f5ab00e Merge pull request #3616 from cesanta/tests
add tests for L2
2026-06-30 11:49:33 -03:00
Sergio R. Caprile e5e13ff156 add tests for L2 2026-06-30 11:41:33 -03:00
Sergio R. Caprile fdcefb087e Merge pull request #3615 from cesanta/tests
fuzz 802.1Q and PPP[oE]
2026-06-29 21:22:44 -03:00
Sergio R. Caprile 699067ff58 fuzz 802.1Q and PPP[oE] 2026-06-29 17:19:27 -03:00
Sergio R. Caprile ed19d5290e Merge pull request #3610 from cesanta/hostnoca
Allow hostname checking even when no CA cert was given
2026-06-26 14:12:09 -03:00
Sergio R. Caprile 848ef126a3 Merge pull request #3613 from cesanta/tests
add tests for MIP BSD
2026-06-26 13:45:22 -03:00
Sergio R. Caprile 80670e9ebc add tests for MIP BSD 2026-06-26 12:31:05 -03:00
Sergio R. Caprile b607a4209a Update SBOM for 7.22 2026-06-25 11:11:29 -03:00
cpq b3ab9c618e Unit test under Win32 2026-06-25 10:32:56 +01:00
cpq 8c0b6e335e Unit test under Win32 2026-06-25 10:22:13 +01:00
Sergio R. Caprile d8e629a048 Allow hostname checking even when no CA cert was given 2026-06-22 14:51:24 -03:00
97 changed files with 6645 additions and 1240 deletions

No files matched your search

@@ -69,6 +69,7 @@ jobs:
- name: Run Claude full codebase security scan
id: claude_scan
uses: anthropics/claude-code-action@v1
timeout-minutes: 30
with:
anthropic_api_key: ${{ secrets.CLAUDE_API_KEY }}
@@ -94,9 +95,8 @@ jobs:
Do not create GitHub issues.
claude_args: |
--max-turns 20
--allowedTools "Read,Glob,Grep,Bash(git:*),Bash(find:*),Bash(grep:*),Bash(sed:*),Bash(cat:*)"
--model claude-opus-4-8
--model claude-opus-5
- name: Prepare private security scan outputs
if: always() && steps.claude_scan.outputs.execution_file != ''
@@ -111,4 +111,4 @@ jobs:
claude_args: |
--allowedTools "Read,Bash(cat:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr comment:*)"
--model claude-opus-4-8
--model claude-opus-5
+90 -4
View File
@@ -12,13 +12,99 @@ jobs:
strategy:
fail-fast: false
matrix:
target: [fuzz, fuzz_tls]
name: ${{ matrix.target }}
ipv6: [0, 1] # fuzz code always feeds IPv6, so this fuzzes ignoring while going parallel in sync
name: fuzz IPV6=${{ matrix.ipv6 }}
env:
TFLAGS: -DMG_ENABLE_IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} ARGS="-max_total_time=14400"
- name: Restore Combined Fuzzing Corpus
uses: dawidd6/action-download-artifact@v21
with:
name: combined-fuzz-corpus
path: test/corpus_dir
branch: ${{ github.base_ref || github.ref_name }}
search_artifacts: true
if_no_artifact_found: warn
- name: Run Matrix Fuzzers
run: mkdir -p test/corpus_dir && make -C test fuzz ARGS="corpus_dir -seed=${{ github.run_id }} -max_total_time=14400"
# Keep failures split by matrix leg so the offending input is easy to find.
- name: Upload Fuzz Failure Artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-failure-ipv6-${{ matrix.ipv6 }}
path: |
test/corpus_dir
test/crash-*
test/leak-*
test/oom-*
test/timeout-*
if-no-files-found: ignore
retention-days: 14
# v4 artifacts are immutable, so each parallel leg uploads its own corpus.
- name: Upload Corpus Shard
uses: actions/upload-artifact@v7
with:
name: fuzz-corpus-ipv6-${{ matrix.ipv6 }}
path: test/corpus_dir
retention-days: 14
# as we're not getting access to test units causing a problem, convert from the log:
merge_fuzz_corpus:
runs-on: ubuntu-latest
needs: fuzz
name: merge fuzz corpus
steps:
# Merge both parallel fuzzing corpuses into the shared seed for next run.
- name: Merge Corpus Shards
uses: actions/upload-artifact/merge@v7
with:
name: combined-fuzz-corpus
pattern: fuzz-corpus-ipv6-*
delete-merged: true
retention-days: 14
# see NOTE
fuzz_tls:
runs-on: ubuntu-latest
name: fuzz_tls
env:
TFLAGS: -DMG_ENABLE_IPV6=0
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- name: Restore Fuzzing Corpus
uses: actions/cache/restore@v6
with:
path: test/corpus_dir
key: fuzz-corpus-${{ github.head_ref || github.ref_name }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ github.head_ref || github.ref_name }}-
fuzz-corpus-main-
fuzz-corpus-
- name: Run Fuzzer
run: mkdir -p test/corpus_dir && make -C test fuzz_tls ARGS="corpus_dir -seed=${{ github.run_id }} -max_total_time=14400"
- name: Upload Fuzz TLS Failure Artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-tls-failure-artifacts
path: |
test/corpus_dir
test/crash-*
test/leak-*
test/oom-*
test/timeout-*
if-no-files-found: ignore
retention-days: 14
- name: Save Updated Corpus to Cache
uses: actions/cache/save@v6
with:
path: test/corpus_dir
key: fuzz-corpus-${{ github.head_ref || github.ref_name }}-${{ github.run_id }}
# NOTE
# In case we can't get access to test units causing a problem, convert from the log:
# base64 -d > dataofdeath
# then paste the base64 data, enter, Ctrl-D; maybe check with hexdump -C dataofdeath
+368 -83
View File
@@ -1,7 +1,7 @@
name: Full build
on:
schedule:
- cron: '0 22 * * *' # run at 10 PM UTC
- cron: '17 22 * * *' # run at 10:17 PM UTC
# Allow manual runs
workflow_dispatch:
env:
@@ -51,6 +51,25 @@ jobs:
cat log
test/health.awk < log > 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:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [diabish, ppc_diabish]
ssl: ["", BUILTIN]
name: C89 ${{ matrix.target }} SSL=${{ matrix.ssl }}
env:
SSL: ${{ matrix.ssl }}
TFLAGS: -DMG_ENABLE_POLL=0 -DMG_ENABLE_EPOLL=0 -DMG_ENABLE_CHACHA20=0 -DMQTT_LOCALHOST -DNO_ZU_TEST
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: | # GH runners seem to overflow the 32-bit FS for the QEMU PPC (works for me in RHEL9)
if [[ "${{ matrix.target }}" = ppc_* ]]; then export TFLAGS="$TFLAGS -D_FILE_OFFSET_BITS=64"; fi
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 ${{ matrix.target }}
mip:
runs-on: ubuntu-latest
strategy:
@@ -59,10 +78,6 @@ jobs:
cc: [gcc, clang, g++, clang++]
target: [mip_test, mip_tap_test]
ssl: ["", BUILTIN, MBEDTLS, OPENSSL, WOLFSSL]
# #3226: built-in TCP is currently not working with WolfSSL (builds fine)
exclude:
- ssl: WOLFSSL
target: mip_tap_test
name: ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }}
env:
CC: ${{ matrix.cc }}
@@ -75,25 +90,222 @@ jobs:
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
# WolfSSL version (5.6.6) in current GH runner (Ubuntu 24.04) is broken for EC
# https://github.com/wolfSSL/wolfssl/pull/10065
- name: Build non-broken WolfSSL version
if: matrix.ssl == 'WOLFSSL'
run: |
sudo apt -y update
sudo apt -y install autoconf automake libtool
git clone --depth 1 --branch v5.9.1-stable https://github.com/wolfSSL/wolfssl
cd wolfssl
./autogen.sh
./configure --prefix="$GITHUB_WORKSPACE/wolfssl-install" --enable-all
make -j"$(nproc)"
make install
echo "WOLFSSL=$GITHUB_WORKSPACE/wolfssl-install" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$GITHUB_WORKSPACE/wolfssl-install/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
if [ "${{ matrix.cc }}" = "g++" ] || [ "${{ matrix.cc }}" = "clang++" ]; then
echo "TFLAGS=$TFLAGS -DWOLFSSL_USE_OPTIONS_H -Wno-pedantic" >> "$GITHUB_ENV"
else
echo "TFLAGS=$TFLAGS -DWOLFSSL_USE_OPTIONS_H" >> "$GITHUB_ENV"
fi
- run: if [ "${{ matrix.target }}" == "mip_tap_test" ]; then source ./test/setup_ga_network.sh ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh; fi && sudo apt -y update ; sudo apt -y install libmbedtls-dev libwolfssl-dev && make -C test ${{ matrix.target }} > log
- if: success() || failure()
run: |
cat log
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json"
mip89:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1 # no parallel runs, to minimize MQTT errors
matrix:
target: [mip_vc98]
name: mip89 ${{ matrix.target }}
target: [mip_vc98, mip_diabish, mip_ppc_diabish]
ipv6: [0, 1]
name: MIP C89 ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} IPV6=1
- 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:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [mip_s390, mip_port_s390, mip_ppc, mip_port_ppc, mip_mips, mip_port_mips]
ipv6: [0, 1]
ssl: ["", BUILTIN]
name: MIP be ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }}
env:
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: |
sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
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
l2:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang, g++, clang++]
target: [eth_test, ppp_test]
ipv6: [0, 1]
name: L2 ${{ matrix.target }} CC=${{ matrix.cc }} IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} CC=${{ matrix.cc }} > log
- if: success() || failure()
run: |
cat log
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
l289:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [eth_vc98, ppp_vc98]
ipv6: [0, 1]
name: L2 C89 ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
l2be:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [eth_s390, ppp_s390]
ipv6: [0, 1]
name: L2 be ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
pppoe_vlan: # run tests, build tests performed by the l2 job may not catch runtime alignment and protocol issues, etc.
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang, g++, clang++]
ipv6: [0, 1]
name: PPPoE over VLAN CC=${{ matrix.cc }} IPV6=${{ matrix.ipv6 }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: |
source ./test/setup_ga_network.sh
make -C test pppoe_tap_test CC=${{ matrix.cc }} IPV6=${{ matrix.ipv6 }} > log
- if: success() || failure()
run: |
cat log
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/pppoe-vlan_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
atcmd:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang, g++, clang++]
name: ATcmd CC=${{ matrix.cc }}
env:
IPV6: 0 # makes no sense, shorten code
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: make -C test atcmd_test CC=${{ matrix.cc }} > log
- if: success() || failure()
run: |
cat log
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"
atcmd89:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [atcmd_vc98]
name: ATcmd C89 ${{ matrix.target }}
env:
IPV6: 0 # makes no sense, shorten code
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }}
atcmdbe:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [atcmd_s390]
name: ATcmd be ${{ matrix.target }}
env:
IPV6: 0 # makes no sense, shorten code
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test ${{ matrix.target }}
bsd:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang, g++, clang++]
name: bsd CC=${{ matrix.cc }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: |
source ./test/setup_ga_network.sh
make -C test/bsd shm_tap_bridge
test/bsd/shm_tap_bridge &
sleep 2
make -C test/bsd CC=${{ matrix.cc }} > log
- if: success() || failure()
run: |
cat log
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
bsdbe:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [bsd_s390]
name: bsd be ${{ matrix.target }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: |
sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
source ./test/setup_ga_network.sh
make -C test port_tap_bridge
test/port_tap_bridge &
sleep 2
make -C test/bsd ${{ matrix.target }}
s390:
runs-on: ubuntu-latest
strategy:
@@ -156,8 +368,6 @@ jobs:
exclude:
- ssl: MBEDTLS
select: -DMG_ENABLE_POLL=0
- ssl: MBEDTLS # see issue#2694
select: -DMG_ENABLE_POLL=1
- ssl: OPENSSL
select: -DMG_ENABLE_POLL=0
- ssl: WOLFSSL
@@ -188,7 +398,7 @@ jobs:
fail-fast: false
max-parallel: 1 # no parallel runs, to minimize MQTT errors
matrix:
target: [vc98, vc17, vc22, mingw, mingw++]
target: [vc98, vc17, vc22, mingw, mingw++] # current mdash/mingw image compiler does not have support for %zu; test disabled below
ssl: ["", BUILTIN]
select: [-DMG_ENABLE_POLL=0, -DMG_ENABLE_POLL=1]
exclude:
@@ -197,14 +407,14 @@ jobs:
name: windows ${{ matrix.target }} SSL=${{ matrix.ssl }} TFLAGS=${{ matrix.select }}
env:
SSL: ${{ matrix.ssl }}
TFLAGS: ${{ matrix.select }} -DMQTT_LOCALHOST -DNO_ABORT
TFLAGS: ${{ matrix.select }} -DMQTT_LOCALHOST -DNO_ABORT
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: ./test/setup_mqtt_server.sh && make -C test ${{ matrix.target }} > log
- run: if [ "${{ matrix.target }}" == "mingw" ]; then export TFLAGS="$TFLAGS -DNO_ZU_TEST"; fi && ./test/setup_mqtt_server.sh && make -C test ${{ matrix.target }} > log
- if: success() || failure()
run: |
cat log
@@ -318,41 +528,66 @@ jobs:
strategy:
fail-fast: false
matrix:
example:
- path: infineon/xmc47_relax-make-baremetal-builtin
- path: infineon/xmc_plt2go_4400-make-baremetal-builtin
- path: infineon/xmc72_evk-make-baremetal-builtin
- path: nxp/frdm-mcxn947-make-baremetal-builtin
- path: nxp/frdm-mcxn947-make-freertos-builtin
- path: nxp/rt1020-evk-make-baremetal-builtin
- path: nxp/rt1060-evk-make-baremetal-builtin
- path: nxp/rt1170-evk-make-baremetal-builtin
- path: rp/pico-w
- path: rp/w5500-evb-pico
- path: rp/w55rp20-evb-pico
- path: rp/pico-rndis
- path: stm32/nucleo-f429zi/minimal
- path: stm32/nucleo-f746zg/minimal
- path: stm32/nucleo-f746zg/minimal-pppoe
- path: stm32/nucleo-f756zg/minimal
- path: stm32/nucleo-h563zi/minimal
- path: stm32/nucleo-h723zg/minimal
- path: stm32/nucleo-h743zi/minimal
- path: stm32/nucleo-n657x0-q/minimal
- path: stm32/nucleo-u5a5zj-q/minimal
- path: stm32/portenta-h7/minimal
- path: stm32/stm32h573i-dk/minimal
- path: stm32/stm32h747i-disco/minimal
- path: ti/ek-tm4c1294xl-make-baremetal-builtin
- path: ti/ek-tm4c1294xl-make-freertos-builtin
name: ${{ matrix.example.path }}
include:
- group: infineon-nxp
examples: |
infineon/xmc47_relax-make-baremetal-builtin
infineon/xmc_plt2go_4400-make-baremetal-builtin
infineon/xmc72_evk-make-baremetal-builtin
nxp/frdm-mcxn947-make-baremetal-builtin
nxp/frdm-mcxn947-make-freertos-builtin
nxp/rt1020-evk-make-baremetal-builtin
nxp/rt1060-evk-make-baremetal-builtin
nxp/rt1170-evk-make-baremetal-builtin
- group: rp-ti
examples: |
rp/pico-w
rp/w5500-evb-pico
rp/w55rp20-evb-pico
rp/pico-rndis
ti/ek-tm4c1294xl-make-baremetal-builtin
ti/ek-tm4c1294xl-make-freertos-builtin
- group: stm32-1
examples: |
stm32/nucleo-f429zi/minimal
stm32/nucleo-f746zg/minimal
stm32/nucleo-f746zg/minimal-pppoe
stm32/nucleo-f756zg/minimal
stm32/nucleo-h563zi/minimal
stm32/nucleo-h723zg/minimal
- group: stm32-2
examples: |
stm32/nucleo-h743zi/minimal
stm32/nucleo-n657x0-q/minimal
stm32/nucleo-u5a5zj-q/minimal
stm32/portenta-h7/minimal
stm32/stm32h573i-dk/minimal
stm32/stm32h747i-disco/minimal
name: wizard ARM ${{ matrix.group }}
env:
GO: 0
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/${{ matrix.example.path }} build
- run: |
failed=0
sep=
examples='${{ matrix.examples }}'
for x in $examples; do
echo "========== $x =========="
if make -C "tutorials/$x" build; then
ok=true
echo "SUCCESS: $x"
else
ok=false
failed=1
echo "FAILURE: $x"
fi
printf 'HEALTH_DASHBOARD\t%s"%s": %s\n' "$sep" "$x" "$ok"
sep=,
done
exit $failed
generic_examples:
@@ -378,40 +613,63 @@ jobs:
strategy:
fail-fast: false
matrix:
example:
- path: mqtt/mqtt-client/microchip/same54-xpro
- path: nxp/nxp-frdmk66f-freertos
- path: nxp/nxp-lpcxpresso54s018m-freertos
- path: nxp/nxp-mimxrt1020-freertos
- path: nxp/nxp-evkbimxrt1050-lwip-freertos
- path: nxp/nxp-evkmimxrt1020-lwip-freertos
- path: nxp/nxp-evkmimxrt1024-lwip-freertos
- path: nxp/nxp-evkmimxrt1060-lwip-freertos
- path: nxp/nxp-evkmimxrt1064-lwip-freertos
- path: nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
- path: nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
- path: nxp/nxp-frdmk64f-lwip-freertos
- path: nxp/nxp-frdmk66f-lwip-freertos
- path: nxp/nxp-lpcxpresso54018-lwip-freertos
- path: nxp/nxp-lpcxpresso54608-lwip-freertos
- path: nxp/nxp-lpcxpresso54618-lwip-freertos
- path: nxp/nxp-lpcxpresso54628-lwip-freertos
- path: nxp/nxp-twrk65f180m-lwip-freertos
- path: nxp/nxp-twrkv58f220m-lwip-freertos
- path: rp/pico-rmii
- path: stm32/nucleo-f746zg/minimal-rm2
- path: stm32/nucleo-f746zg/minimal-rndis
- path: stm32/nucleo-f429zi/minimal-rm2
- path: stm32/nucleo-f429zi/minimal-rndis
- path: stm32/nucleo-g031k8/minimal-w5500
- path: ti/ti-ek-tm4c1294xl-http-server
- path: ti/ek-tm4c1294xl-make-baremetal-builtin-rndis
name: ${{ matrix.example.path }}
include:
- group: nxp-1
examples: |
nxp/nxp-frdmk66f-freertos
nxp/nxp-lpcxpresso54s018m-freertos
nxp/nxp-mimxrt1020-freertos
nxp/nxp-evkbimxrt1050-lwip-freertos
nxp/nxp-evkmimxrt1020-lwip-freertos
nxp/nxp-evkmimxrt1024-lwip-freertos
nxp/nxp-evkmimxrt1060-lwip-freertos
nxp/nxp-evkmimxrt1064-lwip-freertos
nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
- group: nxp-2
examples: |
nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
nxp/nxp-frdmk64f-lwip-freertos
nxp/nxp-frdmk66f-lwip-freertos
nxp/nxp-lpcxpresso54018-lwip-freertos
nxp/nxp-lpcxpresso54608-lwip-freertos
nxp/nxp-lpcxpresso54618-lwip-freertos
nxp/nxp-lpcxpresso54628-lwip-freertos
nxp/nxp-twrk65f180m-lwip-freertos
nxp/nxp-twrkv58f220m-lwip-freertos
- group: other
examples: |
mqtt/mqtt-client/microchip/same54-xpro
rp/pico-rmii
stm32/nucleo-f746zg/minimal-rm2
stm32/nucleo-f746zg/minimal-rndis
stm32/nucleo-f429zi/minimal-rm2
stm32/nucleo-f429zi/minimal-rndis
stm32/nucleo-g031k8/minimal-w5500
ti/ti-ek-tm4c1294xl-http-server
ti/ek-tm4c1294xl-make-baremetal-builtin-rndis
name: generic ARM ${{ matrix.group }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/${{ matrix.example.path }} build
- run: |
failed=0
sep=
examples='${{ matrix.examples }}'
for x in $examples; do
echo "========== $x =========="
if make -C "tutorials/$x" build; then
ok=true
echo "SUCCESS: $x"
else
ok=false
failed=1
echo "FAILURE: $x"
fi
printf 'HEALTH_DASHBOARD\t%s"%s": %s\n' "$sep" "$x" "$ok"
sep=,
done
exit $failed
device_dashboard_examples:
runs-on: ubuntu-latest
@@ -433,19 +691,46 @@ jobs:
strategy:
fail-fast: false
matrix:
ssl: ["", -DMG_TLS=MG_TLS_BUILTIN]
example:
- path: http/device-dashboard/microchip/same54-xpro
- path: nxp/rt1020-evk-make-freertos-builtin
- path: nxp/rt1060-evk-make-freertos-builtin
- path: nxp/rt1170-evk-make-freertos-builtin
- path: renesas/ek-ra6m4-make-baremetal-builtin
name: ${{ matrix.example.path }} ${{ matrix.ssl }}
include:
- group: none
cflags: ""
examples: |
http/device-dashboard/microchip/same54-xpro
nxp/rt1020-evk-make-freertos-builtin
nxp/rt1060-evk-make-freertos-builtin
nxp/rt1170-evk-make-freertos-builtin
renesas/ek-ra6m4-make-baremetal-builtin
- group: builtin
cflags: -DMG_TLS=MG_TLS_BUILTIN
examples: |
http/device-dashboard/microchip/same54-xpro
nxp/rt1020-evk-make-freertos-builtin
nxp/rt1060-evk-make-freertos-builtin
nxp/rt1170-evk-make-freertos-builtin
renesas/ek-ra6m4-make-baremetal-builtin
name: device dashboard ARM ${{ matrix.group }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/${{ matrix.example.path }} build CFLAGS_EXTRA="${{ matrix.ssl }}"
- run: |
failed=0
sep=
examples='${{ matrix.examples }}'
for x in $examples; do
echo "========== $x =========="
if make -C "tutorials/$x" build CFLAGS_EXTRA="${{ matrix.cflags }}"; then
ok=true
echo "SUCCESS: $x"
else
ok=false
failed=1
echo "FAILURE: $x"
fi
printf 'HEALTH_DASHBOARD\t%s"%s": %s\n' "$sep" "$x" "$ok"
sep=,
done
exit $failed
# mqtt_dashboard_examples:
# runs-on: ubuntu-latest
+23
View File
@@ -0,0 +1,23 @@
name: Retry failed nightly jobs
on:
schedule:
- cron: '17 2 * * *'
workflow_dispatch:
permissions:
actions: write
jobs:
retry:
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ github.token }}
run: |
min=$(date -u -d '5 hours ago' +%s)
max=$(date -u -d '2 hours ago' +%s)
gh api --paginate "/repos/$GITHUB_REPOSITORY/actions/workflows/nightly.yml/runs?status=failure&per_page=100" --jq '.workflow_runs[] | select(.run_attempt == 1) | [.id, .updated_at] | @tsv' |
while IFS=$'\t' read -r id updated; do
when=$(date -u -d "$updated" +%s)
if [ "$when" -ge "$min" ] && [ "$when" -le "$max" ]; then
gh api --method POST "/repos/$GITHUB_REPOSITORY/actions/runs/$id/rerun-failed-jobs"
fi
done
+27 -15
View File
@@ -4,35 +4,47 @@ on:
env:
IPV6: 0
jobs:
mip:
atcmd:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc]
target: [mip_tap_test]
ssl: ["", BUILTIN, MBEDTLS, OPENSSL, WOLFSSL]
# #3226: built-in TCP is currently not working with WolfSSL (builds fine)
exclude:
- ssl: WOLFSSL
target: mip_tap_test
name: ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }}
env:
CC: ${{ matrix.cc }}
SSL: ${{ matrix.ssl }}
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT # -DMQTT_HOST_NAME=\"server\"
cc: [gcc, clang, g++, clang++]
name: ATcmd CC=${{ matrix.cc }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.HEALTH_TESTS_SSH_KEY }}
- run: if [ "${{ matrix.target }}" == "mip_tap_test" ]; then source ./test/setup_ga_network.sh ; export IPV6=1; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh; else export IPV6=1 ; fi && sudo apt -y update ; sudo apt -y install libmbedtls-dev libwolfssl-dev && make -C test ${{ matrix.target }} > log
- run: make -C test atcmd_test CC=${{ matrix.cc }} > log
- if: success() || failure()
run: |
cat log
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json"
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
atcmd89:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [atcmd_vc98]
name: ATcmd C89 ${{ matrix.target }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }}
atcmdbe:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [atcmd_s390]
name: ATcmd be ${{ matrix.target }}
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test ${{ matrix.target }}
# macos:
+7 -1
View File
@@ -16,8 +16,13 @@ jobs:
fail-fast: false
matrix:
cc: [gcc, clang++]
target: [test, mip_test]
target: [test, mip_test, eth_test, ppp_test]
ssl: ["", BUILTIN]
exclude:
- target: eth_test
ssl: BUILTIN
- target: ppp_test
ssl: BUILTIN
name: linux ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }}
env:
CC: ${{ matrix.cc }}
@@ -152,6 +157,7 @@ jobs:
- path: stm32/nucleo-f746zg/minimal
- path: stm32/nucleo-h563zi/minimal
- path: stm32/nucleo-h723zg/minimal
- path: stm32/nucleo-h723zg/minimal-bsd
- path: stm32/nucleo-h743zi/minimal
- path: stm32/stm32h573i-dk/minimal
- path: ti/ek-tm4c1294xl-make-baremetal-builtin
+40 -7
View File
@@ -26,6 +26,8 @@ GitHub repo: https://github.com/cesanta/mongoose
functionality such as HTTP, MQTT, Modbus, or device-dashboard logic.
- Once Mongoose is integrated, desktop examples from `tutorials/http`,
`tutorials/mqtt`, and similar directories also work on embedded systems.
- For STM32, NXP IMXRT, RP2040/2350 use Mongoose's built-in TCP/IP stack
- When building an STM32 project from scratch, use the closest tutorials/stm32/*/cube/*.ioc file as a base
## API prefix map
@@ -49,14 +51,17 @@ function signatures, structs, options, examples, and constraints.
- `MG_OTA_*`, `mg_ota_*` - firmware OTA support
## Integration
## How to integrate Mongoose into an existing project
Copy `mongoose.h` and `mongoose.c` into a `mongoose/` subdirectory of the
project and add `mongoose/mongoose.c` to the build. The easiest is to
copy from GitHub:
Create `mongoose/` directory in your project.
Download `mongoose.h` and `mongoose.c` into a `mongoose/` directory.
https://raw.githubusercontent.com/cesanta/mongoose/refs/heads/master/mongoose.c
https://raw.githubusercontent.com/cesanta/mongoose/refs/heads/master/mongoose.h
```sh
curl --fail --silent --create-dirs -o mongoose/mongoose.c https://raw.githubusercontent.com/cesanta/mongoose/refs/heads/master/mongoose.c
curl --fail --silent --create-dirs -o mongoose/mongoose.h https://raw.githubusercontent.com/cesanta/mongoose/refs/heads/master/mongoose.h
```
Add `mongoose/mongoose.c` to the build.
**Desktop/server** (Linux, macOS, Windows): two files are sufficient.
@@ -89,6 +94,27 @@ Minimal `mongoose_config.h` should set `MG_ARCH`. For example, for STM32:
#define MG_ARCH MG_ARCH_CUBE
```
## How to generate a new STM32 project from scratch
Download and unzip the pre-generated project which is the closest to your MCU:
- https://mongoose.ws/downloads/nucleo-h723zg-dashboard-full.zip
- https://mongoose.ws/downloads/nucleo-f429zi-dashboard-full.zip
- https://mongoose.ws/downloads/nucleo-h563zi-dashboard-full.zip
- https://mongoose.ws/downloads/nucleo-f756zg-dashboard-full.zip
- https://mongoose.ws/downloads/nucleo-n657x0-q-dashboard-full.zip
- https://mongoose.ws/downloads/nucleo-u5a5zj-q-dashboard-full.zip
- https://mongoose.ws/downloads/portenta-h7-dashboard-full.zip
## How to generate a new RP2040 / RP2350 project from scratch
Download and unzip the pre-generated project which is the closest to your MCU:
- https://mongoose.ws/downloads/w5500-evb-pico-dashboard-full.zip
- https://mongoose.ws/downloads/w55rp20-evb-pico-dashboard-full.zip
- https://mongoose.ws/downloads/pico-w-dashboard-full.zip
- https://mongoose.ws/downloads/pico-rndis-dashboard-full.zip
## Core API
```c
@@ -204,9 +230,11 @@ device dashboard.
### Required files
The following files must exist at these exact paths, regardless of the build environment:
```
your_project/
├── main.c # your code
├── ... # IDE-specific project scaffolding
└── mongoose/
├── mongoose.h # single header
├── mongoose.c # single source file
@@ -394,6 +422,11 @@ Each fieldset is exported via the get/set JSON-RPC interface, as well via the RE
Do not use the API directly, dashboard.js interfaces with the UI via the data-* attributes.
The dashboard backend must expose these exact functions: mg_dash_init() and mg_dash_poll().
Do not rename, wrap, relocate, or replace these files or functions. Do not introduce alternatives such as app_dashboard_init(), app_dashboard_poll(), web/dashboard.html, or Core/Src/dashboard.c.
Board-specific STM32Cube files may exist outside mongoose/, but the Mongoose dashboard files must retain the structure and API documented above.
## Filesystem
+1212 -461
View File
File diff suppressed because it is too large. Load diff
+307 -24
View File
@@ -20,7 +20,7 @@
#ifndef MONGOOSE_H
#define MONGOOSE_H
#define MG_VERSION "7.22"
#define MG_VERSION "7.23"
#ifdef __cplusplus
extern "C" {
@@ -46,6 +46,7 @@ extern "C" {
#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
#define MG_ARCH_ARMCGT 15 // Texas Semi ARM-CGT
#define MG_ARCH_CUBE 16 // STM32Cube environment
#define MG_ARCH_MCUXPRESSO 17 // MCUXpresso environment
#define MG_ARCH_NEWLIB MG_ARCH_ARMGCC // Alias, deprecate in 2025
@@ -314,6 +315,11 @@ extern "C" {
#include <esp_timer.h> // amalgamation ditching them
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
#include <esp_ota_ops.h>
#include <esp_task_wdt.h>
#include <soc/soc.h>
#include <soc/rtc_cntl_reg.h>
#define MG_PATH_MAX 128
@@ -325,6 +331,57 @@ extern "C" {
#define MG_ENABLE_DIRLIST 1
#endif
#ifndef MG_OTA_ESP32_WDT_TIMEOUT_MS
#define MG_OTA_ESP32_WDT_TIMEOUT_MS 10000U
#endif
static inline void mg_ota_esp32_timer_start(void) {
#if ESP_IDF_VERSION_MAJOR >= 5
esp_task_wdt_config_t cfg = {0};
esp_err_t err;
cfg.timeout_ms = MG_OTA_ESP32_WDT_TIMEOUT_MS;
cfg.idle_core_mask = 0;
cfg.trigger_panic = true;
err = esp_task_wdt_init(&cfg);
if (err == ESP_ERR_INVALID_STATE) esp_task_wdt_reconfigure(&cfg);
#else
esp_task_wdt_init(MG_OTA_ESP32_WDT_TIMEOUT_MS / 1000U, true);
#endif
esp_task_wdt_add(NULL);
esp_task_wdt_reset();
}
static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
if (state == 0) esp_ota_mark_app_valid_cancel_rollback();
REG_WRITE(RTC_CNTL_STORE0_REG, state);
return REG_READ(RTC_CNTL_STORE0_REG);
}
#define MG_OTA_STATE_GET() \
REG_READ(RTC_CNTL_STORE0_REG)
#define MG_OTA_STATE_SET(v) mg_ota_esp32_state_set((uint32_t) (v))
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() mg_ota_esp32_timer_start()
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() \
do { \
(void) esp_task_wdt_reset(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK
#define MG_OTA_ROLLBACK() \
do { \
(void) esp_ota_mark_app_invalid_rollback_and_reboot(); \
} while (0)
#endif
#endif
@@ -354,6 +411,158 @@ extern "C" {
#endif
#if MG_ARCH == MG_ARCH_MCUXPRESSO
#define _POSIX_TIMERS
#include <ctype.h>
#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
#include <errno.h>
#endif
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.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 <unistd.h>
// MCUXpresso-generated header, includes the SoC peripheral definitions.
// NOTE: use angle brackets to prevent amalgamator ditching it
#include <fsl_device_registers.h>
#ifndef MG_PATH_MAX
#define MG_PATH_MAX 100
#endif
#ifndef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 0
#endif
#ifndef MG_ENABLE_TCPIP
#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack
#endif
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \
!defined(MG_ENABLE_DRIVER_IMXRT11)
#if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \
defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \
defined(CPU_MIMXRT1062DVL6A) || defined(CPU_MIMXRT1064DVL6A) || \
defined(CPU_MIMXRT1062DVMAA_cm7) || defined(CPU_MIMXRT1062DVL6A_cm7) || \
defined(CPU_MIMXRT1062DVL6B_cm7) || defined(_MIMXRT1021_H_) || \
defined(_MIMXRT1024_H_) || defined(_MIMXRT1042_H_) || \
defined(_MIMXRT1052_H_) || defined(_MIMXRT1062_H_) || \
defined(_MIMXRT1064_H_)
#define MG_ENABLE_DRIVER_IMXRT10 1
#elif defined(CPU_MIMXRT1176DVMAA_cm7) || defined(CPU_MIMXRT1176DVMAA_cm4) || \
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_)
#define MG_ENABLE_DRIVER_IMXRT11 1
#else
#error Select an Ethernet driver in mongoose_config.h
#endif
#endif
#ifndef MG_TLS
#define MG_TLS MG_TLS_BUILTIN
#endif
#if !defined(MG_OTA) && \
(defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(_MIMXRT1021_H_) || defined(_MIMXRT1024_H_))
#define MG_OTA MG_OTA_RT1020
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1052DVL6B) || defined(_MIMXRT1052_H_))
#define MG_OTA MG_OTA_RT1050
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1062DVL6A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(_MIMXRT1042_H_) || \
defined(CPU_MIMXRT1062DVMAA_cm7) || defined(CPU_MIMXRT1062DVL6A_cm7) || \
defined(CPU_MIMXRT1062DVL6B_cm7) || defined(_MIMXRT1062_H_))
#define MG_OTA MG_OTA_RT1060
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1064DVL6B) || defined(CPU_MIMXRT1064DVL6A) || \
defined(_MIMXRT1064_H_))
#define MG_OTA MG_OTA_RT1064
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1176DVMAA_cm7) || defined(CPU_MIMXRT1176DVMAA_cm4) || \
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_))
#define MG_OTA MG_OTA_RT1170
#endif
#ifndef MG_IRAM
#define MG_IRAM __attribute__((noinline, section(".data_RAM2")))
#endif
#ifndef MG_SET_MAC_ADDRESS
#if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900)
#else
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x401f4410)
#endif
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = (MG_OCOTP_FUSES[0] >> 0) & 255; \
mac[2] = (MG_OCOTP_FUSES[0] >> 10) & 255; \
mac[3] = ((MG_OCOTP_FUSES[0] >> 19) ^ (MG_OCOTP_FUSES[4] >> 19)) & 255; \
mac[4] = (MG_OCOTP_FUSES[4] >> 10) & 255; \
mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \
} while (0)
#endif
#ifndef MG_IMXRT_WDOG1_TIMEOUT_MS
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000
#endif
#define MG_IMXRT_WDOG1_FEED() \
do { \
WDOG1->WSR = 0x5555; \
WDOG1->WSR = 0xaaaa; \
} while (0)
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
uint16_t wt_ = (uint16_t) ((MG_IMXRT_WDOG1_TIMEOUT_MS / 500) - 1); \
if ((WDOG1->WCR & WDOG_WCR_WDE_MASK) == 0) { \
WDOG1->WMCR = 0; \
WDOG1->WCR = (uint16_t) (WDOG_WCR_WDZST_MASK | WDOG_WCR_WDBG_MASK | \
WDOG_WCR_WDE_MASK | WDOG_WCR_WDT_MASK | \
WDOG_WCR_SRS_MASK | WDOG_WCR_WDW_MASK | \
WDOG_WCR_WT(wt_)); \
} \
MG_IMXRT_WDOG1_FEED(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() MG_IMXRT_WDOG1_FEED()
#endif
#ifndef MG_OTA_STATE_GET
#define MG_OTA_STATE_GET() SNVS->LPGPR[0]
#endif
#ifndef MG_OTA_STATE_SET
#define MG_OTA_STATE_SET(v) \
do { \
if (SNVS->LPSR & (1u << 3)) { \
SNVS->LPLVDR = 0x41736166u; \
SNVS->LPSR = 1u << 3; \
} \
SNVS->LPGPR[0] = (uint32_t) (v); \
} while (0)
#endif
#endif
#if MG_ARCH == MG_ARCH_PICOSDK
#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
#include <errno.h>
@@ -778,6 +987,7 @@ typedef int socklen_t;
#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
#define sleep(x) Sleep((x) *1000)
#define mkdir(a, b) _mkdir(a)
#define rmdir(a) _rmdir(a)
#define timegm(x) _mkgmtime(x)
#ifndef S_ISDIR
@@ -896,7 +1106,8 @@ static inline void *mg_calloc(size_t cnt, size_t size) {
((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
(errcode) == -pdFREERTOS_ERRNO_EISCONN || \
(errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
(errcode) == -pdFREERTOS_ERRNO_EAGAIN)
(errcode) == -pdFREERTOS_ERRNO_EAGAIN || \
(errcode) == -pdFREERTOS_ERRNO_ENOSPC)
#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
@@ -1731,6 +1942,9 @@ void mg_free(void *ptr);
// sensitive data (keys, passwords).
void mg_bzero(volatile unsigned char *buf, size_t len);
// Fixed-length constant-time byte equality. Use for MACs, tags, signatures.
bool mg_memeq(const void *a, const void *b, size_t n);
// Fills buf with len cryptographically random bytes. Uses the best available
// hardware or OS source (hardware RNG, /dev/urandom, CryptGenRandom, etc.).
// Falls back to rand() with an error log if no strong source is available.
@@ -1746,6 +1960,10 @@ char *mg_random_str(char *buf, size_t len);
// a new checksum; pass the result of a prior call to extend over more data.
uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
// Computes CRC16 (HDLC, polynomial 0x8408) over buf/len. Pass crc=0 to start
// a new checksum; pass the result of a prior call to extend over more data.
uint16_t mg_crc16(uint16_t crc, const char *buf, size_t len);
// Returns true if path is safe to serve from the filesystem. Rejects paths
// that start with '~' or '..', or contain a '/../' component, to prevent
// directory traversal attacks.
@@ -1755,6 +1973,9 @@ bool mg_path_is_sane(const struct mg_str path);
// calling context; avoid in event handlers.
void mg_delayms(unsigned int ms);
uint64_t mg_timegm(unsigned int year, unsigned int month, unsigned int day,
unsigned int hour, unsigned int min, unsigned int sec);
// Packs four byte values into a uint32_t in big-endian order.
// MG_U32(1, 2, 3, 4) == 0x01020304
#define MG_U32(a, b, c, d) \
@@ -1975,6 +2196,9 @@ size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
size_t mg_base64_final(char *buf, size_t len);
size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *buf,
size_t);
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t);
@@ -2158,6 +2382,10 @@ struct mg_connection {
void *pfn_data; // Protocol-level handler argument
char data[MG_DATA_SIZE]; // Scratch space for protocol state; freely readable
void *tls; // TLS state (internal)
unsigned dscp : 6; // Differentiated Services Code Point
#define MG_DSCP_CS(class_) ((class_) << 3)
#define MG_DSCP_AF(class_, drop_) (((class_) << 3) | ((drop_) << 1))
#define MG_DSCP_EF 46
unsigned is_listening : 1; // Listening connection; accepts inbound connections
unsigned is_client : 1; // Outbound connection created by mg_connect()
unsigned is_accepted : 1; // Inbound connection accepted from a listener
@@ -2238,6 +2466,11 @@ struct mg_connection *mg_connect(struct mg_mgr *, const char *url,
struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
mg_event_handler_t fn, void *fn_data);
// Sets the 6-bit Differentiated Services Code Point for outgoing datagrams.
// Call on MG_EV_OPEN for UDP listeners, MG_EV_CONNECT for clients, or
// MG_EV_ACCEPT for TCP servers, before sending application data.
bool mg_dscp(struct mg_connection *, uint8_t dscp);
// Called internally after DNS resolution completes to create the OS socket
// and initiate the TCP/IP connect. Normally not called by user code.
void mg_connect_resolved(struct mg_connection *);
@@ -2608,6 +2841,12 @@ void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *);
#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30
#if MG_TLS == MG_TLS_BUILTIN
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
// KEY input. Accepts PEM or DER. Returns key size, or 0 on error.
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
#endif
// Low-level IO primitives used by TLS layer
enum { MG_IO_ERR = -1, MG_IO_WAIT = -2 };
long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
@@ -2808,28 +3047,28 @@ if 'dest' was filled with random data, or 0 if the random data could not be
generated. The filled-in values should be either truly random, or from a
cryptographically-secure PRNG.
A correctly functioning RNG function must be set (using mg_uecc_set_rng())
before calling mg_uecc_make_key() or mg_uecc_sign().
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().
A correct RNG function is set by default when building for Windows, Linux, or OS
X. If you are building on another POSIX-compliant system that supports
/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
RNG. For embedded platforms there is no predefined RNG function; you must
provide your own.
*/
typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
// 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.
On platforms where there is no predefined RNG function (eg embedded platforms),
this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
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.
@@ -3362,7 +3601,7 @@ int mg_uecc_generate_random_int(mg_uecc_word_t *random,
#if MG_TLS == MG_TLS_BUILTIN
#if MG_TLS == MG_TLS_BUILTIN && MG_ENABLE_CHACHA20
#ifndef __PORTABLE_8439_H
#define __PORTABLE_8439_H
#if defined(__cplusplus)
@@ -3478,6 +3717,8 @@ int mg_rsa_crt_sign(const uint8_t *em, size_t em_len,
const uint8_t *q, size_t q_len,
const uint8_t *qInv, size_t qInv_len,
uint8_t *signature, size_t sig_len);
bool mg_rsa_pkcs_verify(const uint8_t *em, size_t nlen, const uint8_t *hash,
size_t hashlen);
bool mg_rsa_verify(const uint8_t *em, size_t nlen, const uint8_t *mhash);
#endif // TLS_RSA_H
@@ -3506,13 +3747,14 @@ struct mg_tls {
mbedtls_x509_crt cert; // Parsed certificate
mbedtls_pk_context pk; // Private key context
mbedtls_ssl_context ssl; // SSL/TLS context
mbedtls_ssl_config conf; // SSL-TLS config
mbedtls_ssl_config conf; // SSL/TLS config
#ifdef MBEDTLS_SSL_SESSION_TICKETS
mbedtls_ssl_ticket_context ticket; // Session tickets context
#endif
// https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29
unsigned char *throttled_buf; // see #3074
size_t throttled_len;
bool check_name; // set when hostname was set, but no CA certificate given
};
#endif
@@ -3521,11 +3763,14 @@ struct mg_tls {
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
struct mg_tls {
BIO_METHOD *bm;
SSL_CTX *ctx;
SSL *ssl;
char *name; // matching hostname
bool check_name; // set when hostname was set, but no CA certificate given
};
#endif
@@ -3954,11 +4199,12 @@ struct mg_mdns_req {
bool is_unicast; // True if the client requested a unicast (QU) response
};
// ev_data for MG_EV_MDNS_RESP, carrying the resolved address from an mDNS response.
// ev_data for MG_EV_MDNS_RESP, carrying address/data from a mDNS response
struct mg_mdns_resp {
struct mg_dns_rr *rr; // Resource record from the response
struct mg_dns_rr *rr; // Resource record from the response (1st in chain)
struct mg_str name; // Resolved hostname, without the .local suffix
struct mg_addr addr; // Resolved IP address
struct mg_dnssd_record sd; // Service Discovery data
};
// Parses a DNS query or response from buf/len into dm. Returns true on success.
@@ -3975,6 +4221,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
// Creates an mDNS listener on UDP multicast 224.0.0.251:5353. Registers it as
// the manager's mDNS resolver (mgr->mdns). Fires MG_EV_MDNS_REQ for incoming
// queries and MG_EV_MDNS_RESP for incoming responses. Returns NULL on error.
// fn_data is a NUL-terminated server name, or NULL to pass all requests to fn.
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
void *fn_data);
@@ -4097,7 +4344,7 @@ char *mg_json_get_str(struct mg_str json, const char *path);
// Decodes a hex-encoded JSON string at path into a heap-allocated byte array.
// Sets *len to the decoded byte count. Caller must mg_free() the result.
// Returns NULL if not found or not a string.
// Returns NULL if not found, not a string, or not valid hexadecimal.
char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
// Decodes a base64-encoded JSON string at path into a heap-allocated byte
@@ -4133,6 +4380,33 @@ size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
struct mg_jwt_opts {
struct mg_str claims; // JSON payload
struct mg_str header; // Extra header members, no braces; alg/typ are set
struct mg_str kid; // Key ID protected header
struct mg_str secret; // HS256 secret
const uint8_t *private_key; // ES256 private key (MG_TLS_BUILTIN only)
const uint8_t *public_key; // ES256 public key (MG_TLS_BUILTIN only)
};
// Signs JSON claims as a compact JWT. Returns JWT length, or 0 on error.
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
// Verifies JWT and decodes JSON claims into buf. Returns claims length, or 0.
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
// In-flight JSON-RPC request. Populate and pass to mg_rpc_process() to
// dispatch, or receive one pre-filled in a handler registered with mg_rpc_add().
struct mg_rpc_req {
@@ -4354,7 +4628,7 @@ extern void mg_mqtt_poll(struct mg_mgr *);
//
// This is a low-level OTA API, not intended to be called directly by users.
// Users are provided with a higher-level API:
// - defining MG_OTA_URL enables HTTP pull-based updates
// - defining MG_OTA_URL enables HTTP or HTTPS pull-based updates
// - mg_http_start_ota() enables push-based updates
//
// However, it is possible to use the mg_ota_* API directly. Below is the
@@ -4501,8 +4775,10 @@ enum {
} \
} while (0)
// Pull-based OTA over HTTP. Fetches metadata_url, which must return a JSON
// object like: { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 }
// Pull-based OTA over HTTP or HTTPS. If it's HTTPS, MG_OTA_TLS_CA
// needs to be defined. Fetches metadata_url, which must
// return a JSON object like:
// { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 }
// If the server version differs from MG_OTA_FIRMWARE_VERSION, downloads
// FIRMWARE_URL and performs the OTA update. fn is called on every outcome:
// NULL on successful flash, "Same version" when already up to date, or an
@@ -4512,12 +4788,18 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url,
void (*fn)(const char *error_message));
// Firmware info URL for mg_ota_poll() which calls mg_ota_url_check().
// Example: "http://mongoose.ws/ota/u/0/ota.json". See http://mongoose.ws/ota/
// Example: "http://mongoose.ws/ota/u/0/ota.json". See http://mongoose.ws/ota/
// Settable in mongoose_config.h
#ifndef MG_OTA_URL
#define MG_OTA_URL NULL
#endif
// PEM or DER CA certificate used to authenticate an HTTPS OTA server. Set to
// NULL to use HTTP. Set in mongoose_config.h.
#ifndef MG_OTA_TLS_CA
#define MG_OTA_TLS_CA NULL
#endif
// OTA status callback function for mg_ota_poll()
#ifndef MG_OTA_STATUS_FN
#define MG_OTA_STATUS_FN NULL
@@ -4547,8 +4829,9 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url,
// E.g. on STM32, uses the 96-bit MCU UID converted to a hex string.
void mg_ota_device_id(char *buf, size_t len);
// Checks for a firmware update over HTTP. Called automatically by mg_mgr_poll()
// when MG_OTA_URL is set in mongoose_config.h. Do not call directly.
// Checks for a firmware update over HTTP or HTTPS. Called automatically by
// mg_mgr_poll() when MG_OTA_URL is set in mongoose_config.h. Do not call
// directly.
void mg_ota_poll(struct mg_mgr *);
@@ -4624,7 +4907,7 @@ bool mg_wifi_ap_stop(void);
#if MG_ENABLE_TCPIP
// no config defaults to 0 => Ethernet
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE };
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER };
#if defined(__DCC__)
#pragma pack(1)
@@ -63,3 +63,5 @@
29. Keep custom backend findings only when the issue is in Mongoose’s backend interface, default backend, documented backend contract, or common backend implementation.
30. When uncertain, keep findings with clear external input, reachable code path, and concrete impact in Mongoose library/protocol/driver code; filter internal-access, impossible-state, local-misuse, generic-hardening, and speculative reports.
31. For dashboard reports, do not infer unauthenticated access from a route-specific check alone; account for the broader authentication flow. Keep concrete reachable bugs such as path traversal, unsafe decoded paths, or file read/write/delete issues.
32. Filter reports regarding DNS transaction ID predictability or lack of strict question-name matching in DNS response processing, as these are acknowledged design limitations of the current implementation.
33. Filter reports that unauthenticated SNTP time affects TLS certificate validity when Mongoose only performs the SNTP sync if mg_boot_timestamp_ms has not already been initialized.
@@ -19,6 +19,15 @@
# SECURITY CATEGORIES TO EXAMINE
## Mandatory High-Risk Coverage: Built-In TLS
The built-in TLS implementation (`MG_TLS_BUILTIN`) is a mandatory high-risk review target. Perform a substantial security analysis of all security-relevant built-in TLS code, including parsing, cryptographic processing, authentication and certificate validation, length and buffer handling, protocol state, cross-call behavior, and failure paths.
Trace attacker-controlled TLS peer input through normal client and server execution, including traffic received before peer authentication is complete. Report any credible memory-safety, authentication, confidentiality, integrity, state-corruption, or remotely triggerable availability vulnerability.
Do not set `review_completed` to `true` unless the built-in TLS implementation has been meaningfully inspected as a whole.
**C Memory Safety and Length-Handling Vulnerabilities:**
- Look for writes to fixed-size stack or heap buffers where the loop bound is derived from attacker-controlled protocol fields, including topic counts, header counts, chunk counts, multipart parts, DNS labels, WebSocket fragments, TCP/IP options, or filesystem path components.
- Check all conversions between `size_t`, `int`, `long`, `uint16_t`, `uint32_t`, and signed protocol lengths. Flag integer truncation, wraparound, negative-to-large conversion, or off-by-one behavior that can affect allocation, parsing, copying, or bounds checks.
+4 -4
View File
@@ -5,20 +5,20 @@
"serialNumber": "urn:uuid:d3f8b1e2-4a7c-4d9e-bf2a-1c3e5f7a9b0d",
"version": 1,
"metadata": {
"timestamp": "2026-06-01T00:00:00Z",
"timestamp": "2026-08-12T00:00:00Z",
"component": {
"type": "library",
"bom-ref": "mongoose",
"supplier": { "name": "Cesanta Software Limited", "url": ["https://cesanta.com"] },
"author": "Cesanta Software Limited",
"name": "mongoose",
"version": "7.21",
"version": "7.23",
"description": "Two-file C/C++ embedded networking library. HTTP, WebSocket, MQTT, TLS 1.3, OTA firmware updates, device dashboard. Runs on microcontrollers and desktop OS.",
"licenses": [
{ "expression": "GPL-2.0-only" },
{ "license": { "name": "Cesanta Commercial License", "url": "https://mongoose.ws/licensing/" } }
],
"purl": "pkg:github/cesanta/mongoose@7.21",
"purl": "pkg:github/cesanta/mongoose@7.23",
"externalReferences": [
{ "type": "website", "url": "https://mongoose.ws" },
{ "type": "vcs", "url": "https://github.com/cesanta/mongoose" },
@@ -27,7 +27,7 @@
{ "type": "distribution", "url": "https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c" }
],
"properties": [
{ "name": "cpe", "value": "cpe:2.3:a:cesanta:mongoose:7.21:*:*:*:*:*:*:*" }
{ "name": "cpe", "value": "cpe:2.3:a:cesanta:mongoose:7.23:*:*:*:*:*:*:*" }
]
}
},
+1
View File
@@ -19,6 +19,7 @@
#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
#define MG_ARCH_ARMCGT 15 // Texas Semi ARM-CGT
#define MG_ARCH_CUBE 16 // STM32Cube environment
#define MG_ARCH_MCUXPRESSO 17 // MCUXpresso environment
#define MG_ARCH_NEWLIB MG_ARCH_ARMGCC // Alias, deprecate in 2025
+56
View File
@@ -21,6 +21,11 @@
#include <esp_timer.h> // amalgamation ditching them
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
#include <esp_ota_ops.h>
#include <esp_task_wdt.h>
#include <soc/soc.h>
#include <soc/rtc_cntl_reg.h>
#define MG_PATH_MAX 128
@@ -32,4 +37,55 @@
#define MG_ENABLE_DIRLIST 1
#endif
#ifndef MG_OTA_ESP32_WDT_TIMEOUT_MS
#define MG_OTA_ESP32_WDT_TIMEOUT_MS 10000U
#endif
static inline void mg_ota_esp32_timer_start(void) {
#if ESP_IDF_VERSION_MAJOR >= 5
esp_task_wdt_config_t cfg = {0};
esp_err_t err;
cfg.timeout_ms = MG_OTA_ESP32_WDT_TIMEOUT_MS;
cfg.idle_core_mask = 0;
cfg.trigger_panic = true;
err = esp_task_wdt_init(&cfg);
if (err == ESP_ERR_INVALID_STATE) esp_task_wdt_reconfigure(&cfg);
#else
esp_task_wdt_init(MG_OTA_ESP32_WDT_TIMEOUT_MS / 1000U, true);
#endif
esp_task_wdt_add(NULL);
esp_task_wdt_reset();
}
static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
if (state == 0) esp_ota_mark_app_valid_cancel_rollback();
REG_WRITE(RTC_CNTL_STORE0_REG, state);
return REG_READ(RTC_CNTL_STORE0_REG);
}
#define MG_OTA_STATE_GET() \
REG_READ(RTC_CNTL_STORE0_REG)
#define MG_OTA_STATE_SET(v) mg_ota_esp32_state_set((uint32_t) (v))
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() mg_ota_esp32_timer_start()
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() \
do { \
(void) esp_task_wdt_reset(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK
#define MG_OTA_ROLLBACK() \
do { \
(void) esp_ota_mark_app_invalid_rollback_and_reboot(); \
} while (0)
#endif
#endif
+152
View File
@@ -0,0 +1,152 @@
#pragma once
#if MG_ARCH == MG_ARCH_MCUXPRESSO
#define _POSIX_TIMERS
#include <ctype.h>
#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
#include <errno.h>
#endif
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.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 <unistd.h>
// MCUXpresso-generated header, includes the SoC peripheral definitions.
// NOTE: use angle brackets to prevent amalgamator ditching it
#include <fsl_device_registers.h>
#ifndef MG_PATH_MAX
#define MG_PATH_MAX 100
#endif
#ifndef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 0
#endif
#ifndef MG_ENABLE_TCPIP
#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack
#endif
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \
!defined(MG_ENABLE_DRIVER_IMXRT11)
#if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \
defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \
defined(CPU_MIMXRT1062DVL6A) || defined(CPU_MIMXRT1064DVL6A) || \
defined(CPU_MIMXRT1062DVMAA_cm7) || defined(CPU_MIMXRT1062DVL6A_cm7) || \
defined(CPU_MIMXRT1062DVL6B_cm7) || defined(_MIMXRT1021_H_) || \
defined(_MIMXRT1024_H_) || defined(_MIMXRT1042_H_) || \
defined(_MIMXRT1052_H_) || defined(_MIMXRT1062_H_) || \
defined(_MIMXRT1064_H_)
#define MG_ENABLE_DRIVER_IMXRT10 1
#elif defined(CPU_MIMXRT1176DVMAA_cm7) || defined(CPU_MIMXRT1176DVMAA_cm4) || \
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_)
#define MG_ENABLE_DRIVER_IMXRT11 1
#else
#error Select an Ethernet driver in mongoose_config.h
#endif
#endif
#ifndef MG_TLS
#define MG_TLS MG_TLS_BUILTIN
#endif
#if !defined(MG_OTA) && \
(defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(_MIMXRT1021_H_) || defined(_MIMXRT1024_H_))
#define MG_OTA MG_OTA_RT1020
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1052DVL6B) || defined(_MIMXRT1052_H_))
#define MG_OTA MG_OTA_RT1050
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1062DVL6A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(_MIMXRT1042_H_) || \
defined(CPU_MIMXRT1062DVMAA_cm7) || defined(CPU_MIMXRT1062DVL6A_cm7) || \
defined(CPU_MIMXRT1062DVL6B_cm7) || defined(_MIMXRT1062_H_))
#define MG_OTA MG_OTA_RT1060
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1064DVL6B) || defined(CPU_MIMXRT1064DVL6A) || \
defined(_MIMXRT1064_H_))
#define MG_OTA MG_OTA_RT1064
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1176DVMAA_cm7) || defined(CPU_MIMXRT1176DVMAA_cm4) || \
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_))
#define MG_OTA MG_OTA_RT1170
#endif
#ifndef MG_IRAM
#define MG_IRAM __attribute__((noinline, section(".data_RAM2")))
#endif
#ifndef MG_SET_MAC_ADDRESS
#if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900)
#else
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x401f4410)
#endif
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = (MG_OCOTP_FUSES[0] >> 0) & 255; \
mac[2] = (MG_OCOTP_FUSES[0] >> 10) & 255; \
mac[3] = ((MG_OCOTP_FUSES[0] >> 19) ^ (MG_OCOTP_FUSES[4] >> 19)) & 255; \
mac[4] = (MG_OCOTP_FUSES[4] >> 10) & 255; \
mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \
} while (0)
#endif
#ifndef MG_IMXRT_WDOG1_TIMEOUT_MS
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000
#endif
#define MG_IMXRT_WDOG1_FEED() \
do { \
WDOG1->WSR = 0x5555; \
WDOG1->WSR = 0xaaaa; \
} while (0)
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
uint16_t wt_ = (uint16_t) ((MG_IMXRT_WDOG1_TIMEOUT_MS / 500) - 1); \
if ((WDOG1->WCR & WDOG_WCR_WDE_MASK) == 0) { \
WDOG1->WMCR = 0; \
WDOG1->WCR = (uint16_t) (WDOG_WCR_WDZST_MASK | WDOG_WCR_WDBG_MASK | \
WDOG_WCR_WDE_MASK | WDOG_WCR_WDT_MASK | \
WDOG_WCR_SRS_MASK | WDOG_WCR_WDW_MASK | \
WDOG_WCR_WT(wt_)); \
} \
MG_IMXRT_WDOG1_FEED(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() MG_IMXRT_WDOG1_FEED()
#endif
#ifndef MG_OTA_STATE_GET
#define MG_OTA_STATE_GET() SNVS->LPGPR[0]
#endif
#ifndef MG_OTA_STATE_SET
#define MG_OTA_STATE_SET(v) \
do { \
if (SNVS->LPSR & (1u << 3)) { \
SNVS->LPLVDR = 0x41736166u; \
SNVS->LPSR = 1u << 3; \
} \
SNVS->LPGPR[0] = (uint32_t) (v); \
} while (0)
#endif
#endif
+1
View File
@@ -159,6 +159,7 @@ typedef int socklen_t;
#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
#define sleep(x) Sleep((x) *1000)
#define mkdir(a, b) _mkdir(a)
#define rmdir(a) _rmdir(a)
#define timegm(x) _mkgmtime(x)
#ifndef S_ISDIR
+49 -3
View File
@@ -12,16 +12,16 @@ static int mg_base64_encode_single(int c) {
}
}
static int mg_base64_decode_single(int c) {
static int base64_decode_single(int c, char plus, char slash) {
if (c >= 'A' && c <= 'Z') {
return c - 'A';
} else if (c >= 'a' && c <= 'z') {
return c + 26 - 'a';
} else if (c >= '0' && c <= '9') {
return c + 52 - '0';
} else if (c == '+') {
} else if (c == plus) {
return 62;
} else if (c == '/') {
} else if (c == slash) {
return 63;
} else if (c == '=') {
return 64;
@@ -30,6 +30,14 @@ static int mg_base64_decode_single(int c) {
}
}
static int mg_base64_decode_single(int c) {
return base64_decode_single(c, '+', '/');
}
static int mg_base64url_decode_single(int c) {
return base64_decode_single(c, '-', '_');
}
size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
unsigned long rem = (n & 3) % 3;
if (rem == 0) {
@@ -91,3 +99,41 @@ fail:
if (dl > 0) dst[0] = '\0';
return 0;
}
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *to,
size_t dl) {
size_t i, len = mg_base64_encode(p, n, to, dl);
if (len == 0) return 0;
for (i = 0; i < len; i++) {
if (to[i] == '+') {
to[i] = '-';
} else if (to[i] == '/') {
to[i] = '_';
}
}
while (len > 0 && to[len - 1] == '=') to[--len] = '\0';
return len;
}
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t dl) {
size_t i, len = 0;
unsigned int bits = 0, v = 0;
if (dl == 0 || (n & 3) == 1) goto fail;
for (i = 0; src != NULL && i < n; i++) {
int c = mg_base64url_decode_single(src[i]);
if (c == 64) break;
if (c < 0) goto fail;
v = (v << 6) | (unsigned int) c;
bits += 6;
if (bits >= 8) {
bits -= 8;
if (len + 1 >= dl) goto fail;
dst[len++] = (char) ((v >> bits) & 255U);
}
}
dst[len] = '\0';
return len;
fail:
if (dl > 0) dst[0] = '\0';
return 0;
}
+3
View File
@@ -4,3 +4,6 @@ size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
size_t mg_base64_final(char *buf, size_t len);
size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
size_t mg_base64url_encode(const unsigned char *p, size_t n, char *buf,
size_t);
size_t mg_base64url_decode(const char *src, size_t n, char *dst, size_t);
+274 -81
View File
@@ -2,6 +2,67 @@
#if MG_ENABLE_BSD_SOCKETS
// Queue-based BSD shim is currently TCP/SOCK_STREAM only. UDP/SOCK_DGRAM would
// need datagram boundaries, e.g. datagram queues or SOD/EOD framing.
// Unconnected UDP also needs per-packet peer addresses for sendto()/recvfrom().
// It is also IPv4-only: transports store sockaddr_in and build IPv4 URLs.
#ifndef MG_ENABLE_BSD_LOG
#define MG_ENABLE_BSD_LOG 0
#define bsd_log(type, tag, a, b, c, n)
#else
#define MG_BSD_LOG_SOCK 1
#define MG_BSD_LOG_ACCEPT 2
#define MG_BSD_LOG_CLOSE 3
#define MG_BSD_LOG_TRANSPORT 4
#define MG_BSD_LOG_RESULT 5
#define MG_BSD_LOG_CONNECT 6
// Type is always a constant, so optimised builds fold the switch to one log.
#define bsd_log(type, tag, a, b, c, n) \
do { \
switch (type) { \
case MG_BSD_LOG_SOCK: { \
struct mg_bsd_sock *log_s = (struct mg_bsd_sock *) (a); \
MG_DEBUG(("SOCK %s %ld %p", tag, (long) (n), log_s->t)); \
break; \
} \
case MG_BSD_LOG_ACCEPT: { \
struct mg_connection *log_mc = (struct mg_connection *) (a); \
if ((n) >= 0) { \
MG_DEBUG(("ACCEPT %lu %p %u %ld", log_mc->id, c, \
(unsigned) mg_ntohs(log_mc->rem.port), (long) (n))); \
} else { \
MG_DEBUG(("ACCEPT %lu %p %u", log_mc->id, c, \
(unsigned) mg_ntohs(log_mc->rem.port))); \
} \
break; \
} \
case MG_BSD_LOG_CLOSE: { \
struct mg_connection *log_mc = (struct mg_connection *) (a); \
if ((n) >= 0) MG_INFO(("CLOSE %s %lu %p %ld", tag, log_mc->id, b, \
(long) (n))); \
else MG_DEBUG(("CLOSE %s %lu %p", tag, log_mc->id, b)); \
break; \
} \
case MG_BSD_LOG_TRANSPORT: \
if ((n) >= 0) MG_INFO(("TRANSP %s %p %ld", tag, a, (long) (n))); \
else MG_DEBUG(("TRANSP %s %p", tag, a)); \
break; \
case MG_BSD_LOG_RESULT: \
MG_DEBUG(("RESULT %s %p %ld", tag, a, (long) (n))); \
break; \
case MG_BSD_LOG_CONNECT: \
if ((c) != NULL) { \
MG_DEBUG(("CONNECT %s %p %p %s %ld", tag, a, b, \
(const char *) (c), (long) (n))); \
} else { \
MG_DEBUG(("CONNECT %s %p %p %ld", tag, a, b, (long) (n))); \
} \
break; \
} \
} while (0)
#endif
struct mg_bsd_sock {
void *t; // opaque transport handle
int fd;
@@ -39,12 +100,12 @@ static void release_sock(int fd) {
if (*p) *p = (*p)->next;
}
int socket(int domain, int type, int proto) {
struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s));
if (!s) { errno = ENOMEM; return -1; }
s->t = mg_bsd_transport_new(domain, type, proto);
if (!s->t || alloc_sock(s) < 0) { free(s); errno = ENOMEM; return -1; }
if (!s->t) { free(s); return -1; }
if (alloc_sock(s) < 0) { mg_bsd_transport_free(s->t); free(s); errno = ENOMEM; return -1; }
s->domain = domain; s->type = type; s->proto = proto;
return s->fd;
}
@@ -66,11 +127,12 @@ int listen(int fd, int backlog) {
int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) {
struct mg_bsd_sock *ls = get(fd);
if (!ls) return -1;
struct sockaddr_in peer = {0};
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock);
if (!t) { if (ls->nonblock) errno = EAGAIN; return -1; }
if (!t) return -1; // errno was set by transport_accept()
struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns));
if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_free(t); free(ns); errno = ENOMEM; return -1; }
if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_close(t); free(ns); errno = ENOMEM; return -1; }
ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer;
if (addr && addrlen) {
size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen;
@@ -101,22 +163,16 @@ ssize_t recv(int fd, void *buf, size_t len, int flags) {
ssize_t sendto(int fd, const void *buf, size_t len, int flags,
const struct sockaddr *dest, socklen_t addrlen) {
(void) dest; (void) addrlen;
return send(fd, buf, len, flags);
(void) fd; (void) buf; (void) len; (void) flags; (void) dest; (void) addrlen;
errno = EPROTONOSUPPORT;
return -1;
}
ssize_t recvfrom(int fd, void *buf, size_t len, int flags,
struct sockaddr *src, socklen_t *addrlen) {
ssize_t n = recv(fd, buf, len, flags);
if (n > 0 && src && addrlen) {
struct mg_bsd_sock *s = get(fd);
if (s) {
size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen;
memcpy(src, &s->peer, sz);
*addrlen = (socklen_t) sizeof(s->peer);
}
}
return n;
(void) fd; (void) buf; (void) len; (void) flags; (void) src; (void) addrlen;
errno = EPROTONOSUPPORT;
return -1;
}
ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); }
@@ -125,7 +181,9 @@ ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); }
int close(int fd) {
struct mg_bsd_sock *s = get(fd);
if (!s) return -1;
bsd_log(MG_BSD_LOG_SOCK, "CLOSE", s, NULL, NULL, (long) fd);
mg_bsd_transport_close(s->t);
bsd_log(MG_BSD_LOG_SOCK, "FREE", s, NULL, NULL, (long) fd);
release_sock(fd);
free(s);
return 0;
@@ -193,7 +251,8 @@ void freeaddrinfo(struct addrinfo *res) { (void) res; }
#endif
int inet_pton(int af, const char *src, void *dst) {
struct mg_addr a = {0};
struct mg_addr a;
memset(&a, 0, sizeof(a));
if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; }
return 0;
}
@@ -208,7 +267,8 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
}
in_addr_t inet_addr(const char *cp) {
struct mg_addr a = {0};
struct mg_addr a;
memset(&a, 0, sizeof(a));
return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1;
}
@@ -239,6 +299,9 @@ uint32_t ntohl(uint32_t n) { return mg_htonl(n); }
#ifndef MG_BSD_Q_DEPTH
#define MG_BSD_Q_DEPTH 4
#endif
#ifndef MG_BSD_ACCEPT_MS
#define MG_BSD_ACCEPT_MS 3000 // Timeout for accept queue handoff
#endif
struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; };
@@ -248,7 +311,11 @@ struct mg_xport {
QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL
QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept()
struct sockaddr_in peer;
uint16_t rx_off;
uint64_t accept_expire;
int err;
bool closed;
bool orphan; // accepted connection not handed to socket owner
TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT
int *connect_result; // where to store 0/−1 connect outcome
};
@@ -264,17 +331,48 @@ struct mg_bsd_cmd {
static QueueHandle_t s_cmd_q;
// Single-slot DNS resolve state (not reentrant, sufficient for demos)
static struct { struct mg_addr addr; bool done, error; TaskHandle_t caller; } s_resolve;
static bool bsd_qsend(QueueHandle_t q, const void *item, TickType_t ticks,
bool reserve) {
BaseType_t ok = pdFALSE;
if (!reserve || uxQueueSpacesAvailable(q) > 1) ok = xQueueSend(q, item, ticks);
if (ok != pdTRUE) MG_ERROR(("%p", q));
return ok == pdTRUE;
}
static bool xport_accept(struct mg_xport *x) {
if (bsd_qsend(x->accept_q, &x, 0, true)) {
x->orphan = false; // not an orphan anymore
x->accept_q = NULL;
return true;
}
return false; // still an orphan, retry later
}
// static DNS resolve state; not reentrant (see below)
static struct { struct mg_addr addr; bool error; TaskHandle_t caller; } s_resolve;
// gethostbyname statics (official isn't reentrant anyway, and is obsolete)
static struct hostent s_hostent;
static char *s_h_aliases[1];
static char *s_h_addr_list[2];
static uint32_t s_h_addr;
static char s_h_name[64];
static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_RESOLVE) { s_resolve.addr = c->rem; s_resolve.done = true; c->is_closing = 1; }
else if ((ev == MG_EV_ERROR || ev == MG_EV_CLOSE) && !s_resolve.done) s_resolve.error = true;
if ((s_resolve.done || s_resolve.error) && s_resolve.caller) {
TaskHandle_t h = s_resolve.caller;
s_resolve.caller = NULL; // prevent double-notify on subsequent MG_EV_CLOSE
xTaskNotifyGive(h);
bool notify = false;
if (ev == MG_EV_RESOLVE) {
s_resolve.addr = c->rem;
c->is_closing = 1;
MG_DEBUG(("%lu resolved", c->id));
notify = true;
} else if (ev == MG_EV_ERROR) {
s_resolve.error = true;
MG_DEBUG(("%lu failed", c->id));
notify = true;
} else if (ev == MG_EV_CLOSE) {
s_resolve.caller = NULL; // The resolver connection has fully unwound.
MG_DEBUG(("%lu done", c->id));
}
if (notify) xTaskNotifyGive(s_resolve.caller);
(void) ev_data;
}
@@ -282,9 +380,11 @@ static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) {
static struct mg_xport *xport_alloc(void) {
struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x));
if (!x) return NULL;
x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk));
// +1 keeps a terminal EOF/error slot for MG_EV_CLOSE
x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk));
x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk));
if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; }
x->orphan = true; // haven't attached this connection to its socket
return x;
}
@@ -294,51 +394,101 @@ static void xport_ev(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT) {
// c is the new accepted connection; x is the listening transport
bool ok;
struct mg_xport *nx = xport_alloc();
if (!nx) { c->is_closing = 1; return; }
if (!nx) { c->fn_data = NULL; mg_error(c, "accept OOM"); return; }
nx->c = c;
nx->accept_q = x->accept_q;
nx->accept_expire = mg_millis() + MG_BSD_ACCEPT_MS;
nx->peer.sin_family = AF_INET;
nx->peer.sin_port = c->rem.port;
memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4);
c->fn_data = nx;
xQueueSend(x->accept_q, &nx, 0);
} else if (ev == MG_EV_READ && x->recv_q) {
// Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv
size_t off = 0;
while (off < c->recv.len) {
ok = xport_accept(nx); // leaves orphaned on failure, retry on POLL
bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, x, nx, ok ? 1 : 0);
} else if (ev == MG_EV_POLL && x->orphan && x->accept_q) {
if (uxQueueSpacesAvailable(x->accept_q) > 1 && xport_accept(x)) {
bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, NULL, x, 1);
} else if (mg_millis() > x->accept_expire) { // retried enough, give up
x->err = EIO;
mg_error(c, "accept_q");
}
} else if (ev == MG_EV_READ || ev == MG_EV_POLL) {
if (x->recv_q) { // let POLL resume abandoned READ processing on full queue
// Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv
size_t off = 0;
while (off < c->recv.len) {
struct mg_bsd_chunk chunk;
size_t n = c->recv.len - off;
if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE;
memcpy(chunk.data, c->recv.buf + off, n);
chunk.len = (uint16_t) n;
if (uxQueueSpacesAvailable(x->recv_q) <= 1 ||
!bsd_qsend(x->recv_q, &chunk, 0, false)) break; // retry later
off += n;
}
mg_iobuf_del(&c->recv, 0, off);
}
if (ev == MG_EV_POLL && x->send_q) {
// Drain send_q → mg_send(); task1 owns c
struct mg_bsd_chunk chunk;
size_t n = c->recv.len - off;
if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE;
memcpy(chunk.data, c->recv.buf + off, n);
chunk.len = (uint16_t) n;
xQueueSend(x->recv_q, &chunk, portMAX_DELAY);
off += n;
while (xQueuePeek(x->send_q, &chunk, 0) == pdTRUE) {
if (!mg_send(c, chunk.data, chunk.len)) break; // retry later
xQueueReceive(x->send_q, &chunk, 0);
}
}
mg_iobuf_del(&c->recv, 0, c->recv.len);
} else if (ev == MG_EV_POLL && x->send_q) {
// Drain send_q → mg_send(); task1 owns c
struct mg_bsd_chunk chunk;
while (xQueueReceive(x->send_q, &chunk, 0) == pdTRUE)
mg_send(c, chunk.data, chunk.len);
} else if (ev == MG_EV_CONNECT) {
// Outgoing connection established: wake the task blocked in connect()
if (x->connect_waiter) {
bsd_log(MG_BSD_LOG_CONNECT, "OK", x, c, NULL, 0);
if (x->connect_result) *x->connect_result = 0;
TaskHandle_t h = x->connect_waiter;
x->connect_waiter = NULL; x->connect_result = NULL;
xTaskNotifyGive(h);
}
} else if (ev == MG_EV_ERROR) { // remember error condition
if (x->err == 0) x->err = EIO; // and let CLOSE handle it
bsd_log(MG_BSD_LOG_CLOSE, "ERR", c, x, NULL, (long) x->err);
} else if (ev == MG_EV_CLOSE) {
bsd_log(MG_BSD_LOG_CLOSE, "IN", c, x, NULL, -1);
x->c = NULL; x->closed = true; c->fn_data = NULL;
// If connect() is still waiting, signal failure
if (x->connect_waiter) {
bsd_log(MG_BSD_LOG_CONNECT, "FAIL", x, c, NULL, -1);
if (x->connect_result) *x->connect_result = -1;
TaskHandle_t h = x->connect_waiter;
x->connect_waiter = NULL; x->connect_result = NULL;
xTaskNotifyGive(h);
// The notified task owns the transport and can close/free it immediately.
return;
}
if (x->recv_q) { struct mg_bsd_chunk eof = {.len = 0}; xQueueSend(x->recv_q, &eof, 0); }
if (x->accept_q) { struct mg_xport *nil = NULL; xQueueSend(x->accept_q, &nil, 0); }
if (x->orphan) { // connection --> socket attachment failed
x->accept_q = NULL; // borrowed from listener; do not delete it here
bsd_log(MG_BSD_LOG_CLOSE, "FREE", c, x, NULL, -1);
mg_bsd_transport_free(x); // connection closed, release resources
return;
}
if (x->recv_q) {
struct mg_bsd_chunk eof;
bool ok;
memset(&eof, 0, sizeof(eof));
eof.len = 0;
ok = bsd_qsend(x->recv_q, &eof, 0, false);
bsd_log(MG_BSD_LOG_CLOSE, "EOF>", c, x, NULL, ok ? 1 : 0);
if (!ok) MG_ERROR(("recv_q close notification failed"));
// recv() wakeup transfers control to the transport owner.
return;
}
if (x->accept_q) {
struct mg_xport *nil = NULL;
bool ok;
ok = bsd_qsend(x->accept_q, &nil, 0, false);
bsd_log(MG_BSD_LOG_CLOSE, "ACCEPT>", c, x, NULL, ok ? 1 : 0);
if (!ok) MG_ERROR(("accept_q close notification failed"));
// accept() wakeup transfers control to the transport owner.
return;
}
bsd_log(MG_BSD_LOG_CLOSE, "OUT", c, x, NULL, -1);
}
(void) ev_data;
}
@@ -357,36 +507,52 @@ void mg_bsd_poll(struct mg_mgr *mgr) {
cmd.x->c = c;
*cmd.result = c ? 0 : -1;
} else if (cmd.type == BSD_CMD_CLOSE) {
bsd_log(MG_BSD_LOG_TRANSPORT, "CMD", cmd.x, NULL, NULL, -1);
if (cmd.x->c) {
cmd.x->c->fn_data = NULL;
cmd.x->c->is_draining = 1;
cmd.x->c = NULL;
}
cmd.x->closed = true;
*cmd.result = 0;
} else if (cmd.type == BSD_CMD_CONNECT) {
cmd.x->connect_waiter = cmd.caller;
cmd.x->connect_result = cmd.result;
struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x);
bsd_log(MG_BSD_LOG_CONNECT, "NEW", cmd.x, c, cmd.url, c ? 1 : 0);
cmd.x->c = c;
if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; }
else notify = false; // xport_ev notifies when connected or on error
} else if (cmd.type == BSD_CMD_RESOLVE) {
s_resolve.done = s_resolve.error = false;
s_resolve.error = false;
s_resolve.caller = cmd.caller;
char url[80];
snprintf(url, sizeof(url), "tcp://%s:0", cmd.url);
if (!mg_connect(mgr, url, resolve_cb, NULL)) s_resolve.error = true;
else notify = false; // resolve_cb notifies when done
struct mg_connection *c;
c = mg_alloc_conn(mgr);
if (c == NULL) {
} else {
c->fn = resolve_cb;
LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
mg_call(c, MG_EV_OPEN, NULL);
MG_DEBUG(("%lu resolve %s", c->id, cmd.url));
mg_resolve(c, cmd.url);
notify = false; // resolve_cb notifies when done
}
}
if (notify) xTaskNotifyGive(cmd.caller);
}
}
void *mg_bsd_transport_new(int domain, int type, int proto) {
(void) domain; (void) type; (void) proto;
// For socket() calls: allocate accept_q only; recv/send added when needed
if (domain != AF_INET || type != SOCK_STREAM ||
(proto != 0 && proto != IPPROTO_TCP)) {
errno = EPROTONOSUPPORT;
return NULL;
}
// For socket() calls: allocate accept_q only; recv/send added when needed.
// +1 keeps a terminal slot for MG_EV_CLOSE without blocking Mongoose.
struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x));
if (!x) return NULL;
x->accept_q = xQueueCreate(MG_BSD_BACKLOG, sizeof(struct mg_xport *));
x->accept_q = xQueueCreate(MG_BSD_BACKLOG + 1, sizeof(struct mg_xport *));
if (!x->accept_q) { free(x); return NULL; }
return x;
}
@@ -394,6 +560,7 @@ void *mg_bsd_transport_new(int domain, int type, int proto) {
void mg_bsd_transport_free(void *t) {
struct mg_xport *x = (struct mg_xport *) t;
if (!x) return;
bsd_log(MG_BSD_LOG_TRANSPORT, "FREE", x, NULL, NULL, -1);
if (x->recv_q) vQueueDelete(x->recv_q);
if (x->send_q) vQueueDelete(x->send_q);
if (x->accept_q) vQueueDelete(x->accept_q);
@@ -405,7 +572,7 @@ int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) {
int result = -1;
struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result};
snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port));
xQueueSend(s_cmd_q, &cmd, portMAX_DELAY);
if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1;
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
return result;
}
@@ -414,28 +581,50 @@ void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock)
struct mg_xport *x = (struct mg_xport *) t;
struct mg_xport *nx = NULL;
TickType_t ticks = nonblock ? 0 : portMAX_DELAY;
if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE || !nx) return NULL;
if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE) {
errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO);
return NULL;
}
if (!nx) { errno = x->err ? x->err : EIO; return NULL; }
if (peer) *peer = nx->peer;
return nx;
}
ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) {
struct mg_xport *x = (struct mg_xport *) t;
struct mg_bsd_chunk chunk;
uint8_t *p = (uint8_t *) buf;
size_t recvd = 0;
TickType_t ticks = nonblock ? 0 : portMAX_DELAY;
if (xQueueReceive(x->recv_q, &chunk, ticks) != pdTRUE) {
errno = EAGAIN;
return -1;
while (recvd < len) {
struct mg_bsd_chunk chunk;
if (xQueuePeek(x->recv_q, &chunk, recvd == 0 ? ticks : 0) != pdTRUE) {
if (recvd > 0) break;
errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO);
return -1;
}
if (chunk.len == 0) {
if (recvd > 0) break;
xQueueReceive(x->recv_q, &chunk, 0);
if (x->err) { errno = x->err; return -1; }
return 0; // EOF
} else {
size_t n = chunk.len - x->rx_off;
if (n > len - recvd) n = len - recvd;
memcpy(p + recvd, chunk.data + x->rx_off, n);
recvd += n;
x->rx_off = (uint16_t) (x->rx_off + n);
if (x->rx_off >= chunk.len) {
xQueueReceive(x->recv_q, &chunk, 0);
x->rx_off = 0;
}
}
}
if (chunk.len == 0) return 0; // EOF
size_t n = chunk.len < len ? chunk.len : len;
memcpy(buf, chunk.data, n);
return (ssize_t) n;
return (ssize_t) recvd;
}
ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) {
struct mg_xport *x = (struct mg_xport *) t;
if (x->closed) return -1;
if (x->closed) { errno = x->err ? x->err : EPIPE; return -1; }
size_t sent = 0;
TickType_t ticks = nonblock ? 0 : portMAX_DELAY;
while (sent < len) {
@@ -444,7 +633,10 @@ ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonbloc
if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE;
memcpy(chunk.data, (const uint8_t *) buf + sent, n);
chunk.len = (uint16_t) n;
if (xQueueSend(x->send_q, &chunk, ticks) != pdTRUE) break;
if (!bsd_qsend(x->send_q, &chunk, ticks, false)) {
errno = x->closed ? (x->err ? x->err : EPIPE) : (nonblock ? EAGAIN : EIO);
return sent > 0 ? (ssize_t) sent : -1;
}
sent += n;
}
return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1);
@@ -453,7 +645,7 @@ ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonbloc
int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) {
struct mg_xport *x = (struct mg_xport *) t;
(void) nonblock;
if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk));
if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk));
if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk));
if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; }
int result = -1;
@@ -461,22 +653,18 @@ int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonbl
uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr;
snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d",
ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port));
xQueueSend(s_cmd_q, &cmd, portMAX_DELAY);
bsd_log(MG_BSD_LOG_CONNECT, "REQ", x, NULL, cmd.url, -1);
if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1;
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (result != 0) errno = x->err ? x->err : EIO;
return result;
}
// gethostbyname: resolve via Mongoose DNS (not reentrant)
static struct hostent s_hostent;
static char *s_h_aliases[1];
static char *s_h_addr_list[2];
static uint32_t s_h_addr;
static char s_h_name[64];
struct hostent *gethostbyname(const char *name) {
struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL};
snprintf(cmd.url, sizeof(cmd.url), "%s", name);
xQueueSend(s_cmd_q, &cmd, portMAX_DELAY);
if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return NULL;
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (s_resolve.error) return NULL;
s_h_addr = s_resolve.addr.addr.ip4;
@@ -521,12 +709,18 @@ void freeaddrinfo(struct addrinfo *res) {
void mg_bsd_transport_close(void *t) {
struct mg_xport *x = (struct mg_xport *) t;
bsd_log(MG_BSD_LOG_TRANSPORT, "CLOSE", x, NULL, NULL, -1);
if (!x->closed && x->c) {
int result = 0;
struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result};
xQueueSend(s_cmd_q, &cmd, portMAX_DELAY);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
bool ok = bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false);
bsd_log(MG_BSD_LOG_TRANSPORT, "CMD>", x, NULL, NULL, ok ? 1 : 0);
if (ok) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
bsd_log(MG_BSD_LOG_RESULT, "CMD", x, NULL, NULL, (long) result);
}
}
bsd_log(MG_BSD_LOG_TRANSPORT, "FREE_REQ", x, NULL, NULL, -1);
mg_bsd_transport_free(x);
}
@@ -599,9 +793,8 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
m->id = conn_id;
m->len = len;
memcpy(m->data, buf, len);
if (xQueueSend((QueueHandle_t) mgr->pipe.q, &m, 0) != pdTRUE) {
if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0, false)) {
free(m);
MG_ERROR(("xQueueSend"));
return false;
}
return true;
+4 -2
View File
@@ -167,9 +167,11 @@ static size_t mg_dash_print_endpoint(mg_pfn_t fn, void *arg, va_list *ap) {
void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
struct mg_dash_cdata *d = (struct mg_dash_cdata *) c->data;
struct mg_dash_user *u = d->u;
struct mg_dash_cdata *d;
struct mg_dash_user *u;
if (!c->is_websocket) continue;
d = (struct mg_dash_cdata *) c->data;
u = d->u;
if (u == NULL) continue;
if (set->index != NULL && *set->index < 0) {
int sz = mg_dash_array_size(set, u);
+154 -45
View File
@@ -228,16 +228,16 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
}
}
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
uint16_t txnid, bool ipv6) {
struct {
static bool dns_send(struct mg_connection *c, const struct mg_str *name,
unsigned int rtype, uint16_t txnid, uint16_t flags) {
struct { // RFC-1035 4.1.2
struct mg_dns_header header;
uint8_t data[256];
} pkt;
size_t i, n;
memset(&pkt, 0, sizeof(pkt));
pkt.header.txnid = mg_htons(txnid);
pkt.header.flags = mg_htons(0x100);
pkt.header.flags = mg_htons(flags);
pkt.header.num_questions = mg_htons(1);
for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
if (name->buf[i] == '.' || i >= name->len) {
@@ -247,14 +247,19 @@ static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
}
if (i >= name->len) break;
}
memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
memset(&pkt.data[n], 0, 5); // nul, QTYPE, QCLASS
pkt.data[n + 2] = (uint8_t) rtype; // QTYPE = rtype, only 0-255 supported
pkt.data[n + 4] = 1; // QCLASS = IN
n += 5;
if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
// memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query
// n += 6;
return mg_send(c, &pkt, sizeof(pkt.header) + n);
}
static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
uint16_t txnid, bool ipv6) {
return dns_send(c, name, ipv6 ? MG_DNS_RTYPE_AAAA : MG_DNS_RTYPE_A, txnid,
0x100); // RD
}
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) {
@@ -458,7 +463,6 @@ static void handle_mdns_query(struct mg_connection *c) {
MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype,
(unsigned int) rr.aclass, name));
if (rr.atype == MG_DNS_RTYPE_A) {
// TODO(): ensure c->fn_data ends in \0
// if we have a name to match, go; otherwise users will match and fill
// req.r.name and set req.is_resp
if (c->fn_data != NULL && mg_casecmp((char *) c->fn_data, name) != 0)
@@ -574,43 +578,149 @@ static void handle_mdns_query(struct mg_connection *c) {
}
}
static size_t srvtoname(char *svc) {
char *dot = strchr(svc, '.');
if (dot == NULL) return 0;
*dot = '\0';
return (size_t) (dot - svc);
}
#define MG_MAX_MDNS_RECORDS 8
// Condense one useful mDNS/DNS-SD chain from a response datagram into a single
// MG_EV_MDNS_RESP event. A/AAAA as the first supported record are terminal
// address answers (AAAA currently ignored). For PTR/SRV/TXT, subsequent
// records are attached only when their label matches the service instance, or
// the SRV target for address records.
// Some devices send SRV/TXT as extra Answer RRs instead of Additional RRs, so
// both sections are parsed as one chain.
static void handle_mdns_response(struct mg_connection *c) {
struct mg_dns_header *rh = (struct mg_dns_header *) c->recv.buf;
struct mg_dns_rr rr;
size_t n;
// Parse first response, offset 12 is header size
n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, false, &rr);
MG_VERBOSE(("mDNS response parsed, result=%d", (int) n));
if (n > 0) {
// RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
char name[256];
uint8_t name_len;
struct mg_mdns_resp resp;
memset(&resp, 0, sizeof(resp));
if (rh->num_answers > mg_htons(1)) MG_DEBUG(("ignoring > 1 answers"));
mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name));
name_len = (uint8_t) strlen(name);
MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype,
(unsigned int) rr.aclass, name));
if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A &&
(rr.aclass & 0x7FFF) == 1) {
resp.addr.is_ip6 = false;
memcpy(resp.addr.addr.ip, (char *) (rh + 1) + n - 4, 4);
MG_DEBUG(("A response from %.*s = %M", name_len, name, mg_print_ip,
&resp.addr));
// } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA &&
// (rr.aclass & 0x7FFF) == 1) {
// resp.addr.is_ip6 = true;
// memcpy(resp.addr.addr.ip, (char *)(rh + 1) + n - 16], 16);
// MG_DEBUG(("AAAA response from %.*s = %M", name_len, name,
// mg_print_ip, &resp.addr));
} else {
return;
}
resp.name = mg_str_n(name, name_len);
resp.rr = &rr;
mg_call(c, MG_EV_MDNS_RESP, &resp);
struct mg_dns_rr rr, rr_[5]; // A, PTR, SRV, TXT, AAAA, in that order
struct mg_mdns_resp resp;
size_t n, i, num, answers, roff_[5], roff = 12; // offset 12 is header size
// RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
char name[256], srvcproto[256], instance[256], host[256];
uint16_t atype = 0;
answers = mg_ntohs(rh->num_answers);
num = answers + mg_ntohs(rh->num_other_prs);
if (answers == 0) return;
MG_VERBOSE(("mDNS response: %u answer, %u additional", (unsigned) answers,
(unsigned) (num - answers)));
if (num > MG_MAX_MDNS_RECORDS) {
num = MG_MAX_MDNS_RECORDS;
MG_DEBUG(("ignoring > %u records", MG_MAX_MDNS_RECORDS));
}
memset(roff_, 0, sizeof(roff_));
memset(&rr_, 0, sizeof(rr_));
memset(&resp, 0, sizeof(resp));
instance[0] = host[0] = '\0';
for (i = 0; i < num; i++) { // First Answer RR is primary; the rest must match it
n = mg_dns_parse_rr(c->recv.buf, c->recv.len, roff, false, &rr);
MG_VERBOSE(("mDNS record parsed, result=%u", n));
if (n == 0) return;
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff, name, sizeof(name)) == 0) return;
MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, (unsigned int) rr.aclass, name));
if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && (rr.aclass & 0x7FFF) == 1 &&
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
if (rr_[0].atype != 0) break;
if (i == 0) atype = rr.atype;
resp.name = mg_str(name);
resp.addr.is_ip6 = false;
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 4, 4);
MG_VERBOSE(("A record"));
roff_[0] = roff, rr_[0] = rr;
if (atype == MG_DNS_RTYPE_A) break;
} else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1 &&
(i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
(host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
# if 0
if (rr_[4].atype != 0) break;
resp.addr.is_ip6 = true;
if (i == 0) atype = rr.atype;
resp.name = mg_str(name);
memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 16, 16);
MG_VERBOSE(("AAAA record"));
roff_[4] = roff, rr_[4] = rr;
if (atype == MG_DNS_RTYPE_AAAA) break;
#else
MG_VERBOSE(("ignored AAAA record"));
#endif
} else if (rr.atype == MG_DNS_RTYPE_PTR && (rr.aclass & 0x7FFF) == 1 && i == 0) {
if (rr_[1].atype != 0) break;
atype = rr.atype;
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 10,
instance, sizeof(instance)) == 0) return;
MG_VERBOSE(("PTR record"));
roff_[1] = roff, rr_[1] = rr;
} else if (rr.atype == MG_DNS_RTYPE_SRV && rr.alen >= 6 && (rr.aclass & 0x7FFF) == 1 &&
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
if (rr_[2].atype != 0) break;
if (i == 0) atype = rr.atype;
resp.sd.port = MG_LOAD_BE16(c->recv.buf + roff + rr.nlen + 14);
resp.addr.port = mg_htons(resp.sd.port);
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 16,
host, sizeof(host)) == 0) return;
MG_VERBOSE(("SRV record"));
roff_[2] = roff, rr_[2] = rr;
} else if (rr.atype == MG_DNS_RTYPE_TXT && (rr.aclass & 0x7FFF) == 1 &&
(i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
if (rr_[3].atype != 0) break;
if (i == 0) atype = rr.atype;
if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
resp.sd.txt = mg_str_n((char *) c->recv.buf + roff + rr.nlen + 10, rr.alen);
MG_VERBOSE(("TXT record"));
roff_[3] = roff, rr_[3] = rr;
}
roff += n;
if (i == 0 && atype == 0) return;
}
if (atype == MG_DNS_RTYPE_A) {
resp.rr = &rr_[0];
MG_DEBUG(("A response from %s = %M", name, mg_print_ip, &resp.addr));
#if 0
} else if (atype == MG_DNS_RTYPE_AAAA) {
resp.rr = &rr_[4];
MG_DEBUG(("AAAA response from %s = %M", name, mg_print_ip6, resp.addr.addr.ip));
#endif
} else if (atype == MG_DNS_RTYPE_PTR) {
size_t len;
resp.rr = &rr_[1];
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[1], srvcproto,
sizeof(srvcproto)) == 0) return;
len = strlen(srvcproto);
if (len <= 6) return;
resp.sd.srvcproto = mg_str_n(srvcproto, len - 6); // remove .local
if (host[0] != '\0') {
resp.name = mg_str(host);
} else if (rr_[0].atype == 0) {
mg_snprintf(name, sizeof(name), "%s", instance);
resp.name = mg_str_n(name, srvtoname(name));
} else {
if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[0], name,
sizeof(name)) == 0) return;
resp.name = mg_str(name);
}
MG_DEBUG(("PTR response for %s from %s", srvcproto, name));
} else if (atype == MG_DNS_RTYPE_SRV || atype == MG_DNS_RTYPE_TXT) {
size_t len, name_len;
resp.rr = atype == MG_DNS_RTYPE_SRV ? &rr_[2] : &rr_[3];
if (mg_dns_parse_name(c->recv.buf, c->recv.len,
atype == MG_DNS_RTYPE_SRV ? roff_[2] : roff_[3],
srvcproto, sizeof(srvcproto)) == 0)
return;
len = strlen(srvcproto);
name_len = srvtoname(srvcproto);
if (name_len == 0 || len <= name_len + 7) return; // remove .local
resp.sd.srvcproto = mg_str_n(srvcproto + name_len + 1, len - name_len - 7);
resp.name = mg_str_n(srvcproto, name_len);
MG_DEBUG(("%s response for %s from %s", atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", srvcproto, name));
} else {
return;
}
mg_call(c, MG_EV_MDNS_RESP, &resp);
}
static void handle_mdns_record(struct mg_connection *c) {
@@ -684,8 +794,7 @@ struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
static bool mdns_query(struct mg_connection *c, struct mg_str *name,
unsigned int rtype) {
mg_multicast_restore(c, (uint8_t *) &c->loc);
(void) rtype;
return mg_dns_send(c, name, 0, false);
return dns_send(c, name, rtype, 0, 0); // RFC-6762 18.1 id = 0, 18.6 RD = 0
}
bool mg_mdns_query(struct mg_connection *c, const char *name,
+4 -2
View File
@@ -60,11 +60,12 @@ struct mg_mdns_req {
bool is_unicast; // True if the client requested a unicast (QU) response
};
// ev_data for MG_EV_MDNS_RESP, carrying the resolved address from an mDNS response.
// ev_data for MG_EV_MDNS_RESP, carrying address/data from a mDNS response
struct mg_mdns_resp {
struct mg_dns_rr *rr; // Resource record from the response
struct mg_dns_rr *rr; // Resource record from the response (1st in chain)
struct mg_str name; // Resolved hostname, without the .local suffix
struct mg_addr addr; // Resolved IP address
struct mg_dnssd_record sd; // Service Discovery data
};
// Parses a DNS query or response from buf/len into dm. Returns true on success.
@@ -81,6 +82,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
// Creates an mDNS listener on UDP multicast 224.0.0.251:5353. Registers it as
// the manager's mDNS resolver (mgr->mdns). Fires MG_EV_MDNS_REQ for incoming
// queries and MG_EV_MDNS_RESP for incoming responses. Returns NULL on error.
// fn_data is a NUL-terminated server name, or NULL to pass all requests to fn.
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
void *fn_data);
+7 -5
View File
@@ -9,7 +9,9 @@
static size_t print_atcmd(void (*out)(char, void *), void *arg, va_list *ap) {
struct mg_str s = va_arg(*ap, struct mg_str);
for (size_t i = 0; i < s.len; i++) out(s.buf[i] < 0x20 ? '.' : s.buf[i], arg);
size_t i;
for (i = 0; i < s.len; i++)
out((char) (s.buf[i] < 0x20 ? '.' : s.buf[i]), arg);
return s.len;
}
@@ -27,7 +29,7 @@ static bool mg_atcmd_handle(struct mg_tcpip_if *ifp) {
if (dd->script_index % 2 == 0) { // send AT command
const char *cmd = dd->script[dd->script_index];
MG_DEBUG(("send AT[%d]: %M", dd->script_index, print_atcmd, mg_str(cmd)));
while (*cmd) dd->tx(dd->usart, *cmd++);
while (*cmd != '\0') dd->tx(dd->usart, (uint8_t) *cmd++);
dd->script_index++;
ifp->recv_queue.head = 0;
} else { // check AT command response
@@ -43,7 +45,7 @@ static bool mg_atcmd_handle(struct mg_tcpip_if *ifp) {
return false; // FAIL
}
if ((c = dd->rx(dd->usart)) < 0) return false; // no data
q->buf[q->head++] = c;
q->buf[q->head++] = (char) c;
if (mg_match(mg_str_n(q->buf, q->head), mg_str(expect), NULL)) {
MG_DEBUG(("recv AT[%d]: %M", dd->script_index, print_atcmd,
mg_str_n(q->buf, q->head)));
@@ -84,7 +86,7 @@ static size_t mg_atcmd_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
}
}
if (!dd->no_byte_stuff && !byte_unstuff(dd, &b)) continue;
q->buf[q->head++] = b;
q->buf[q->head++] = (char) b;
}
len = (q->head <= len) ? q->head : 0;
memmove(buf, q->buf, len);
@@ -130,7 +132,7 @@ struct mg_tcpip_driver mg_tcpip_driver_atcmd = {mg_atcmd_init, mg_atcmd_tx, mg_a
static void byte_stuff(struct mg_tcpip_driver_atcmd_data *dd, uint8_t b) {
if ((b < 0x20) || (b == MG_HDLC_ESC) || (b == MG_HDLC_FLAG)) {
dd->tx(dd->usart, MG_HDLC_ESC);
dd->tx(dd->usart, b ^ 0x20);
dd->tx(dd->usart, (uint8_t) (b ^ 0x20));
} else {
dd->tx(dd->usart, b);
}
+3
View File
@@ -37,6 +37,9 @@ bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) {
bool ok = false;
if (s_size == 0) {
MG_ERROR(("OTA is not started, call mg_ota_begin()"));
} else if (s_addr + MG_ROUND_UP(len, flash->align) >
(char *) flash->start + flash->size) {
MG_ERROR(("Flash overflow: attempting to write past the flash boundary"));
} else {
size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align);
if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down);
+1
View File
@@ -173,6 +173,7 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
is_long++, c = fmt[++i];
if (c == 'l') is_long++, c = fmt[++i];
}
if (c == 'z') is_long = 1, c = fmt[++i];
if (c == 'p') x = 1, is_long = 1;
if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
c == 'g' || c == 'f') {
+8 -33
View File
@@ -1,44 +1,19 @@
#include "arch.h"
#include "event.h"
#include "fs.h"
#include "util.h"
#if MG_ENABLE_FATFS
#include <ff.h>
static int mg_days_from_epoch(int y, int m, int d) {
y -= m <= 2;
int era = y / 400;
int yoe = y - era * 400;
int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + doe - 719468;
}
static time_t mg_timegm(const struct tm *t) {
int year = t->tm_year + 1900;
int month = t->tm_mon; // 0-11
if (month > 11) {
year += month / 12;
month %= 12;
} else if (month < 0) {
int years_diff = (11 - month) / 12;
year -= years_diff;
month += 12 * years_diff;
}
int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
}
static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
tm.tm_sec = (ftime << 1) & 0x3e;
tm.tm_min = ((ftime >> 5) & 0x3f);
tm.tm_hour = ((ftime >> 11) & 0x1f);
tm.tm_mday = (fdate & 0x1f);
tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
return mg_timegm(&tm);
unsigned int sec = (ftime << 1) & 0x3e;
unsigned int min = (ftime >> 5) & 0x3f;
unsigned int hour = (ftime >> 11) & 0x1f;
unsigned int day = fdate & 0x1f;
unsigned int month = (fdate >> 5) & 0x0f;
unsigned int year = ((fdate >> 9) & 0x7f) + 1980;
return (time_t) mg_timegm(year, month, day, hour, min, sec);
}
static int ff_stat(const char *path, size_t *size, time_t *mtime) {
+5 -6
View File
@@ -1030,7 +1030,7 @@ static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) {
size_t alignment = 512;
size_t left = p->expected > p->received ? p->expected - p->received : 0;
size_t aligned = c->recv.len < left ? MG_ROUND_DOWN(c->recv.len, alignment)
: c->recv.len;
: left;
bool ok = true;
if (aligned > 0) {
if (p->fd != NULL) {
@@ -1114,15 +1114,14 @@ static bool is_hex_digit(int c) {
}
static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
int i = 0, n = 0;
int i = 0, n = 0; // pass int to mg_str_to_num, treats as unsigned
if (len < 3) return 0;
while (i < len && is_hex_digit(buf[i])) i++;
if (i == 0) return -1; // Error, no length specified
if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big
if (i == 0) return -1; // Error, no length specified
if (len < i + 2 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // Error
if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false)
if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(n)) == false)
return -1; // Decode chunk length, overflow
if (n < 0) return -1; // Error. TODO(): some checks now redundant
if (n < 0) return -1; // Chunk length is too big <0 = >0x80000000
if (n > len - i - 4) return 0; // Chunk not yet fully buffered
if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // Error
*pl = i + 2, *dl = n;
+8 -5
View File
@@ -363,15 +363,18 @@ char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
char *result = NULL;
int len = 0, off = mg_json_get(json, path, &len);
if (off >= 0 && json.buf[off] == '"' && len > 1 &&
if (off >= 0 && json.buf[off] == '"' && len > 1 && (len - 2) % 2 == 0 &&
(result = (char *) mg_calloc(1, (size_t) len / 2)) != NULL) {
int i;
for (i = 0; i < len - 2; i += 2) {
mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1],
sizeof(uint8_t));
if (!mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16,
&result[i >> 1], sizeof(uint8_t))) break;
}
if (i < len - 2) mg_free(result), result = NULL;
if (result != NULL) {
result[len / 2 - 1] = '\0';
if (slen != NULL) *slen = len / 2 - 1;
}
result[len / 2 - 1] = '\0';
if (slen != NULL) *slen = len / 2 - 1;
}
return result;
}
+1 -1
View File
@@ -57,7 +57,7 @@ char *mg_json_get_str(struct mg_str json, const char *path);
// Decodes a hex-encoded JSON string at path into a heap-allocated byte array.
// Sets *len to the decoded byte count. Caller must mg_free() the result.
// Returns NULL if not found or not a string.
// Returns NULL if not found, not a string, or not valid hexadecimal.
char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
// Decodes a base64-encoded JSON string at path into a heap-allocated byte
+164
View File
@@ -0,0 +1,164 @@
#include "jwt.h"
#include "base64.h"
#include "json.h"
#include "log.h"
#include "printf.h"
#include "sha256.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN
#include "tls_uecc.h"
#endif
static size_t jwt_split(struct mg_str jwt, struct mg_str *h, struct mg_str *p,
struct mg_str *s) {
size_t i, a = jwt.len, b = jwt.len;
for (i = 0; i < jwt.len; i++) {
if (jwt.buf[i] == '.') {
if (a == jwt.len) a = i;
if (a != i) b = i;
if (b != jwt.len) break;
}
}
if (a == jwt.len || b == jwt.len || b + 1 >= jwt.len) return 0;
*h = mg_str_n(jwt.buf, a);
*p = mg_str_n(jwt.buf + a + 1, b - a - 1);
*s = mg_str_n(jwt.buf + b + 1, jwt.len - b - 1);
return b;
}
static bool jwt_alg(struct mg_str h, const char *alg) {
char got[16];
size_t len = h.len * 3 / 4 + 2;
char *buf = (char *) mg_calloc(1, len);
bool ok;
if (buf == NULL) return false;
ok = mg_base64url_decode(h.buf, h.len, buf, len) > 0 &&
mg_json_unescape(mg_str(buf), "$.alg", got, sizeof(got)) > 0 &&
strcmp(got, alg) == 0;
mg_free(buf);
return ok;
}
static size_t jwt_header_payload(const char *alg, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
char *header, *kid = NULL;
size_t n, m;
if (opts->kid.len > 0) {
kid = mg_mprintf(",%m:%m", MG_ESC("kid"), mg_print_esc,
(int) opts->kid.len, opts->kid.buf);
if (kid == NULL) return 0;
}
header = mg_mprintf("{%m:%m,%m:%m%.*s%.*s%.*s}", MG_ESC("alg"),
MG_ESC(alg), MG_ESC("typ"), MG_ESC("JWT"),
kid == NULL ? 0 : (int) strlen(kid),
kid == NULL ? "" : kid,
opts->header.len > 0 ? 1 : 0, ",",
(int) opts->header.len, opts->header.buf);
mg_free(kid);
if (header == NULL) return 0;
n = mg_base64url_encode((uint8_t *) header, strlen(header), buf, len);
mg_free(header);
if (n == 0 || n + 1 >= len) return 0;
buf[n++] = '.';
m = mg_base64url_encode((uint8_t *) opts->claims.buf, opts->claims.len,
buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
uint8_t sig[32];
size_t n = jwt_header_payload("HS256", opts, buf, len);
size_t m;
if (n == 0 || n + 1 >= len) return 0;
mg_hmac_sha256(sig, (uint8_t *) opts->secret.buf, opts->secret.len,
(uint8_t *) buf, n);
buf[n++] = '.';
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
struct mg_str h, p, s;
uint8_t sig[33], digest[32];
size_t n = jwt_split(jwt, &h, &p, &s);
if (n == 0 || !jwt_alg(h, "HS256")) return 0;
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) !=
sizeof(digest))
return 0;
mg_hmac_sha256(digest, (uint8_t *) opts->secret.buf, opts->secret.len,
(uint8_t *) jwt.buf, n);
if (!mg_memeq(sig, digest, sizeof(digest))) return 0;
return mg_base64url_decode(p.buf, p.len, buf, len);
}
#if MG_TLS == MG_TLS_BUILTIN
typedef struct {
MG_UECC_HashContext uECC;
mg_sha256_ctx sha256;
} jwt_hash_ctx;
static void jwt_hash_init(const MG_UECC_HashContext *ctx) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_init(&c->sha256);
}
static void jwt_hash_update(const MG_UECC_HashContext *ctx, const uint8_t *msg,
unsigned len) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_update(&c->sha256, msg, len);
}
static void jwt_hash_finish(const MG_UECC_HashContext *ctx, uint8_t *hash) {
jwt_hash_ctx *c = (jwt_hash_ctx *) ctx;
mg_sha256_final(hash, &c->sha256);
}
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
uint8_t hash[32], sig[64], tmp[2 * 32 + 64];
jwt_hash_ctx ctx = {
{jwt_hash_init, jwt_hash_update, jwt_hash_finish, 64, 32, tmp},
{{0}, 0, 0, {0}}};
size_t n = jwt_header_payload("ES256", opts, buf, len);
size_t m;
if (n == 0 || n + 1 >= len) return 0;
mg_sha256(hash, (uint8_t *) buf, n);
if (mg_uecc_sign_deterministic(opts->private_key, hash, sizeof(hash),
&ctx.uECC, sig, mg_uecc_secp256r1()) != 1)
return 0;
buf[n++] = '.';
m = mg_base64url_encode(sig, sizeof(sig), buf + n, len - n);
return m == 0 ? 0 : n + m;
}
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
struct mg_str h, p, s;
uint8_t sig[65], hash[32];
size_t n = jwt_split(jwt, &h, &p, &s);
if (n == 0 || !jwt_alg(h, "ES256")) return 0;
if (mg_base64url_decode(s.buf, s.len, (char *) sig, sizeof(sig)) != 64)
return 0;
mg_sha256(hash, (uint8_t *) jwt.buf, n);
if (mg_uecc_verify(opts->public_key, hash, sizeof(hash), sig,
mg_uecc_secp256r1()) != 1)
return 0;
return mg_base64url_decode(p.buf, p.len, buf, len);
}
#else
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len) {
(void) opts, (void) buf, (void) len;
MG_ERROR(("JWT ES256 requires built-in TLS"));
return 0;
}
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len) {
(void) jwt, (void) opts, (void) buf, (void) len;
return mg_jwt_sign_es256(NULL, NULL, 0);
}
#endif
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "arch.h"
#include "str.h"
#include "tls.h"
struct mg_jwt_opts {
struct mg_str claims; // JSON payload
struct mg_str header; // Extra header members, no braces; alg/typ are set
struct mg_str kid; // Key ID protected header
struct mg_str secret; // HS256 secret
const uint8_t *private_key; // ES256 private key (MG_TLS_BUILTIN only)
const uint8_t *public_key; // ES256 public key (MG_TLS_BUILTIN only)
};
// Signs JSON claims as a compact JWT. Returns JWT length, or 0 on error.
size_t mg_jwt_sign_hs256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
// Verifies JWT and decodes JSON claims into buf. Returns claims length, or 0.
size_t mg_jwt_verify_hs256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
size_t mg_jwt_sign_es256(const struct mg_jwt_opts *opts, char *buf,
size_t len);
size_t mg_jwt_verify_es256(struct mg_str jwt, const struct mg_jwt_opts *opts,
char *buf, size_t len);
+1 -1
View File
@@ -6,7 +6,7 @@
#if MG_ENABLE_TCPIP
// no config defaults to 0 => Ethernet
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE };
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER };
#if defined(__DCC__)
#pragma pack(1)
+13 -5
View File
@@ -94,6 +94,13 @@ size_t mg_l2_eth_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) {
struct mg_l2addr *mg_l2_eth_mapip(enum mg_l2addrtype addrtype,
struct mg_addr *addr);
// Read an unaligned little-endian value from byte pointer p into a native integer.
// Safe on architectures that forbid unaligned access (e.g. Cortex-M0).
#define MG_LOAD_LE32(p) \
((uint32_t) (((uint32_t) MG_U8P(p)[3] << 24U) | \
((uint32_t) MG_U8P(p)[2] << 16U) | \
((uint32_t) MG_U8P(p)[1] << 8U) | MG_U8P(p)[0]))
bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
struct mg_str *pay, struct mg_str *raw) {
struct eth *eth = (struct eth *) raw->buf;
@@ -109,7 +116,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
} else { // We do, check 802.1Q tag
struct qtag *qtag = (struct qtag *) &eth->type;
if (qtag->tpid != mg_htons(0x8100)) return false; // Untagged frame
if (mg_ntohs(VLAN_ID(qtag->tci)) != VLAN_ID(d->vlan_id))
if (VLAN_ID(mg_ntohs(qtag->tci)) != VLAN_ID(d->vlan_id))
return false; // Not our VLAN
type = MG_LOAD_BE16(qtag + 1);
}
@@ -119,10 +126,11 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
sizeof(eth->dst)) != 0)
return false; // TODO(): add multicast addresses
if (ifp->enable_fcs_check && len > hdrlen + 4) {
uint32_t crc;
len -= 4; // TODO(scaprile): check on bigendian
uint32_t crc, crc_rx;
len -= 4;
crc = mg_crc32(0, (const char *) raw->buf, len);
if (memcmp((void *) ((size_t) raw->buf + len), &crc, sizeof(crc)))
crc_rx = MG_LOAD_LE32(raw->buf + len);
if (crc_rx != crc)
return false;
}
pay->buf = ((char *) eth) + hdrlen;
@@ -130,7 +138,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) {
if (type == eth_types[i]) break;
}
if (i == sizeof(eth_types)) {
if (i == sizeof(eth_types) / sizeof(eth_types[0])) {
MG_DEBUG(("Unknown eth type %x", type));
if (mg_log_level >= MG_LL_VERBOSE)
mg_hexdump(raw->buf, raw->len >= 32 ? 32 : raw->len);
+19 -34
View File
@@ -169,30 +169,14 @@ uint8_t *mg_l2_pppoe_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto,
s_id, src, dst, frame));
}
// Calculate FCS/CRC for PPP frames. Could be implemented faster using lookup
// tables.
static uint16_t fcs_do(uint8_t *frame, size_t len) {
uint32_t fcs = 0xffff;
unsigned int j;
for (j = 0; j < len; j++) {
unsigned int i;
uint8_t x = frame[j];
for (i = 0; i < 8; i++) {
fcs = ((fcs ^ x) & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1;
x >>= 1;
}
}
return (uint16_t) (fcs & 0xffff);
}
size_t mg_l2_ppp_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur) {
uint16_t crc;
uint8_t *frame;
len += sizeof(struct ppp) + sizeof(struct hdlc_);
frame = cur - len;
crc = fcs_do(frame, len);
*cur++ = (uint8_t) ~crc; // add CRC, note the byte order
*cur++ = (uint8_t) (~crc >> 8);
crc = mg_crc16(0, (const char *) frame, len);
*cur++ = (uint8_t) crc; // add CRC, note the byte order
*cur++ = (uint8_t) (crc >> 8);
// there is no len field in PPP
(void) ifp;
return len + 2;
@@ -243,6 +227,7 @@ static void ppp_handle_lcp(struct mg_tcpip_if *ifp, uint8_t *lcpp,
if (lcpsz < sizeof(*lcp)) return;
id = lcp->id;
len = mg_ntohs(lcp->len);
if (len < sizeof(*lcp) || len > lcpsz) return;
switch (lcp->code) {
case MG_PPP_LCP_CFG_REQ: {
if (len == sizeof(*lcp)) {
@@ -299,7 +284,7 @@ static void ppp_handle_ipcp(struct mg_tcpip_if *ifp, uint8_t *ipcpp,
if (ipcpsz < sizeof(*ipcp)) return;
id = ipcp->id;
len = mg_ntohs(ipcp->len);
if (len > ipcpsz) return;
if (len < sizeof(*ipcp) || len > ipcpsz) return;
switch (ipcp->code) {
case MG_PPP_IPCP_CFG_REQ:
MG_VERBOSE(("got IPCP config request, acknowledging..."));
@@ -359,6 +344,7 @@ static void ppp_handle_ipv6cp(struct mg_tcpip_if *ifp, uint8_t *ipv6cpp,
if (ipv6cpsz < sizeof(*ipv6cp)) return;
id = ipv6cp->id;
len = mg_ntohs(ipv6cp->len);
if (len < sizeof(*ipv6cp) || len > ipv6cpsz) return;
switch (ipv6cp->code) {
case MG_PPP_IPV6CP_CFG_REQ:
MG_VERBOSE(("got IPV6CP config request..."));
@@ -366,10 +352,10 @@ static void ppp_handle_ipv6cp(struct mg_tcpip_if *ifp, uint8_t *ipv6cpp,
find_opt(
MG_PPP_IPV6CP_OPT_IFCID, 10, (const uint8_t *) (ipv6cp + 1),
len - sizeof(*ipv6cp),
(uint8_t *) &((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64)) {
if (((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64 != 0) {
(uint8_t *) &((struct mg_l2addr *) (ifp->gw6mac))->addr.ieee64)) {
if (((struct mg_l2addr *) (ifp->gw6mac))->addr.ieee64 != 0) {
MG_DEBUG(("IPV6CP cfg, GW IFCID: %M", mg_print_ieee64,
&((struct mg_l2addr *) (ifp->gwmac))->addr.ieee64));
&((struct mg_l2addr *) (ifp->gw6mac))->addr.ieee64));
ipv6cp->code = MG_PPP_IPV6CP_CFG_ACK;
ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len);
req[1] = id;
@@ -393,22 +379,21 @@ static void ppp_handle_ipv6cp(struct mg_tcpip_if *ifp, uint8_t *ipv6cpp,
// Our peer accepted our ifc id
MG_VERBOSE(("got IPV6CP config ack"));
break;
case MG_PPP_IPV6CP_CFG_NACK:
case MG_PPP_IPV6CP_CFG_NACK: {
struct mg_l2addr l2;
MG_VERBOSE(("got IPV6CP config nack"));
// NACK contains our "suggested" IFC id, use it
if (len >= 10 &&
find_opt(
MG_PPP_IPV6CP_OPT_IFCID, 10, (const uint8_t *) (ipv6cp + 1),
len - sizeof(*ipv6cp),
(uint8_t *) &((struct mg_l2addr *) (ifp->mac))->addr.ieee64)) {
MG_DEBUG(("IPV6CP cfg, IFCID: %M", mg_print_ieee64,
&((struct mg_l2addr *) (ifp->mac))->addr.ieee64));
find_opt(MG_PPP_IPV6CP_OPT_IFCID, 10, (const uint8_t *) (ipv6cp + 1),
len - sizeof(*ipv6cp), (uint8_t *) &l2.addr.ieee64)) {
MG_DEBUG(("IPV6CP cfg, IFCID: %M", mg_print_ieee64, &l2.addr.ieee64));
ifp->ip6ll[1] = l2.addr.ieee64; // RFC-5072 5, signal L3 we're ready
ipv6cp->code = MG_PPP_IPV6CP_CFG_REQ;
ppp_tx_frame(ifp, MG_PPP_PROTO_IPV6CP, ipv6cpp, len);
} else {
MG_ERROR(("Peer is not able to offer an interface id"));
}
break;
} break;
case MG_PPP_IPV6CP_CFG_REJECT:
MG_ERROR(("Peer rejected our interface id"));
break;
@@ -573,10 +558,10 @@ bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
uint8_t *p = (uint8_t *) (pppoe + 1);
uint16_t taglen;
while (len >= 4) { // parse tags for a possible AC-Cookie
uint16_t curtag = *((uint16_t *) p);
taglen = mg_ntohs(*(((uint16_t *) p) + 1));
uint16_t curtag = MG_LOAD_BE16(p);
taglen = MG_LOAD_BE16(p + 2);
if (taglen > len - 4) return false; // truncated / malformed
if (curtag == mg_htons(0x0104)) {
if (curtag == 0x0104) {
has_cookie = true;
break;
}
+43 -10
View File
@@ -29,6 +29,28 @@ size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
return len;
}
bool mg_dscp(struct mg_connection *c, uint8_t dscp) {
#if MG_ENABLE_SOCKET
#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_THREADX || \
MG_ARCH == MG_ARCH_TIRTOS || MG_ARCH == MG_ARCH_WIN32
(void) c;
(void) dscp;
#else
int tos = dscp << 2, level = IPPROTO_IP, optname = IP_TOS;
c->dscp = (unsigned) (dscp & 63);
#if MG_ENABLE_IPV6 && !MG_ENABLE_LWIP
if (c->loc.is_ip6)
level = IPPROTO_IPV6, optname = IPV6_TCLASS;
#endif
return setsockopt((MG_SOCKET_TYPE) (size_t) c->fd, level, optname, (char *) &tos, sizeof(tos)) == 0;
#endif
#elif MG_ENABLE_TCPIP
c->dscp = (unsigned) (dscp & 63);
return true;
#endif
return false;
}
static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
uint32_t localhost = mg_htonl(0x7f000001);
if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
@@ -85,22 +107,31 @@ static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
size_t i, j = 0, n = 0, dc = 42;
uint16_t val = 0;
bool have = false;
addr->scope_id = 0;
if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
if (mg_v4mapped(str, addr)) return true; // sets addr->is_ip6
for (i = 0; i < str.len; i++) {
if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
(str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
(str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
unsigned long val = 0; // TODO(): This loops on chars, refactor
int x = str.buf[i] >= '0' && str.buf[i] <= '9' ? str.buf[i] - '0'
: str.buf[i] >= 'a' && str.buf[i] <= 'f' ? str.buf[i] - 'a' + 10
: str.buf[i] >= 'A' && str.buf[i] <= 'F' ? str.buf[i] - 'A' + 10
: -1;
if (x >= 0) {
if (i > j + 3) return false;
// MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j]));
mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val));
addr->addr.ip[n] = (uint8_t) ((val >> 8) & 255);
addr->addr.ip[n + 1] = (uint8_t) (val & 255);
} else if (str.buf[i] == ':') {
val = (uint16_t) ((val << 4) | (uint16_t) x);
have = true;
continue;
}
if (have) {
addr->addr.ip[n] = (uint8_t) (val >> 8),
addr->addr.ip[n + 1] = (uint8_t) val;
val = 0, have = false;
}
if (str.buf[i] == ':') {
j = i + 1;
if (i > 0 && str.buf[i - 1] == ':') {
if (dc != 42) return false;
dc = n; // Double colon
if (i > 1 && str.buf[i - 2] == ':') return false;
} else if (i > 0) {
@@ -120,12 +151,14 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
return false;
}
}
if (have)
addr->addr.ip[n] = (uint8_t) (val >> 8),
addr->addr.ip[n + 1] = (uint8_t) val;
if (n < 14 && dc == 42) return false;
if (n < 14) {
memmove(&addr->addr.ip[dc + (14 - n)], &addr->addr.ip[dc], n - dc + 2);
memset(&addr->addr.ip[dc], 0, 14 - n);
}
addr->is_ip6 = true;
return true;
}
+9
View File
@@ -74,6 +74,10 @@ struct mg_connection {
void *pfn_data; // Protocol-level handler argument
char data[MG_DATA_SIZE]; // Scratch space for protocol state; freely readable
void *tls; // TLS state (internal)
unsigned dscp : 6; // Differentiated Services Code Point
#define MG_DSCP_CS(class_) ((class_) << 3)
#define MG_DSCP_AF(class_, drop_) (((class_) << 3) | ((drop_) << 1))
#define MG_DSCP_EF 46
unsigned is_listening : 1; // Listening connection; accepts inbound connections
unsigned is_client : 1; // Outbound connection created by mg_connect()
unsigned is_accepted : 1; // Inbound connection accepted from a listener
@@ -154,6 +158,11 @@ struct mg_connection *mg_connect(struct mg_mgr *, const char *url,
struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
mg_event_handler_t fn, void *fn_data);
// Sets the 6-bit Differentiated Services Code Point for outgoing datagrams.
// Call on MG_EV_OPEN for UDP listeners, MG_EV_CONNECT for clients, or
// MG_EV_ACCEPT for TCP servers, before sending application data.
bool mg_dscp(struct mg_connection *, uint8_t dscp);
// Called internally after DNS resolution completes to create the OS socket
// and initiate the TCP/IP connect. Normally not called by user code.
void mg_connect_resolved(struct mg_connection *);
+122 -92
View File
@@ -46,7 +46,7 @@ struct connstate {
struct ip {
uint8_t ver; // Version
uint8_t tos; // Unused
uint8_t tos; // DS field
uint16_t len; // Datagram length
uint16_t id; // Unused
uint16_t frag; // Fragmentation
@@ -412,13 +412,15 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
}
static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint8_t proto,
uint32_t ip_src, uint32_t ip_dst, size_t plen) {
uint8_t dscp, uint32_t ip_src, uint32_t ip_dst,
size_t plen) {
// ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct ip *ip = (struct ip *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV4,
ifp->mac, l2_dst, l2p);
memset(ip, 0, sizeof(*ip));
ip->ver = 0x45; // Version 4, header length 5 words
ip->ver = 0x45; // Version 4, header length 5 words
ip->tos = (uint8_t) (dscp << 2);
ip->frag = mg_htons(0x4000); // Don't fragment
ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
ip->ttl = 64;
@@ -431,12 +433,12 @@ static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint8_t proto,
#if MG_ENABLE_IPV6
static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
size_t plen);
uint8_t next, uint8_t dscp, uint64_t *ip_src,
uint64_t *ip_dst, size_t plen);
#endif
static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst, uint8_t dscp,
const void *buf, size_t len) {
uint8_t *l3p;
size_t l2_len;
@@ -445,7 +447,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
#if MG_ENABLE_IPV6
struct ip6 *ip6 = NULL;
if (ip_dst->is_ip6) {
ip6 = tx_ip6(ifp, l2_dst, 17, ip_src->addr.ip6, ip_dst->addr.ip6,
ip6 = tx_ip6(ifp, l2_dst, 17, dscp, ip_src->addr.ip6, ip_dst->addr.ip6,
len + sizeof(struct udp));
udp = (struct udp *) (ip6 + 1);
l2_len = sizeof(*ip6) + sizeof(*udp) + len;
@@ -453,7 +455,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
} else
#endif
{
ip = tx_ip(ifp, l2_dst, 17, ip_src->addr.ip4, ip_dst->addr.ip4,
ip = tx_ip(ifp, l2_dst, 17, dscp, ip_src->addr.ip4, ip_dst->addr.ip4,
len + sizeof(struct udp));
udp = (struct udp *) (ip + 1);
l2_len = sizeof(*ip) + sizeof(*udp) + len;
@@ -486,7 +488,7 @@ static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint32_t ip_src,
memset(&ipd, 0, sizeof(ipd));
ipd.addr.ip4 = ip_dst;
ipd.port = dport;
return tx_udp(ifp, l2_dst, &ips, &ipd, buf, len);
return tx_udp(ifp, l2_dst, &ips, &ipd, 0, buf, len);
}
static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint32_t ip_src,
@@ -642,7 +644,7 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL)
return; // safety net for lousy networks
if (plen > room) plen = room;
ip = tx_ip(ifp, l2addr, 1, ifp->ip, pkt->ip->src, sizeof(*icmp) + plen);
ip = tx_ip(ifp, l2addr, 1, 0, ifp->ip, pkt->ip->src, sizeof(*icmp) + plen);
icmp = (struct icmp *) (ip + 1);
memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0
memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX
@@ -666,24 +668,31 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
*end = (uint8_t *) &pkt->pay.buf[pkt->pay.len];
// min header length checked at payload calculation, options are optional
if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
while (p + 1 < end && p[0] != 255) { // Parse options, get #1; RFC-2132 9
if (p[0] == 1 && p[1] == 4 && p + 6 < end) { // Mask, 3.3
while (p < end && p[0] != 255) { // RFC-2132 9
if (p[0] == 0) { p++; continue; } // Pad
if ((size_t) (end - p) < 2 || (size_t) (end - p) < 2U + p[1]) break;
if (p[0] == 1) { // Mask, 3.3
if (p[1] != 4) break;
memcpy(&mask, p + 2, sizeof(mask));
} else if (p[0] == 3 && dhcp_opt_len_ok(p[1], p, end)) { // GW, 3.5
} else if (p[0] == 3) { // GW, 3.5
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&gw, p + 2, sizeof(gw));
ip = pkt->dhcp->yiaddr;
} else if (ifp->enable_req_dns && p[0] == 6 &&
dhcp_opt_len_ok(p[1], p, end)) { // DNS, 3.8
} else if (ifp->enable_req_dns && p[0] == 6) { // DNS, 3.8
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&dns, p + 2, sizeof(dns));
} else if (ifp->enable_req_sntp && p[0] == 42 &&
dhcp_opt_len_ok(p[1], p, end)) { // SNTP, 8.3
} else if (ifp->enable_req_sntp && p[0] == 42) { // SNTP, 8.3
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&sntp, p + 2, sizeof(sntp));
} else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
} else if (p[0] == 51) { // Lease
if (p[1] != 4) break;
memcpy(&lease, p + 2, sizeof(lease));
lease = mg_ntohl(lease);
} else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type
} else if (p[0] == 53) { // Msg Type
if (p[1] != 1) break;
msgtype = p[2];
} else if (p[0] == 54 && p[1] == 4 && p + 6 < end) { // Server id 9.7
} else if (p[0] == 54) { // Server id 9.7
if (p[1] != 4) break;
memcpy(&owner, p + 2, sizeof(sntp)); // This is the lease owner
}
p += p[1] + 2;
@@ -738,8 +747,11 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
// min header length checked at payload calculation, options are optional
res.yiaddr = ifp->ip;
((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
while (p + 1 < end && p[0] != 255) { // Parse options
if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
while (p < end && p[0] != 255) { // Parse options
if (p[0] == 0) { p++; continue; } // Pad
if ((size_t) (end - p) < 2 || (size_t) (end - p) < 2U + p[1]) break;
if (p[0] == 53) { // Message type
if (p[1] != 1) break;
op = p[2];
}
p += p[1] + 2;
@@ -773,14 +785,15 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
#if MG_ENABLE_IPV6
static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
size_t plen) {
uint8_t next, uint8_t dscp, uint64_t *ip_src,
uint64_t *ip_dst, size_t plen) {
// ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6,
ifp->mac, l2_dst, l2p);
memset(ip6, 0, sizeof(*ip6));
ip6->ver = 0x60; // Version 6, traffic class 0
ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class
ip6->label[0] = (uint8_t) (dscp << 6);
ip6->plen = mg_htons((uint16_t) plen);
ip6->next = next;
ip6->hops = 255; // NDP requires max
@@ -796,7 +809,7 @@ static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src,
const void *buf, size_t len) {
struct ip6 *ip6;
struct icmp6 *icmp6;
ip6 = tx_ip6(ifp, l2_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len);
ip6 = tx_ip6(ifp, l2_dst, 58, 0, ip_src, ip_dst, sizeof(*icmp6) + len);
icmp6 = (struct icmp6 *) (ip6 + 1);
memset(icmp6, 0, sizeof(*icmp6)); // Set csum to 0
icmp6->type = type;
@@ -828,15 +841,16 @@ static void onstate6change(struct mg_tcpip_if *ifp);
static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct ndp_na *na = (struct ndp_na *) pkt->pay.buf;
uint8_t *opts = (uint8_t *) (na + 1), *endp = opts + pkt->pay.len - sizeof(*na);
if (pkt->pay.len < (sizeof(*na) + 2)) return; // first 2 bytes in opts
if ((na->res[0] & 0x40) == 0) return; // not "solicited"
if (*opts++ != 2) return; // no target hwaddr, must have
uint8_t *opts = (uint8_t *) (na + 1),
*endp = opts + pkt->pay.len - sizeof(*na);
if (pkt->pay.len < (sizeof(*na) + 2)) return; // first 2 bytes in opts
if ((na->res[0] & 0x40) == 0) return; // not "solicited"
if (*opts++ != 2) return; // no target hwaddr, must have
MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr));
if (MG_IP6MATCH(na->addr, ifp->gw6)) {
// Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, ifp->gw6mac, opts, len)) return;
ifp->gw6_ready = true;
if (ifp->state6 == MG_TCPIP_STATE_IP) {
@@ -847,8 +861,8 @@ static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
if (c != NULL && c->is_arplooking) {
struct connstate *s = (struct connstate *) (c + 1);
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, s->mac, opts, len)) return;
MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6,
&c->rem.addr.ip6, mg_print_l2addr, ifp->l2type, s->mac));
@@ -860,17 +874,18 @@ static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) {
// Neighbor Solicitation, 4.3
static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct ndp_na *ns = (struct ndp_na *) pkt->pay.buf; // struct ndp_ns = ndp_na
struct ndp_na *ns = (struct ndp_na *) pkt->pay.buf; // struct ndp_ns = ndp_na
uint64_t target[2];
if (pkt->pay.len < (sizeof(*ns) + 2)) return; // first 2 bytes in opts
if (pkt->pay.len < (sizeof(*ns) + 2)) return; // first 2 bytes in opts
memcpy(target, ns->addr, sizeof(target));
if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) {
uint64_t req[2]; // requester address
uint8_t l2[sizeof(struct mg_l2addr)];
uint8_t len, *opts = (uint8_t *) (ns + 1), *endp = opts + pkt->pay.len - sizeof(*ns);
if (*opts++ != 1) return; // no requester hwaddr (source)
len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
uint8_t len, *opts = (uint8_t *) (ns + 1),
*endp = opts + pkt->pay.len - sizeof(*ns);
if (*opts++ != 1) return; // no requester hwaddr (source)
len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, l2, opts, len)) return;
req[0] = pkt->ip6->src[0], req[1] = pkt->ip6->src[1]; // align to 64-bit
tx_ndp_na(ifp, l2, target, req, true, ifp->mac);
@@ -972,7 +987,7 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
bool gotl2addr = false, gotprefix = false, changed = false;
uint8_t l2[sizeof(struct mg_l2addr)];
uint32_t mtu = 0;
uint8_t *prefix, prefix_len;
uint8_t *prefix = NULL, prefix_len = 0;
if (pkt->pay.len < sizeof(*ra)) return;
if (ifp->state6 == MG_TCPIP_STATE_UP) {
@@ -989,13 +1004,14 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
} else if (type == 5 && length >= 8) {
// process MTU if available, ignore if it smells
mtu = MG_LOAD_BE32(opts + 4);
if (mtu < 1280 || mtu > ifp->l2mtu) mtu = 0; // RFC-8200, minimum MTU
if (mtu < 1280 || mtu > ifp->l2mtu) mtu = 0; // RFC-8200, minimum MTU
} else if (type == 3 && length >= 32) {
// process prefix, 4.6.2
// process prefix, 4.6.2, ignore if it smells
uint8_t pfx_flags = opts[3]; // L=0x80, A=0x40
uint32_t valid = MG_LOAD_BE32(opts + 4);
uint32_t pref_lifetime = MG_LOAD_BE32(opts + 8);
prefix_len = opts[2];
if (prefix_len >= 128) break;
prefix = opts + 16;
// TODO (robertc2000): handle prefix options if necessary
@@ -1004,7 +1020,8 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
(void) pref_lifetime;
gotprefix = true;
if (prefix_len != ifp->prefix_len || !match_prefix(prefix, ifp->prefix, ifp->prefix_len))
if (prefix_len != ifp->prefix_len ||
!match_prefix(prefix, ifp->prefix, ifp->prefix_len))
changed = true;
}
opts += length;
@@ -1016,7 +1033,8 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (changed) ifp->is_ip6_changed = true;
ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1];
if (gotl2addr) memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac));
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) {
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP ||
ifp->l2type == MG_TCPIP_L2_PPPoE) {
ifp->state6 = MG_TCPIP_STATE_READY;
ifp->gw6_ready = true;
}
@@ -1101,7 +1119,8 @@ static void onstate6change(struct mg_tcpip_if *ifp) {
ifp->is_ip6_changed = false;
}
} else if (ifp->state6 == MG_TCPIP_STATE_IP) {
if ((ifp->gw6[0] != 0 || ifp->gw6[1] != 0) && (ifp->l2type != MG_TCPIP_L2_PPP && ifp->l2type != MG_TCPIP_L2_PPPoE))
if ((ifp->gw6[0] != 0 || ifp->gw6[1] != 0) &&
(ifp->l2type != MG_TCPIP_L2_PPP && ifp->l2type != MG_TCPIP_L2_PPPoE))
tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution
} else if (ifp->state6 == MG_TCPIP_STATE_UP) {
MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll));
@@ -1191,8 +1210,8 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst,
uint8_t flags, uint32_t seq, uint32_t ack, const void *buf,
size_t len) {
uint8_t dscp, uint8_t flags, uint32_t seq, uint32_t ack,
const void *buf, size_t len) {
uint8_t *l3p;
struct ip *ip = NULL;
struct tcp *tcp;
@@ -1217,14 +1236,15 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
#if MG_ENABLE_IPV6
if (ip_dst->is_ip6) {
ip6 =
tx_ip6(ifp, l2_dst, 6, ip_src->addr.ip6, ip_dst->addr.ip6, hlen + len);
ip6 = tx_ip6(ifp, l2_dst, 6, dscp, ip_src->addr.ip6, ip_dst->addr.ip6,
hlen + len);
tcp = (struct tcp *) (ip6 + 1);
l3p = (uint8_t *) ip6;
} else
#endif
{
ip = tx_ip(ifp, l2_dst, 6, ip_src->addr.ip4, ip_dst->addr.ip4, hlen + len);
ip = tx_ip(ifp, l2_dst, 6, dscp, ip_src->addr.ip4, ip_dst->addr.ip4,
hlen + len);
tcp = (struct tcp *) (ip + 1);
l3p = (uint8_t *) ip;
}
@@ -1274,11 +1294,12 @@ static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
ipd.port = pkt->tcp->sport;
if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL)
return 0; // safety net for lousy networks
return tx_tcp(ifp, l2addr, &ips, &ipd, flags, seqno, ackno, NULL, 0);
return tx_tcp(ifp, l2addr, &ips, &ipd, 0, flags, seqno, ackno, NULL, 0);
}
static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
return tx_tcp_ctrlresp(ifp, pkt, toack ? TH_RST : (TH_RST | TH_ACK),
return tx_tcp_ctrlresp(ifp, pkt,
(uint8_t) (toack ? TH_RST : (TH_RST | TH_ACK)),
toack ? pkt->tcp->ack : 0);
}
@@ -1294,7 +1315,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
s = (struct connstate *) (c + 1);
s->dmss = mss; // from options in client SYN
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t)(s->seq + s->win);
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
#if MG_ENABLE_IPV6
if (lsn->loc.is_ip6) {
c->rem.addr.ip6[0] = pkt->ip6->src[0],
@@ -1379,7 +1400,7 @@ static bool udp_send(struct mg_connection *c, const void *buf, size_t len) {
ips.addr.ip4 = ifp->ip;
}
ips.port = c->loc.port;
return tx_udp(ifp, s->mac, &ips, &c->rem, buf, len);
return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len);
}
long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
@@ -1394,7 +1415,7 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
if (room == 0) return MG_IO_WAIT;
if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts
if ((uint32_t) len > room) len = room;
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK,
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
if (sent == 0) {
return MG_IO_WAIT;
@@ -1409,16 +1430,16 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
}
static void handle_tls_recv(struct mg_connection *c) {
size_t avail = mg_tls_pending(c); // will change after mg_tls_recv()
size_t avail = mg_tls_pending(c); // will change after mg_tls_recv()
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)) {
mg_error(c, "oom");
} else {
// Decrypt data directly into c->recv. If io->buf = NULL or
// io->len = io->size (no room), there can be outstanding data that can't be
// moved (mg_tls_pending() > 0 after mg_tls_recv() returns). So mg_tls_recv()
// takes care of returning 0 in this case (commented as "MIP")
// moved (mg_tls_pending() > 0 after mg_tls_recv() returns). So
// mg_tls_recv() takes care of returning 0 in this case (commented as "MIP")
long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf,
io->size - io->len);
if (n == MG_IO_ERR) {
@@ -1433,7 +1454,7 @@ static void handle_tls_recv(struct mg_connection *c) {
static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) {
if (ackno < (s->seq - s->win) || ackno > s->seq) return;
s->maxseq = (uint32_t)(ackno + win);
s->maxseq = (uint32_t) (ackno + win);
s->win = win;
}
@@ -1445,8 +1466,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
uint8_t flags = TH_ACK;
if (mg_ntohl(pkt->tcp->seq) != s->ack) {
MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), "", 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
return;
}
// If we initiated the closure, we reply with ACK upon receiving FIN
@@ -1467,14 +1488,14 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
// is flushed, then send our FIN.
c->is_draining = 1;
}
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq),
mg_htonl(s->ack), "", 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, flags,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
if (pkt->pay.len == 0) return; // if no data, we're done
} else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) {
// Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations)
MG_VERBOSE(("%lu keepalive ACK", c->id));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), NULL, 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
return; // no data to process
} else if (pkt->pay.len == 0) { // this is an ACK
if (pkt->tcp->flags & TH_ACK)
@@ -1487,8 +1508,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
MG_VERBOSE(("ignoring duplicate pkt"));
} else {
MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), "", 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
}
return; // drop it
} else if (io->size - io->len < pkt->pay.len &&
@@ -1496,8 +1517,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
mg_error(c, "oom");
return; // drop it
}
if (pkt->tcp->flags & TH_ACK)
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
if (pkt->tcp->flags & TH_ACK)
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
// Copy TCP payload into the IO buffer. If the connection is plain text,
// we copy to c->recv. If the connection is TLS, this data is encrypted,
// therefore we copy that encrypted data to the c->rtls iobuffer instead,
@@ -1513,8 +1534,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
if (s->unacked > MG_TCPIP_WIN / 2 && s->acked != s->ack) {
// Send ACK immediately
MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), NULL, 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
s->unacked = 0;
s->acked = s->ack;
if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
@@ -1587,9 +1608,9 @@ static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) {
s->dmss = ip6 ? 1220 : 536; // assume default, RFC-9293 3.7.1
while (len > 0) { // RFC-9293 3.1 3.2
uint8_t kind = opts[0], optlen = 1;
if (kind != 1) { // No-Operation
if (kind == 0) break; // End of Option List
if (len < 2 || opts[1] == 0) return false; // Malformed options
if (kind != 1) { // No-Operation
if (kind == 0) break; // End of Option List
if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed
optlen = opts[1];
if (kind == 2 && optlen == 4) // set received MSS
s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]);
@@ -1616,7 +1637,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (!handle_opt(s, pkt->tcp, pkt->ip6 != NULL))
return; // process options (MSS)
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t)(s->seq + s->win);
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack);
c->is_connecting = 0; // Client connected
settmout(c, MIP_TTYPE_KEEPALIVE);
@@ -1922,11 +1943,12 @@ 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) {
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
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 (l2_up && ifp->ip6ll[0] == 0 && ifp->ip6ll[1] == 0) { // gen ll address
uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] == 0) { // gen ll address
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
memcpy(ifp->ip6ll, px, 8); // RFC-4291 2.5.4
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
} // just got our link local address if we didn't have one.
// If static configuration is used, global addresses,
// prefix length, and gw are already filled at this point.
@@ -1934,14 +1956,21 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
if (!l2_up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0;
ifp->state6 = !drv_up ? MG_TCPIP_STATE_DOWN
: !l2_up ? MG_TCPIP_STATE_LINK_UP
: ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_IP;
: ifp->ip6ll[0] == 0 || ifp->enable_slaac || ifp->ip6[0] == 0
? MG_TCPIP_STATE_UP
: MG_TCPIP_STATE_IP;
onstate6change(ifp);
} else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP &&
ifp->ip6[0]) {
ifp->ip6ll[0] != 0 && ifp->ip6[0] != 0) {
ifp->state6 = MG_TCPIP_STATE_IP; // ifp->fn has set an IP
onstate6change(ifp);
}
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
memcpy(ifp->ip6ll, px, 8); // RFC-5072 5
onstate6change(ifp);
} // just got our link local address if we didn't have one.
}
#else
#define mg_ip6_poll(x, y)
@@ -1994,8 +2023,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
drv_up = ifp->driver->poll ? ifp->driver->poll(ifp, expired_1000ms) : true;
l2_up = mg_l2_poll(ifp, expired_1000ms); // Handle L2 up/down link status;
if (expired_1000ms) { // ifp->state rules over state6
mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4
mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6
mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4
mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6
if (ifp->state < MG_TCPIP_STATE_UP) MG_ERROR(("Network is down"));
mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
}
@@ -2034,7 +2063,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
continue;
} else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), NULL, 0);
s->acked = s->ack;
} else if (s->ttype == MIP_TTYPE_SYN) {
@@ -2047,8 +2076,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
mg_error(c, "keepalive");
} else {
MG_VERBOSE(("%lu keepalive", c->id));
tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq - 1),
mg_htonl(s->ack), NULL, 0);
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
}
}
@@ -2111,7 +2140,8 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
static void send_syn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0);
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_SYN, isn, 0, NULL,
0);
}
static void l2addr_resolved(struct mg_connection *c) {
@@ -2132,9 +2162,9 @@ void mg_connect_resolved(struct mg_connection *c) {
c->loc.port = mg_htons(ifp->eport++);
#if MG_ENABLE_IPV6
if (c->rem.is_ip6) {
if(c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1];
} else { // use global address
} else { // use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
}
c->loc.is_ip6 = true;
@@ -2214,7 +2244,7 @@ static void init_closure(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
if (c->is_udp == false && c->is_listening == false &&
c->is_connecting == false) { // For TCP conns,
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK,
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_FIN | TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
settmout(c, MIP_TTYPE_FIN);
}
+2 -1
View File
@@ -29,7 +29,8 @@
((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
(errcode) == -pdFREERTOS_ERRNO_EISCONN || \
(errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
(errcode) == -pdFREERTOS_ERRNO_EAGAIN)
(errcode) == -pdFREERTOS_ERRNO_EAGAIN || \
(errcode) == -pdFREERTOS_ERRNO_ENOSPC)
#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
+31 -1
View File
@@ -3,6 +3,7 @@
#include "json.h"
#include "log.h"
#include "printf.h"
#include "tls.h"
#include "url.h"
#include "util.h"
@@ -26,6 +27,19 @@ static struct mg_ota_state {
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data);
static bool s_tls_init(struct mg_connection *c, const char *url) {
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts));
opts.ca = mg_str(MG_OTA_TLS_CA);
opts.name = mg_url_host(url);
if (opts.ca.len == 0) {
mg_error(c, "MG_OTA_TLS_CA is not configured");
} else {
mg_tls_init(c, &opts);
}
return c->is_tls_hs != 0 && !c->is_closing;
}
#if MG_ENABLE_CUSTOM_DEVICE_ID
#else
void mg_ota_device_id(char *buf, size_t len) {
@@ -58,6 +72,11 @@ void mg_ota_device_id(char *buf, size_t len) {
static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
uint64_t expiration = *(uint64_t *) c->data;
if (s_ota == NULL) {
c->fn = NULL;
c->is_closing = 1;
return;
}
if (ev == MG_EV_POLL) {
if (mg_millis() > expiration) mg_error(c, "Metadata timeout");
} else if (ev == MG_EV_CONNECT) {
@@ -65,6 +84,9 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
struct mg_str host = mg_url_host(s_ota->json_url);
const char *uri = mg_url_uri(s_ota->json_url);
const char *sep = strchr(uri, '?') == NULL ? "?" : "&";
if (mg_url_is_ssl(s_ota->json_url)) {
if (!s_tls_init(c, s_ota->json_url)) return;
}
mg_ota_device_id(id, sizeof(id));
id[sizeof(id) - 1] = '\0';
mg_printf(
@@ -130,10 +152,18 @@ static void status_fn_2(struct mg_connection *c, const char *errmsg) {
static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
uint64_t expiration = *(uint64_t *) c->data;
if (s_ota == NULL) {
c->fn = NULL;
c->is_closing = 1;
return;
}
if (ev == MG_EV_POLL) {
if (mg_millis() > expiration) mg_error(c, "OTA timeout");
} else if (ev == MG_EV_CONNECT) {
struct mg_str host = mg_url_host(s_ota->url);
if (mg_url_is_ssl(s_ota->url)) {
if (!s_tls_init(c, s_ota->url)) return;
}
mg_printf(c,
"GET %s HTTP/1.1\r\n"
"Host: %.*s\r\n"
@@ -185,7 +215,7 @@ void mg_ota_poll(struct mg_mgr *mgr) {
static uint64_t t = 5000; // Fire first time 5 sec after boot
static uint64_t feed_timer; // Advances 500ms per tick; tracks elapsed time
if (MG_OTA_STATE_GET() == MG_OTA_TESTING &&
if (MG_OTA_STATE_GET() == MG_OTA_FAILED &&
mg_timer_expired(&feed_timer, 500, mg_millis())) {
if (feed_timer < (uint64_t) MG_OTA_ROLLBACK_TIMEOUT_SECONDS * 1000) {
MG_OTA_ROLLBACK_TIMER_FEED(); // Feed watchdog every 500ms
+15 -6
View File
@@ -2,7 +2,7 @@
//
// This is a low-level OTA API, not intended to be called directly by users.
// Users are provided with a higher-level API:
// - defining MG_OTA_URL enables HTTP pull-based updates
// - defining MG_OTA_URL enables HTTP or HTTPS pull-based updates
// - mg_http_start_ota() enables push-based updates
//
// However, it is possible to use the mg_ota_* API directly. Below is the
@@ -149,8 +149,10 @@ enum {
} \
} while (0)
// Pull-based OTA over HTTP. Fetches metadata_url, which must return a JSON
// object like: { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 }
// Pull-based OTA over HTTP or HTTPS. If it's HTTPS, MG_OTA_TLS_CA
// needs to be defined. Fetches metadata_url, which must
// return a JSON object like:
// { "version": "1.2.3", "url": "FIRMWARE_URL", "size": 324645 }
// If the server version differs from MG_OTA_FIRMWARE_VERSION, downloads
// FIRMWARE_URL and performs the OTA update. fn is called on every outcome:
// NULL on successful flash, "Same version" when already up to date, or an
@@ -160,12 +162,18 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url,
void (*fn)(const char *error_message));
// Firmware info URL for mg_ota_poll() which calls mg_ota_url_check().
// Example: "http://mongoose.ws/ota/u/0/ota.json". See http://mongoose.ws/ota/
// Example: "http://mongoose.ws/ota/u/0/ota.json". See http://mongoose.ws/ota/
// Settable in mongoose_config.h
#ifndef MG_OTA_URL
#define MG_OTA_URL NULL
#endif
// PEM or DER CA certificate used to authenticate an HTTPS OTA server. Set to
// NULL to use HTTP. Set in mongoose_config.h.
#ifndef MG_OTA_TLS_CA
#define MG_OTA_TLS_CA NULL
#endif
// OTA status callback function for mg_ota_poll()
#ifndef MG_OTA_STATUS_FN
#define MG_OTA_STATUS_FN NULL
@@ -195,6 +203,7 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *metadata_url,
// E.g. on STM32, uses the 96-bit MCU UID converted to a hex string.
void mg_ota_device_id(char *buf, size_t len);
// Checks for a firmware update over HTTP. Called automatically by mg_mgr_poll()
// when MG_OTA_URL is set in mongoose_config.h. Do not call directly.
// Checks for a firmware update over HTTP or HTTPS. Called automatically by
// mg_mgr_poll() when MG_OTA_URL is set in mongoose_config.h. Do not call
// directly.
void mg_ota_poll(struct mg_mgr *);
+4
View File
@@ -46,6 +46,10 @@ bool mg_ota_end(void) {
}
MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
s_ota_update_partition = NULL;
if (s_ota_success) {
MG_OTA_STATE_SET(MG_OTA_TESTING);
esp_restart();
}
return s_ota_success;
}
+27 -25
View File
@@ -491,10 +491,6 @@ MG_IRAM static bool mg_imxrt_erase(void *addr) {
}
#endif
MG_IRAM bool mg_imxrt_swap(void) {
return true;
}
MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) {
struct mg_flexspi_nor_config config, *config_ptr = &config;
bool ok = false;
@@ -550,16 +546,40 @@ fwxit:
return ok;
}
// just overwrite instead of swap
MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) {
// no stdlib calls here
char *scratch = p2 + s;
for (size_t ofs = 0; ofs < s; ofs += ss) {
MG_OTA_ROLLBACK_TIMER_FEED();
mg_imxrt_write(scratch, p1 + ofs, ss);
MG_OTA_ROLLBACK_TIMER_FEED();
mg_imxrt_write(p1 + ofs, p2 + ofs, ss);
MG_OTA_ROLLBACK_TIMER_FEED();
mg_imxrt_write(p2 + ofs, scratch, ss);
}
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
}
MG_IRAM static bool mg_imxrt_swap(void) {
size_t ss = s_mg_flash_imxrt.secsz;
char *p1 = (char *) s_mg_flash_imxrt.start;
char *p2 = p1 + s_mg_flash_imxrt.size / 2;
size_t s = s_mg_flash_imxrt.size / 2 - ss;
MG_INFO(("Swapping partitions, %u bytes", s));
MG_INFO(("Do NOT power off..."));
MG_OTA_ROLLBACK_TIMER_FEED();
mg_log_level = MG_LL_NONE;
s_flash_irq_disabled = true;
single_bank_swap(p1, p2, s, ss);
return true; // unreachable
}
bool mg_ota_begin(size_t new_firmware_size) {
size_t max = s_mg_flash_imxrt.size / 2 - s_mg_flash_imxrt.secsz;
if (new_firmware_size > max) {
MG_ERROR(("Firmware %lu too big for single-bank OTA, max %lu",
new_firmware_size, max));
return false;
}
return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt);
}
@@ -568,25 +588,7 @@ bool mg_ota_write(const void *buf, size_t len) {
}
bool mg_ota_end(void) {
if (mg_ota_flash_end(&s_mg_flash_imxrt)) {
if (0) { // is_dualbank()
// TODO(): no devices so far
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
} else {
// Swap partitions. Pray power does not go away
MG_INFO(("Swapping partitions, size %u (%u sectors)",
s_mg_flash_imxrt.size,
s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz));
MG_INFO(("Do NOT power off..."));
mg_log_level = MG_LL_NONE;
s_flash_irq_disabled = true;
// Runs in RAM, will reset when finished
single_bank_swap(
(char *) s_mg_flash_imxrt.start,
(char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2,
s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz);
}
}
mg_ota_flash_end(&s_mg_flash_imxrt);
return false;
}
struct mg_flash *mg_flash = &s_mg_flash_imxrt;
+1 -3
View File
@@ -45,11 +45,9 @@ MG_IRAM static int is_dualbank(void) {
}
MG_IRAM static void flash_unlock(void) {
static bool unlocked = false;
if (unlocked == false) {
if (MG_REG(MG_FLASH_BASE + MG_FLASH_CR) & MG_BIT(31)) {
MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0x45670123;
MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0xcdef89ab;
unlocked = true;
}
}
+5 -4
View File
@@ -39,13 +39,14 @@ static uint32_t sectors_per_bank(void) {
}
static void flash_unlock(void) {
static bool unlocked = false;
if (unlocked == false) {
if (MG_REG(FLASH_NSCR) & MG_BIT(0)) {
MG_REG(FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_KEYR) = 0Xcdef89ab;
MG_REG(FLASH_KEYR) = 0xcdef89ab;
}
if (MG_REG(FLASH_OPTCR) & MG_BIT(0)) {
MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
unlocked = true;
}
}
+11 -11
View File
@@ -53,17 +53,17 @@ MG_IRAM static bool is_dualbank(void) {
}
MG_IRAM static void flash_unlock(void) {
static bool unlocked = false;
if (unlocked == false) {
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
if (is_dualbank()) {
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
}
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared"
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once
unlocked = true;
if (MG_REG(FLASH_BASE1 + FLASH_CR) & MG_BIT(0)) {
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
}
if (is_dualbank() && MG_REG(FLASH_BASE2 + FLASH_CR) & MG_BIT(0)) {
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
}
if (MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(0)) {
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared"
MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once
}
}
+9
View File
@@ -449,6 +449,7 @@ void mg_connect_resolved(struct mg_connection *c) {
if (rc == 0) { // Success
setlocaddr(FD(c), &c->loc);
mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user
if (c->is_tls_hs) mg_tls_handshake(c);
if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init()
} else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake
MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem));
@@ -465,6 +466,14 @@ static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
do {
memset(usa, 0, sizeof(*usa));
fd = accept(sock, &usa->sa, len);
#if MG_ENABLE_FREERTOS_TCP
// FreeRTOS_accept() returns NULL to mean "no pending connection",
// therefore we avoid retrying it forever on non-blocking listeners
if (fd == NULL) {
fd = MG_INVALID_SOCKET;
break;
}
#endif
} while (MG_SOCK_INTR(fd));
return fd;
}
+6
View File
@@ -87,6 +87,12 @@ void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *);
#define MG_IS_DER(buf) (((uint8_t *) (buf))[0] == 0x30) // DER begins with 0x30
#if MG_TLS == MG_TLS_BUILTIN
// Extracts a 32-byte P-256 private scalar from EC PRIVATE KEY or PKCS#8 PRIVATE
// KEY input. Accepts PEM or DER. Returns key size, or 0 on error.
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len);
#endif
// Low-level IO primitives used by TLS layer
enum { MG_IO_ERR = -1, MG_IO_WAIT = -2 };
long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
+5 -9
View File
@@ -22,6 +22,7 @@
#include "arch.h"
#include "tls.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN
/******************************************************************************/
#define AES_DECRYPTION 1 // whether AES decryption is supported
@@ -1061,10 +1062,9 @@ int gcm_crypt_and_tag(
prepare the gcm context with the keying material, we simply
invoke each of the three GCM sub-functions in turn...
*/
gcm_start(ctx, mode, iv, iv_len, add, add_len);
gcm_update(ctx, length, input, output);
gcm_finish(ctx, tag, tag_len);
return (0);
if (gcm_start(ctx, mode, iv, iv_len, add, add_len) != 0) return -1;
if (gcm_update(ctx, length, input, output) != 0) return -1;
return gcm_finish(ctx, tag, tag_len);
}
/******************************************************************************
@@ -1116,8 +1116,6 @@ int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
int ret = 0; // our return value
gcm_context ctx; // includes the AES context structure
unsigned char computed_tag[16];
size_t i;
int diff = 0;
if (tag_len > sizeof(computed_tag)) return -1;
@@ -1128,9 +1126,7 @@ int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
gcm_zero_ctx(&ctx);
// compare tags
for (i = 0; i < tag_len; i++) diff |= computed_tag[i] ^ tag[i];
if (diff != 0) ret = -1;
if (!mg_memeq(computed_tag, tag, tag_len)) ret = -1;
return (ret);
}
+99 -46
View File
@@ -112,6 +112,8 @@ struct tls_data {
bool skip_verification; // do not perform checks on server certificate
bool cert_requested; // client received a CertificateRequest
bool is_twoway; // server is configured to authenticate clients
bool is_sntp_pending; // TLS handshake is waiting for wall-clock time
struct mg_connection *timec; // SNTP connection for wall-clock time
struct mg_str cert_der; // certificate in DER format
struct mg_str ca_der; // current CA certificate
struct mg_str *ca_bundle_der; // bundle of CA certificates
@@ -192,16 +194,14 @@ static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) {
if (tlv->len > 0x7f) { // long-form length
uint32_t i;
n = tlv->len - 0x80;
if (n > 4 || dersz < (2 + n)) return -1;
if (n == 0 || n > 4 || dersz < (2 + n)) return -1;
tlv->len = 0;
for (i = 0; i < n; i++) {
tlv->len = (tlv->len << 8) | der[2 + i];
}
if (n > (dersz - 2)) return -1;
tlv->value += n;
} else {
if (der + dersz < tlv->value + tlv->len) return -1;
}
if (tlv->len > dersz - (size_t) (tlv->value - der)) return -1;
return (int) n;
}
@@ -557,7 +557,7 @@ static int mg_tls_recv_record(struct mg_connection *c) {
msgsz = MG_LOAD_BE16(rio->buf + 3);
msg = rio->buf + 5;
if (msgsz < 16) {
if (msgsz < 17) {
mg_error(c, "wrong size");
return -1;
}
@@ -1319,6 +1319,7 @@ static int mg_tls_client_recv_hello(struct mg_connection *c) {
if (rio->len < 5 + 39 + 32 + 3 + 2) goto fail;
msgsz = MG_LOAD_BE16(rio->buf + 3);
if (msgsz > rio->len - 5) goto fail;
mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
@@ -1404,8 +1405,7 @@ static void mg_der_debug_cert_name(const char *name, struct mg_der_tlv *tlv) {
}
static uint64_t asnt2t(uint8_t *v, uint8_t type) {
unsigned int y, mo, d, h, mi, ss, ly;
uint16_t dm[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
unsigned int y, mo, d, h, mi, ss;
y = 10U * (*v++ - '0'), y += (*v++ - '0');
if (type == 0x17) { // UTCTime, RFC-5280 4.1.2.5.1 YYMMDDHHMMSSZ
if (y >= 50) return (uint64_t) 0; // 19YY is in the past
@@ -1413,17 +1413,13 @@ static uint64_t asnt2t(uint8_t *v, uint8_t type) {
} else { // GeneralizedTime, RFC-5280 4.1.2.5.2 YYYYMMDDHHMMSSZ
y *= 100U, y += 10U * (*v++ - '0'), y += (*v++ - '0');
}
y -= 1900;
mo = 10U * (*v++ - '0'), mo += (*v++ - '0');
d = 10U * (*v++ - '0'), d += (*v++ - '0');
h = 10U * (*v++ - '0'), h += (*v++ - '0');
mi = 10U * (*v++ - '0'), mi += (*v++ - '0');
ss = 10U * (*v++ - '0'), ss += (*v++ - '0');
if (*v != 'Z') return 0; // invalid
ly = (mo > 2) ? y + 1 : y;
return (uint64_t) ss + 60U * mi + 3600U * h + 86400U * (dm[mo - 1] + d - 1) +
31536000U * (y - 70) + 86400U * ((ly - 69) / 4U) -
86400U * ((ly - 1) / 100U) + 86400U * ((ly + 299) / 400U);
return mg_timegm(y, mo, d, h, mi, ss);
}
static int mg_tls_parse_cert_der(void *buf, size_t dersz,
@@ -1704,7 +1700,6 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
return 0;
}
} else {
uint8_t r;
const uint8_t *n;
size_t nlen;
uint8_t sig2[512]; // 4096 bits
@@ -1724,15 +1719,7 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
return 0;
}
r = 0;
{
const uint8_t *p, *q;
size_t i;
p = sig2 + nlen - cert->tbshashsz;
q = cert->tbshash;
for (i = 0; i < cert->tbshashsz; i++) r |= (uint8_t)(*p++ ^ *q++);
}
return r == 0;
return mg_rsa_pkcs_verify(sig2, nlen, cert->tbshash, cert->tbshashsz);
}
}
@@ -1903,7 +1890,11 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
mg_error(c, r < 0 ? "failed to parse CA bundle"
: "failed to find issuing CA in bundle");
return -1;
} // else r > 0 => issuer found
} else if (!ca.is_ca || // candidate issuer found
!mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) {
mg_error(c, "failed to verify CA");
return -1;
} // issuer verified. NOTE(): may fail if two diff CAs share a CN
MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
}
if (!found_ca && tls->ca_der.len > 0) { // single cert or server
@@ -2159,6 +2150,7 @@ void mg_tls_handshake(struct mg_connection *c) {
long n;
bool res;
if (c->is_closing) return; // we don't clear rx buf, so ignore what's left
if (tls->is_sntp_pending) return;
if (c->is_client) {
// will clear is_hs when sending last chunk
res = mg_tls_client_handshake(c);
@@ -2198,7 +2190,7 @@ static int mg_rsa_parse_der_int(const uint8_t **p, const uint8_t *end,
// Long form length
uint8_t len_bytes = len & 0x7F;
MG_VERBOSE(("DER INT: long form, %d length bytes", len_bytes));
if (end - *p < len_bytes) {
if (len_bytes == 0 || len_bytes > 4 || (size_t) (end - *p) < len_bytes) {
MG_VERBOSE(("DER INT: not enough bytes for length"));
return -1;
}
@@ -2211,7 +2203,7 @@ static int mg_rsa_parse_der_int(const uint8_t **p, const uint8_t *end,
MG_VERBOSE(("DER INT: length=%u, remaining=%d", len, (int) (end - *p)));
if (end - *p < (long) len) {
if ((size_t) (end - *p) < len) {
MG_VERBOSE(("DER INT: length exceeds remaining bytes"));
return -1;
}
@@ -2253,12 +2245,12 @@ static int mg_parse_ec_private_key(const uint8_t *der, size_t dersz,
return -1;
}
if (mg_der_next(&root, &version) < 0 || version.type != 0x02) {
if (mg_der_next(&root, &version) <= 0 || version.type != 0x02) {
MG_ERROR(("EC private key: invalid version"));
return -1;
}
if (mg_der_next(&root, &private_key_octets) < 0 ||
if (mg_der_next(&root, &private_key_octets) <= 0 ||
private_key_octets.type != 0x04) {
MG_ERROR(("EC private key: invalid privateKey OCTET STRING"));
return -1;
@@ -2312,7 +2304,7 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
// Long form length
uint8_t i, len_bytes = seq_len & 0x7F;
MG_VERBOSE(("Long form length: %d bytes", len_bytes));
if (end - p < len_bytes) {
if (len_bytes == 0 || len_bytes > 4 || (size_t) (end - p) < len_bytes) {
MG_ERROR(("Not enough bytes for long form length"));
return -1;
}
@@ -2326,7 +2318,7 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
MG_VERBOSE(
("SEQUENCE length: %u, total DER size: %u", seq_len, (unsigned) dersz));
if (end - p < (long) seq_len) {
if ((size_t) (end - p) < seq_len) {
MG_ERROR(("SEQUENCE length exceeds buffer"));
return -1;
}
@@ -2334,7 +2326,9 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
// Parse version (should be 0)
MG_VERBOSE(("Before version: offset=%d, bytes: %02x %02x %02x %02x",
(int) (p - der), p[0], p[1], p[2], p[3]));
(int) (p - der), p < end ? p[0] : 0xFF,
p + 1 < end ? p[1] : 0xFF, p + 2 < end ? p[2] : 0xFF,
p + 3 < end ? p[3] : 0xFF));
if (mg_rsa_parse_der_int(&p, end, &version) < 0) {
MG_ERROR(("Failed to parse version"));
return -1;
@@ -2345,7 +2339,10 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
// Parse the 8 components: n, e, d, p, q, dP, dQ, qInv
MG_VERBOSE(("Before n: offset=%d, bytes: %02x %02x %02x %02x %02x %02x",
(int) (p - der), p[0], p[1], p[2], p[3], p[4], p[5]));
(int) (p - der), p < end ? p[0] : 0xFF,
p + 1 < end ? p[1] : 0xFF, p + 2 < end ? p[2] : 0xFF,
p + 3 < end ? p[3] : 0xFF, p + 4 < end ? p[4] : 0xFF,
p + 5 < end ? p[5] : 0xFF));
if (mg_rsa_parse_der_int(&p, end, &key->n) < 0) {
MG_ERROR(("Failed to parse n (modulus)"));
return -1;
@@ -2353,10 +2350,14 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
MG_VERBOSE(("Parsed n: %d bytes, offset now=%d, consumed=%d bytes total",
(int) key->n.len, (int) (p - der), (int) (p - der)));
MG_VERBOSE((" First 8 bytes of n: %02x %02x %02x %02x %02x %02x %02x %02x",
(unsigned char) key->n.buf[0], (unsigned char) key->n.buf[1],
(unsigned char) key->n.buf[2], (unsigned char) key->n.buf[3],
(unsigned char) key->n.buf[4], (unsigned char) key->n.buf[5],
(unsigned char) key->n.buf[6], (unsigned char) key->n.buf[7]));
key->n.len > 0 ? (unsigned char) key->n.buf[0] : 0xFF,
key->n.len > 1 ? (unsigned char) key->n.buf[1] : 0xFF,
key->n.len > 2 ? (unsigned char) key->n.buf[2] : 0xFF,
key->n.len > 3 ? (unsigned char) key->n.buf[3] : 0xFF,
key->n.len > 4 ? (unsigned char) key->n.buf[4] : 0xFF,
key->n.len > 5 ? (unsigned char) key->n.buf[5] : 0xFF,
key->n.len > 6 ? (unsigned char) key->n.buf[6] : 0xFF,
key->n.len > 7 ? (unsigned char) key->n.buf[7] : 0xFF));
MG_VERBOSE((" Next bytes after n: %02x %02x %02x %02x %02x %02x",
p < end ? p[0] : 0xFF, p + 1 < end ? p[1] : 0xFF,
p + 2 < end ? p[2] : 0xFF, p + 3 < end ? p[3] : 0xFF,
@@ -2410,7 +2411,7 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
// parameters ANY OPTIONAL
// }
static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
struct tls_data *tls) {
struct mg_rsa_key *rsa, uint8_t *ec_key) {
struct mg_der_tlv root, version, alg_id, private_key_octets;
struct mg_der_tlv alg_oid, alg_params;
@@ -2419,22 +2420,22 @@ static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
return -1;
}
if (mg_der_next(&root, &version) < 0 || version.type != 0x02) {
if (mg_der_next(&root, &version) <= 0 || version.type != 0x02) {
MG_ERROR(("PKCS#8: invalid version"));
return -1;
}
if (mg_der_next(&root, &alg_id) < 0 || alg_id.type != 0x30) {
if (mg_der_next(&root, &alg_id) <= 0 || alg_id.type != 0x30) {
MG_ERROR(("PKCS#8: invalid AlgorithmIdentifier SEQUENCE"));
return -1;
}
if (mg_der_next(&alg_id, &alg_oid) < 0 || alg_oid.type != 0x06) {
if (mg_der_next(&alg_id, &alg_oid) <= 0 || alg_oid.type != 0x06) {
MG_ERROR(("PKCS#8: invalid algorithm OID"));
return -1;
}
if (mg_der_next(&root, &private_key_octets) < 0 ||
if (mg_der_next(&root, &private_key_octets) <= 0 ||
private_key_octets.type != 0x04) {
MG_ERROR(("PKCS#8: invalid privateKey OCTET STRING"));
return -1;
@@ -2443,18 +2444,20 @@ static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
if (alg_oid.len == sizeof(mg_rsa_oid) &&
memcmp(alg_oid.value, mg_rsa_oid, sizeof(mg_rsa_oid)) == 0) {
struct mg_rsa_key rsa_key;
if (rsa == NULL) return -1;
if (mg_rsa_parse_key(private_key_octets.value, private_key_octets.len,
&rsa_key) < 0) {
MG_ERROR(("PKCS#8: failed to parse inner RSA key"));
return -1;
}
tls->rsa = rsa_key;
*rsa = rsa_key;
return 0;
} else if (alg_oid.len == sizeof(mg_ec_public_key_oid) &&
memcmp(alg_oid.value, mg_ec_public_key_oid,
sizeof(mg_ec_public_key_oid)) == 0) {
if (mg_der_next(&alg_id, &alg_params) < 0 || alg_params.type != 0x06) {
if (ec_key == NULL) return -1;
if (mg_der_next(&alg_id, &alg_params) <= 0 || alg_params.type != 0x06) {
MG_ERROR(("PKCS#8: invalid EC parameters OID"));
return -1;
}
@@ -2467,7 +2470,7 @@ static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
}
return mg_parse_ec_private_key(private_key_octets.value,
private_key_octets.len, tls->ec_key);
private_key_octets.len, ec_key);
} else {
MG_ERROR(("PKCS#8: unsupported algorithm"));
@@ -2572,6 +2575,38 @@ static int mg_parse_pem_certs(const struct mg_str pem, struct mg_str **ders) {
return count;
}
size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len) {
struct mg_str der = mg_str_n(NULL, 0);
size_t n = 0;
if (buf == NULL || len < 32) return 0;
// current mg_parse_ec_private_key() only handles 32-byte keys
if (mg_parse_pem(key, mg_str_s("EC PRIVATE KEY"), &der) == 0) {
if (mg_parse_ec_private_key((uint8_t *) der.buf, der.len, buf) == 0) n = 32;
} else if (mg_parse_pem(key, mg_str_s("PRIVATE KEY"), &der) == 0) {
if (mg_parse_pkcs8_key((uint8_t *) der.buf, der.len, NULL, buf) == 0) n = 32;
}
mg_free((void *) der.buf);
return n;
}
static void sync_time_cancel(struct mg_connection *c) {
c->fn = NULL;
c->is_closing = 1;
}
static void sync_time_cb(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_SNTP_TIME || (ev == MG_EV_CLOSE)) {
struct mg_connection *tlsc = (struct mg_connection *) c->fn_data;
struct tls_data *tls = (struct tls_data *) tlsc->tls;
sync_time_cancel(c); // c = tls->timec
tls->timec = NULL;
tls->is_sntp_pending = false;
if (ev == MG_EV_SNTP_TIME) mg_tls_handshake(tlsc);
if (ev == MG_EV_CLOSE) mg_error(tlsc, "time sync failed");
}
(void) ev_data;
}
void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
struct mg_str key;
struct tls_data *tls =
@@ -2617,7 +2652,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
} else { // parse again for a possible DER (or a truncated begin string)
if (mg_parse_pem(opts->ca, mg_str_s("CERTIFICATE"), &tls->ca_der) < 0) {
MG_ERROR(("Failed to load CA certificate"));
return;
goto xit;
}
} // ca_bundle_len != 0 && ca_der.len = 0 => bundle
if (!c->is_client) tls->is_twoway = true; // server + CA: two-way auth
@@ -2625,7 +2660,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
if (opts->cert.buf == NULL) {
MG_VERBOSE(("No certificate provided"));
return;
goto xit;
}
// parse PEM or DER certificate
@@ -2696,7 +2731,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
// Copy parsed RSA key components to tls->rsa for signing operations
tls->rsa = rsa_key;
} else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) {
if (mg_parse_pkcs8_key((const uint8_t *) key.buf, key.len, tls) == 0) {
if (mg_parse_pkcs8_key((const uint8_t *) key.buf, key.len, &tls->rsa,
tls->ec_key) == 0) {
if (tls->rsa.n.len > 0) {
tls->rsa_key_der = key;
MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
@@ -2712,6 +2748,19 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
} else {
mg_error(
c, "Expected EC PRIVATE KEY, RSA PRIVATE KEY, or PRIVATE KEY (PKCS#8)");
return;
}
xit:
if (mg_boot_timestamp_ms == 0 && !tls->skip_verification &&
(tls->ca_bundle_len > 0 || tls->ca_der.len > 0)) {
struct mg_connection *timec;
timec = mg_sntp_connect(c->mgr, NULL, sync_time_cb, c);
if (timec == NULL) {
mg_error(c, "time sync failed");
return;
}
tls->is_sntp_pending = true;
tls->timec = timec;
}
}
@@ -2719,6 +2768,10 @@ void mg_tls_free(struct mg_connection *c) {
struct tls_data *tls = (struct tls_data *) c->tls;
size_t i;
if (tls != NULL) {
if (tls->timec != NULL) {
sync_time_cancel(tls->timec);
tls->timec = NULL;
}
mg_iobuf_free(&tls->send);
if (tls->chain_der != NULL) {
for (i = 0; i < tls->chain_len; i++) {
+4 -7
View File
@@ -6,8 +6,9 @@
#include "tls_chacha20.h"
#include "tls.h"
#include "util.h"
#if MG_TLS == MG_TLS_BUILTIN
#if MG_TLS == MG_TLS_BUILTIN && MG_ENABLE_CHACHA20
// ******* BEGIN: chacha-portable/chacha-portable.h ********
#if !defined(__cplusplus) && !defined(_MSC_VER) && \
@@ -1336,8 +1337,6 @@ PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt(
// first we calculate the mac and see if it lines up, only then do we decrypt
size_t actual_size = cipher_text_size - RFC_8439_TAG_SIZE;
uint8_t computed_mac[RFC_8439_TAG_SIZE];
int diff = 0;
size_t i;
if (MG_OVERLAPPING(plain_text, actual_size, cipher_text, cipher_text_size)) {
return (size_t) -1;
}
@@ -1345,10 +1344,8 @@ PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt(
poly1305_calculate_mac(computed_mac, cipher_text, actual_size, key, nonce, ad,
ad_size);
// compare tags
for (i = 0; i < RFC_8439_TAG_SIZE; i++)
diff |= computed_mac[i] ^ cipher_text[actual_size + i];
if (diff != 0) return (size_t) -1;
if (!mg_memeq(computed_mac, cipher_text + actual_size, RFC_8439_TAG_SIZE))
return (size_t) -1;
chacha20_xor_stream(plain_text, cipher_text, actual_size, key, nonce, 1);
return actual_size;
+1 -1
View File
@@ -8,7 +8,7 @@
#include "config.h"
#include "tls.h"
#if MG_TLS == MG_TLS_BUILTIN
#if MG_TLS == MG_TLS_BUILTIN && MG_ENABLE_CHACHA20
#ifndef __PORTABLE_8439_H
#define __PORTABLE_8439_H
#if defined(__cplusplus)
+26 -14
View File
@@ -92,6 +92,11 @@ void mg_tls_handshake(struct mg_connection *c) {
struct mg_tls *tls = (struct mg_tls *) c->tls;
int rc = mbedtls_ssl_handshake(&tls->ssl);
if (rc == 0) { // Success
if (tls->check_name && (mbedtls_ssl_get_verify_result(&tls->ssl) &
MBEDTLS_X509_BADCERT_CN_MISMATCH)) {
mg_error(c, "failed to verify hostname");
return;
} // ignore MBEDTLS_X509_BADCERT_NOT_TRUSTED, no CA cert given
MG_DEBUG(("%lu success", c->id));
c->is_tls_hs = 0;
mg_call(c, MG_EV_TLS_HS, NULL);
@@ -113,6 +118,7 @@ static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls));
int rc = 0;
bool check_name = false;
c->tls = tls;
if (c->tls == NULL) {
mg_error(c, "TLS OOM");
@@ -143,24 +149,30 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
#endif
if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 &&
opts->name.buf[0] != '\0') {
char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
mbedtls_ssl_set_hostname(&tls->ssl, host);
#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03030000 || \
MBEDTLS_VERSION_NUMBER >= 0x04000000
// NOTE: MBEDTLS_SSL_VERIFY_OPTIONAL is not supported for TLS1.3 on client
// side See https://github.com/Mbed-TLS/mbedtls/issues/7075
check_name = true;
#endif
MG_DEBUG(("%lu hostname verification: %s", c->id, host));
mg_free(host);
} else {
MG_DEBUG(("%lu skipping hostname verification", c->id));
mbedtls_ssl_set_hostname(&tls->ssl, NULL);
}
if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
// NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client side
// See https://github.com/Mbed-TLS/mbedtls/issues/7075
mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
mbedtls_ssl_conf_authmode(&tls->conf, check_name // see set comment above
? MBEDTLS_SSL_VERIFY_OPTIONAL
: MBEDTLS_SSL_VERIFY_NONE);
tls->check_name = check_name; // host name set but no CA cert given
} else {
if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
if (c->is_client) {
if (opts->name.buf != NULL && opts->name.buf[0] != '\0') {
char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
mbedtls_ssl_set_hostname(&tls->ssl, host);
MG_DEBUG(("%lu hostname verification: %s", c->id, host));
mg_free(host);
} else {
MG_DEBUG(("%lu skipping hostname verification", c->id));
mbedtls_ssl_set_hostname(&tls->ssl, NULL);
}
}
mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
}
if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
+2 -1
View File
@@ -24,12 +24,13 @@ struct mg_tls {
mbedtls_x509_crt cert; // Parsed certificate
mbedtls_pk_context pk; // Private key context
mbedtls_ssl_context ssl; // SSL/TLS context
mbedtls_ssl_config conf; // SSL-TLS config
mbedtls_ssl_config conf; // SSL/TLS config
#ifdef MBEDTLS_SSL_SESSION_TICKETS
mbedtls_ssl_ticket_context ticket; // Session tickets context
#endif
// https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29
unsigned char *throttled_buf; // see #3074
size_t throttled_len;
bool check_name; // set when hostname was set, but no CA certificate given
};
#endif
+33 -8
View File
@@ -141,6 +141,7 @@ void mg_tls_free(struct mg_connection *c) {
SSL_free(tls->ssl);
SSL_CTX_free(tls->ctx);
BIO_meth_free(tls->bm);
mg_free(tls->name);
mg_free(tls);
c->tls = NULL;
}
@@ -148,6 +149,7 @@ void mg_tls_free(struct mg_connection *c) {
void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
struct mg_tls *tls = (struct mg_tls *) mg_calloc(1, sizeof(*tls));
const char *id = "mongoose";
bool check_name = false;
static unsigned char s_initialised = 0;
BIO *bio = NULL;
int rc;
@@ -189,6 +191,14 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
#endif
if (c->is_client && opts->name.buf != NULL && opts->name.len > 0 && opts->name.buf[0] != '\0') {
tls->name = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf);
if (tls->name == NULL) {
mg_error(c, "TLS OOM");
goto fail;
}
check_name = true;
}
#if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS)
if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
// Older versions require that either the CA is loaded or SSL_VERIFY_NONE
@@ -197,7 +207,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
}
#endif
if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
if (opts->ca.buf != NULL && opts->ca.len > 0 && opts->ca.buf[0] != '\0') {
SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
NULL);
#if MG_TLS == MG_TLS_WOLFSSL
@@ -219,6 +229,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
goto fail;
}
#endif
} else {
tls->check_name = check_name; // host name set but no CA cert given
}
if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
@@ -243,15 +255,13 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
(void) SSL_set_ecdh_auto(tls->ssl, 1);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (opts->name.len > 0) {
char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf);
if (tls->name != NULL) {
#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002
SSL_set1_host(tls->ssl, s);
SSL_set1_host(tls->ssl, tls->name);
#else
X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), tls->name, 0);
#endif
SSL_set_tlsext_host_name(tls->ssl, s);
mg_free(s);
SSL_set_tlsext_host_name(tls->ssl, tls->name);
}
#endif
#if MG_TLS == MG_TLS_WOLFSSL
@@ -279,6 +289,16 @@ void mg_tls_handshake(struct mg_connection *c) {
struct mg_tls *tls = (struct mg_tls *) c->tls;
int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
if (rc == 1) {
if (tls->check_name) { // host name set but no CA cert given
X509 *cert = SSL_get_peer_certificate(tls->ssl);
bool ok =
cert != NULL && X509_check_host(cert, tls->name, 0, 0, NULL) == 1;
X509_free(cert);
if (!ok) {
mg_error(c, "failed to verify hostname");
return;
}
}
MG_DEBUG(("%lu success", c->id));
c->is_tls_hs = 0;
mg_call(c, MG_EV_TLS_HS, NULL);
@@ -295,7 +315,12 @@ size_t mg_tls_pending(struct mg_connection *c) {
long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
struct mg_tls *tls = (struct mg_tls *) c->tls;
int n = SSL_read(tls->ssl, buf, (int) len);
int n;
#if MG_TLS == MG_TLS_WOLFSSL
uint8_t dummy; // WolfSSL requires destination != NULL
if (buf == NULL && len == 0) buf = &dummy;
#endif
n = SSL_read(tls->ssl, buf, (int) len);
if (!c->is_tls_hs && (buf == NULL || len == 0) && n == 0) return 0; // MIP
if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
if (n <= 0) return MG_IO_ERR;
+3
View File
@@ -4,10 +4,13 @@
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
struct mg_tls {
BIO_METHOD *bm;
SSL_CTX *ctx;
SSL *ssl;
char *name; // matching hostname
bool check_name; // set when hostname was set, but no CA certificate given
};
#endif
+37
View File
@@ -950,4 +950,41 @@ bool mg_rsa_verify(const uint8_t *em, size_t nlen, const uint8_t *mhash) {
return pss_verify_sha256(em, nlen, mhash);
}
// Full RSASSA-PKCS1-v1_5-VERIFY for rsa_pkcs1_sha256/sha384 (RFC 8017 9.2)
// em - output of RSA public-key primitive (nlen bytes)
// nlen - modulus byte length
// hash - SHA-256 or SHA-384 of the certificate TBS data
// hashlen - hash length, 32 for SHA-256 or 48 for SHA-384
static bool pkcs1_v15_verify(const uint8_t *em, size_t nlen,
const uint8_t *hash, size_t hashlen) {
static const uint8_t sha256_di[] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
static const uint8_t sha384_di[] = {
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30};
const uint8_t *di = hashlen == 32 ? sha256_di : sha384_di;
size_t disz = hashlen == 32 ? sizeof(sha256_di) : sizeof(sha384_di);
size_t pslen, i;
uint8_t bad = 0;
if (hashlen != 32 && hashlen != 48) return false;
if (nlen < 3 + 8 + disz + hashlen) return false;
pslen = nlen - 3 - disz - hashlen;
bad |= (uint8_t) (em[0] | (em[1] ^ 0x01));
for (i = 0; i < pslen; i++) bad |= (uint8_t) (em[2 + i] ^ 0xff);
bad |= em[2 + pslen];
for (i = 0; i < disz; i++) bad |= (uint8_t) (em[3 + pslen + i] ^ di[i]);
for (i = 0; i < hashlen; i++)
bad |= (uint8_t) (em[3 + pslen + disz + i] ^ hash[i]);
return bad == 0;
}
bool mg_rsa_pkcs_verify(const uint8_t *em, size_t nlen, const uint8_t *hash,
size_t hashlen) {
return pkcs1_v15_verify(em, nlen, hash, hashlen);
}
#endif /* MG_TLS == MG_TLS_BUILTIN */
+2
View File
@@ -10,5 +10,7 @@ int mg_rsa_crt_sign(const uint8_t *em, size_t em_len,
const uint8_t *q, size_t q_len,
const uint8_t *qInv, size_t qInv_len,
uint8_t *signature, size_t sig_len);
bool mg_rsa_pkcs_verify(const uint8_t *em, size_t nlen, const uint8_t *hash,
size_t hashlen);
bool mg_rsa_verify(const uint8_t *em, size_t nlen, const uint8_t *mhash);
#endif // TLS_RSA_H
+4 -12
View File
@@ -222,11 +222,7 @@ static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
#define asm_mmod_fast_secp256r1 0
#endif
#if defined(default_RNG_defined) && default_RNG_defined
static MG_UECC_RNG_Function g_rng_function = &default_RNG;
#else
static MG_UECC_RNG_Function g_rng_function = 0;
#endif
static MG_UECC_RNG_Function g_rng_function = NULL;
void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
g_rng_function = rng_function;
@@ -2560,15 +2556,11 @@ MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random,
mg_uecc_word_t tries;
bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
if (!g_rng_function) {
return 0;
}
for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
if (!g_rng_function((uint8_t *) random,
(unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
unsigned int len = (unsigned int) (num_words * MG_UECC_WORD_SIZE);
if (!(g_rng_function == NULL ? mg_random((uint8_t *) random, len)
: g_rng_function((uint8_t *) random, len)))
return 0;
}
random[num_words - 1] &=
mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits));
if (!mg_uecc_vli_isZero(random, num_words) &&
+10 -10
View File
@@ -113,28 +113,28 @@ if 'dest' was filled with random data, or 0 if the random data could not be
generated. The filled-in values should be either truly random, or from a
cryptographically-secure PRNG.
A correctly functioning RNG function must be set (using mg_uecc_set_rng())
before calling mg_uecc_make_key() or mg_uecc_sign().
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().
A correct RNG function is set by default when building for Windows, Linux, or OS
X. If you are building on another POSIX-compliant system that supports
/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
RNG. For embedded platforms there is no predefined RNG function; you must
provide your own.
*/
typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
// 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.
On platforms where there is no predefined RNG function (eg embedded platforms),
this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
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.
+38
View File
@@ -9,6 +9,30 @@ void mg_bzero(volatile unsigned char *buf, size_t len) {
}
}
uint64_t mg_timegm(unsigned int year, unsigned int month, unsigned int day,
unsigned int hour, unsigned int min, unsigned int sec) {
static const uint16_t dm[12] = {0, 31, 59, 90, 120, 151,
181, 212, 243, 273, 304, 334};
const uint64_t day_secs = 86400;
const uint64_t year_secs = 31536000;
unsigned int y, ly;
if (year < 1970) return 0;
y = year - 1900;
ly = month > 2 ? y + 1 : y;
return (uint64_t) sec + 60 * min + 3600 * hour +
day_secs * (dm[month - 1] + day - 1) + year_secs * (y - 70) +
day_secs * ((ly - 69) / 4) - day_secs * ((ly - 1) / 100) +
day_secs * ((ly + 299) / 400);
}
bool mg_memeq(const void *a, const void *b, size_t n) {
const uint8_t *p = (const uint8_t *) a, *q = (const uint8_t *) b;
uint8_t r = 0;
size_t i;
for (i = 0; i < n; i++) r = (uint8_t) (r | (uint8_t) (p[i] ^ q[i]));
return r == 0;
}
#if MG_ENABLE_CUSTOM_RANDOM
#else
bool mg_random(void *buf, size_t len) {
@@ -104,6 +128,20 @@ uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
return ~crc;
}
uint16_t mg_crc16(uint16_t crc, const char *buf, size_t len) {
static const uint16_t crclut[16] = {
// table for polynomial 0x8408 (reflected)
0x0000, 0x1081, 0x2102, 0x3183, 0x4204, 0x5285, 0x6306, 0x7387,
0x8408, 0x9489, 0xA50A, 0xB58B, 0xC60C, 0xD68D, 0xE70E, 0xF78F};
unsigned int c = (unsigned int) ~crc & 0xffff;
while (len--) {
uint8_t b = *(uint8_t *) buf++;
c = crclut[(c ^ b) & 0x0F] ^ (c >> 4);
c = crclut[(c ^ (b >> 4)) & 0x0F] ^ (c >> 4);
}
return (uint16_t) ~c;
}
static int isbyte(int n) {
return n >= 0 && n <= 255;
}
+10
View File
@@ -22,6 +22,9 @@ void mg_free(void *ptr);
// sensitive data (keys, passwords).
void mg_bzero(volatile unsigned char *buf, size_t len);
// Fixed-length constant-time byte equality. Use for MACs, tags, signatures.
bool mg_memeq(const void *a, const void *b, size_t n);
// Fills buf with len cryptographically random bytes. Uses the best available
// hardware or OS source (hardware RNG, /dev/urandom, CryptGenRandom, etc.).
// Falls back to rand() with an error log if no strong source is available.
@@ -37,6 +40,10 @@ char *mg_random_str(char *buf, size_t len);
// a new checksum; pass the result of a prior call to extend over more data.
uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
// Computes CRC16 (HDLC, polynomial 0x8408) over buf/len. Pass crc=0 to start
// a new checksum; pass the result of a prior call to extend over more data.
uint16_t mg_crc16(uint16_t crc, const char *buf, size_t len);
// Returns true if path is safe to serve from the filesystem. Rejects paths
// that start with '~' or '..', or contain a '/../' component, to prevent
// directory traversal attacks.
@@ -46,6 +53,9 @@ bool mg_path_is_sane(const struct mg_str path);
// calling context; avoid in event handlers.
void mg_delayms(unsigned int ms);
uint64_t mg_timegm(unsigned int year, unsigned int month, unsigned int day,
unsigned int hour, unsigned int min, unsigned int sec);
// Packs four byte values into a uint32_t in big-endian order.
// MG_U32(1, 2, 3, 4) == 0x01020304
#define MG_U32(a, b, c, d) \
+1 -1
View File
@@ -1 +1 @@
#define MG_VERSION "7.22"
#define MG_VERSION "7.23"
+219 -65
View File
@@ -1,6 +1,6 @@
SRCS = mongoose.c unit_test.c packed_fs.c
HDRS = $(wildcard ../src/*.h) $(wildcard ../src/drivers/*.h)
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1 -DMG_ENABLE_IPV6=$(IPV6)
WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef
OPTS ?= -O3 -g3
INCS ?= -Isrc -I.
@@ -25,11 +25,11 @@ PREFIX ?= /usr/local
LIBDIR ?= $(PREFIX)/lib
INCLUDEDIR ?= $(PREFIX)/include
VERSION ?= $(shell cut -d'"' -f2 ../src/version.h)
COMMON_CFLAGS ?= $(C_WARN) $(WARN) $(INCS) $(DEFS) -DMG_ENABLE_IPV6=$(IPV6) $(TFLAGS) -pthread
COMMON_CFLAGS ?= $(C_WARN) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -pthread
CFLAGS ?= $(OPTS) $(ASAN) $(COMMON_CFLAGS)
VALGRIND_CFLAGS ?= $(OPTS) $(COMMON_CFLAGS)
VALGRIND_RUN ?= valgrind --tool=memcheck --gen-suppressions=all --leak-check=full --show-leak-kinds=all --leak-resolution=high --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes --fair-sched=yes
.PHONY: clean_tutorials tutorials mip_test test valgrind
.PHONY: tutorials mip_test eth_test ppp_test atcmd_test eth_vc98 ppp_vc98 atcmd_vc98 eth_s390 ppp_s390 atcmd_s390 test valgrind
ifeq "$(findstring ++,$(CC))" ""
# $(CC) does not end with ++, i.e. we're using C. Apply C flags
@@ -78,40 +78,44 @@ ifeq "$(SSL)" "BUILTIN"
DEFS += -DMG_TLS=MG_TLS_BUILTIN
endif
tls_multirec/server: FORCE
ifneq "$(SSL)" ""
ifeq "$(MULTIREC)" ""
$(MAKE) -C tls_multirec CC=gcc
endif
endif
true
FORCE:
true
all:
$(MAKE) -C ../tutorials/http/http-server
tall: mg_prefix unamalgamated test valgrind fuzz fuzz_tls vc98 vc17 vc22 mingw mingw++ arm armhf riscv s390 tutorials tutorials_win tutorials_embedded mip_test mip_vc98
tall: mg_prefix unamalgamated test valgrind fuzz fuzz_tls vc98 vc17 vc22 mingw mingw++ arm armhf riscv \
diabish ppc_diabish s390 ppc mips tutorials tutorials_win tutorials_embedded
mip_test: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile
$(CC) mip_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
tmip: mip_test eth_test ppp_test atcmd_test mip_tap_test pppoe_tap_test mip_port_test mip_diabish \
mip_vc98 eth_vc98 ppp_vc98 atcmd_vc98 mip_ppc_diabish mip_s390 eth_s390 ppp_s390 \
atcmd_s390 mip_port_s390 mip_ppc eth_ppc ppp_ppc atcmd_ppc mip_port_ppc mip_mips \
eth_mips ppp_mips atcmd_mips mip_port_mips
mip_tap_test: mip_x_test.c mip_tap_test.c mongoose.c mongoose.h packed_fs.c Makefile tls_multirec/server
$(CC) mip_tap_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
# Amalgamated source files
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))> $@
# bridge UDP port <--> TAP interface
port_tap_bridge: port_tap_bridge.c
cc port_tap_bridge.c -o $@
# 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
$(CC) mip_port_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
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) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
packed_fs.c: Makefile data/ssi.h fuzz.c data/a.txt data/ca.pem certs/ca.crt certs/server.crt certs/server.key
$(CC) $(CFLAGS) pack.c -o pack
$(RUN) ./pack Makefile data/ssi.h fuzz.c data/a.txt data/range.txt data/ca.pem certs/ca.crt certs/server.crt certs/server.key certs/client.key certs/client.crt > $@
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/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/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')> $@
# Check that all external (exported) symbols have "mg_" prefix
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
test: Makefile mongoose.h $(SRCS) tls_multirec/server
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
musl: test
musl: ASAN =
musl: WARN += -Wno-sign-conversion
@@ -143,22 +147,6 @@ fuzz_tls2: mongoose.c mongoose.h Makefile fuzz_tls.c
$(RUN) ./fuzzer_tls $(FUZZDATA)
test: Makefile mongoose.h $(SRCS) tls_multirec/server
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
tls_multirec/server: FORCE
ifneq "$(SSL)" ""
ifeq "$(MULTIREC)" ""
$(MAKE) -C tls_multirec CC=gcc
endif
endif
true
FORCE:
true
coverage: CFLAGS += -coverage
coverage: test
gcov -l -n *.gcno | sed '/^$$/d' | sed 'N;s/\n/ /'
@@ -178,6 +166,16 @@ misra:
cppcheck --addon=misra --clang -DMG_ENABLE_LINES=1 -DMG_ENABLE_SSI=1 mongoose.c >>/tmp/x 2>&1
less /tmp/x
arm: DEFS += -DMG_ENABLE_POSIX_FS=0 -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ARCH=MG_ARCH_ARMGCC -DNO_SLEEP_ABORT
arm: mongoose.h $(SRCS) mip_test.c
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test -nostartfiles --specs nosys.specs -e 0
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb mip_test.c packed_fs.c $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o mip_test -nostartfiles --specs nosys.specs -e 0
riscv: DEFS += -DMG_ENABLE_POSIX_FS=0 -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ARCH=MG_ARCH_ARMGCC -DNO_SLEEP_ABORT
riscv: mongoose.h $(SRCS) mip_test.c
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 mip_test.c packed_fs.c $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o mip_test
# https://ddanilov.me/how-signals-are-handled-in-a-docker-container
armhf: ASAN=
armhf: IPV6=0
@@ -185,6 +183,21 @@ armhf: CC = $(DOCKER) mdashnet/armhf cc
armhf: RUN = $(DOCKER) --init mdashnet/armhf
armhf: test
# C89 FOLKS
diabish: CC = gcc
diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
diabish: test
# end-of
# C89 + BIG-ENDIAN FOLKS
ppc_diabish: ASAN=
ppc_diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
ppc_diabish: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static
ppc_diabish: RUN = qemu-ppc-static
ppc_diabish: test
# end-of
# BIG-ENDIAN FOLKS
s390: ASAN=
s390: IPV6=0
s390: CFLAGS += -Wno-stringop-overflow
@@ -192,27 +205,19 @@ s390: CC = $(DOCKER) mdashnet/s390 cc
s390: RUN = $(DOCKER) --init mdashnet/s390
s390: test
mip_s390: ASAN=
mip_s390: CC = $(DOCKER) mdashnet/s390 cc
mip_s390: RUN = $(DOCKER) --init mdashnet/s390
mip_s390: mip_test
ppc: ASAN=
ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static
ppc: RUN = $(DOCKER) --init vicamo/debian:stable-powerpc # doesn't have curl
ppc: test
mip_port_s390: ASAN=
mip_port_s390: IPV6=0
# This test requires curl, not present in this image
mip_port_s390: CFLAGS += -DNO_HTTPSERVER_TEST
mip_port_s390: CC = $(DOCKER) mdashnet/s390 cc
mip_port_s390: RUN = $(DOCKER) --init mdashnet/s390
mip_port_s390: mip_port_test
arm: DEFS += -DMG_ENABLE_POSIX_FS=0 -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ARCH=MG_ARCH_ARMGCC -DNO_SLEEP_ABORT
arm: mongoose.h $(SRCS)
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test -nostartfiles --specs nosys.specs -e 0
riscv: DEFS += -DMG_ENABLE_POSIX_FS=0 -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ARCH=MG_ARCH_ARMGCC -DNO_SLEEP_ABORT
riscv: mongoose.h $(SRCS)
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) -o unit_test
mips: ASAN=
mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
mips: RUN = $(DOCKER) --init vicamo/debian:stable-mips # not working in GH (?)
mips: test
# end-of
# Windows
vc98: IPV6=0 # VC98 does NOT support IPV6 sockets
vc98: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/vc98 wine cl $(SRCS) $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
@@ -232,7 +237,9 @@ mingw: Makefile mongoose.h $(SRCS)
mingw++: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/mingw x86_64-w64-mingw32-g++ $(SRCS) -W -Wall -Werror -I. $(DEFS) $(TFLAGS) -lws2_32 -lbcrypt -o $@.exe
@echo "NOT RUNNING"
# end-of
# Linux easter eggs
linux-libs: CFLAGS += -fPIC
linux-libs: LDFLAGS += -Wl,-soname,libmongoose.so.$(VERSION)
linux-libs: mongoose.o
@@ -247,11 +254,10 @@ install: linux-libs
uninstall:
rm -rf $(DESTDIR)$(LIBDIR)/libmongoose.a $(DESTDIR)$(LIBDIR)/libmongoose.so.$(VERSION) $(DESTDIR)$(INCLUDEDIR)/mongoose.h $(DESTDIR)$(LIBDIR)/libmongoose.so
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))> $@
mongoose.h: $(HDRS) Makefile
cd .. && (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/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/rpc.h src/dash.h src/ota.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')> $@
packed_fs.c: Makefile data/ssi.h fuzz.c data/a.txt data/range.txt data/ca.pem certs/ca.crt certs/server.crt certs/server.key certs/client.key certs/client.crt
$(CC) $(CFLAGS) pack.c -o pack
$(RUN) ./pack Makefile data/ssi.h fuzz.c data/a.txt data/range.txt data/ca.pem certs/ca.crt certs/server.crt certs/server.key certs/client.key certs/client.crt > $@
clean: clean_tutorials clean_tutorials_embedded
@@ -261,6 +267,154 @@ clean: clean_tutorials clean_tutorials_embedded
#find tutorials -maxdepth 3 -name zephyr -prune -o -name Makefile -print | xargs dirname | xargs -n1 make clean -C
# MIP (net_builtin + L2 + driver) TESTS
mip_test: mip_test.c mongoose.c mongoose.h packed_fs.c Makefile
$(CC) mip_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
eth_test: eth_test.c mongoose.c mongoose.h Makefile
$(CC) eth_test.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
ppp_test: ppp_test.c mongoose.c mongoose.h Makefile
$(CC) ppp_test.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
atcmd_test: atcmd_test.c mongoose.c mongoose.h Makefile
$(CC) atcmd_test.c $(CFLAGS) $(LDFLAGS) -o $@
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
$(CC) mip_tap_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
pppoe_tap_test: pppoe_tap_test.c mongoose.c mongoose.h Makefile
$(CC) pppoe_tap_test.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
# bridge UDP port <--> TAP interface
port_tap_bridge: port_tap_bridge.c
cc port_tap_bridge.c -o $@
# 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
$(CC) mip_port_test.c packed_fs.c $(CFLAGS) $(LDFLAGS) -o $@
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./$@
# C89 FOLKS
mip_diabish: CC = gcc
mip_diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
mip_diabish: mip_test
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) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
eth_vc98: eth_test.c mongoose.c mongoose.h Makefile
$(DOCKER) mdashnet/vc98 wine cl eth_test.c $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
ppp_vc98: ppp_test.c driver_mock.c mongoose.c mongoose.h Makefile
$(DOCKER) mdashnet/vc98 wine cl ppp_test.c $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
atcmd_vc98: atcmd_test.c mongoose.c mongoose.h Makefile
$(DOCKER) mdashnet/vc98 wine cl atcmd_test.c $(VCFLAGS) $(DEFS) $(TFLAGS) /Fe$@.exe
$(DOCKER) -e WINEPREFIX=/tmp/wineprefix mdashnet/vc98 wine $@.exe
# end-of
# C89 + BIG-ENDIAN FOLKS
mip_ppc_diabish: ASAN=
mip_ppc_diabish: CFLAGS += -std=gnu89 -Werror=declaration-after-statement -Werror=vla -Wno-pedantic
mip_ppc_diabish: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static
mip_ppc_diabish: RUN = qemu-ppc-static
mip_ppc_diabish: mip_test
# end-of
# BIG-ENDIAN FOLKS
mip_s390: ASAN=
mip_s390: CFLAGS += -Wno-stringop-overflow
mip_s390: CC = $(DOCKER) mdashnet/s390 cc
mip_s390: RUN = $(DOCKER) --init mdashnet/s390
mip_s390: mip_test
eth_s390: ASAN=
eth_s390: CC = $(DOCKER) mdashnet/s390 cc
eth_s390: RUN = $(DOCKER) --init mdashnet/s390
eth_s390: eth_test
ppp_s390: ASAN=
ppp_s390: CC = $(DOCKER) mdashnet/s390 cc
ppp_s390: RUN = $(DOCKER) --init mdashnet/s390
ppp_s390: ppp_test
atcmd_s390: ASAN=
atcmd_s390: CC = $(DOCKER) mdashnet/s390 cc
atcmd_s390: RUN = $(DOCKER) --init mdashnet/s390
atcmd_s390: atcmd_test
mip_port_s390: ASAN=
mip_port_s390: CFLAGS += -Wno-stringop-overflow -DNO_HTTPSERVER_TEST # This test requires curl, not present in this image
mip_port_s390: CC = $(DOCKER) mdashnet/s390 cc
mip_port_s390: RUN = $(DOCKER) --init mdashnet/s390
mip_port_s390: mip_port_test
mip_ppc: ASAN=
mip_ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static -Wno-maybe-uninitialized
mip_ppc: RUN = qemu-ppc-static
mip_ppc: mip_test
eth_ppc: ASAN=
eth_ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static -Wno-maybe-uninitialized
eth_ppc: RUN = qemu-ppc-static
eth_ppc: eth_test
ppp_ppc: ASAN=
ppp_ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static -Wno-maybe-uninitialized
ppp_ppc: RUN = qemu-ppc-static
ppp_ppc: ppp_test
atcmd_ppc: ASAN=
atcmd_ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static -Wno-maybe-uninitialized
atcmd_ppc: RUN = qemu-ppc-static
atcmd_ppc: atcmd_test
mip_port_ppc: ASAN=
mip_port_ppc: CFLAGS += -DNO_HTTPSERVER_TEST # *** avoid system() calls, no OS image !
mip_port_ppc: CC = $(DOCKER) dockcross/linux-ppc powerpc-unknown-linux-gnu-gcc -static -Wno-maybe-uninitialized
mip_port_ppc: RUN = qemu-ppc-static
mip_port_ppc: mip_port_test
mip_mips: ASAN=
mip_mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
mip_mips: RUN = qemu-mips-static
mip_mips: mip_test
eth_mips: ASAN=
eth_mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
eth_mips: RUN = qemu-mips-static
eth_mips: eth_test
ppp_mips: ASAN=
ppp_mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
ppp_mips: RUN = qemu-mips-static
ppp_mips: ppp_test
atcmd_mips: ASAN=
atcmd_mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
atcmd_mips: RUN = qemu-mips-static
atcmd_mips: atcmd_test
mip_port_mips: ASAN=
mip_port_mips: CFLAGS += -DNO_HTTPSERVER_TEST # *** avoid system() calls, no OS image !
mip_port_mips: CC = $(DOCKER) dockcross/linux-mips mips-unknown-linux-gnu-gcc -static
mip_port_mips: RUN = qemu-mips-static
mip_port_mips: mip_port_test
# end-of
# TUTORIALs TESTS
tutorials:
@for X in $(TUTORIALS); do test -f $$X/Makefile || continue; $(MAKE) -C $$X example || exit 1; done
clean_tutorials:
+173
View File
@@ -0,0 +1,173 @@
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#define MG_ENABLE_DRIVER_ATCMD 1
#include "mongoose.c"
static int s_num_tests = 0;
static bool s_error = false;
#ifdef NO_ABORT
static int s_abort = 0;
#define ABORT() ++s_abort, s_error = true
#else
#ifdef NO_SLEEP_ABORT
#define ABORT() abort()
#else
#define ABORT() \
sleep(2); /* 2s, GH print reason */ \
abort();
#endif
#endif
#define ASSERT(expr) \
do { \
s_num_tests++; \
if (!(expr)) { \
printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \
fflush(stdout); \
ABORT(); \
} \
} while (0)
struct uart_mock {
uint8_t rx[2048];
uint8_t tx[256];
size_t rx_len;
size_t rx_pos;
size_t tx_len;
int reset_count;
};
struct test_ctx {
struct mg_tcpip_if ifp;
struct mg_tcpip_driver_atcmd_data dd;
struct uart_mock mock;
char qbuf[1506];
};
static void mock_reset(void *arg) {
struct uart_mock *mock = (struct uart_mock *) arg;
mock->reset_count++;
}
static void mock_tx(void *arg, uint8_t b) {
struct uart_mock *mock = (struct uart_mock *) arg;
ASSERT(mock->tx_len < sizeof(mock->tx));
mock->tx[mock->tx_len++] = b;
}
static int mock_rx(void *arg) {
struct uart_mock *mock = (struct uart_mock *) arg;
return mock->rx_pos < mock->rx_len ? mock->rx[mock->rx_pos++] : -1;
}
static void mock_rx_add(struct uart_mock *mock, const void *buf, size_t len) {
ASSERT(mock->rx_len + len <= sizeof(mock->rx));
memcpy(mock->rx + mock->rx_len, buf, len);
mock->rx_len += len;
}
static void init_if(struct test_ctx *ctx) {
memset(ctx, 0, sizeof(*ctx));
ctx->ifp.driver_data = &ctx->dd;
ASSERT(mg_atcmd_init(&ctx->ifp) == true);
ASSERT(ctx->ifp.recv_queue.size <= sizeof(ctx->qbuf));
ctx->ifp.recv_queue.buf = ctx->qbuf;
}
static void reset_if(struct test_ctx *ctx, const char **script,
bool no_byte_stuff) {
memset(&ctx->dd, 0, sizeof(ctx->dd));
memset(&ctx->mock, 0, sizeof(ctx->mock));
ctx->ifp.driver_data = &ctx->dd;
ctx->ifp.recv_queue.head = 0;
ctx->ifp.recv_queue.tail = 0;
ctx->dd.usart = &ctx->mock;
ctx->dd.reset = mock_reset;
ctx->dd.tx = mock_tx;
ctx->dd.rx = mock_rx;
ctx->dd.script = script;
ctx->dd.no_byte_stuff = no_byte_stuff;
}
#define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
int main(void) {
struct test_ctx ctx;
init_if(&ctx);
s_error = false;
{ // Send AT command, receive OK
static const char *script[] = {"AT\r\n", "\r\nOK\r\n", NULL};
static const uint8_t at[] = {'A', 'T', '\r', '\n'};
static const uint8_t ok[] = {'\r', '\n', 'O', 'K', '\r', '\n'};
reset_if(&ctx, script, false);
ASSERT(mg_atcmd_poll(&ctx.ifp, true) == false);
ASSERT(ctx.mock.tx_len == sizeof(at));
ASSERT(memcmp(ctx.mock.tx, at, sizeof(at)) == 0);
ASSERT(ctx.dd.link == false);
mock_rx_add(&ctx.mock, ok, sizeof(ok));
ASSERT(mg_atcmd_poll(&ctx.ifp, true) == true);
ASSERT(ctx.dd.link == true);
ASSERT(ctx.dd.script_index == -1);
ASSERT(ctx.mock.reset_count == 0);
}
DASHBOARD("atcmd");
s_error = false;
{ // HDLC byte stuffing and unstuffing
static const uint8_t payload[] = {0x00, 0x01, 'A', 0x7d, 0x7e, 0x20};
static const uint8_t frame[] = {0x7e, 0x7d, 0x20, 0x7d, 0x21, 'A',
0x7d, 0x5d, 0x7d, 0x5e, 0x20, 0x7e};
uint8_t buf[sizeof(payload)];
reset_if(&ctx, NULL, false);
ASSERT(mg_atcmd_poll(&ctx.ifp, true) == true);
ASSERT(mg_atcmd_tx(payload, sizeof(payload), &ctx.ifp) == sizeof(payload));
ASSERT(ctx.mock.tx_len == sizeof(frame));
ASSERT(memcmp(ctx.mock.tx, frame, sizeof(frame)) == 0);
mock_rx_add(&ctx.mock, frame, sizeof(frame));
ASSERT(mg_atcmd_rx(buf, sizeof(buf), &ctx.ifp) == sizeof(payload));
ASSERT(memcmp(buf, payload, sizeof(payload)) == 0);
}
{ // HDLC framing with byte stuffing disabled
static const uint8_t payload[] = {0x00, 0x01, 'A', 0x7d, 0x20};
static const uint8_t frame[] = {0x7e, 0x00, 0x01, 'A', 0x7d, 0x20, 0x7e};
uint8_t buf[sizeof(payload)];
reset_if(&ctx, NULL, true);
ASSERT(mg_atcmd_poll(&ctx.ifp, true) == true);
ASSERT(mg_atcmd_tx(payload, sizeof(payload), &ctx.ifp) == sizeof(payload));
ASSERT(ctx.mock.tx_len == sizeof(frame));
ASSERT(memcmp(ctx.mock.tx, frame, sizeof(frame)) == 0);
mock_rx_add(&ctx.mock, frame, sizeof(frame));
ASSERT(mg_atcmd_rx(buf, sizeof(buf), &ctx.ifp) == sizeof(payload));
ASSERT(memcmp(buf, payload, sizeof(payload)) == 0);
}
{ // Oversized frame is rejected
uint8_t buf[8];
reset_if(&ctx, NULL, true);
ASSERT(mg_atcmd_poll(&ctx.ifp, true) == true);
mock_rx_add(&ctx.mock, "\x7e", 1);
memset(ctx.mock.rx + ctx.mock.rx_len, 'A', sizeof(buf) + 1);
ctx.mock.rx_len += sizeof(buf) + 1;
mock_rx_add(&ctx.mock, "\x7e", 1);
ASSERT(mg_atcmd_rx(buf, sizeof(buf), &ctx.ifp) == 0);
}
printf("HEALTH_DASHBOARD\t\"hdlc\": %s\n",
s_error ? "false" : "true");
#ifdef NO_ABORT
if (s_abort != 0) return EXIT_FAILURE;
#endif
printf("SUCCESS. Total tests: %d\n", s_num_tests);
return EXIT_SUCCESS;
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#define FREERTOS_CONFIG_H
#include <stdio.h>
#define configUSE_PREEMPTION 1
#define configCPU_CLOCK_HZ SYS_FREQUENCY
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 5
#define configUSE_16_BIT_TICKS 0
#define configUSE_TICK_HOOK 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TIMERS 0
#define configUSE_CO_ROUTINES 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE (1024 * 128)
#define INCLUDE_vTaskDelay 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_xTaskGetSchedulerState 1
#define configPRIO_BITS 4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY \
(configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configASSERT(expr) \
if (!(expr)) printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr)
//#define vPortSVCHandler SVC_Handler
//#define xPortPendSVHandler PendSV_Handler
//xPortSysTickHandler
+77
View File
@@ -0,0 +1,77 @@
WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef
OPTS ?= -O3 -g3
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
ASAN_OPTIONS ?= detect_leaks=1
COMMON_CFLAGS ?= $(C_WARN) $(WARN) -I. -I../.. $(TFLAGS)
CFLAGS ?= $(OPTS) $(ASAN) $(COMMON_CFLAGS)
ifeq "$(findstring ++,$(CC))" ""
# $(CC) does not end with ++, i.e. we're using C. Apply C flags
C_WARN ?= -Wmissing-prototypes -Wstrict-prototypes
else
# $(CC) ends with ++, i.e. we're using C++. Apply C++ flags
C_WARN ?= -Wno-deprecated
endif
.PHONY: all bsd_test bsd_client shm_tap_bridge shm_port_bridge bsd_s390 clean
TEST_SOURCES = bsd_shm_test.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.
C_SOURCES = ../../mongoose.c
C_SOURCES += FreeRTOS-Kernel/portable/MemMang/heap_4.c
C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c
C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c
C_SOURCES += $(wildcard FreeRTOS-Kernel/*.c)
C_OBJECTS = $(notdir $(C_SOURCES:.c=.o))
C_INPUTS = $(foreach S,$(C_SOURCES),-x c $(S))
CFLAGS += -IFreeRTOS-Kernel/include
CFLAGS += -IFreeRTOS-Kernel/portable/ThirdParty/GCC/Posix -Wno-conversion -Wno-undef
ifeq "$(findstring ++,$(CC))" ""
TEST_LANGUAGE = c
else
TEST_LANGUAGE = c++
endif
DOCKER_BIN ?= docker
CWD ?= $(realpath $(CURDIR))
ROOT_DIR = $(realpath $(CWD)/../..)
ENV ?= -e Tmp=. -e WINEDEBUG=-all
DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(ROOT_DIR):$(ROOT_DIR) -w $(CWD)
# Mongoose options are defined in mongoose_config.h, this forces loading and skips OS detection
CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM
all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
# requires shm_tap_bridge to be running
bsd_test: FreeRTOS-Kernel
$(CC) $(C_INPUTS) -c $(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 $@
bsd_client: bsd_client.c
$(CC) bsd_client.c -pthread -o $@
# bridge shm queue <--> TAP interface
shm_tap_bridge: shm_tap_bridge.c shm_queue.h
$(CC) shm_tap_bridge.c -pthread -o $@
# requires test/port_tap_bridge to be running
bsd_s390: ASAN=
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
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'
shm_port_bridge: shm_port_bridge.c shm_queue.h
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
clean: # Cleanup. Delete built program and all build artifacts
rm -rf bsd_test bsd_client shm_port_bridge *.o *.obj FreeRTOS-Kernel
FreeRTOS-Kernel: # FreeRTOS sources
git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@
+114
View File
@@ -0,0 +1,114 @@
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define PORT 1234
#define NUM_CONNECTIONS 12
#define DATA_SIZE (4 * 1024)
static int read_full(int fd, void *buf, size_t len) {
static const size_t sizes[] = {1, 7, 17, 255, 256, 257, 511, 512, 4096};
char *p = (char *) buf;
size_t i = 0;
while (len > 0) {
size_t nreq = sizes[i++ % (sizeof(sizes) / sizeof(sizes[0]))];
ssize_t n;
if (nreq > len) nreq = len;
n = recv(fd, p, nreq, 0);
if (n <= 0) return -1;
p += n;
len -= (size_t) n;
}
return 0;
}
static int write_full(int fd, const void *buf, size_t len) {
const char *p = (const char *) buf;
while (len > 0) {
ssize_t n = send(fd, p, len, 0);
if (n <= 0) return -1;
p += n;
len -= (size_t) n;
}
return 0;
}
static void fill_text(char *buf, size_t len) {
size_t i;
for (i = 0; i < len; i++) buf[i] = (char) (' ' + random() % 95);
}
static void *worker(void *arg) {
const char *host = (const char *) arg;
char tx[DATA_SIZE], rx[DATA_SIZE];
struct sockaddr_in sa;
struct timeval timeout;
unsigned port = 0;
int fd;
fill_text(tx, sizeof(tx));
memset(rx, 0, sizeof(rx));
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) goto fail;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0)
goto fail_close;
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_close;
printf("%d CONNECTING\n", fd);
if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) goto fail_close;
{
socklen_t len = sizeof(sa);
if (getsockname(fd, (struct sockaddr *) &sa, &len) != 0) goto fail_close;
port = (unsigned) ntohs(sa.sin_port);
}
printf("%d CONNECTED port: %u\n", fd, port);
printf("%d WRITING\n", fd);
if (write_full(fd, tx, sizeof(tx)) != 0) goto fail_close;
printf("%d READING\n", fd);
if (read_full(fd, rx, sizeof(rx)) != 0) goto fail_close;
if (memcmp(tx, rx, sizeof(tx)) != 0) goto fail_close;
printf("%d OK-CLOSING\n", fd);
close(fd);
return NULL;
fail_close:
printf("%d ERROR-CLOSING\n", fd);
close(fd);
fail:
exit(1);
}
int main(int argc, char *argv[]) {
pthread_t threads[NUM_CONNECTIONS];
int i;
if (argc != 2) {
fprintf(stderr, "usage: %s IP\n", argv[0]);
return 1;
}
srandom(1);
for (i = 0; i < NUM_CONNECTIONS; i++) {
if (pthread_create(&threads[i], NULL, worker, argv[1]) != 0) return 1;
}
for (i = 0; i < NUM_CONNECTIONS; i++) {
if (pthread_join(threads[i], NULL) != 0) return 1;
}
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "mongoose.h"
#include "shm_queue.h"
#ifndef O_RDWR
#define O_RDWR 02
#endif
struct shm_driver_data {
const char *name;
FILE *fp;
struct shm_link *shm;
};
static struct shm_driver_data shm_data = {
SHM_NAME,
NULL,
NULL,
};
static bool shm_init(struct mg_tcpip_if *ifp) {
struct shm_driver_data *d = (struct shm_driver_data *) ifp->driver_data;
int fd;
fd = shm_open(d->name, O_RDWR, 0600);
if (fd < 0) {
printf("shm_open failed: %d\n", errno);
return false;
}
d->fp = fdopen(fd, "r+");
if (d->fp == NULL) {
printf("fdopen failed: %d\n", errno);
return false;
}
d->shm = (struct shm_link *) mmap(NULL, sizeof(*d->shm),
PROT_READ | PROT_WRITE, MAP_SHARED,
fileno(d->fp), 0);
if (d->shm == MAP_FAILED) {
printf("mmap failed: %d\n", errno);
fclose(d->fp);
d->fp = NULL;
d->shm = NULL;
return false;
}
return true;
}
static size_t shm_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
struct shm_driver_data *d = (struct shm_driver_data *) ifp->driver_data;
if (!shm_queue_push(&d->shm->to_tap, buf, len)) {
printf("\nFRAME LOST\n");
return 0;
}
return len;
}
static size_t shm_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
struct shm_driver_data *d = (struct shm_driver_data *) ifp->driver_data;
return shm_queue_pop(&d->shm->from_tap, buf, len);
}
static bool shm_poll(struct mg_tcpip_if *ifp, bool s1) {
return s1 && ifp->driver_data ? true : false;
}
static struct mg_tcpip_driver shm_driver = {
shm_init,
shm_tx,
shm_rx,
shm_poll,
};
bool mg_random(void *buf, size_t len) {
bool success = false;
unsigned char *p = (unsigned char *) buf;
FILE *fp = fopen("/dev/urandom", "rb");
if (fp != NULL) {
if (fread(buf, 1, len, fp) == len) success = true;
fclose(fp);
}
// If everything above did not work, fallback to a pseudo random generator
if (success == false) {
printf("\nWeak RNG: using rand()\n");
while (len--) *p++ = (unsigned char) (rand() & 255);
}
return success;
}
#include "bsd_x_test.c"
int main(void) {
bool result;
const char *debug_level = getenv("V");
// Setup interface
const char *mac = "02:00:01:02:03:78"; // MAC address
printf("Opening SHM interface\n");
usleep(200000); // 200 ms
result = bsd_x_test(&shm_driver, &shm_data, mac, debug_level);
if (shm_data.shm != NULL) munmap(shm_data.shm, sizeof(*shm_data.shm));
if (shm_data.fp != NULL) fclose(shm_data.fp);
if (!result) return EXIT_FAILURE;
return EXIT_SUCCESS;
}
+275
View File
@@ -0,0 +1,275 @@
#define BSDTEST_USING_DHCP 1
#include <spawn.h>
#include <sys/wait.h>
static int s_num_tests = 0;
static bool s_error = false;
static volatile bool s_done = false, s_wait;
#ifdef NO_ABORT
static int s_abort = 0;
#define ABORT() ++s_abort, s_error = true
#else
#ifdef NO_SLEEP_ABORT
#define ABORT() abort()
#else
#define ABORT() \
sleep(2); /* 2s, GH print reason */ \
abort();
#endif
#endif
#define ASSERT(expr) \
do { \
s_num_tests++; \
if (!(expr)) { \
printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \
fflush(stdout); \
ABORT(); \
} \
} while (0)
struct client_data {
char hostname[64];
uint32_t ip;
bool done;
};
// client_task: connects to hostname:80, fetches "/", logs response length.
static void client_task(void *args) {
int fd, c;
struct client_data *d = (struct client_data *) args;
char req[256];
fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(80);
memcpy(&sa.sin_addr, &d->ip, 4);
c = connect(fd, (struct sockaddr *) &sa, sizeof(sa));
ASSERT(c >= 0);
if (c < 0) {
MG_ERROR(("connect failed"));
close(fd);
vTaskDelete(NULL);
return;
}
snprintf(req, sizeof(req),"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", d->hostname);
send(fd, req, strlen(req), 0);
size_t total = 0;
char buf[512];
ssize_t n;
while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) total += (size_t) n;
MG_INFO(("%s response: %lu bytes", d->hostname, (unsigned long) total));
ASSERT(total > 0);
close(fd);
d->done = true;
vTaskDelete(NULL);
(void) args;
}
// echo_task: one per accepted connection. Echoes data back.
static void echo_task(void *args) {
int fd = (int) (uintptr_t) args;
static const size_t sizes[] = {1, 7, 17, 255, 256, 257, 511, 512};
char buf[512];
size_t i = 0;
ssize_t n;
MG_INFO(("Echo task started: %d", fd));
while ((n = recv(fd, buf, sizes[i++ % (sizeof(sizes) / sizeof(sizes[0]))], 0)) > 0)
send(fd, buf, (size_t) n, 0);
close(fd);
vTaskDelete(NULL);
}
// accept loop. Waits for incoming connections on port 1234 and spawns
// an echo_task for each one, allowing concurrent clients.
static void atask(void *args) {
BaseType_t result;
mg_bsd_init();
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(1234);
sa.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr *) &sa, sizeof(sa));
listen(lfd, 5);
fcntl(lfd, F_SETFL, O_NONBLOCK);
MG_INFO(("BSD echo server on :1234"));
while (!s_wait) {
int fd = accept(lfd, NULL, NULL);
if (fd < 0) { vTaskDelay(pdMS_TO_TICKS(10)); continue; }
// echo_task at higher priority than task1
result = xTaskCreate(echo_task, "echo", 512, (void *) (uintptr_t) fd,
configMAX_PRIORITIES - 1, NULL);
MG_INFO(("Echo task %d: %s", fd, result == pdPASS ? "created" : "FAILED"));
ASSERT(result == pdPASS);
}
close(lfd);
s_wait = false;
vTaskDelete(NULL);
(void) args;
}
// task2: tests owner.
static void task2(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
char *ip, *cmd[3];
pid_t pid;
int status;
extern char **environ;
#define DASHBOARD(x) printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false":"true");
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false;
s_wait = false;
// atask at higher priority than task1
xTaskCreate(atask, "atask", 256, NULL, configMAX_PRIORITIES - 1, NULL);
ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip);
cmd[0] = (char *) "./bsd_client";
cmd[1] = ip;
cmd[2] = NULL;
ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0);
for (;;) {
pid_t r = waitpid(pid, &status, WNOHANG);
ASSERT(r >= 0);
if (r == pid) break;
vTaskDelay(pdMS_TO_TICKS(10));
}
ASSERT(WIFEXITED(status));
ASSERT(WEXITSTATUS(status) == 0);
mg_free(ip);
s_wait = true;
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
DASHBOARD("accept");
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false;
{
struct hostent *h = gethostbyname("invalidhostname");
ASSERT(h == NULL);
h = gethostbyname("mongoose.ws");
ASSERT(h != NULL);
if (h == NULL) {
MG_ERROR(("DNS failed"));
vTaskDelete(NULL);
return;
}
}
DASHBOARD("gethostbyname");
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false;
s_wait = true;
{
struct client_data cd[3];
struct hostent *h = gethostbyname("mongoose.ws");
strcpy(cd[0].hostname, "mongoose.ws");
memcpy(&cd[0].ip, h->h_addr, 4);
cd[0].done = false;
// client_task at higher priority than task1
xTaskCreate(client_task, "client1", 1024, &cd[0], configMAX_PRIORITIES - 1, NULL);
h = gethostbyname("cesanta.com");
strcpy(cd[1].hostname, "cesanta.com");
memcpy(&cd[1].ip, h->h_addr, 4);
cd[1].done = false;
// client_task at higher priority than task1
xTaskCreate(client_task, "client2", 1024, &cd[1], configMAX_PRIORITIES - 1, NULL);
h = gethostbyname("google.com");
strcpy(cd[2].hostname, "google.com");
memcpy(&cd[2].ip, h->h_addr, 4);
cd[2].done = false;
// client_task at higher priority than task1
xTaskCreate(client_task, "client3", 1024, &cd[2], configMAX_PRIORITIES - 1, NULL);
while (!cd[0].done || !cd[1].done || !cd[2].done) vTaskDelay(pdMS_TO_TICKS(10));
}
DASHBOARD("connect");
// End task 1
s_done = true;
while (s_done) vTaskDelay(pdMS_TO_TICKS(10));
// Clear
s_error = false;
mg_mgr_free(mgr);
ASSERT(mgr->conns == NULL); // Deconstruction OK
printf("HEALTH_DASHBOARD\t\"cleanup\": %s\n", s_error ? "false":"true");
// last entry with no comma
vTaskEndScheduler();
}
// task1: network owner. Runs the Mongoose event loop and the BSD command queue.
// The interface needs to be started here, as FreeRTOS needs to be running (time base)
static void task1(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
// Stack initialization, Network configuration (DHCP lease, ...)
#if BSDTEST_USING_DHCP == 0
MG_INFO(("MIF configuration: Static IP"));
ASSERT(mif.ip != 0); // Check we have a satic IP assigned
mg_mgr_poll(&mgr, 0); // For initialisation
#else
MG_INFO(("MIF configuration: DHCP"));
ASSERT(mgr->ifp->ip == 0); // Check we are set for DHCP
while (mgr->ifp->ip == 0 && mg_millis() < 5000) {
mg_mgr_poll(mgr, 0);
}
if (mgr->ifp->ip == 0) MG_ERROR(("No ip assigned (DHCP lease may have failed).\n"));
ASSERT(mgr->ifp->ip != 0); // We have an IP (lease or static)
#endif
while (mgr->ifp->state != MG_TCPIP_STATE_READY) {
mg_mgr_poll(mgr, 0);
}
MG_INFO(("Interface started, starting main loop"));
xTaskCreate(task2, "task2", 256, mgr, configMAX_PRIORITIES - 2, NULL);
while (!s_done) {
mg_mgr_poll(mgr, 0);
mg_bsd_poll(mgr);
vTaskDelay(1); // Let idle reclaim deleted echo tasks
}
s_done = false;
vTaskDelete(NULL);
}
static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const char *mac, const char *debug_level) {
if (debug_level == NULL) debug_level = "3";
mg_log_set(atoi(debug_level));
struct mg_mgr mgr; // Event manager
mg_mgr_init(&mgr); // Initialise event manager
struct mg_tcpip_if mif;
memset(&mif, 0, sizeof(mif));
mif.driver = driver;
mif.driver_data = driver_data;
#if BSDTEST_USING_DHCP == 1
#else
mif.ip = mg_htonl(MG_U32(192, 168, 32, 2)); // Triggering a network failure
mif.mask = mg_htonl(MG_U32(255, 255, 255, 0));
mif.gw = mg_htonl(MG_U32(192, 168, 32, 1));
#endif
sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mif.mac[0], &mif.mac[1],
&mif.mac[2], &mif.mac[3], &mif.mac[4], &mif.mac[5]);
mg_tcpip_init(&mgr, &mif);
ASSERT(mgr.ifp != NULL); // driver init succeeded
MG_INFO(("Init done, starting interface"));
usleep(200000); // 200 ms
xTaskCreate(task1, "task1", 2048, &mgr, configMAX_PRIORITIES - 2, NULL);
vTaskStartScheduler();
#ifdef NO_ABORT
if (s_abort != 0) return false;
#endif
printf("SUCCESS. Total tests: %d\n", s_num_tests);
return true;
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
// 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_DRIVER_INIT 0
#define MG_ENABLE_BSD_SOCKETS 1
#define MG_ENABLE_BSD_LOG 1
#define MG_ENABLE_LINES 1
#define MG_ENABLE_CUSTOM_RANDOM 1
//#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.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 <unistd.h>
+138
View File
@@ -0,0 +1,138 @@
#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <poll.h>
#include <unistd.h>
#include "shm_queue.h"
static volatile sig_atomic_t s_signo;
static void signal_handler(int signo) {
s_signo = signo;
}
static int open_udp_socket(void) {
const uint16_t port = 0xAA55; // Local UDP port
const uint16_t tap_port = 0x55AA; // Host port_tap_bridge UDP port
struct sockaddr_in port_addr, tap_addr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
memset(&port_addr, 0, sizeof(port_addr));
port_addr.sin_family = AF_INET;
port_addr.sin_addr.s_addr = htonl(INADDR_ANY);
port_addr.sin_port = htons(port);
if (bind(fd, (struct sockaddr *) &port_addr, sizeof(port_addr)) < 0) {
perror("bind");
close(fd);
return -1;
}
printf("Opened UDP socket: 127.0.0.1:%u (0x%04x)\n", (unsigned) port,
(unsigned) port);
memset(&tap_addr, 0, sizeof(tap_addr));
tap_addr.sin_family = AF_INET;
tap_addr.sin_addr.s_addr = htonl(0x7f000001U); // 127.0.0.1
tap_addr.sin_port = htons(tap_port);
if (connect(fd, (struct sockaddr *) &tap_addr, sizeof(tap_addr)) < 0) {
perror("connect");
close(fd);
return -1;
}
printf("Connected to TAP UDP socket: 127.0.0.1:%u (0x%04x)\n",
(unsigned) tap_port, (unsigned) tap_port);
return fd;
}
static struct shm_link *open_shm(int *shmfd) {
struct shm_link *shm;
shm_unlink(SHM_NAME);
*shmfd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (*shmfd < 0) {
perror("shm_open");
return NULL;
}
if (ftruncate(*shmfd, (off_t) sizeof(*shm)) != 0) {
perror("ftruncate");
close(*shmfd);
shm_unlink(SHM_NAME);
return NULL;
}
shm = (struct shm_link *) mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE,
MAP_SHARED, *shmfd, 0);
if (shm == MAP_FAILED) {
perror("mmap");
close(*shmfd);
shm_unlink(SHM_NAME);
return NULL;
}
memset(shm, 0, sizeof(*shm));
printf("Opened shared memory: %s\n", SHM_NAME);
return shm;
}
int main(void) {
int sockfd, shmfd = -1;
struct shm_link *shm;
sockfd = open_udp_socket();
if (sockfd < 0) return EXIT_FAILURE;
shm = open_shm(&shmfd);
if (shm == NULL) {
close(sockfd);
return EXIT_FAILURE;
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
while (s_signo == 0) {
struct pollfd pfd = {sockfd, (short) (POLLIN | POLLPRI), 0};
int result = poll(&pfd, 1, 1);
if (result < 0 && errno != EINTR) {
perror("poll");
break;
}
if (result > 0 && (pfd.revents & (POLLIN | POLLPRI))) {
uint8_t buf[FRAME_SIZE];
ssize_t nread = recv(sockfd, buf, sizeof(buf), 0);
if (nread > 0 &&
!shm_queue_push(&shm->from_tap, buf, (size_t) nread)) {
printf("FRAME LOST: port -> SHM\n");
}
}
{
uint8_t buf[FRAME_SIZE];
size_t n = shm_queue_pop(&shm->to_tap, buf, sizeof(buf));
if (n > 0) {
if (send(sockfd, buf, n, 0) < 0) perror("send");
}
}
}
munmap(shm, sizeof(*shm));
close(shmfd);
shm_unlink(SHM_NAME);
close(sockfd);
return EXIT_SUCCESS;
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define FRAME_SIZE 1540
#define FRAME_QUEUE_DEPTH 32
#define SHM_NAME "/mongoose-shm"
// Keep this SHM ABI semaphore-free. Some S390/Linux headers pull fcntl/socket
// declarations through <semaphore.h>, colliding with Mongoose BSD prototypes.
// Each direction is single-producer/single-consumer and polled, so a simple
// ring is enough for this test path.
struct shm_frame {
size_t len;
uint8_t buf[FRAME_SIZE];
};
struct shm_queue {
volatile size_t head;
volatile size_t tail;
struct shm_frame frames[FRAME_QUEUE_DEPTH];
};
struct shm_link {
struct shm_queue to_tap;
struct shm_queue from_tap;
};
static inline bool shm_queue_push(struct shm_queue *q, const void *buf,
size_t len) {
size_t tail = q->tail, next = (tail + 1) % FRAME_QUEUE_DEPTH;
struct shm_frame *f;
if (len > sizeof(q->frames[0].buf) || next == q->head) return false;
f = &q->frames[tail];
memcpy(f->buf, buf, len);
f->len = len;
__sync_synchronize();
q->tail = next;
return true;
}
static inline size_t shm_queue_pop(struct shm_queue *q, void *buf, size_t len) {
size_t head = q->head, n;
struct shm_frame *f;
if (head == q->tail) return 0;
__sync_synchronize();
f = &q->frames[head];
n = f->len;
if (n > len) {
q->head = (head + 1) % FRAME_QUEUE_DEPTH;
return 0;
}
memcpy(buf, f->buf, n);
__sync_synchronize();
q->head = (head + 1) % FRAME_QUEUE_DEPTH;
return n;
}
+110
View File
@@ -0,0 +1,110 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <signal.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include "shm_queue.h"
static volatile sig_atomic_t s_signo;
void signal_handler(int signo) {
s_signo = signo;
}
int main(void) {
// Setup interface
const char *iface = "tap0"; // Network iface
const char *tuntap_device = "/dev/net/tun";
int fd = open(tuntap_device, O_RDWR);
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, IFNAMSIZ);
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
printf("Failed to setup TAP interface: %s", ifr.ifr_name);
return EXIT_FAILURE;
}
printf("Opened TAP interface: %s\n", iface);
int shmfd;
struct shm_link *shm;
shm_unlink(SHM_NAME);
shmfd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (shmfd < 0) {
printf("shm_open failed: %d\n", errno);
return EXIT_FAILURE;
}
if (ftruncate(shmfd, sizeof(*shm)) != 0) {
printf("ftruncate failed: %d\n", errno);
return EXIT_FAILURE;
}
shm = (struct shm_link *) mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE,
MAP_SHARED, shmfd, 0);
if (shm == MAP_FAILED) {
printf("mmap failed: %d\n", errno);
return EXIT_FAILURE;
}
memset(shm, 0, sizeof(*shm));
printf("Opened shared memory: %s\n", SHM_NAME);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
while (s_signo == 0) {
struct pollfd pfd[1];
int result;
do {
pfd[0].fd = fd;
pfd[0].events = POLLIN | POLLPRI; // will wait for input available (read ready), use POLLOUT for write
// only read ready shown here, for write ready use the 3rd arg, and the 4th for exceptions
result = poll(pfd, 1, 1); // 1 pfd, TIMEOUT in milliseconds
switch(result) {
case -1: // error
break;
case 0: // timeout (if a timeout struct was passed to select)
break;
default: // the number of fd/sockets with data available (see pfd.revents for each one);
if (pfd[0].revents & (POLLIN | POLLPRI)) {
uint8_t buf[FRAME_SIZE];
ssize_t nread = read(fd, buf, sizeof(buf));
// printf("Got at TAP: %d bytes\n", (int) nread);
if (nread > 0) {
if (!shm_queue_push(&shm->from_tap, buf, (size_t) nread)) {
printf("FRAME LOST: TAP -> SHM\n");
}
// printf("Sent to SHM\n");
}
}
break;
}
{
uint8_t buf[FRAME_SIZE];
size_t n = shm_queue_pop(&shm->to_tap, buf, sizeof(buf));
if (n > 0) {
// printf("Got at SHM: %d bytes\n", (int) f->len);
write(fd, buf, n);
// printf("Sent to TAP\n");
}
}
} while (result >= 0 && s_signo == 0);
if (result < 0)
printf("looks like we got an error here... should I quit ? Waiting for CTRL-C\n");
}
munmap(shm, sizeof(*shm));
close(shmfd);
shm_unlink(SHM_NAME);
close(fd);
}
+283
View File
@@ -0,0 +1,283 @@
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#include "mongoose.c"
static int s_num_tests = 0;
static bool s_error = false;
#ifdef NO_ABORT
static int s_abort = 0;
#define ABORT() ++s_abort, s_error = true
#else
#ifdef NO_SLEEP_ABORT
#define ABORT() abort()
#else
#define ABORT() \
sleep(2); /* 2s, GH print reason */ \
abort();
#endif
#endif
#define ASSERT(expr) \
do { \
s_num_tests++; \
if (!(expr)) { \
printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \
fflush(stdout); \
ABORT(); \
} \
} while (0)
static const uint8_t s_mac[6] = {2, 3, 4, 5, 6, 7};
static const uint8_t s_src[6] = {8, 9, 10, 11, 12, 13};
static void reset_if(struct mg_tcpip_if *ifp) {
memset(ifp, 0, sizeof(*ifp));
memcpy(ifp->mac, s_mac, sizeof(s_mac));
ifp->l2type = MG_TCPIP_L2_ETH;
}
static size_t mk_eth(uint8_t *buf, const uint8_t *dst, uint16_t type,
const uint8_t *payload, size_t len) {
struct eth *eth = (struct eth *) buf;
memcpy(eth->dst, dst, sizeof(eth->dst));
memcpy(eth->src, s_src, sizeof(eth->src));
eth->type = mg_htons(type);
memcpy(eth + 1, payload, len);
return sizeof(*eth) + len;
}
static size_t mk_vlan(uint8_t *buf, const uint8_t *dst, uint16_t vlan,
uint16_t type, const uint8_t *payload, size_t len) {
struct eth *eth = (struct eth *) buf;
struct qtag *qtag = (struct qtag *) &eth->type;
memcpy(eth->dst, dst, sizeof(eth->dst));
memcpy(eth->src, s_src, sizeof(eth->src));
qtag->tpid = mg_htons(0x8100);
qtag->tci = mg_htons(vlan);
MG_STORE_BE16(qtag + 1, type);
memcpy((uint8_t *) (qtag + 1) + sizeof(uint16_t), payload, len);
return sizeof(*eth) + sizeof(*qtag) + len;
}
static size_t add_fcs(uint8_t *buf, size_t len) {
uint32_t crc = mg_crc32(0, (const char *) buf, len);
buf[len++] = (uint8_t) crc; // store as LE32
buf[len++] = (uint8_t) (crc >> 8);
buf[len++] = (uint8_t) (crc >> 16);
buf[len++] = (uint8_t) (crc >> 24);
return len;
}
#define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
static void test_eth(void) {
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
size_t len;
uint8_t frame[64];
{ // Plain Ethernet frame is accepted
uint8_t payload[] = {1, 2, 3, 4};
reset_if(&ifp);
mg_l2_init(&ifp);
MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize));
ASSERT(ifp.l2mtu != 0);
ASSERT(ifp.framesize != 0);
len = mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // Unknown Ethernet type is discarded
uint8_t payload[] = {1, 2, 3, 4};
char sentinel[] = "discard";
reset_if(&ifp);
mg_l2_init(&ifp);
len = mk_eth(frame, s_mac, 0x1234, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
pay = mg_str_n(sentinel, sizeof(sentinel));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(proto == MG_TCPIP_L2PROTO_ARP);
}
}
static void test_mac(void) {
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
size_t len;
uint8_t frame[64];
{ // MAC check accepts frames addressed to us
uint8_t payload[] = {1, 2, 3, 4};
reset_if(&ifp);
ifp.enable_mac_check = true;
mg_l2_init(&ifp);
len = mk_eth(frame, s_mac, 0x0800, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // MAC check discards alien unicast frames
uint8_t payload[] = {1, 2, 3, 4}, dst[6] = {1, 1, 1, 1, 1, 1};
char sentinel[] = "discard";
reset_if(&ifp);
ifp.enable_mac_check = true;
mg_l2_init(&ifp);
len = mk_eth(frame, dst, 0x0800, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
pay = mg_str_n(sentinel, sizeof(sentinel));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(proto == MG_TCPIP_L2PROTO_ARP);
ASSERT(pay.buf == sentinel);
ASSERT(pay.len == sizeof(sentinel));
}
}
static void test_fcs(void) {
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
size_t len;
uint8_t frame[64];
{ // verify add_fcs() stores the CRC bytes in the proper order
// (internal consistency test)
uint8_t payload[] = {1, 2, 3, 4};
uint8_t fcs[] = {0xf4, 0xd4, 0x37, 0xf6};
len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload,
sizeof(payload)));
ASSERT(memcmp(frame + len - sizeof(fcs), fcs, sizeof(fcs)) == 0);
}
{ // FCS check strips a valid CRC
uint8_t payload[] = {1, 2, 3, 4};
reset_if(&ifp);
ifp.enable_fcs_check = true;
mg_l2_init(&ifp);
len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload,
sizeof(payload)));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // FCS check rejects a bad CRC
uint8_t payload[] = {1, 2, 3, 4};
char sentinel[] = "discard";
reset_if(&ifp);
ifp.enable_fcs_check = true;
mg_l2_init(&ifp);
len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload,
sizeof(payload)));
frame[len - 1] ^= 1;
raw = mg_str_n((char *) frame, len);
pay = mg_str_n(sentinel, sizeof(sentinel));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(proto == MG_TCPIP_L2PROTO_ARP);
ASSERT(pay.buf == sentinel);
ASSERT(pay.len == sizeof(sentinel));
}
{ // Disabled FCS check leaves the CRC in payload
uint8_t payload[] = {1, 2, 3, 4};
reset_if(&ifp);
mg_l2_init(&ifp);
len = add_fcs(frame, mk_eth(frame, s_mac, 0x0800, payload,
sizeof(payload)));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload) + sizeof(uint32_t));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
}
static void test_vlan(void) {
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
size_t len;
uint8_t frame[64];
{ // Configured VLAN accepts matching tagged frames
uint8_t payload[] = {5, 6, 7, 8};
reset_if(&ifp);
ifp.l2data.eth.vlan_id = 123;
mg_l2_init(&ifp);
MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize));
ASSERT(ifp.l2mtu != 0);
ASSERT(ifp.framesize != 0);
len = mk_vlan(frame, s_mac, 123, 0x86dd, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV6);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // Configured VLAN rejects non-matching tags
uint8_t payload[] = {5, 6, 7, 8};
char sentinel[] = "discard";
reset_if(&ifp);
ifp.l2data.eth.vlan_id = 123;
mg_l2_init(&ifp);
len = mk_vlan(frame, s_mac, 124, 0x86dd, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
pay = mg_str_n(sentinel, sizeof(sentinel));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(proto == MG_TCPIP_L2PROTO_ARP);
ASSERT(pay.buf == sentinel);
ASSERT(pay.len == sizeof(sentinel));
}
{ // Tagged frames are rejected when no VLAN is configured
uint8_t payload[] = {5, 6, 7, 8};
reset_if(&ifp);
mg_l2_init(&ifp);
len = mk_vlan(frame, s_mac, 123, 0x86dd, payload, sizeof(payload));
raw = mg_str_n((char *) frame, len);
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(proto == MG_TCPIP_L2PROTO_ARP);
}
}
int main(void) {
s_error = false;
test_eth();
DASHBOARD("eth");
s_error = false;
test_mac();
DASHBOARD("mac");
s_error = false;
test_fcs();
DASHBOARD("fcs");
s_error = false;
test_vlan();
printf("HEALTH_DASHBOARD\t\"vlan\": %s\n", s_error ? "false" : "true");
#ifdef NO_ABORT
if (s_abort != 0) return EXIT_FAILURE;
#endif
printf("SUCCESS. Total tests: %d\n", s_num_tests);
return EXIT_SUCCESS;
}
+233 -49
View File
@@ -23,6 +23,52 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
}
}
// deduplicate layer-3 mocking among layer-2s
struct fuzz_l3_state {
size_t ip_proto, udp_port;
size_t ip6_next, icmp6_type;
};
static void fuzz_ip4(void *buf, size_t len, struct fuzz_l3_state *st) {
if (len >= sizeof(struct ip)) {
uint8_t ip_protos[] = {1, 6, 17}; // ICMP, TCP, UDP
struct ip *ip4 = (struct ip *) buf;
ip4->ver = (uint8_t) ((ip4->ver & ~0xf0) | (4 << 4));
// send all handled IP protos, then 2 random ones
if (st->ip_proto >= (sizeof(ip_protos) / sizeof(ip_protos[0]) + 2)) st->ip_proto = 0;
if (st->ip_proto < (sizeof(ip_protos) / sizeof(ip_protos[0]))) ip4->proto = ip_protos[st->ip_proto++];
if (ip4->proto == 1) { // ICMP
} else if (ip4->proto == 6) { // TCP
} else if (ip4->proto == 17 && len >= sizeof(struct ip) + sizeof(struct udp)) { // UDP
uint16_t udp_ports[] = {67, 68}; // DHCP server and client
struct udp *udp = (struct udp *) (ip4 + 1);
// send all handled UDP ports, then 2 random ones
if (st->udp_port >= (sizeof(udp_ports) / sizeof(udp_ports[0]) + 2)) st->udp_port = 0;
if (st->udp_port < (sizeof(udp_ports) / sizeof(udp_ports[0]))) udp->dport = mg_htons(udp_ports[st->udp_port++]);
}
}
}
static void fuzz_ip6(void *buf, size_t len, struct fuzz_l3_state *st) {
if (len >= sizeof(struct ip6)) {
uint8_t ip6_protos[] = {6, 17, 58}; // TCP, UDP, ICMPv6
struct ip6 *ip6 = (struct ip6 *) buf;
ip6->ver = (uint8_t) ((ip6->ver & ~0xf0) | (6 << 4));
// send all handled IPv6 "next headers", then 2 random ones
if (st->ip6_next >= (sizeof(ip6_protos) / sizeof(ip6_protos[0]) + 2)) st->ip6_next = 0;
if (st->ip6_next < (sizeof(ip6_protos) / sizeof(ip6_protos[0]))) ip6->next = ip6_protos[st->ip6_next++];
if (ip6->next == 6) { // TCP
} else if (ip6->next == 17) { // UDP
} else if (ip6->next == 58 && len >= sizeof(struct ip6) + sizeof(struct icmp6)) { // ICMPv6
uint8_t icmp6_types[] = {128, 134, 135, 136}; // Echo Request, RA, NS, NA
struct icmp6 *icmp6 = (struct icmp6 *) (ip6 + 1);
// send all handled ICMPv6 types, then 2 random ones
if (st->icmp6_type >= (sizeof(icmp6_types) / sizeof(icmp6_types[0]) + 2)) st->icmp6_type = 0;
if (st->icmp6_type < (sizeof(icmp6_types) / sizeof(icmp6_types[0]))) icmp6->type = icmp6_types[st->icmp6_type++];
}
}
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
mg_log_set(MG_LL_INFO);
@@ -94,21 +140,26 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Test built-in TCP/IP stack
if (size > 0) {
static size_t t;
struct mg_tcpip_if mif = {.ip = 1,
.mask = 255,
.gw = 1,
.gw_ready = true,
.state = MG_TCPIP_STATE_READY,
#if MG_ENABLE_IPV6
.ip6[0] = 1;
.prefix[0] = 1;
.prefix_len = 64;
.gw6[0] = 1;
.gw6_ready = true;
.state6 = MG_TCPIP_STATE_READY; // so mg_send() works and RS stops
.ip6[0] = 1,
.prefix[0] = 1,
.prefix_len = 64,
.gw6[0] = 1,
.gw6_ready = true,
.state6 = MG_TCPIP_STATE_READY, // so mg_send() works and RS stops
#endif
.driver = &mg_tcpip_driver_mock};
struct mg_mgr mgr;
if (t >= MG_TCPIP_L2_USER) t = 0; // defined in l2.h
mif.l2type = (enum mg_l2type) t++; // configure to a L2 type
mg_mgr_init(&mgr);
mg_tcpip_init(&mgr, &mif);
@@ -116,56 +167,186 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
void *pkt = malloc(size);
struct eth *eth = (struct eth *) pkt;
memcpy(pkt, data, size);
if (size > sizeof(*eth)) {
static size_t i;
if ((mif.l2type == MG_TCPIP_L2_ETH || mif.l2type == MG_TCPIP_L2_PPPoE) && size > sizeof(*eth)) {
static size_t i, v;
static struct fuzz_l3_state l3;
uint8_t *ethpay = (uint8_t *) (eth + 1);
size_t ethhdrsz = sizeof(*eth);
uint16_t etype;
// eth_types[] exists in l2_eth.c
memcpy(eth->dst, mif.mac, 6); // Set valid destination MAC
// send all handled eth types, then 2 random ones
if (i >= (sizeof(eth_types) / sizeof(eth_types[0]) + 2)) i = 0;
if (i < (sizeof(eth_types) / sizeof(eth_types[0]))) eth->type = mg_htons(eth_types[i++]);
etype = mg_ntohs(eth->type);
if (v >= 10) v = 0; // 802.1Q tag 3 out of 10 frames
if (v < 3) mif.l2data.eth.vlan_id = 123;
if (size > sizeof(*eth) + sizeof(struct qtag)) {
struct qtag *qtag = (struct qtag *) &eth->type;
if (v == 0) {
// v=0: configured for VLAN 123, untagged frame, discard path
} else if (v == 1) {
// v=1: configured for VLAN 123, tagged VLAN 123, accept path
qtag->tpid = mg_htons(0x8100);
qtag->tci = mg_htons(123);
MG_STORE_BE16(qtag + 1, etype);
ethhdrsz += sizeof(*qtag);
ethpay += sizeof(*qtag);
} else if (v == 2) {
// v=2: configured for VLAN 123, tagged random VLAN, mismatch path
qtag->tpid = mg_htons(0x8100);
MG_STORE_BE16(qtag + 1, etype);
ethhdrsz += sizeof(*qtag);
ethpay += sizeof(*qtag);
} else if (v == 3) {
// v=3: unconfigured VLAN, tagged frame, discard path
qtag->tpid = mg_htons(0x8100);
MG_STORE_BE16(qtag + 1, etype);
ethhdrsz += sizeof(*qtag);
ethpay += sizeof(*qtag);
} // else unconfigured VLAN, untagged frame, normal path
}
++v;
if (etype == 0x8863 && size > (ethhdrsz + sizeof(struct pppoe))) { // PPPoE disc
static size_t j;
uint8_t pppoe_codes[] = {MG_PPPoE_PADO, MG_PPPoE_PADS, MG_PPPoE_PADT}; // defined in l2_ppp.c
struct pppoe *pppoe = (struct pppoe *) ethpay;
pppoe->vertype = 0x11;
// send all handled PPPoE codes, then 2 random ones
if (j >= (sizeof(pppoe_codes) / sizeof(pppoe_codes[0]) + 2)) j = 0;
if (j < (sizeof(pppoe_codes) / sizeof(pppoe_codes[0]))) pppoe->code = (pppoe_codes[j++]);
if (pppoe->code == MG_PPPoE_PADO) {
static size_t k;
// send id=0, then 2 random ids
if (k >= 3) k = 0;
if (k == 0) pppoe->id = 0;
++k;
} else if (pppoe->code == MG_PPPoE_PADS) {
static size_t k;
// send id=x, then 2 random ids
if (k >= 3) k = 0;
if (k == 0) pppoe->id = 0x1234;
++k;
} else if (pppoe->code == MG_PPPoE_PADT) { // keep separate so they do not correlate too much
static size_t k;
// send id=x, then 2 random ids
if (k >= 3) k = 0;
if (k == 0) pppoe->id = 0x1234;
++k;
}
} else if (etype == 0x8864 && size > (ethhdrsz + sizeof(struct pppoe))) { // PPPoE sess
// PPP rx is covered via HDLC too; PPPoE tx wrapping may remain unexercised.
struct pppoe *pppoe = (struct pppoe *) ethpay;
static size_t k, l;
// send code=0, then 2 random codes
if (k >= 3) k = 0;
if (k == 0) {
static size_t m;
pppoe->code = 0;
// send id=x, then 2 random ids
if (m >= 3) m = 0;
if (m == 0) pppoe->id = 0x1234;
++m;
}
++k;
// set s_state to valid and invalid
if (l >= 2) l = 0;
if (l == 0) s_state = MG_PPPoE_ST_SESS; // defined in l2_ppp.c
if (l == 1) s_state = MG_PPPoE_ST_REQ;
++l;
// PPP exercised in other block
}
// build proper layer-3 datagrams, to be able to exercise layers above
if (eth->type == mg_htons(0x800) && size > (sizeof(*eth) + sizeof(struct ip))) { // IPv4
static size_t j;
uint8_t ip_protos[] = {1, 6, 17}; // ICMP, TCP, UDP
struct ip *ip4 = (struct ip *) (eth + 1);
ip4->ver = (ip4->ver & ~0xf0) | (4 << 4);
// send all handled IP protos, then 2 random ones
if (j >= (sizeof(ip_protos) / sizeof(ip_protos[0]) + 2)) j = 0;
if (j < (sizeof(ip_protos) / sizeof(ip_protos[0]))) ip4->proto = (ip_protos[j++]);
if (ip4->proto == 1) { // ICMP
} else if (ip4->proto == 6) { // TCP
} else if (ip4->proto == 17) { // UDP
if (size > (sizeof(*eth) + sizeof(struct ip) + sizeof(struct udp))) {
static size_t k;
uint16_t udp_ports[] = {67, 68}; // DHCP server and client
struct udp *udp = (struct udp *) (ip4 + 1);
// send all handled UDP ports, then 2 random ones
if (k >= (sizeof(udp_ports) / sizeof(udp_ports[0]) + 2)) k = 0;
if (k < (sizeof(udp_ports) / sizeof(udp_ports[0]))) udp->dport = mg_htons(udp_ports[k++]);
}
}
} else if (eth->type == mg_htons(0x806)) { // ARP
else if (etype == 0x800 && size > (ethhdrsz + sizeof(struct ip))) { // IPv4
fuzz_ip4(ethpay, size - ethhdrsz, &l3);
} else if (etype == 0x806) { // ARP
} else if (eth->type == mg_htons(0x86dd) && size > (sizeof(*eth) + sizeof(struct ip6))) { // IPv6
static size_t j;
uint8_t ip6_protos[] = {6, 17, 58}; // TCP, UDP, ICMPv6
struct ip6 *ip6 = (struct ip6 *) (eth + 1);
ip6->ver = (ip6->ver & ~0xf0) | (6 << 4);
// send all handled IPv6 "next headers", then 2 random ones
if (j >= (sizeof(ip6_protos) / sizeof(ip6_protos[0]) + 2)) j = 0;
if (j < (sizeof(ip6_protos) / sizeof(ip6_protos[0]))) ip6->next = (ip6_protos[j++]);
if (ip6->next == 6) { // TCP
} else if (ip6->next == 17) { // UDP
} else if (ip6->next == 58) { // ICMPv6
if (size >= (sizeof(*eth) + sizeof(struct ip6) + sizeof(struct icmp6))) {
static size_t k;
uint8_t icmp6_types[] = {128, 134, 135, 136}; // Echo Request, RA, NS, NA
struct icmp6 *icmp6 = (struct icmp6 *) (ip6 + 1);
// send all handled ICMPv6 types, then 2 random ones
if (k >= (sizeof(icmp6_types) / sizeof(icmp6_types[0]) + 2)) k = 0;
if (k < (sizeof(icmp6_types) / sizeof(icmp6_types[0]))) icmp6->type = icmp6_types[k++];
}
} else if (etype == 0x86dd && size > (ethhdrsz + sizeof(struct ip6))) { // IPv6
fuzz_ip6(ethpay, size - ethhdrsz, &l3);
}
} else if (mif.l2type == MG_TCPIP_L2_PPP && size > (sizeof(struct hdlc_) + sizeof(struct ppp) + 2)) {
static size_t i;
static struct fuzz_l3_state l3;
uint16_t ppp_protos[] = {
MG_PPP_PROTO_LCP, MG_PPP_PROTO_IPCP, MG_PPP_PROTO_IP, MG_PPP_PROTO_IPV6CP, MG_PPP_PROTO_IPV6
};
struct hdlc_ *hdlc = (struct hdlc_ *) pkt;
struct ppp *ppp = (struct ppp *) (hdlc + 1);
size_t len = size - sizeof(*hdlc) - sizeof(*ppp) - 2;
hdlc->addr = MG_PPP_ADDR;
hdlc->ctrl = MG_PPP_CTRL;
s_lcpup = true;
// send all handled PPP protocols, then 2 random ones
if (i >= (sizeof(ppp_protos) / sizeof(ppp_protos[0]) + 2)) i = 0;
if (i < (sizeof(ppp_protos) / sizeof(ppp_protos[0]))) ppp->proto = mg_htons(ppp_protos[i++]);
if (ppp->proto == mg_htons(MG_PPP_PROTO_LCP) && len >= sizeof(struct lcp)) {
static size_t j, k;
uint8_t lcp_codes[] = {MG_PPP_LCP_CFG_REQ, MG_PPP_LCP_CFG_ACK, MG_PPP_LCP_CFG_TERM_REQ, MG_PPP_LCP_ECHO_REQ};
struct lcp *lcp = (struct lcp *) (ppp + 1);
// send all handled LCP codes, then 2 random ones
if (j >= (sizeof(lcp_codes) / sizeof(lcp_codes[0]) + 2)) j = 0;
if (j < (sizeof(lcp_codes) / sizeof(lcp_codes[0]))) lcp->code = lcp_codes[j++];
// send valid length, then 2 random lengths
if (k >= 3) k = 0;
if (k == 0) lcp->len = mg_htons((uint16_t) sizeof(*lcp));
++k;
} else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPCP) && len >= 10) {
static size_t j, k, m;
uint8_t ipcp_codes[] = {MG_PPP_IPCP_CFG_REQ, MG_PPP_IPCP_CFG_ACK, MG_PPP_IPCP_CFG_NACK, MG_PPP_IPCP_CFG_REJECT};
struct ipcp *ipcp = (struct ipcp *) (ppp + 1);
uint8_t *opts = (uint8_t *) (ipcp + 1);
// send all handled IPCP codes, then 2 random ones
if (j >= (sizeof(ipcp_codes) / sizeof(ipcp_codes[0]) + 2)) j = 0;
if (j < (sizeof(ipcp_codes) / sizeof(ipcp_codes[0]))) ipcp->code = ipcp_codes[j++];
// send valid length, then 2 random lengths
if (m >= 3) m = 0;
if (m == 0) ipcp->len = mg_htons(10);
++m;
// send valid IPADDR option, then 2 random option types
if (k >= 3) k = 0;
if (k == 0) {
static size_t l;
opts[0] = MG_PPP_IPCP_OPT_IPADDR;
// for valid option type, send valid length, then random length
if (l >= 2) l = 0;
if (l == 0) opts[1] = 6;
++l;
}
++k;
} else if (ppp->proto == mg_htons(MG_PPP_PROTO_IP) && len >= sizeof(struct ip)) {
fuzz_ip4(ppp + 1, len, &l3);
} else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPV6CP) && len >= 14) {
static size_t j, k, m;
uint8_t ipv6cp_codes[] = {MG_PPP_IPV6CP_CFG_REQ, MG_PPP_IPV6CP_CFG_ACK, MG_PPP_IPV6CP_CFG_NACK, MG_PPP_IPV6CP_CFG_REJECT};
struct ipv6cp *ipv6cp = (struct ipv6cp *) (ppp + 1);
uint8_t *opts = (uint8_t *) (ipv6cp + 1);
// send all handled IPV6CP codes, then 2 random ones
if (j >= (sizeof(ipv6cp_codes) / sizeof(ipv6cp_codes[0]) + 2)) j = 0;
if (j < (sizeof(ipv6cp_codes) / sizeof(ipv6cp_codes[0]))) ipv6cp->code = ipv6cp_codes[j++];
// send valid length, then 2 random lengths
if (m >= 3) m = 0;
if (m == 0) ipv6cp->len = mg_htons(14);
++m;
// send valid IFCID option, then 2 random option types
if (k >= 3) k = 0;
if (k == 0) {
static size_t l;
opts[0] = MG_PPP_IPV6CP_OPT_IFCID;
// for valid option type, send valid length, then random length
if (l >= 2) l = 0;
if (l == 0) {
static size_t o;
opts[1] = 10;
// for valid option length, send non-zero IFCID, then 2 random IFCIDs
if (o >= 3) o = 0;
if (o == 0) opts[9] = 1;
++o;
}
++l;
}
++k;
} else if (ppp->proto == mg_htons(MG_PPP_PROTO_IPV6) && len >= sizeof(struct ip6)) {
fuzz_ip6(ppp + 1, len, &l3);
}
}
@@ -175,8 +356,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
#endif
mg_tcpip_rx(&mif, pkt, size);
// prior actions may have had consequences on link state
mif.state = MG_TCPIP_STATE_READY;
// Test HTTP serving (via our built-in TCP/IP stack)
const char *url = "http://localhost:12345";
const char *url = "http://localhost:12345"; // internally resolves
// creates a connection and sends SYN via the router, unless OOM
struct mg_connection *c = mg_http_connect(&mgr, url, fn, NULL);
mg_iobuf_add(&c->recv, 0, data, size);
c->pfn(c, MG_EV_READ, NULL); // manually invoke protocol event handler
+15 -5
View File
@@ -110,7 +110,8 @@ static void ph(struct mg_connection *c, int ev, void *ev_data) {
}
static void fn(struct mg_connection *c, int ev, void *ev_data) {
(void) c, (void) ev, (void) ev_data;
if (ev == MG_EV_ACCEPT) ASSERT(mg_dscp(c, MG_DSCP_AF(4, 1)));
(void) ev_data;
}
static void tcpclosure_fn(struct mg_connection *c, int ev, void *ev_data) {
@@ -158,7 +159,7 @@ static void frag_send_fn(struct mg_connection *c, int ev, void *ev_data) {
}
} else if (ev == MG_EV_WRITE) {
// Checking TCP segment sizes (ev_data points to the TCP payload length)
ASSERT(*(int *) ev_data == s_seg_sizes[s_seg_sent++]);
ASSERT(*(long *) ev_data == s_seg_sizes[s_seg_sent++]);
}
(void) c, (void) ev_data;
}
@@ -298,7 +299,7 @@ static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
mif->ip = mg_htonl(0x1000000);
mif->gw = mg_htonl(0x1000000);
mif->gw_ready = true;
mif->mask = 255; // use router, to avoid firing an ARP request
mif->mask = mg_htonl(0xff000000); // use router, avoid firing ARP request
mif->state = MG_TCPIP_STATE_READY; // so mg_send() works and DHCP stops
}
mg_tcpip_init(mgr, mif);
@@ -306,7 +307,7 @@ static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
// setting the Ethernet header
memset(e, 0, sizeof(*e));
memcpy(e->dst, mif->mac, 6 * sizeof(uint8_t));
e->type = mg_htons(ipp->ip4 != NULL ? 0x800 : 0x86dd);
e->type = mg_htons((uint16_t)(ipp->ip4 != NULL ? 0x800 : 0x86dd));
// setting the IP header
#if MG_ENABLE_IPV6
@@ -499,8 +500,11 @@ static void test_tcp_basics(bool ipv6) {
if (ipv6) {
ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 &&
i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0);
ASSERT((uint8_t) (((i6->ver & 15) << 2) | (i6->label[0] >> 6)) ==
MG_DSCP_AF(4, 1));
} else {
ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000));
ASSERT(i->tos == (MG_DSCP_AF(4, 1) << 2));
}
// segment with seq_no way out of window
@@ -1125,8 +1129,11 @@ static void test_tcp(bool ipv6) {
}
static void udp_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_READ && c->recv.len == 2 && c->recv.buf[0] == 'p')
if (ev == MG_EV_OPEN) {
ASSERT(mg_dscp(c, MG_DSCP_AF(4, 1)));
} else if (ev == MG_EV_READ && c->recv.len == 2 && c->recv.buf[0] == 'p') {
mg_send(c, "P90", 3);
}
(void) ev_data;
}
@@ -1221,9 +1228,12 @@ static void test_udp(bool ipv6) {
if (ipv6) {
ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 &&
i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0);
ASSERT((uint8_t) (((i6->ver & 15) << 2) | (i6->label[0] >> 6)) ==
MG_DSCP_AF(4, 1));
ASSERT(udp6csum_ok(i6, u));
} else {
ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000));
ASSERT(i->tos == (MG_DSCP_AF(4, 1) << 2));
ASSERT(udpcsum_ok(i, u));
}
+8 -4
View File
@@ -1,7 +1,7 @@
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#define FETCH_BUF_SIZE (16 * 1024)
#define FETCH_BUF_SIZE (32 * 1024)
#include "mongoose.c"
@@ -196,6 +196,7 @@ static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
va_list ap;
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
// timestamp (from an ancient call in other test)
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
c = mg_http_connect(mgr, url, fcb, &fd);
ASSERT(c != NULL);
va_start(ap, fmt);
@@ -355,12 +356,13 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
data.url = strdup(MQTT_URL);
#endif
#endif // MG_TLS
mg_mgr_poll(mgr, 0); // update interface timing
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 (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) {
mg_mgr_poll(mgr, 0);
usleep(10000); // 10 ms. Slow down poll loop to ensure packets transit
usleep(5000); // 5 ms (*) See fetch() above for reasons
}
ASSERT(data.passed);
mg_mgr_poll(mgr, 0);
@@ -400,11 +402,13 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
#endif
#endif // MG_TLS
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 (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) {
mg_mgr_poll(mgr, 0);
usleep(10000); // 10 ms. Slow down poll loop to ensure packets transit
usleep(5000); // 5 ms (*) See fetch() above for reasons
}
ASSERT(data.passed);
mg_mgr_poll(mgr, 0);
+7 -3
View File
@@ -10,6 +10,7 @@
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <signal.h>
#ifndef __OpenBSD__
#include <linux/if.h>
@@ -20,7 +21,7 @@
#include <net/if_types.h>
#endif
static int s_signo;
static volatile sig_atomic_t s_signo;
void signal_handler(int signo) {
s_signo = signo;
}
@@ -49,6 +50,8 @@ int main(void) {
return EXIT_FAILURE;
}
printf("Opened TAP interface: %s\n", iface);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
int sockfd;
struct sockaddr_in tap_addr, port_addr;
@@ -97,8 +100,9 @@ int main(void) {
}
break;
}
} while (result >= 0);
printf("looks like we got an error here... should I quit ? Waiting for CTRL-C\n");
} while (result >= 0 && s_signo == 0);
if (result < 0)
printf("looks like we got an error here... should I quit ? Waiting for CTRL-C\n");
}
close(sockfd);
close(fd);
+431
View File
@@ -0,0 +1,431 @@
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#include "mongoose.c"
#include "driver_mock.c"
static int s_num_tests = 0;
static bool s_error = false;
#ifdef NO_ABORT
static int s_abort = 0;
#define ABORT() ++s_abort, s_error = true
#else
#ifdef NO_SLEEP_ABORT
#define ABORT() abort()
#else
#define ABORT() \
sleep(2); /* 2s, GH print reason */ \
abort();
#endif
#endif
#define ASSERT(expr) \
do { \
s_num_tests++; \
if (!(expr)) { \
printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \
fflush(stdout); \
ABORT(); \
} \
} while (0)
static const uint8_t s_mac[6] = {2, 3, 4, 5, 6, 7};
static const uint8_t s_src[6] = {8, 9, 10, 11, 12, 13};
static uint8_t s_tx[2048];
static void init_if(struct mg_tcpip_if *ifp, enum mg_l2type type) {
memset(ifp, 0, sizeof(*ifp));
memcpy(ifp->mac, s_mac, sizeof(s_mac));
ifp->driver = &mg_tcpip_driver_mock;
ifp->l2type = type;
ifp->tx.buf = (char *) s_tx;
ifp->tx.len = sizeof(s_tx);
mg_l2_init(ifp);
}
static size_t mk_ppp(uint8_t *buf, uint16_t proto, const void *payload,
size_t len) {
struct hdlc_ *hdlc = (struct hdlc_ *) buf;
struct ppp *ppp = (struct ppp *) (hdlc + 1);
hdlc->addr = MG_PPP_ADDR;
hdlc->ctrl = MG_PPP_CTRL;
ppp->proto = mg_htons(proto);
memcpy(ppp + 1, payload, len);
memset((uint8_t *) (ppp + 1) + len, 0, 2);
return sizeof(*hdlc) + sizeof(*ppp) + len + 2;
}
static void *ppp_payload(uint8_t *frame) {
return (struct ppp *) ((struct hdlc_ *) frame + 1) + 1;
}
static void test_fcs(void) {
struct mg_tcpip_if ifp;
uint8_t frame[] = {0xff, 0x03, 0x00, 0x21, 1, 2, 3, 4, 0, 0};
uint8_t fcs[] = {0xae, 0x77};
init_if(&ifp, MG_TCPIP_L2_PPP);
// PPP trailer stores the FCS bytes in the proper order
ASSERT(mg_l2_ppp_trailer(&ifp, 4, frame + sizeof(frame) - sizeof(fcs)) ==
sizeof(frame));
ASSERT(memcmp(frame + sizeof(frame) - sizeof(fcs), fcs, sizeof(fcs)) == 0);
}
static size_t mk_pppoe_disc(uint8_t *buf, uint8_t code, uint16_t id,
const void *payload, size_t len) {
struct eth *eth = (struct eth *) buf;
struct pppoe *pppoe = (struct pppoe *) (eth + 1);
memcpy(eth->dst, s_mac, sizeof(eth->dst));
memcpy(eth->src, s_src, sizeof(eth->src));
eth->type = mg_htons(0x8863);
pppoe->vertype = 0x11;
pppoe->code = code;
pppoe->id = mg_htons(id);
pppoe->len = mg_htons((uint16_t) len);
if (len > 0) memcpy(pppoe + 1, payload, len);
return sizeof(*eth) + sizeof(*pppoe) + len;
}
static size_t mk_pppoe_sess(uint8_t *buf, uint16_t id, uint16_t proto,
const void *payload, size_t len) {
struct eth *eth = (struct eth *) buf;
struct pppoe *pppoe = (struct pppoe *) (eth + 1);
struct ppp *ppp = (struct ppp *) (pppoe + 1);
memcpy(eth->dst, s_mac, sizeof(eth->dst));
memcpy(eth->src, s_src, sizeof(eth->src));
eth->type = mg_htons(0x8864);
pppoe->vertype = 0x11;
pppoe->code = 0;
pppoe->id = mg_htons(id);
pppoe->len = mg_htons((uint16_t) (sizeof(*ppp) + len));
ppp->proto = mg_htons(proto);
memcpy(ppp + 1, payload, len);
return sizeof(*eth) + sizeof(*pppoe) + sizeof(*ppp) + len;
}
static void test_lcp(void) {
uint8_t frame[64];
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
{ // LCP ACK brings PPP link up
struct lcp lcp = {MG_PPP_LCP_CFG_ACK, 1, mg_htons(sizeof(lcp))};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = false;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_LCP, &lcp, sizeof(lcp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_lcpup == true);
}
{ // Short LCP frame is discarded
uint8_t lcp[] = {MG_PPP_LCP_CFG_ACK, 1};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = false;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_LCP, lcp, sizeof(lcp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_lcpup == false);
}
}
static void test_ipcp(void) {
uint8_t frame[64];
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
{ // IPCP request with peer IP address is ACKed
uint8_t ipcp[] = {MG_PPP_IPCP_CFG_REQ, 1, 0, 10,
MG_PPP_IPCP_OPT_IPADDR, 6, 192, 0, 2, 1};
uint32_t peer;
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
memcpy(&peer, ipcp + 6, sizeof(peer));
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPCP, ipcp, sizeof(ipcp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(ifp.gw == peer);
ASSERT(((struct ipcp *) ppp_payload(frame))->code == MG_PPP_IPCP_CFG_ACK);
ASSERT(ifp.nsent == 2);
}
{ // IPCP request without peer IP address is rejected
uint8_t ipcp[] = {MG_PPP_IPCP_CFG_REQ, 1, 0, 4};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPCP, ipcp, sizeof(ipcp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(ifp.gw == 0);
ASSERT(((struct ipcp *) ppp_payload(frame))->code ==
MG_PPP_IPCP_CFG_REJECT);
ASSERT(ifp.nsent == 3);
}
}
#if MG_ENABLE_IPV6
static void test_ipv6cp(void) {
uint8_t frame[64];
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
{ // IPv6CP request with interface ID is ACKed
uint8_t ipv6cp[] = {MG_PPP_IPV6CP_CFG_REQ, 1, 0, 14,
MG_PPP_IPV6CP_OPT_IFCID, 10, 2, 3, 4, 5, 6, 7, 8, 9};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPV6CP, ipv6cp,
sizeof(ipv6cp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(memcmp(&((struct mg_l2addr *) ifp.gw6mac)->addr.ieee64, ipv6cp + 6,
8) == 0);
ASSERT(((struct ipv6cp *) ppp_payload(frame))->code ==
MG_PPP_IPV6CP_CFG_ACK);
ASSERT(ifp.nsent == 2);
}
{ // IPv6CP request with zero interface ID is rejected
uint8_t ipv6cp[] = {MG_PPP_IPV6CP_CFG_REQ, 1, 0, 14,
MG_PPP_IPV6CP_OPT_IFCID, 10, 0, 0, 0, 0, 0, 0, 0, 0};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPV6CP, ipv6cp,
sizeof(ipv6cp)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(((struct ipv6cp *) ppp_payload(frame))->code ==
MG_PPP_IPV6CP_CFG_REJECT);
ASSERT(ifp.nsent == 1);
}
}
#endif
static void test_ppp(void) {
uint8_t frame[96];
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
{ // IPv4 packet is accepted after LCP is up
uint8_t payload[sizeof(struct ip)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPP);
MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize));
ASSERT(ifp.l2mtu != 0);
ASSERT(ifp.framesize != 0);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IP, payload, sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // IPv4 packet is discarded while LCP is down
uint8_t payload[sizeof(struct ip)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = false;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IP, payload, sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
}
#if MG_ENABLE_IPV6
{ // IPv6 packet is accepted after LCP is up
uint8_t payload[sizeof(struct ip6)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPV6, payload,
sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV6);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // IPv6 packet is discarded while LCP is down
uint8_t payload[sizeof(struct ip6)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = false;
raw = mg_str_n((char *) frame,
mk_ppp(frame, MG_PPP_PROTO_IPV6, payload,
sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
}
#endif
{ // Unknown protocol is rejected after LCP is up
uint8_t payload[] = {1, 2, 3, 4};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_ppp(frame, 0x1234, payload, sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(ifp.nsent == 1);
}
{ // Unknown protocol is discarded while LCP is down
uint8_t payload[] = {1, 2, 3, 4};
init_if(&ifp, MG_TCPIP_L2_PPP);
s_lcpup = false;
raw = mg_str_n((char *) frame,
mk_ppp(frame, 0x1234, payload, sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(ifp.nsent == 0);
}
}
static void test_pppoe(void) {
uint8_t frame[96];
struct mg_tcpip_if ifp;
struct mg_str raw, pay;
enum mg_l2proto proto;
{ // PADO moves discovery to request state
init_if(&ifp, MG_TCPIP_L2_PPPoE);
MG_DEBUG(("MTU: %u, frame size: %u", ifp.l2mtu, ifp.framesize));
ASSERT(ifp.l2mtu != 0);
ASSERT(ifp.framesize != 0);
s_state = MG_PPPoE_ST_DISC;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADO, 0, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_REQ);
ASSERT(ifp.nsent == 1);
}
{ // PADO is ignored outside discovery state
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_REQ;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADO, 0, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_REQ);
ASSERT(ifp.nsent == 0);
}
{ // PADS establishes the session
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_REQ;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADS, 0x1234, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_SESS);
ASSERT(s_id == mg_htons(0x1234));
}
{ // PADS is ignored outside request state
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_DISC;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADS, 0x1234, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_DISC);
}
{ // PADT matching the session tears it down
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_SESS;
s_id = mg_htons(0x1234);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADT, 0x1234, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_DISC);
ASSERT(s_id == 0);
ASSERT(s_lcpup == false);
}
{ // PADT for a different session is ignored
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_SESS;
s_id = mg_htons(0x4321);
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_pppoe_disc(frame, MG_PPPoE_PADT, 0x1234, NULL, 0));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
ASSERT(s_state == MG_PPPoE_ST_SESS);
ASSERT(s_id == mg_htons(0x4321));
ASSERT(s_lcpup == true);
}
{ // PPPoE session packet is accepted in session state
uint8_t payload[sizeof(struct ip)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_SESS;
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_pppoe_sess(frame, 0x1234, MG_PPP_PROTO_IP, payload,
sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == true);
ASSERT(proto == MG_TCPIP_L2PROTO_IPV4);
ASSERT(pay.len == sizeof(payload));
ASSERT(memcmp(pay.buf, payload, sizeof(payload)) == 0);
}
{ // PPPoE session packet is discarded outside session state
uint8_t payload[sizeof(struct ip)];
memset(payload, 0, sizeof(payload));
init_if(&ifp, MG_TCPIP_L2_PPPoE);
s_state = MG_PPPoE_ST_REQ;
s_lcpup = true;
raw = mg_str_n((char *) frame,
mk_pppoe_sess(frame, 0x1234, MG_PPP_PROTO_IP, payload,
sizeof(payload)));
proto = MG_TCPIP_L2PROTO_ARP;
ASSERT(mg_l2_rx(&ifp, &proto, &pay, &raw) == false);
}
}
#define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
int main(void) {
s_error = false;
test_fcs();
DASHBOARD("fcs");
s_error = false;
test_lcp();
DASHBOARD("lcp");
s_error = false;
test_ipcp();
DASHBOARD("ipcp");
#if MG_ENABLE_IPV6
s_error = false;
test_ipv6cp();
DASHBOARD("ipv6cp");
#endif
s_error = false;
test_ppp();
DASHBOARD("ppp");
s_error = false;
test_pppoe();
printf("HEALTH_DASHBOARD\t\"pppoe\": %s\n", s_error ? "false" : "true");
#ifdef NO_ABORT
if (s_abort != 0) return EXIT_FAILURE;
#endif
printf("SUCCESS. Total tests: %d\n", s_num_tests);
return EXIT_SUCCESS;
}
+7
View File
@@ -0,0 +1,7 @@
# PPP options for the PPPoE server
noauth
10.0.0.1:10.67.15.1
lcp-echo-interval 1
lcp-echo-failure 2
+ipv6
ipv6cp-use-ipaddr
+142
View File
@@ -0,0 +1,142 @@
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#include <sys/socket.h>
#ifndef __OpenBSD__
#include <linux/if.h>
#include <linux/if_tun.h>
#else
#include <net/if.h>
#include <net/if_tun.h>
#include <net/if_types.h>
#endif
#include <sys/ioctl.h>
#include "mongoose.c"
#define WAIT_MS 5000
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);
usleep(1);
if (received < 0) return 0;
return (size_t) received;
}
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
ssize_t res = write(*(int *) ifp->driver_data, buf, len);
if (res < 0) {
MG_ERROR(("tap_tx failed: %d", errno));
return 0;
}
return (size_t) res;
}
static bool tap_poll(struct mg_tcpip_if *ifp, bool s1) {
return s1 && ifp->driver_data ? true : false;
}
int main(int argc, char *argv[]) {
int res = EXIT_SUCCESS;
const char *iface = "tap0";
const char *mac = "02:00:01:02:03:78";
#ifndef __OpenBSD__
const char *tuntap_device = "/dev/net/tun";
#else
const char *tuntap_device = "/dev/tap0";
#endif
uint16_t vlan_id = 3;
int fd, i;
struct ifreq ifr;
struct mg_mgr mgr;
struct mg_tcpip_driver driver;
struct mg_tcpip_if mif;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
iface = argv[++i];
} else if (strcmp(argv[i], "-mac") == 0 && i + 1 < argc) {
mac = argv[++i];
} else if (strcmp(argv[i], "-v") == 0 && i + 1 < argc) {
mg_log_set(atoi(argv[++i]));
} else if (strcmp(argv[i], "-vlan") == 0 && i + 1 < argc) {
vlan_id = (uint16_t) atoi(argv[++i]);
} else {
MG_ERROR(("unknown option %s", argv[i]));
return EXIT_FAILURE;
}
}
fd = open(tuntap_device, O_RDWR);
if (fd < 0) {
MG_ERROR(("Failed to open TAP device: %d", errno));
return EXIT_FAILURE;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, IFNAMSIZ);
#ifndef __OpenBSD__
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
MG_ERROR(("Failed to setup TAP interface: %s", ifr.ifr_name));
close(fd);
return EXIT_FAILURE;
}
#else
ifr.ifr_flags = (short) (IFF_UP | IFF_BROADCAST | IFF_MULTICAST);
if (ioctl(fd, TUNSIFMODE, (void *) &ifr) < 0) {
MG_ERROR(("Failed to setup TAP interface: %s", ifr.ifr_name));
close(fd);
return EXIT_FAILURE;
}
#endif
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
memset(&driver, 0, sizeof(driver));
driver.tx = tap_tx;
driver.rx = tap_rx;
driver.poll = tap_poll;
memset(&mif, 0, sizeof(mif));
mif.driver = &driver;
mif.driver_data = &fd;
#if MG_ENABLE_IPV6
mif.ip6[0] = mg_htonll(0x100000000000000);
mif.prefix[0] = 1;
mif.prefix_len = 64;
mif.gw6[0] = mg_htonll(0x100000000000000);
mif.gw6_ready = true;
#endif
mif.l2type = MG_TCPIP_L2_PPPoE;
mif.l2data.eth.vlan_id = vlan_id;
sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mif.mac[0], &mif.mac[1],
&mif.mac[2], &mif.mac[3], &mif.mac[4], &mif.mac[5]);
mg_mgr_init(&mgr);
mg_tcpip_init(&mgr, &mif);
MG_INFO(("Opened TAP interface %s, VLAN %u", iface, vlan_id));
for (i = 0; i < WAIT_MS / 10 && (mif.state != MG_TCPIP_STATE_READY
#if MG_ENABLE_IPV6
|| mif.state6 != MG_TCPIP_STATE_READY
#endif
); i++) {
mg_mgr_poll(&mgr, 10);
usleep(10000);
}
if (mif.state != MG_TCPIP_STATE_READY) {
MG_ERROR(("IP FAILED %u", mif.state));
res = EXIT_FAILURE;
}
#if MG_ENABLE_IPV6
if (mif.state6 != MG_TCPIP_STATE_READY) {
MG_ERROR(("IPv6 FAILED %u", mif.state6));
res = EXIT_FAILURE;
}
#endif
mg_mgr_free(&mgr);
close(fd);
return res;
}
+21 -9
View File
@@ -23,18 +23,20 @@ echo
# Package installation
echo "Package installation"
sudo apt-get -y install isc-dhcp-server net-tools radvd
sudo systemctl stop radvd # just in case they think it is cool to start it
sudo apt-get -y install isc-dhcp-server net-tools radvd ppp pppoe
sudo systemctl stop radvd # they think it is cool to start it
# sudo apt-get -y install build-essential sshpass
echo
echo "Network configuration script: Bridge"
sudo ip link add $BRIDGE type bridge # Create brige
sudo ifconfig $BRIDGE $BRIDGE_IP netmask $BRIDGE_MASK up
sudo ifconfig $BRIDGE:1 $GATEWAY_IP netmask $BRIDGE_MASK up
sudo ifconfig $BRIDGE:2 $HOST_IP netmask $BRIDGE_MASK up
sudo ip -6 addr add $BRIDGE_IPV6/64 dev $BRIDGE
sudo ip -6 addr add $HOST_IPV6/64 dev $BRIDGE:1
sudo ifconfig $BRIDGE $BRIDGE_IP netmask $BRIDGE_MASK up # its IP
sudo ifconfig $BRIDGE:1 $GATEWAY_IP netmask $BRIDGE_MASK up # IP announced by DHCP as gateway
sudo ifconfig $BRIDGE:2 $HOST_IP netmask $BRIDGE_MASK up # IP exported for LAN (dut) connections
sudo ip -6 addr add $BRIDGE_IPV6/64 dev $BRIDGE # its IPv6
sudo ip -6 addr add $HOST_IPV6/64 dev $BRIDGE:1 # IPv6 exported for LAN (dut) connections
sudo ip link add link $BRIDGE name $BRIDGE.3 type vlan id 3 # Create VLAN for PPPoE+VLAN tests
sudo ifconfig $BRIDGE.3 up
echo
echo "Network configuration script: TAP"
@@ -73,10 +75,20 @@ sudo cp test/dhcpd.conf /etc/dhcp/dhcpd.conf
sudo chmod a+w /var/lib/dhcp/*
sudo dhcpd mg_bridge0 &
echo
# Setup Router Advertising server
echo "radvd listening at $BRIDGE_IPV6"
sudo ip link set $BRIDGE up # there's no physical port, so we need to set link up for radvd to work
sudo radvd -n -d 5 -C test/radvd.conf &
echo
# Setup PPPoE server
echo "pppoe-server listening at $BRIDGE.3"
printf "lock\n" | sudo tee /etc/ppp/options
sudo pppoe-server -F -D -O test/pppoe-server-options -I $BRIDGE.3 &
echo
# Export generic server addresses so DUTs can connect to them
echo "Host listening at $HOST_IP and $HOST_IPV6"
export HOST_IP
export HOST_IPV6
@@ -88,8 +100,8 @@ wget https://cesanta.com/robots.txt
echo robots.txt:
cat robots.txt
rm robots.txt
wget https://ipv6.google.com/
rm index.html
wget https://ipv6.google.com/ || true
rm index.html || true
echo
# Confirm OK
+206 -15
View File
@@ -301,6 +301,13 @@ static void test_base64(void) {
ASSERT(mg_base64_decode("AAA=", 4, buf, 2) == 0);
ASSERT(mg_base64_decode("AAA=", 4, buf, 3) == 0);
ASSERT(mg_base64_decode("AAA=", 4, buf, 4) == 2);
ASSERT(mg_base64url_encode((uint8_t *) "???", 3, buf, sizeof(buf)) == 4);
ASSERT(strcmp(buf, "Pz8_") == 0);
ASSERT(mg_base64url_decode("Pz8_", 4, buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "???") == 0);
ASSERT(mg_base64url_decode("Pz8/", 4, buf, sizeof(buf)) == 0);
ASSERT(mg_base64url_decode("A", 1, buf, sizeof(buf)) == 0);
}
static void test_iobuf(void) {
@@ -339,7 +346,7 @@ static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) {
int64_t received = *(int64_t *) ev_data;
*(int64_t *) c->fn_data = received;
MG_DEBUG(("got time: %lld", received));
#if MG_ARCH == MG_ARCH_UNIX
#if MG_ARCH == MG_ARCH_UNIX && defined(__STDC__) && defined(__STDC_VERSION__)
struct timeval tv = {0, 0};
gettimeofday(&tv, 0);
int64_t ms = (int64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
@@ -1462,7 +1469,7 @@ static void test_tls(void) {
const char *url = "https://localhost:12347";
char buf[FETCH_BUF_SIZE];
struct mg_tls_opts opts;
struct mg_str data = mg_unpacked("/Makefile");
struct mg_str data = mg_unpacked("/fuzz.c");
char bigdata[FETCH_BUF_SIZE - 256]; // leave extra room
struct mg_str bd;
ASSERT(data.buf != NULL && data.len > 0);
@@ -1505,6 +1512,7 @@ static void test_tls(void) {
// 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 {
@@ -1612,9 +1620,8 @@ static void test_http_client(void) {
opts.name = mg_url_host(url);
// Test empty CA
// Disable mbedTLS: https://github.com/Mbed-TLS/mbedtls/issues/7075
#if MG_TLS != MG_TLS_MBED
opts.ca = mg_str("");
opts.name = mg_url_host(url);
c = mg_http_connect(&mgr, url, f3, &ok);
mg_tls_init(c, &opts);
ok = 0;
@@ -1622,7 +1629,21 @@ static void test_http_client(void) {
MG_INFO(("OK: %d", ok));
ASSERT(ok == 200);
mg_mgr_poll(&mgr, 1);
// Make host validation fail
c = mg_http_connect(&mgr, url, f3, &ok);
ASSERT(c != NULL);
opts.name = mg_str("dummy"); // Set some invalid hostname value
mg_tls_init(c, &opts);
ok = 0;
for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10);
MG_INFO(("OK: %d", ok));
#if MG_TLS != MG_TLS_MBED || !defined(MBEDTLS_VERSION_NUMBER) || \
MBEDTLS_VERSION_NUMBER < 0x03030000 || \
MBEDTLS_VERSION_NUMBER >= 0x04000000
// Ignore result for 3.3.0 <= mbedTLS < 4.0.0 : https://github.com/Mbed-TLS/mbedtls/issues/7075
ASSERT(ok == 777);
#endif
mg_mgr_poll(&mgr, 1);
#endif
#if MG_ENABLE_IPV6
@@ -2308,6 +2329,19 @@ static void test_str(void) {
ASSERT(chkdbl(mg_str("123e-3"), 0.123));
}
#if defined(NO_ZU_TEST) || (defined(_MSC_VER) && _MSC_VER < 1700)
// VC98 does not have '%zu', some old compilers may also not support it
#else
ASSERT(sn("%zu", 0));
ASSERT(sn("%zu", -1));
ASSERT(sn("%zu", -2));
ASSERT(sn("%zd", 123));
#if !defined(_MSC_VER)
ASSERT(sn("%zd", -3)); // MSVC casts to unsigned
#endif
ASSERT(sn("%zu", 0x1234));
#endif
ASSERT(sn("%d", 0));
ASSERT(sn("%d", 1));
ASSERT(sn("%d", -1));
@@ -2759,6 +2793,11 @@ static void test_util(void) {
((uint8_t *) &ipv4)[2] == 0x45);
ASSERT(MG_LOAD_BE24(&ipv4) == 0xef2345);
ASSERT(mg_timegm(2023, 1, 1, 0, 0, 0) == 1672531200);
ASSERT(mg_timegm(2025, 12, 31, 23, 59, 59) == 1767225599);
ASSERT(mg_timegm(2024, 2, 1, 0, 0, 0) == 1706745600);
ASSERT(mg_timegm(2000, 4, 1, 0, 0, 0) == 954547200);
memcpy(&ipv3, d64, sizeof(ipv3));
#if defined(_MSC_VER) && _MSC_VER < 1700
// VC98 doesn't suppport LL suffix
@@ -2816,6 +2855,13 @@ static void test_util(void) {
e = "\x20\x01\x48\x60\x48\x60\x00\x00\x00\x00\x00\x00\x00\x00\x88\x88";
ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0);
ASSERT(mg_aton(mg_str("12345::1"), &a) == false);
ASSERT(mg_aton(mg_str("1:::2"), &a) == false);
ASSERT(mg_aton(mg_str("1::2::3"), &a) == false);
ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7"), &a) == false);
ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:8:9"), &a) == false);
ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:g"), &a) == false);
ASSERT(mg_url_decode("a=%", 3, buf, sizeof(buf), 0) < 0);
ASSERT(mg_url_decode("&&&a=%", 6, buf, sizeof(buf), 0) < 0);
ASSERT(mg_url_decode("a=%1", 4, buf, sizeof(buf), 0) < 0);
@@ -2988,11 +3034,18 @@ static void test_util(void) {
static void test_crc32(void) {
// echo -n aaa | cksum -o3
ASSERT(mg_crc32(0, 0, 0) == 0);
ASSERT(mg_crc32(0, "a", 1) == 3904355907);
ASSERT(mg_crc32(0, "a", 1) == 3904355907U);
ASSERT(mg_crc32(0, "abc", 3) == 891568578);
ASSERT(mg_crc32(mg_crc32(0, "ab", 2), "c", 1) == 891568578);
}
static void test_crc16(void) {
ASSERT(mg_crc16(0, 0, 0) == 0);
ASSERT(mg_crc16(0, "a", 1) == 0x82f7);
ASSERT(mg_crc16(0, "abc", 3) == 0x9e25);
ASSERT(mg_crc16(mg_crc16(0, "ab", 2), "c", 1) == 0x9e25);
}
static void us(struct mg_connection *c, int ev, void *ev_data) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (ev == MG_EV_HTTP_MSG && mg_match(hm->uri, mg_str("/upload"), NULL)) {
@@ -3160,7 +3213,7 @@ static void test_http_chunked_case(mg_event_handler_t s, mg_event_handler_t c,
struct mg_mgr mgr;
uint32_t i, crc = 0, expected_crc = mg_crc32(0, expected, strlen(expected));
struct mg_connection *conn;
static uint16_t port = 32344; // To prevent bind errors on Windows
static uint16_t port = 32344; // To prevent bind errors on Windows
mg_snprintf(url, sizeof(url), "http://127.0.0.1:%d", port++);
mg_mgr_init(&mgr);
mg_http_listen(&mgr, url, s, NULL);
@@ -3714,13 +3767,23 @@ static void test_json(void) {
ASSERT(b == true);
ASSERT(mg_json_get(json, "$.b[2]", &len) < 0);
json = mg_str("[\"YWJj\", \"0100026869\"]");
json = mg_str("[\"YWJj\", \"0100026869\", \"\", \"abc\", \"0g\"]");
ASSERT((str = mg_json_get_b64(json, "$[0]", &len)) != NULL);
ASSERT(len == 3 && memcmp(str, "abc", (size_t) len) == 0);
mg_free(str);
ASSERT((str = mg_json_get_hex(json, "$[1]", &len)) != NULL);
ASSERT(len == 5 && memcmp(str, "\x01\x00\x02hi", (size_t) len) == 0);
mg_free(str);
ASSERT((str = mg_json_get_hex(json, "$[2]", &len)) != NULL);
ASSERT(len == 0 && str[0] == '\0');
mg_free(str);
len = 42;
str = mg_json_get_hex(json, "$[3]", &len);
ASSERT(str == NULL && len == 42);
mg_free(str);
str = mg_json_get_hex(json, "$[4]", &len);
ASSERT(str == NULL && len == 42);
mg_free(str);
json = mg_str("{\"a\":[1,2,3], \"ab\": 2}");
ASSERT(mg_json_get_long(json, "$.a[0]", -42) == 1);
@@ -3930,8 +3993,7 @@ static void test_poll(void) {
int count = 0, i;
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://127.0.0.1:42346", ph,
&count); // To prevent bind errors on Windows
mg_http_listen(&mgr, "http://127.0.0.1:12340", ph, &count);
for (i = 0; i < 10; i++) mg_mgr_poll(&mgr, 0);
ASSERT(count == 10);
mg_mgr_free(&mgr);
@@ -4298,6 +4360,128 @@ static void test_rsa(void) {
ASSERT(!mg_rsa_verify(em, 256, tv_mhash));
em[50] = tv_em[50];
}
{
static const uint8_t sha256_di[] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
static const uint8_t sha384_di[] = {
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30};
uint8_t hash[48], em[256];
size_t i, pslen;
for (i = 0; i < sizeof(hash); i++) hash[i] = (uint8_t) i;
memset(em, 0xff, sizeof(em));
em[0] = 0, em[1] = 1;
pslen = sizeof(em) - 3 - sizeof(sha256_di) - 32;
em[2 + pslen] = 0;
memcpy(em + 3 + pslen, sha256_di, sizeof(sha256_di));
memcpy(em + 3 + pslen + sizeof(sha256_di), hash, 32);
ASSERT(mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
em[1] = 2; // bad block type
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
em[1] = 1;
em[10] = 0; // bad padding
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
em[10] = 0xff;
em[3 + pslen + 15] ^= 1; // bad DigestInfo
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
em[3 + pslen + 15] ^= 1;
em[sizeof(em) - 1] ^= 1; // bad hash
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
memset(em, 0, sizeof(em)); // former suffix-only bypass
memcpy(em + sizeof(em) - 32, hash, 32);
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32));
memset(em, 0xff, sizeof(em));
em[0] = 0, em[1] = 1;
pslen = sizeof(em) - 3 - sizeof(sha384_di) - 48;
em[2 + pslen] = 0;
memcpy(em + 3 + pslen, sha384_di, sizeof(sha384_di));
memcpy(em + 3 + pslen + sizeof(sha384_di), hash, 48);
ASSERT(mg_rsa_pkcs_verify(em, sizeof(em), hash, 48));
ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 20));
}
#endif
}
static void test_jwt(void) {
char jwt[512], claims[128], got[128], hdr[128], extra[64], tampered[512];
struct mg_jwt_opts opts, bad;
const char *dot;
char *str;
size_t n, m;
mg_snprintf(claims, sizeof(claims), "{%m:%m,%m:%d}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("iat"), 123);
mg_snprintf(extra, sizeof(extra), "%m:%m", MG_ESC("cty"), MG_ESC("JWT"));
memset(&opts, 0, sizeof(opts));
opts.claims = mg_str(claims);
opts.header = mg_str(extra);
opts.kid = mg_str("hmac-key-1");
opts.secret = mg_str("secret");
n = mg_jwt_sign_hs256(&opts, jwt, sizeof(jwt));
ASSERT(n > 0 && n + 1 < sizeof(jwt));
jwt[n] = '\0';
dot = strchr(jwt, '.');
ASSERT(dot != NULL);
ASSERT(mg_base64url_decode(jwt, (size_t) (dot - jwt), hdr,
sizeof(hdr)) > 0);
str = mg_json_get_str(mg_str(hdr), "$.kid");
ASSERT(str != NULL && strcmp(str, "hmac-key-1") == 0);
mg_free(str);
str = mg_json_get_str(mg_str(hdr), "$.cty");
ASSERT(str != NULL && strcmp(str, "JWT") == 0);
mg_free(str);
m = mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, sizeof(got));
ASSERT(m == strlen(claims));
ASSERT(strcmp(got, claims) == 0);
bad = opts;
bad.secret = mg_str("bad-secret");
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got,
sizeof(got)) == 0);
memcpy(tampered, jwt, n + 1);
tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A';
ASSERT(mg_jwt_verify_hs256(mg_str_n(tampered, n), &opts, got,
sizeof(got)) == 0);
ASSERT(mg_jwt_sign_hs256(&opts, got, 10) == 0);
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, 4) == 0);
#if MG_TLS == MG_TLS_BUILTIN
{
const char *key_pem =
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n";
uint8_t key[32], pub[64];
memset(&opts, 0, sizeof(opts));
opts.claims = mg_str(claims);
opts.kid = mg_str("ec-key-1");
opts.private_key = key;
opts.public_key = pub;
ASSERT(mg_uecc_parse_private_key(mg_str(key_pem), key, sizeof(key)) == 32);
ASSERT(mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) == 1);
n = mg_jwt_sign_es256(&opts, jwt, sizeof(jwt));
ASSERT(n > 0 && n + 1 < sizeof(jwt));
jwt[n] = '\0';
m = mg_jwt_verify_es256(mg_str_n(jwt, n), &opts, got, sizeof(got));
ASSERT(m == strlen(claims));
ASSERT(strcmp(got, claims) == 0);
ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got,
sizeof(got)) == 0);
memcpy(tampered, jwt, n + 1);
tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A';
ASSERT(mg_jwt_verify_es256(mg_str_n(tampered, n), &opts, got,
sizeof(got)) == 0);
}
#endif
}
@@ -4308,6 +4492,7 @@ static void test_crypto(void) {
test_sha384();
test_x25519();
test_rsa();
test_jwt();
}
static void dash_custom(struct mg_connection *c, int ev, void *ev_data) {
@@ -4422,15 +4607,15 @@ static bool set3_fn(enum mg_dash_op op, struct mg_dash_user *u) {
static bool test_upload_dir(const struct mg_dash_user *u, char *buf, size_t len) {
(void) u;
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
mkdir("/tmp/mongoose_dash_test", 0755);
mkdir("mongoose_dash_test", 0755);
#endif
mg_snprintf(buf, len, "%s", "/tmp/mongoose_dash_test");
mg_snprintf(buf, len, "%s", "mongoose_dash_test");
return true;
}
struct test_file_entry {
char name[64];
size_t size;
uint64_t size;
};
static struct test_file_entry s_test_file;
static int s_test_file_index;
@@ -4588,7 +4773,7 @@ static void test_dash(void) {
memset(&dash, 0, sizeof(dash));
dash.session_auto_expiration_seconds = 1;
remove("/tmp/mongoose_dash_test/test.bin");
remove("mongoose_dash_test/test.bin");
MG_DASH_ADD_FIELD_SET(&dash, &test_files_set);
MG_DASH_ADD_FIELD_SET(&dash, &set1);
@@ -4917,6 +5102,11 @@ static void test_dash(void) {
ASSERT(strstr(buf, test_bin_probe) != NULL);
mg_mgr_free(&mgr3);
}
remove("mongoose_dash_test/test.bin");
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
rmdir("mongoose_dash_test");
#endif
}
#define DASHBOARD(x) \
@@ -5288,8 +5478,8 @@ static void test_wakeup(void) {
struct wudata *data;
int i;
mg_mgr_init(&mgr); // Initialise event manager
mg_log_set(MG_LL_DEBUG); // Set debug log level
c = mg_http_listen(&mgr, "http://127.0.0.1:42347", hwu, NULL);
mg_log_set(MG_LL_DEBUG);
c = mg_http_listen(&mgr, "http://127.0.0.1:12341", hwu, NULL);
mg_wakeup_init(&mgr); // Initialise wakeup socket pair
data = (struct wudata *) calloc(1, sizeof(*data)); // wuthread owns it
data->conn_id = c->id;
@@ -5328,6 +5518,7 @@ int main(void) {
test_str();
test_match();
test_crc32();
test_crc16();
DASHBOARD("misc");
s_error = false;
+26
View File
@@ -0,0 +1,26 @@
PROG ?= example # Program we are building
DELETE = rm -rf # Command to remove files
OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c # Source code files
CFLAGS = -W -Wall -Wextra -g -I. # Build options
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
PROG ?= example.exe # Use .exe suffix for the binary
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 -lbcrypt # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
OUT ?= -o $(PROG) # Build output
endif
all: $(PROG) # Default target. Build and run program
$(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
+62
View File
@@ -0,0 +1,62 @@
# JWT Bearer Authentication
## Build and run:
```sh
make
```
## HS256:
This path signs and verifies JWTs with the shared secret from `main.c`.
```sh
curl -i http://127.0.0.1:8000/login-hs256
TOKEN=$(curl -s http://127.0.0.1:8000/login-hs256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 47
{"sub":"admin","scope":"dash:read dash:write"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## ES256 (Requires MG_TLS_BUILTIN):
This path parses the EC private key from `main.c`, signs the JWT with it, and
derives the public key for verification to keep the example self-contained.
```sh
curl -i http://127.0.0.1:8000/login-es256
TOKEN=$(curl -s http://127.0.0.1:8000/login-es256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 36
{"sub":"admin","scope":"dash:read"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## Unauthorized access attempt example
```sh
curl -i -H "Authorization: Bearer bad.token.here" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Length: 13
Unauthorized
```
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// HTTP server example. It issues and verifies JWT Bearer tokens.
#include "mongoose.h"
static const char *s_listen_on = "http://0.0.0.0:8000";
static const char *s_secret = "change-me";
enum { TOKEN_LEN = 1024, CLAIMS_LEN = 256, JWT_LEN = 1024 };
static const char *s_ec_private_key =
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n";
#if MG_TLS == MG_TLS_BUILTIN
static size_t verify_es256(struct mg_str token, char *claims, size_t len) {
uint8_t key[32], pub[64];
struct mg_jwt_opts opts = {.public_key = pub};
// This keeps the example self-contained. A real verifier would usually keep
// only the issuer public key, selected by kid, not the signing private key.
if (mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key)) !=
32 ||
mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) != 1) {
return 0;
}
return mg_jwt_verify_es256(token, &opts, claims, len);
}
#endif
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
char user[1], *token, *claims, *jwt;
token = (char *) mg_calloc(1, TOKEN_LEN);
claims = (char *) mg_calloc(1, CLAIMS_LEN);
jwt = (char *) mg_calloc(1, JWT_LEN);
if (token == NULL || claims == NULL || jwt == NULL) {
mg_http_reply(c, 500, "", "OOM\n");
mg_free(token);
mg_free(claims);
mg_free(jwt);
return;
}
mg_http_creds(hm, user, sizeof(user), token, TOKEN_LEN);
if (mg_match(hm->uri, mg_str("/login-hs256"), NULL)) {
struct mg_jwt_opts opts = {.kid = mg_str("hmac-key-1"),
.secret = mg_str(s_secret)};
size_t n;
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"),
MG_ESC("dash:read dash:write"));
opts.claims = mg_str(claims);
n = mg_jwt_sign_hs256(&opts, jwt, JWT_LEN);
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#if MG_TLS == MG_TLS_BUILTIN
} else if (mg_match(hm->uri, mg_str("/login-es256"), NULL)) {
uint8_t key[32];
struct mg_jwt_opts opts = {.kid = mg_str("ec-key-1"),
.private_key = key};
size_t keysz, n;
keysz =
mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key));
// ES256 uses a raw 64-byte r || s signature. mg_jwt_sign_es256() signs it
// deterministically; applications can also hash the signing input and
// call mg_uecc_sign() directly if they need random-nonce ECDSA.
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"), MG_ESC("dash:read"));
opts.claims = mg_str(claims);
n = keysz == 32 ? mg_jwt_sign_es256(&opts, jwt, JWT_LEN) : 0;
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#endif
} else {
struct mg_jwt_opts opts = {.secret = mg_str(s_secret)};
if (mg_jwt_verify_hs256(mg_str(token), &opts, claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#if MG_TLS == MG_TLS_BUILTIN
} else if (verify_es256(mg_str(token), claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#endif
} else {
mg_http_reply(c, 401, "WWW-Authenticate: Bearer\r\n", "Unauthorized\n");
}
}
mg_free(token);
mg_free(claims);
mg_free(jwt);
}
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG);
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr);
return 0;
}
+1
View File
@@ -0,0 +1 @@
../../../mongoose.c
+1
View File
@@ -0,0 +1 @@
../../../mongoose.h
@@ -12,7 +12,13 @@ struct mg_tcpip_spi spi = {
NULL, // SPI metadata
[](void *) { digitalWrite(SS_PIN, LOW); SPI.beginTransaction(SPISettings()); },
[](void *) { digitalWrite(SS_PIN, HIGH); SPI.endTransaction(); },
[](void *, uint8_t c) { return SPI.transfer(c); }, // Execute transaction
[](void *, uint8_t *w, uint8_t *r, size_t n) { // Execute transaction
while (n--) {
uint8_t c = (w != NULL) ? *w++ : 0xFF;
uint8_t d = SPI.transfer(c);
if (r != NULL) *r++ = d;
}
},
};
struct mg_mgr mgr; // Mongoose event manager
struct mg_tcpip_if mif = {.mac = {2, 0, 1, 2, 3, 5}}; // Network interface
@@ -249,7 +249,7 @@ static struct mg_tcpip_if *s_ifp;
static void deliver_packet_above(struct pb *buf, int ifc) {
struct eth_hdr *ethhdr = (struct eth_hdr *) buf->data;
w_pkt_d("Data RX: Driver=>Kernel, if %d, len %d", ifc, buf->len);
if (mg_ntohs(ethhdr->type == 0x888E)) {
if (ethhdr->type == mg_htons(0x888E)) {
#if CONFIG_WPS2
if (wps_rx_callback != NULL) wps_rx_callback(buf->data, buf->len);
#endif
+2 -1
View File
@@ -8,7 +8,8 @@ char s_theone[] = "Server.local";
static void mdns_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_MDNS_RESP) { // is this the one ?
struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data;
if (mg_strcasecmp(mg_str(s_theone), resp->name) == 0)
if (resp->rr->atype == MG_DNS_RTYPE_A &&
mg_strcasecmp(mg_str(s_theone), resp->name) == 0)
MG_INFO(("%.*s is: %M", resp->name.len, resp->name.buf, mg_print_ip, &resp->addr));
}
(void) c;
+25
View File
@@ -0,0 +1,25 @@
PROG ?= example # Program we are building
DELETE = rm -rf # Command to remove files
OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c # Source code files
CFLAGS = -W -Wall -Wextra -g -I. # Build options
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
PROG ?= example.exe # Use .exe suffix for the binary
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
OUT ?= -o $(PROG) # Build output
endif
all: $(PROG) # Default target. Build and run program
$(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
+43
View File
@@ -0,0 +1,43 @@
Browses for `_http._tcp.local` services on the local network using mDNS/DNS-SD.
```sh
make
cc main.c mongoose.c -W -Wall -Wextra -g -I. -DMG_ENABLE_LINES -o example
./example
1304aa594 3 net.c:311:mg_mgr_init MG_IO_SIZE: 16384, TLS: none
1304aa594 3 net.c:224:mg_listen 1 4 udp://224.0.0.251:5353
1304aa594 3 sock.c:167:mg_send 1 4 0:0:0 34 err 0
1304aa594 3 sock.c:361:read_conn 1 4 0:0:0 34 err 0
1304aa594 3 dns.c:478:handle_mdns_query PTR request for _http._tcp
1304aa5d0 3 sock.c:361:read_conn 1 4 0:0:0 289 err 0
1304aa5d1 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from 3D03F3CPAU00096.local
1304aa5d1 2 main.c:15:mdns_ev_handler Got a response
SERVICE -> _http._tcp
NAME -> 3D03F3CPAU00096.local
PORT -> 80
TXT -> host=3D03F3CPAU00096mac=e0:50:8b:8d:2f:75ip=192.168.69.220
URL -> http://3D03F3CPAU00096.local:80
1304aa622 3 sock.c:361:read_conn 1 4 0:0:0 226 err 0
1304aa622 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from DIP-3TIR3020140717CCWR473261824.local
1304aa622 2 main.c:15:mdns_ev_handler Got a response
SERVICE -> _http._tcp
NAME -> DIP-3TIR3020140717CCWR473261824.local
PORT -> 80
ADDRESS -> 192.168.69.225
TXT -> path=/
URL -> http://192.168.69.225:80
1304aa6f5 3 sock.c:361:read_conn 1 4 0:0:0 169 err 0
1304aa6f5 3 dns.c:703:handle_mdns_respo PTR response for _http._tcp.local from PHILIPS_HD9255_3_6B7E5E.local
1304aa6f5 2 main.c:15:mdns_ev_handler Got a response
SERVICE -> _http._tcp
NAME -> PHILIPS_HD9255_3_6B7E5E.local
PORT -> 80
ADDRESS -> 192.168.69.167
TXT -> Device Type=Domestic Appliance
URL -> http://192.168.69.167:80
```
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static char s_name[] = "Mongoose";
static char s_service[] = "_http._tcp";
static void mdns_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_MDNS_RESP) {
struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data;
if (resp->rr->atype == MG_DNS_RTYPE_PTR &&
mg_strcasecmp(mg_str(s_service), resp->sd.srvcproto) == 0) {
char addr[40];
MG_INFO(("Got a response"));
printf("\tSERVICE -> %.*s\n", (int) resp->sd.srvcproto.len,
resp->sd.srvcproto.buf);
printf("\tNAME -> %.*s\n", (int) resp->name.len, resp->name.buf);
if (resp->sd.port != 0)
printf("\tPORT -> %u\n", resp->sd.port);
if (resp->addr.addr.ip4 != 0) {
mg_snprintf(addr, sizeof(addr), "%M", mg_print_ip, &resp->addr);
printf("\tADDRESS -> %s\n", addr);
}
if (resp->sd.txt.buf != NULL)
printf("\tTXT -> %.*s\n", (int) resp->sd.txt.len, resp->sd.txt.buf);
if (resp->addr.addr.ip4 != 0 && resp->addr.port != 0) {
mg_snprintf(addr, sizeof(addr), "http://%M:%u", mg_print_ip, &resp->addr,
mg_ntohs(resp->addr.port));
printf("\tURL -> %s\n", addr);
} else if (resp->sd.port != 0 && resp->name.buf != NULL) {
printf("\tURL -> http://%.*s:%u\n", (int) resp->name.len,
resp->name.buf, resp->sd.port);
}
printf("\n");
}
}
(void) c;
}
int main(void) {
struct mg_mgr mgr;
char query[256];
static struct mg_connection *c;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr); // Initialise event manager
c = mg_mdns_listen(&mgr, mdns_ev_handler, s_name); // Start mDNS server
if (c == NULL) return 1;
mg_snprintf(query, sizeof(query), "%s.local", s_service);
// Request service providers
if (!mg_mdns_query(c, query, MG_DNS_RTYPE_PTR)) return 1;
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
mg_mgr_free(&mgr);
return 0;
}
+1
View File
@@ -0,0 +1 @@
../../../mongoose.c
+1
View File
@@ -0,0 +1 @@
../../../mongoose.h