@mikrojs/firmware 0.16.0 → 0.17.0-next.20260722220623
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/mikrojs/CMakeLists.txt +9 -2
- package/components/mikrojs/include/mikrojs_esp32.h +23 -0
- package/components/mikrojs/mik_config.cpp +159 -5
- package/components/mikrojs/mik_deploy.cpp +54 -35
- package/components/mikrojs/mik_main.cpp +66 -5
- package/components/mikrojs/mik_nvs_kv.cpp +64 -18
- package/components/mikrojs/mik_ota.cpp +1791 -0
- package/components/mikrojs/platform_esp32.cpp +59 -0
- package/components/mikrojs/test/CMakeLists.txt +1 -0
- package/components/mikrojs/test/ble_test.cpp +2 -2
- package/components/mikrojs/test/fs_js_test.cpp +1 -1
- package/components/mikrojs/test/fs_pub_test.cpp +23 -23
- package/components/mikrojs/test/http_server_test.cpp +12 -2
- package/components/mikrojs/test/http_test.cpp +2 -2
- package/components/mikrojs/test/modules_extended_test.cpp +2 -2
- package/components/mikrojs/test/modules_test.cpp +2 -2
- package/components/mikrojs/test/nvs_kv_test.cpp +245 -0
- package/components/mikrojs/test/ota_host/gunzip_host_test.cpp +118 -0
- package/components/mikrojs/test/ota_host/run.sh +174 -0
- package/components/mikrojs/test/rtc_test.cpp +1 -1
- package/components/mikrojs/test/sleep_test.cpp +1 -1
- package/components/mikrojs/test/sntp_test.cpp +1 -1
- package/components/mikrojs/test/stdio_test.cpp +1 -1
- package/components/mikrojs/test/sys_test.cpp +1 -1
- package/components/mikrojs/test/udp_test.cpp +5 -5
- package/components/mikrojs/test/wifi_test.cpp +2 -2
- package/package.json +3 -3
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c5/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c5/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Host regression test for the pure unpack path in ../../mik_ota.cpp:
|
|
2
|
+
// unpack_tgz() (streaming gunzip -> untar) and sha256_file().
|
|
3
|
+
//
|
|
4
|
+
// Two things this guards:
|
|
5
|
+
//
|
|
6
|
+
// 1. The esp32c6 ROM tinfl over-reads the gzip trailer. On the chip the tinfl
|
|
7
|
+
// implementation comes from the mask ROM (an old miniz) which, at the end of
|
|
8
|
+
// the deflate stream, draws the 8-byte gzip trailer into its bit-buffer
|
|
9
|
+
// lookahead and reports it consumed. Code that recovers the trailer from
|
|
10
|
+
// tinfl's *leftover input* then sees < 8 bytes and rejects a good build.
|
|
11
|
+
// rom_tinfl_decompress() below models that behaviour; the real unpack_tgz
|
|
12
|
+
// must still succeed because it reads the trailer from the file's last 8
|
|
13
|
+
// bytes (a rolling tail). Revert to buffer-based trailer reading and this
|
|
14
|
+
// fails.
|
|
15
|
+
//
|
|
16
|
+
// 2. Streaming gunzip -> untar must be byte-identical to the archive. We unpack
|
|
17
|
+
// into a dir and run.sh diffs it against `tar -xzf`.
|
|
18
|
+
#include <cstdint>
|
|
19
|
+
#include <cstdio>
|
|
20
|
+
#include <cstdlib>
|
|
21
|
+
#include <cstring>
|
|
22
|
+
|
|
23
|
+
#define MIK_OTA_HOST_TEST 1
|
|
24
|
+
#include "miniz.h"
|
|
25
|
+
|
|
26
|
+
// Non-zero after a run means the modelled over-read actually fired (so the test
|
|
27
|
+
// isn't passing vacuously).
|
|
28
|
+
static size_t g_rom_ate = 0;
|
|
29
|
+
|
|
30
|
+
static tinfl_status rom_tinfl_decompress(tinfl_decompressor* r, const mz_uint8* pin,
|
|
31
|
+
size_t* pin_size, mz_uint8* pout_start,
|
|
32
|
+
mz_uint8* pout_next, size_t* pout_size,
|
|
33
|
+
const mz_uint32 flags) {
|
|
34
|
+
size_t avail_before = *pin_size;
|
|
35
|
+
tinfl_status st = tinfl_decompress(r, pin, pin_size, pout_start, pout_next, pout_size, flags);
|
|
36
|
+
if (st == TINFL_STATUS_DONE) {
|
|
37
|
+
size_t leftover = avail_before - *pin_size; // unconsumed input still in buffer
|
|
38
|
+
size_t eat = leftover < 4 ? leftover : 4; // ROM swallows a few trailer bytes
|
|
39
|
+
*pin_size += eat;
|
|
40
|
+
g_rom_ate += eat;
|
|
41
|
+
}
|
|
42
|
+
return st;
|
|
43
|
+
}
|
|
44
|
+
// From here on, unpack_tgz()'s tinfl_decompress(...) call hits the shim.
|
|
45
|
+
#define tinfl_decompress rom_tinfl_decompress
|
|
46
|
+
|
|
47
|
+
#ifndef MIK_OTA_SRC
|
|
48
|
+
#define MIK_OTA_SRC "../../mik_ota.cpp"
|
|
49
|
+
#endif
|
|
50
|
+
#include MIK_OTA_SRC // brings unpack_tgz/sha256_file into this TU
|
|
51
|
+
|
|
52
|
+
static int cmd_unpack(const char* build, const char* dest, bool expect_ok) {
|
|
53
|
+
const char* err = "(none)";
|
|
54
|
+
OtaErr kind = OtaErr::kTransient;
|
|
55
|
+
bool ok = unpack_tgz(build, dest, &err, &kind);
|
|
56
|
+
printf("unpack_tgz(%s) -> %s (err=%s kind=%s) rom_shim_ate=%zu\n", build,
|
|
57
|
+
ok ? "OK" : "FAIL", err, err_kind(kind), g_rom_ate);
|
|
58
|
+
if (expect_ok) return ok ? 0 : 1;
|
|
59
|
+
return ok ? 1 : 0; // expect-fail: rejection is success
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static int count_open_fds() {
|
|
63
|
+
DIR* d = opendir("/dev/fd");
|
|
64
|
+
if (!d) return -1;
|
|
65
|
+
int n = 0;
|
|
66
|
+
while (readdir(d)) n++;
|
|
67
|
+
closedir(d);
|
|
68
|
+
return n;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Regression: a failed unpack must not strand the open member handle. Only
|
|
72
|
+
// untar_finish closes Untar::cur, and it is skipped when the unpack already
|
|
73
|
+
// failed — so without an unconditional close, every aborted attempt leaks one
|
|
74
|
+
// FILE. On device that is an lfs_file_t plus its caches on a ~200 KB heap,
|
|
75
|
+
// pointing into a tree install_build is about to rmtree. Needs a fixture that
|
|
76
|
+
// fails *mid-member*: the corrupt.tgz fixture dies before any file is opened.
|
|
77
|
+
static int cmd_leak(const char* build, const char* dest) {
|
|
78
|
+
const char* err = "(none)";
|
|
79
|
+
OtaErr kind = OtaErr::kTransient;
|
|
80
|
+
if (unpack_tgz(build, dest, &err, &kind)) {
|
|
81
|
+
printf("fixture unpacked cleanly — it must fail mid-member to test this\n");
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
int before = count_open_fds();
|
|
85
|
+
for (int i = 0; i < 5; i++) unpack_tgz(build, dest, &err, &kind);
|
|
86
|
+
int after = count_open_fds();
|
|
87
|
+
printf("failed mid-member unpack (err=%s); open fds before=%d after=%d\n", err, before, after);
|
|
88
|
+
if (before < 0 || after < 0) {
|
|
89
|
+
printf(" (cannot enumerate /dev/fd on this host — check skipped)\n");
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
return after > before ? 1 : 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static int cmd_sha256(const char* file) {
|
|
96
|
+
char hex[65];
|
|
97
|
+
if (!sha256_file(file, hex)) {
|
|
98
|
+
fprintf(stderr, "sha256_file: cannot read %s\n", file);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
printf("%s\n", hex);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
int main(int argc, char** argv) {
|
|
106
|
+
if (argc >= 4 && strcmp(argv[1], "unpack") == 0) return cmd_unpack(argv[2], argv[3], true);
|
|
107
|
+
if (argc >= 4 && strcmp(argv[1], "expect-fail") == 0) return cmd_unpack(argv[2], argv[3], false);
|
|
108
|
+
if (argc >= 4 && strcmp(argv[1], "leak") == 0) return cmd_leak(argv[2], argv[3]);
|
|
109
|
+
if (argc >= 3 && strcmp(argv[1], "sha256") == 0) return cmd_sha256(argv[2]);
|
|
110
|
+
fprintf(stderr,
|
|
111
|
+
"usage:\n"
|
|
112
|
+
" %s unpack <build.tgz> <destdir>\n"
|
|
113
|
+
" %s expect-fail <build.tgz> <destdir>\n"
|
|
114
|
+
" %s leak <build.tgz> <destdir>\n"
|
|
115
|
+
" %s sha256 <file>\n",
|
|
116
|
+
argv[0], argv[0], argv[0], argv[0]);
|
|
117
|
+
return 2;
|
|
118
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Host test for the pure OTA unpack path in ../../mik_ota.cpp. Compiles the REAL
|
|
3
|
+
# unpack_tgz()/sha256_file() on the host, runs them through a tinfl shim that
|
|
4
|
+
# models the esp32c6 ROM over-reading the gzip trailer, and asserts:
|
|
5
|
+
# - a real `tar -czf` build unpacks byte-identical to `tar -xzf`
|
|
6
|
+
# - the ROM over-read actually fires (test isn't vacuous)
|
|
7
|
+
# - truncated and corrupt builds are rejected (kind=corrupt)
|
|
8
|
+
# - a member that escapes dest_dir is rejected and writes nothing
|
|
9
|
+
# - sha256_file matches a known vector
|
|
10
|
+
#
|
|
11
|
+
# Needs: a C/C++ compiler, tar, gzip, and miniz (auto-fetched into .cache/, or
|
|
12
|
+
# set MINIZ_DIR to a dir containing miniz.c + miniz.h).
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# Stop macOS tar from injecting AppleDouble (._*) resource-fork entries, so the
|
|
16
|
+
# fixture archive matches what `mikro app pack` produces on any platform.
|
|
17
|
+
export COPYFILE_DISABLE=1
|
|
18
|
+
|
|
19
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
SRC="$DIR/../../mik_ota.cpp"
|
|
21
|
+
CACHE="${MINIZ_DIR:-$DIR/.cache}"
|
|
22
|
+
BUILD="$DIR/.build"
|
|
23
|
+
CXX="${CXX:-c++}"
|
|
24
|
+
CC="${CC:-cc}"
|
|
25
|
+
MINIZ_URL="https://github.com/richgel999/miniz/releases/download/3.0.2/miniz-3.0.2.zip"
|
|
26
|
+
# Pinned so a test that hardens decompression does not itself run an unverified
|
|
27
|
+
# decompression library. This is the digest of the 3.0.2 release asset as first
|
|
28
|
+
# fetched; re-pin deliberately when bumping the version.
|
|
29
|
+
MINIZ_SHA256="ada38db0b703a56d3dd6d57bf84a9c5d664921d870d8fea4db153979fb5332c5"
|
|
30
|
+
|
|
31
|
+
mkdir -p "$BUILD"
|
|
32
|
+
|
|
33
|
+
# ---- miniz ----
|
|
34
|
+
if [ ! -f "$CACHE/miniz.c" ] || [ ! -f "$CACHE/miniz.h" ]; then
|
|
35
|
+
echo "miniz not found in $CACHE — fetching $MINIZ_URL"
|
|
36
|
+
mkdir -p "$CACHE"
|
|
37
|
+
curl -sSL --max-time 60 -o "$CACHE/miniz.zip" "$MINIZ_URL"
|
|
38
|
+
GOT_ZIP="$(shasum -a 256 "$CACHE/miniz.zip" | cut -d' ' -f1)"
|
|
39
|
+
if [ "$GOT_ZIP" != "$MINIZ_SHA256" ]; then
|
|
40
|
+
rm -f "$CACHE/miniz.zip"
|
|
41
|
+
echo "FAIL: miniz checksum mismatch (got $GOT_ZIP, want $MINIZ_SHA256)"; exit 1
|
|
42
|
+
fi
|
|
43
|
+
(cd "$CACHE" && unzip -o miniz.zip >/dev/null)
|
|
44
|
+
[ -f "$CACHE/miniz.c" ] || { echo "FAIL: could not obtain miniz (set MINIZ_DIR)"; exit 1; }
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# miniz is C — build it once as a C object (it isn't valid C++), then link.
|
|
48
|
+
"$CC" -O2 -I"$CACHE" -c "$CACHE/miniz.c" -o "$BUILD/miniz.o"
|
|
49
|
+
"$CXX" -std=c++17 -O2 -Wall -Wno-unused-function -Wno-unneeded-internal-declaration \
|
|
50
|
+
-I"$CACHE" "$DIR/gunzip_host_test.cpp" "$BUILD/miniz.o" -o "$BUILD/test"
|
|
51
|
+
|
|
52
|
+
TEST="$BUILD/test"
|
|
53
|
+
|
|
54
|
+
# ---- fixtures, packed exactly like `mikro app pack` (tar -C build app) ----
|
|
55
|
+
make_fixture() { # <name> <payload-bytes>
|
|
56
|
+
rm -rf "$BUILD/src_$1" "$BUILD/ref_$1" "$BUILD/out_$1"
|
|
57
|
+
mkdir -p "$BUILD/src_$1/app/nested"
|
|
58
|
+
head -c "$2" /dev/urandom | base64 > "$BUILD/src_$1/app/blob.txt"
|
|
59
|
+
printf 'export const x = 42\n' > "$BUILD/src_$1/app/index.js"
|
|
60
|
+
printf 'nested\n' > "$BUILD/src_$1/app/nested/n.txt"
|
|
61
|
+
tar -czf "$BUILD/$1.tgz" -C "$BUILD/src_$1" app
|
|
62
|
+
mkdir -p "$BUILD/ref_$1"
|
|
63
|
+
tar -xzf "$BUILD/$1.tgz" -C "$BUILD/ref_$1" # reference extraction
|
|
64
|
+
}
|
|
65
|
+
# small: compresses to one read so the trailer sits in tinfl's final buffer —
|
|
66
|
+
# the over-read definitely fires.
|
|
67
|
+
make_fixture small 256
|
|
68
|
+
# large: multiple 4 KiB reads, exercising the window-wrapping decode path and
|
|
69
|
+
# the 512-byte tar-block reassembly across inflate-output boundaries.
|
|
70
|
+
make_fixture large 60000
|
|
71
|
+
|
|
72
|
+
run_unpack() { # <name> <require-overread:0|1>
|
|
73
|
+
local name="$1" require="$2"
|
|
74
|
+
echo "== $name fixture ($(wc -c < "$BUILD/$name.tgz" | tr -d ' ') byte .tgz) =="
|
|
75
|
+
local out
|
|
76
|
+
out="$("$TEST" unpack "$BUILD/$name.tgz" "$BUILD/out_$name")" || {
|
|
77
|
+
echo " $out"; echo "FAIL: unpack_tgz rejected a valid build under the ROM shim"; exit 1; }
|
|
78
|
+
echo " $out"
|
|
79
|
+
diff -r "$BUILD/out_$name/app" "$BUILD/ref_$name/app" || {
|
|
80
|
+
echo "FAIL: streaming unpack differs from tar -xzf"; exit 1; }
|
|
81
|
+
if [ "$require" = 1 ] && ! printf '%s' "$out" | grep -qE 'rom_shim_ate=[1-9]'; then
|
|
82
|
+
echo "FAIL: the ROM over-read never fired — test would be vacuous"; exit 1
|
|
83
|
+
fi
|
|
84
|
+
echo " output matches tar -xzf (byte-identical) ✓"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
run_unpack small 1
|
|
88
|
+
run_unpack large 0
|
|
89
|
+
|
|
90
|
+
# ---- rejection cases ----
|
|
91
|
+
echo "== corrupt/truncated rejection =="
|
|
92
|
+
# Truncated: drop the last 16 bytes (trailer + tail of deflate).
|
|
93
|
+
head -c "$(( $(wc -c < "$BUILD/small.tgz") - 16 ))" "$BUILD/small.tgz" > "$BUILD/trunc.tgz"
|
|
94
|
+
"$TEST" expect-fail "$BUILD/trunc.tgz" "$BUILD/out_trunc" >/dev/null && echo " truncated rejected ✓" \
|
|
95
|
+
|| { echo "FAIL: truncated build was accepted"; exit 1; }
|
|
96
|
+
# Corrupt: flip a byte in the deflate body.
|
|
97
|
+
cp "$BUILD/large.tgz" "$BUILD/corrupt.tgz"
|
|
98
|
+
printf '\xff' | dd of="$BUILD/corrupt.tgz" bs=1 seek=64 count=1 conv=notrunc 2>/dev/null
|
|
99
|
+
"$TEST" expect-fail "$BUILD/corrupt.tgz" "$BUILD/out_corrupt" >/dev/null && echo " corrupt rejected ✓" \
|
|
100
|
+
|| { echo "FAIL: corrupt build was accepted"; exit 1; }
|
|
101
|
+
# Not gzip at all.
|
|
102
|
+
head -c 200 /dev/urandom > "$BUILD/garbage.tgz"
|
|
103
|
+
"$TEST" expect-fail "$BUILD/garbage.tgz" "$BUILD/out_garbage" >/dev/null && echo " non-gzip rejected ✓" \
|
|
104
|
+
|| { echo "FAIL: non-gzip build was accepted"; exit 1; }
|
|
105
|
+
|
|
106
|
+
# ---- no leaked handle on a mid-member failure ----
|
|
107
|
+
# Half a .tgz: enough deflate output to open app/blob.txt, then the input runs
|
|
108
|
+
# out while it is still open. That is the shape that strands the handle; the
|
|
109
|
+
# corrupt.tgz above fails at byte 64, before any member exists.
|
|
110
|
+
echo "== no fd leak on mid-member failure =="
|
|
111
|
+
head -c "$(( $(wc -c < "$BUILD/large.tgz") / 2 ))" "$BUILD/large.tgz" > "$BUILD/midtrunc.tgz"
|
|
112
|
+
LEAK_OUT="$("$TEST" leak "$BUILD/midtrunc.tgz" "$BUILD/out_midtrunc")" || {
|
|
113
|
+
echo " $LEAK_OUT"; echo "FAIL: aborted unpack leaks the open member handle"; exit 1; }
|
|
114
|
+
echo " $LEAK_OUT"
|
|
115
|
+
echo " aborted unpack releases the member handle ✓"
|
|
116
|
+
|
|
117
|
+
# ---- path traversal rejection ----
|
|
118
|
+
# The checksum is no defence here: it ships in the same offer as the URL, so
|
|
119
|
+
# whoever serves the offer controls both. A member that escapes dest_dir would
|
|
120
|
+
# land on the live app or the rollback baseline during boot reconcile.
|
|
121
|
+
echo "== path traversal rejection =="
|
|
122
|
+
rm -rf "$BUILD/src_evil" "$BUILD/out_evil" "$BUILD/zz.txt"
|
|
123
|
+
mkdir -p "$BUILD/src_evil/app"
|
|
124
|
+
printf 'pwned\n' > "$BUILD/src_evil/app/zz.txt"
|
|
125
|
+
tar -cf "$BUILD/evil.tar" -C "$BUILD/src_evil" app
|
|
126
|
+
# Rewrite the member name in place: `app/zz.txt` -> `..//zz.txt`, same length so
|
|
127
|
+
# every other header field (including the size) stays valid. tar itself refuses
|
|
128
|
+
# to create such a member, hence the patch. The header checksum is left stale,
|
|
129
|
+
# which is fine: untar_block doesn't verify it, and if it ever does this test
|
|
130
|
+
# will fail loudly rather than silently stop testing traversal.
|
|
131
|
+
OFF="$(grep -abo 'app/zz.txt' "$BUILD/evil.tar" | head -1 | cut -d: -f1)"
|
|
132
|
+
[ -n "$OFF" ] || { echo "FAIL: could not locate the member name to patch"; exit 1; }
|
|
133
|
+
printf '../' | dd of="$BUILD/evil.tar" bs=1 seek="$OFF" count=3 conv=notrunc 2>/dev/null
|
|
134
|
+
gzip -c "$BUILD/evil.tar" > "$BUILD/evil.tgz"
|
|
135
|
+
"$TEST" expect-fail "$BUILD/evil.tgz" "$BUILD/out_evil" >/dev/null && echo " ../ member rejected ✓" \
|
|
136
|
+
|| { echo "FAIL: a build with a ../ member was accepted"; exit 1; }
|
|
137
|
+
[ ! -e "$BUILD/zz.txt" ] || { echo "FAIL: the ../ member escaped dest_dir"; exit 1; }
|
|
138
|
+
echo " nothing written outside dest_dir ✓"
|
|
139
|
+
|
|
140
|
+
# ---- deep nesting rejection ----
|
|
141
|
+
# Every level created here is a level rmtree recurses over during boot reconcile,
|
|
142
|
+
# where a stack overflow reboots into the same call and never clears the tree.
|
|
143
|
+
# `a/` twice per level fits ~49 levels in the 100-byte name field, so the cap is
|
|
144
|
+
# what keeps a hostile build from bricking the device rather than just failing.
|
|
145
|
+
echo "== deep nesting rejection =="
|
|
146
|
+
rm -rf "$BUILD/src_deep" "$BUILD/out_deep"
|
|
147
|
+
DEEP="$BUILD/src_deep/app"; for _ in $(seq 1 12); do DEEP="$DEEP/a"; done
|
|
148
|
+
mkdir -p "$DEEP"
|
|
149
|
+
printf 'deep\n' > "$DEEP/x.txt"
|
|
150
|
+
tar -czf "$BUILD/deep.tgz" -C "$BUILD/src_deep" app
|
|
151
|
+
"$TEST" expect-fail "$BUILD/deep.tgz" "$BUILD/out_deep" >/dev/null && echo " deep member rejected ✓" \
|
|
152
|
+
|| { echo "FAIL: a build nested past the depth cap was accepted"; exit 1; }
|
|
153
|
+
# A shallow build must still unpack, or the cap has just broken every real build.
|
|
154
|
+
rm -rf "$BUILD/src_ok" "$BUILD/out_ok"
|
|
155
|
+
mkdir -p "$BUILD/src_ok/app/a/b"
|
|
156
|
+
printf 'ok\n' > "$BUILD/src_ok/app/a/b/x.txt"
|
|
157
|
+
tar -czf "$BUILD/okdepth.tgz" -C "$BUILD/src_ok" app
|
|
158
|
+
"$TEST" unpack "$BUILD/okdepth.tgz" "$BUILD/out_ok" >/dev/null \
|
|
159
|
+
&& [ -f "$BUILD/out_ok/app/a/b/x.txt" ] && echo " ordinary nesting still accepted ✓" \
|
|
160
|
+
|| { echo "FAIL: the depth cap rejected an ordinary build"; exit 1; }
|
|
161
|
+
|
|
162
|
+
# ---- SHA-256 known vector ----
|
|
163
|
+
echo "== sha256 vector =="
|
|
164
|
+
# echo -n "abc" | sha256sum -> ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
|
|
165
|
+
printf 'abc' > "$BUILD/abc.bin"
|
|
166
|
+
GOT="$("$TEST" sha256 "$BUILD/abc.bin")"
|
|
167
|
+
WANT="ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
|
|
168
|
+
[ "$GOT" = "$WANT" ] && echo " sha256('abc') = $GOT ✓" \
|
|
169
|
+
|| { echo "FAIL: sha256 mismatch: got $GOT want $WANT"; exit 1; }
|
|
170
|
+
|
|
171
|
+
echo
|
|
172
|
+
echo "PASS: unpack_tgz unpacks valid builds byte-identical (even when tinfl eats the"
|
|
173
|
+
echo " gzip trailer), rejects corrupt/truncated builds and path traversal, and"
|
|
174
|
+
echo " SHA-256 is correct."
|
|
@@ -16,7 +16,7 @@ static void setup() {
|
|
|
16
16
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
17
17
|
|
|
18
18
|
static JSValue eval_module(const char* code) {
|
|
19
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
19
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
20
20
|
if (!JS_IsException(ret)) {
|
|
21
21
|
JS_FreeValue(ctx, ret);
|
|
22
22
|
mik__execute_jobs(ctx);
|
|
@@ -17,7 +17,7 @@ static void setup() {
|
|
|
17
17
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
18
18
|
|
|
19
19
|
static JSValue eval_module(const char* code) {
|
|
20
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
20
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
21
21
|
if (!JS_IsException(ret)) {
|
|
22
22
|
JS_FreeValue(ctx, ret);
|
|
23
23
|
mik__execute_jobs(ctx);
|
|
@@ -20,7 +20,7 @@ static void setup() {
|
|
|
20
20
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
21
21
|
|
|
22
22
|
static JSValue eval_module(const char* code) {
|
|
23
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
23
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
24
24
|
if (!JS_IsException(ret)) {
|
|
25
25
|
JS_FreeValue(ctx, ret);
|
|
26
26
|
mik__execute_jobs(ctx);
|
|
@@ -7,7 +7,7 @@ static MIKRuntime* rt;
|
|
|
7
7
|
static JSContext* ctx;
|
|
8
8
|
|
|
9
9
|
static JSValue eval_module(const char* code) {
|
|
10
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
10
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
11
11
|
if (!JS_IsException(ret)) {
|
|
12
12
|
JS_FreeValue(ctx, ret);
|
|
13
13
|
mik__execute_jobs(ctx);
|
|
@@ -7,7 +7,7 @@ static MIKRuntime* rt;
|
|
|
7
7
|
static JSContext* ctx;
|
|
8
8
|
|
|
9
9
|
static JSValue eval_module(const char* code) {
|
|
10
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
10
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
11
11
|
if (!JS_IsException(ret)) {
|
|
12
12
|
JS_FreeValue(ctx, ret);
|
|
13
13
|
mik__execute_jobs(ctx);
|
|
@@ -19,7 +19,7 @@ static void setup() {
|
|
|
19
19
|
static void teardown() { MIK_FreeRuntime(rt); }
|
|
20
20
|
|
|
21
21
|
static JSValue eval_module(const char* code) {
|
|
22
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
22
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
23
23
|
if (!JS_IsException(ret)) {
|
|
24
24
|
JS_FreeValue(ctx, ret);
|
|
25
25
|
mik__execute_jobs(ctx);
|
|
@@ -27,11 +27,11 @@ static JSValue eval_module(const char* code) {
|
|
|
27
27
|
return ret;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
TEST_CASE("
|
|
30
|
+
TEST_CASE("mikro/udp module is importable", "[modules]") {
|
|
31
31
|
setup();
|
|
32
32
|
|
|
33
33
|
JSValue ret = eval_module(R"(
|
|
34
|
-
import {bind} from "
|
|
34
|
+
import {bind} from "mikro/udp";
|
|
35
35
|
globalThis.__hasBind = typeof bind === "function";
|
|
36
36
|
)");
|
|
37
37
|
TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
|
|
@@ -45,13 +45,13 @@ TEST_CASE("mikrojs/udp module is importable", "[modules]") {
|
|
|
45
45
|
teardown();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
TEST_CASE("
|
|
48
|
+
TEST_CASE("mikro/udp bind and close work on device", "[modules]") {
|
|
49
49
|
setup();
|
|
50
50
|
|
|
51
51
|
/* Drives a bind+close, holds the socket on globalThis to keep it alive
|
|
52
52
|
* past the IIFE, then verifies port assignment and idempotent close. */
|
|
53
53
|
JSValue ret = eval_module(R"(
|
|
54
|
-
import {bind} from "
|
|
54
|
+
import {bind} from "mikro/udp";
|
|
55
55
|
globalThis.__phase = "init";
|
|
56
56
|
(async () => {
|
|
57
57
|
const r = await bind({port: 0, family: 'ipv4'});
|
|
@@ -37,7 +37,7 @@ static void setup() {
|
|
|
37
37
|
/* WiFi module is self-registered and lazily initialized.
|
|
38
38
|
* The import below triggers init + loop consumer registration. */
|
|
39
39
|
const char* init_code = "import { Wifi } from \"native:mikro/wifi\"; new Wifi();";
|
|
40
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
40
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test-setup", init_code, strlen(init_code));
|
|
41
41
|
if (!JS_IsException(ret)) {
|
|
42
42
|
JS_FreeValue(ctx, ret);
|
|
43
43
|
mik__execute_jobs(ctx);
|
|
@@ -56,7 +56,7 @@ static void teardown() {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
static JSValue eval_module(const char* code) {
|
|
59
|
-
JSValue ret = MIK_EvalModuleContent(ctx, "
|
|
59
|
+
JSValue ret = MIK_EvalModuleContent(ctx, "mikro/test", code, strlen(code));
|
|
60
60
|
if (!JS_IsException(ret)) {
|
|
61
61
|
JS_FreeValue(ctx, ret);
|
|
62
62
|
mik__execute_jobs(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/firmware",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-next.20260722220623",
|
|
4
4
|
"description": "Mikro.js ESP32 firmware: ESP-IDF component, build tools, and project template",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp-idf",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"esbuild": "^0.28.0",
|
|
50
|
-
"@mikrojs/
|
|
51
|
-
"@mikrojs/
|
|
50
|
+
"@mikrojs/native": "0.17.0-next.20260722220623+e03f4a6",
|
|
51
|
+
"@mikrojs/quickjs": "0.17.0-next.20260722220623+e03f4a6"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=24.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|