@@ -7,11 +7,11 @@ native Swift).
77
88---
99
10- > ** Implementation status (M0–M2 + app integration done) .** The runtime, host engine, native
11- > parsers, ABI, a bundled reference plugin, tests, and full Store/UI integration are implemented and
12- > building. See ` Sources/BluetoothExplorerPluginEngine/ ` , ` Documentation/PluginABI.md ` , and the
13- > M0-driven pin change below. Remaining: guest SDK repo (Rust PDK + ` bleplug ` CLI), user-import UX
14- > (M4), and the Android build verification ( still a gate — see §9).
10+ > ** Implementation status.** The runtime, host engine, native parsers, ABI, the Embedded Swift guest
11+ > SDK, a bundled Embedded-Swift reference plugin, tests, and full Store/UI integration are
12+ > implemented and building. See ` Sources/BluetoothExplorerPluginEngine/ ` , ` PluginSDK/ ` , and
13+ > ` Documentation/PluginABI.md ` . Remaining: user-import UX (M4) and Android build verification
14+ > (still a gate — see §9).
1515
1616## 1. Runtime decision: WasmKit 0.3.x (app floor raised to iOS 18 / macOS 15)
1717
@@ -78,12 +78,9 @@ Extism (no Swift host SDK; wasmtime-based — copy its manifest/ABI ideas only).
7878│ PluginEngine actor → WasmKit (per-plugin Store/Instance, │
7979│ ResourceLimiter, deadline watchdog, quarantine) │
8080└─────────────────────────────────────────────────────────────────┘
81- ble-plugin-sdk (separate repo)
82- spec/ABI.md + conformance vectors (normative, frozen first)
83- rust/ble-plugin-pdk (reference PDK, wasm32-unknown-unknown)
84- swift/BLEPluginSDK (Embedded Swift, wasip1 reactor, template)
85- bleplug CLI (new / run / pack / conformance)
86- examples/ (ibeacon, heart-rate, battery-level)
81+ PluginSDK/ (in-repo, never part of the app build)
82+ BLEPluginSDK/ Embedded Swift guest SDK (envelope, PayloadReader, Fields/CBOR)
83+ Examples/ plugin projects; `make install` builds + installs the .wasm
8784```
8885
8986Key layering choice: the ` ParserPlugin ` protocol + registry ship ** before** any wasm code,
@@ -92,16 +89,19 @@ with the existing native decoders (`AppleBeacon` iBeacon parser, the
9289gets the decoded-fields UI and routing in a fully shippable milestone with zero wasm risk;
9390WasmKit plugins then join the same registry.
9491
95- ## 4. Normative ABI v1 — freeze first, in ` spec/ABI .md`
92+ ## 4. Normative ABI v1 — see ` Documentation/PluginABI .md`
9693
97- The three drafts produced during design diverged on envelope encoding, UUID byte order, and
98- export names. ** One byte-level spec with test vectors must be written and frozen before any
99- host or SDK code. ** The decisions:
94+ The design drafts diverged on envelope encoding, UUID byte order, and export names, so one
95+ byte-level spec was frozen before the host and SDK were written. It lives in
96+ ` Documentation/PluginABI.md ` ; the summary below is informative.
10097
101- ** Module forms accepted:** import-free core module (` wasm32-unknown-unknown ` ) — canonical;
102- or ` wasm32-unknown-wasip1 ` ** reactor** (for Embedded Swift; host links WASI with * no*
103- preopens/env/args — allocator plumbing only; host calls ` _initialize ` once). Any other
104- import ⇒ load rejection. No host-callback imports in v1 (one-directional trust boundary).
98+ ** Module forms accepted:** ` wasm32-unknown-wasip1 ` ** reactor** (what the Embedded Swift SDK emits;
99+ the host calls ` _initialize ` once), and import-free core modules. Imports are permitted ** only** from
100+ ` wasi_snapshot_preview1 ` ; any other import module is rejected at load. Those WASI imports are
101+ satisfied by a minimal in-house shim (§5.4) rather than a full WASI implementation: ` random_get `
102+ returns real randomness because the Embedded Swift runtime requires it, and everything else is
103+ stubbed to success. Plugins get no filesystem, network, environment or clock access, and there are
104+ no host-callback imports (the trust boundary stays one-directional).
105105
106106** Exports:**
107107
@@ -122,7 +122,7 @@ Return `u64 = (result_ptr << 32) | result_len`; `0` = "not mine / no parse" (not
122122trap = plugin failure. Only i32/i64 cross the boundary.
123123
124124** Input envelope** — fixed little-endian binary header + payload (trivially readable from
125- ` no_std ` Rust and Embedded Swift, no decoder dependency):
125+ Embedded Swift without a decoder dependency):
126126
127127```
128128offset size field
@@ -333,37 +333,35 @@ Precondition: give `AttributeValue` a proper unique `id` (UUID) — it is curren
333333 imports + memory caps + deadline + quarantine. No signing in v1; decide the authenticity
334334 story before any non-local distribution (and see §2 — no marketplace, ever).
335335
336- ## 6. Guest SDKs (` ble-plugin-sdk ` , separate repo)
337-
338- - ** ` spec/ABI.md ` + ` spec/vectors/*.json ` ** — the normative spec (§4) and conformance
339- vectors (hex input → expected fields). Everything else conforms to this.
340- - ** Rust reference PDK** (` ble-plugin-pdk ` , ` no_std ` + alloc, ` wasm32-unknown-unknown ` ):
341- author writes one pure function over ` AdvertisementInput ` /` Fields ` ; a ` ble_plugin! ` macro
342- emits the exports, allocator, and CBOR (minicbor). ` opt-level="z" ` + lto + ` panic="abort" `
343- + ` wasm-opt -Oz ` → 1–30 KB. This is the always-working path.
344- - ** Embedded Swift SDK** (` BLEPluginSDK ` , wasip1 reactor, ` swift-6.3_wasm-embedded ` SDK,
345- pinned): same author experience (` PayloadReader ` , ` Fields ` builder, hand-rolled ~ 100-line
346- CBOR emitter — Embedded-safe: no existentials, no Codable, no Foundation). Because
347- ` @_expose(wasm) ` symbols are dropped from dependency modules (swiftlang/swift #77812 ), the
348- export shims live in the ** author's module via template** , not in the SDK library.
349- Flagship but experimental — docs must not promise parity with Rust. Sizes: tens–hundreds
350- of KB. AssemblyScript documented; TinyGo tolerated; full non-embedded Swift explicitly
351- unsupported (9–50 MB binaries).
352- - ** ` bleplug ` CLI** : ` new ` (scaffold), ` run <plugin.wasm> --adv --company 004C --hex … `
353- (instant feedback under real WasmKit, no app/device), ` pack ` (validate exports vs
354- manifest, sha256, emit pair), ` conformance ` (run vectors).
355- - ** Two-tier author testing** : the parse function is a plain function — unit-test it
356- natively with LLDB; only the thin shim needs wasm-level conformance runs.
357- - ** Reference plugins** (examples + bundled in the app):
358- 1 . ` battery-level.wasm ` (Rust, ~ 1 KB) — 0x2A19; the "hello world"; also exercises
359- coexistence with the native decoder for the same UUID.
360- 2 . ` ibeacon.wasm ` (Rust) — conformance twin of the native ` AppleBeacon ` parser; tests
361- diff their outputs byte-for-byte on the ` MockAdvertisement ` fixtures.
362- 3 . ` heart-rate.wasm ` (Embedded Swift) — 0x2A37 flags/8-16-bit BPM/RR intervals; fills a
363- real gap (` GATTHeartRateMeasurement ` doesn't exist in BluetoothGATT) and exercises
364- notifications live with any HR strap.
365- 4 . (Post-v1) Eddystone 0xFEAA — exercises the service-data route.
366- Built artifacts checked into the app repo; SDK-repo CI rebuilds and diffs hashes.
336+ ## 6. Guest SDK — Embedded Swift (` PluginSDK/ ` , implemented)
337+
338+ Plugins are authored in ** Embedded Swift** and compiled to wasm32 with the
339+ ` swift-<version>_wasm-embedded ` Swift SDK. This is the only supported guest toolchain: it keeps
340+ plugin authoring in the same language as the app, and the SDK is a normal SwiftPM package.
341+
342+ - ** ` PluginSDK/BLEPluginSDK ` ** — the guest SDK, Embedded-safe (no Foundation, no existentials, no
343+ ` Codable ` ): ` ParseInput ` /` UUIDBytes ` envelope decoding, an allocation-free ` PayloadReader `
344+ cursor, a ` Fields ` builder that emits the CBOR result incrementally, and ` PluginRuntime ` for
345+ guest allocation and the packed-` u64 ` return convention. Keys/labels/units are ` StaticString ` ,
346+ so they cost no allocation.
347+ - ** Author experience** — one pure function, ` (ParseInput) -> Fields? ` . Returning ` nil ` means "not
348+ mine" and is not an error. ` Exports.swift ` is copied boilerplate declaring the ABI exports; it
349+ lives in the author's module because ` @_expose(wasm) ` symbols are dropped when they come from a
350+ dependency module (swiftlang/swift #77812 ).
351+ - ** Build** — ` make install ` in an example: ` swift build --swift-sdk …_wasm-embedded ` , then
352+ ` wasm-opt -Oz ` , then copy into ` Sources/BluetoothExplorerPluginEngine/Plugins/ ` and refresh the
353+ manifest ` sha256 ` . Expect ~ 90–110 KB per plugin.
354+ - ** Verified end to end** : multi-module Embedded Swift (SDK as a separate package) links cleanly for
355+ wasm32; the resulting module exports exactly the ABI and imports only
356+ ` wasi_snapshot_preview1.random_get ` , which the host satisfies (§5.4).
357+ - ** Bundled reference plugin** : ` battery-level.wasm ` (GATT Battery Level ` 0x2A19 ` ), authored in
358+ Embedded Swift, tested for byte-parity against the native parser across values.
359+ - ** Testing** — because the parse function is a plain Swift function over plain structs, authors can
360+ unit-test it natively before it ever becomes wasm; the app's test suite additionally runs the
361+ built module under the real interpreter.
362+
363+ Full non-embedded Swift is not supported for plugins (9–50 MB binaries). Other guest languages are
364+ out of scope: the ABI is language-agnostic, but only the Embedded Swift path is maintained here.
367365
368366## 7. Testing
369367
@@ -405,12 +403,12 @@ MockCentral tests. App behaves identically-or-better with zero wasm.
405403
406404** M2 — Wasm execution (2–3 weeks, shippable).**
407405` PluginEngine ` actor, ABI v1 host side, MicroCBOR, resource limiter, deadline/quarantine,
408- memoization + CPU budget + kill switch. Bundled battery + iBeacon (Rust) plugins .
406+ memoization + CPU budget + kill switch. Bundled Embedded Swift battery-level plugin .
409407` PluginManager ` bundled discovery + enable/disable. Basic ` PluginsView ` (toggles).
410408Golden-file tests both platforms.
411409
412410** M3 — SDK + author tooling (2 weeks, parallelizable with M2 tail).**
413- ` ble-plugin-sdk ` repo: Rust PDK + macro, ` bleplug ` CLI, conformance harness , Embedded Swift
411+ ` PluginSDK/ ` : Embedded Swift guest SDK, example plugins, build/install tooling , Embedded Swift
414412template + pinned toolchain docs, heart-rate reference plugin, docs site page.
415413
416414** M4 — User-imported plugins + hardening (1.5–2 weeks).**
@@ -431,7 +429,7 @@ Total ≈ 8–11 weeks. M1 and M2 are each independently shippable.
431429| No fuel/interruption in WasmKit — runaway plugin = abandoned spinning thread | dedicated thread per plugin, quarantine on first timeout, memory caps, CPU budget + kill switch; WAMR if containment becomes hard requirement |
432430| App Store review of user-imported plugins (2.5.2 / 3.3.1(B)) | bundled-first rollout; import framed as same-purpose interpreted content; no marketplace ever |
433431| ` .copy ` binary resources through skipstone into the APK unproven | M0 item 5; base64-embed escape hatch |
434- | Embedded Swift toolchain churn (#77812 export-dropping, duplicate-symbol issues) | shims-in-template design, pinned SDK versions, Rust as reference PDK |
432+ | Embedded Swift toolchain churn (#77812 export-dropping, duplicate-symbol issues) | export shims live in the author's module by design; pin the wasm-embedded SDK version |
435433| Dense-environment scan load (dozens of devices × ~ 10 adv/s) at interpreter speed | synchronous fingerprint dedupe before Task spawn, manifest-only routing, memoization, CPU budget; profile on low-end Android |
436434| Hand-rolled CBOR in three places (host Swift, Embedded Swift, minicbor) | shared conformance vectors exercised on all three |
437435
0 commit comments