From 7312dd0ca0f8d55b473d61f867311dd83bcfea1b Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 17 Aug 2026 00:41:00 +0200 Subject: [PATCH] docs(calibration.js): explain why one UTF-8 repair pass suffices in JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TextEncoder always emits well-formed UTF-8, so truncation can strand at most a lead byte plus three continuations — one repair pass covers it. The C++ hub needs a loop because its input is raw bytes off the wire. Also drops a dead variable from the byte-boundary test. Co-Authored-By: Claude Opus 4.6 --- Client/udpstreamer/static/calibration.js | 7 +++++++ Client/udpstreamer/test/calibration.test.js | 7 +------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Client/udpstreamer/static/calibration.js b/Client/udpstreamer/static/calibration.js index 238dbb2..1c3c557 100644 --- a/Client/udpstreamer/static/calibration.js +++ b/Client/udpstreamer/static/calibration.js @@ -53,6 +53,13 @@ // Drop continuation bytes (10xxxxxx) from the tail until the last byte // is either an ASCII byte (< 0x80) or a lead byte whose sequence is // complete (i.e. all expected continuation bytes are present). + // + // A single pass suffices here, where the C++ needs a loop. The C++ input + // is a raw const char* straight off the wire and may hold arbitrary bytes; + // `bytes` here comes from TextEncoder, which always emits well-formed + // UTF-8 (unpaired surrogates become U+FFFD = EF BF BD, and no byte is ever + // >= 0xF8). Truncating well-formed UTF-8 can therefore strand at most a + // lead byte plus three continuation bytes, which one pass fully repairs. var b = bytes.slice(0, MAX_UNIT_LEN); var len = b.length; // Walk back over continuation bytes (up to 3) to find the lead byte of diff --git a/Client/udpstreamer/test/calibration.test.js b/Client/udpstreamer/test/calibration.test.js index 6315b1c..9a108d7 100644 --- a/Client/udpstreamer/test/calibration.test.js +++ b/Client/udpstreamer/test/calibration.test.js @@ -69,12 +69,7 @@ test('normaliseCal cuts a mid-rune byte boundary back to the last complete rune' }); test('normaliseCal leaves a unit that is exactly 16 bytes ending on a complete multi-byte rune untouched', () => { - // 7 ASCII chars + 'Ω' (2 bytes) + 6 ASCII chars + 'µ' (2 bytes) - 1 = let's - // build exactly 16 bytes ending on a complete 2-byte rune. - // 7 × 'a' (7 bytes) + 'Ω' (2 bytes) + 5 × 'b' (5 bytes) + '°' (2 bytes) = - // 7 + 2 + 5 + 2 = 16 bytes. - const exact = 'aaaaaaаbbbbb°'; // avoid confusion: use simple construction below - // Simple: 'abcdefgΩhijklµ' → 7 + 2 + 5 + 2 = 16 bytes + // 'abcdefgΩhijklµ' → 7 ASCII + 'Ω' (2 bytes) + 5 ASCII + 'µ' (2 bytes) = 16 bytes const u = 'abcdefgΩhijklµ'; assert.strictEqual(new TextEncoder().encode(u).length, 16); assert.strictEqual(C.normaliseCal({source: 'w', signal: 's', unit: u}).unit, u);