@mhome/app-facade-protocol 1.40.0 → 1.41.0
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/contract/pod-commissioning-v1.md +69 -31
- package/fixtures/host-provision.completed.json +1 -0
- package/fixtures/pod-commission.awaiting-authorization.json +1 -0
- package/fixtures/pod-commission.failed.json +1 -0
- package/fixtures/pod-commission.reprovision-completed.json +1 -0
- package/fixtures/pod-discovery.snapshot.json +2 -0
- package/manifest/app-facade.v1.json +1 -1
- package/manifest/host-provision-targets.v1.json +1 -1
- package/manifest/hub-targets.v1.json +1 -1
- package/manifest/pod-targets.v1.json +1 -1
- package/manifest/routing.v1.json +1 -1
- package/manifest/targets.v1.json +1 -1
- package/package.json +1 -1
- package/protocol.json +1 -1
- package/schema/person.v1.schema.json +1 -1
- package/schema/pod-commission.v1.schema.json +4 -0
- package/schema/pod-discovery.v1.schema.json +8 -3
|
@@ -6,10 +6,11 @@ first implementation, not a requirement. Commissioners are the native Clients:
|
|
|
6
6
|
desktop/CLI Clientd, Android and iOS. The shared UI and CLI never talk BLE; they
|
|
7
7
|
call the `/local/pod/*` targets of their native Client.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Network provisioning and activation are separate transactions. BLE provisions
|
|
10
|
+
Wi-Fi; Ethernet and already-connected Wi-Fi skip it. Every initialization then
|
|
11
|
+
uses a direct native Client ↔ Pod LAN security-2 session. No Core relay is used.
|
|
12
|
+
The current credential API below is the existing personal Pod credential; this
|
|
13
|
+
transport change does not implement the separately planned Space Client issuer.
|
|
13
14
|
|
|
14
15
|
## 1. Device identity
|
|
15
16
|
|
|
@@ -47,7 +48,7 @@ credential invalidation, or inside a re-provision window. A commissioned pod doe
|
|
|
47
48
|
|
|
48
49
|
## 3. Secure session
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
Wi-Fi provisioning uses ESP protocomm over BLE GATT. Endpoints are GATT characteristics
|
|
51
52
|
named by their Characteristic User Description descriptor (`0x2901`). A
|
|
52
53
|
commissioner that cannot read descriptors falls back to fixed UUIDs: the service
|
|
53
54
|
UUID with bytes 2–3 replaced by `prov-ctrl` `0xFF4F`, `prov-scan` `0xFF50`,
|
|
@@ -58,7 +59,7 @@ custom endpoints in table order from `0xFF54` (`pod-info` `0xFF54`,
|
|
|
58
59
|
- Security: protocomm security 2 (SRP6a 3072-bit with SHA-512, then AES-256-GCM).
|
|
59
60
|
Username `meow`. Password: a 6-digit decimal code.
|
|
60
61
|
- Pods with a display generate a uniformly random code, precompute salt and
|
|
61
|
-
verifier at boot, and show
|
|
62
|
+
verifier at boot, and show it while connected over BLE or available for LAN initialization. A new
|
|
62
63
|
code is generated after a successful commissioning, after 5 failed handshakes,
|
|
63
64
|
or 10 minutes after it was first shown. Pods without a display are out of scope
|
|
64
65
|
for v1.
|
|
@@ -70,8 +71,8 @@ custom endpoints in table order from `0xFF54` (`pod-info` `0xFF54`,
|
|
|
70
71
|
`M` uses it as sent; `u` pads both to 384 bytes.
|
|
71
72
|
- The record nonce is 8 random bytes followed by a 32-bit big-endian counter
|
|
72
73
|
starting at 1, advanced after every encrypt and decrypt on both sides
|
|
73
|
-
(security 2 patch version 1; patch 0 keeps it fixed). Commissioners
|
|
74
|
-
patch
|
|
74
|
+
(security 2 patch version 1; patch 0 keeps it fixed). Commissioners require
|
|
75
|
+
patch version 1; fixed-nonce patch 0 is rejected. A lost or failed exchange leaves the two counters
|
|
75
76
|
unknown, so the commissioner abandons the secure session after any transport or
|
|
76
77
|
decryption failure. A new `SessionCmd0` restarts the handshake on the same link.
|
|
77
78
|
- While a device refuses handshakes after repeated wrong codes it clears the
|
|
@@ -81,8 +82,8 @@ custom endpoints in table order from `0xFF54` (`pod-info` `0xFF54`,
|
|
|
81
82
|
such as Insufficient Authorization), so after every rejected handshake the
|
|
82
83
|
commissioner reads `proto-ver` again and fails the session with `code_locked`
|
|
83
84
|
when either field is present. A pod session also fails with `code_locked` after
|
|
84
|
-
the fifth rejected code.
|
|
85
|
-
|
|
85
|
+
the fifth rejected code. Pods generate a fresh code after five rejected handshakes. Once network
|
|
86
|
+
configuration is saved, code rotation preserves that network.
|
|
86
87
|
- One BLE connection at a time; a second central receives a disconnect.
|
|
87
88
|
- Session deadline: 10 minutes from connect, or 2 minutes waiting for a
|
|
88
89
|
handshake. On expiry the pod discards all unpersisted state.
|
|
@@ -95,15 +96,17 @@ custom endpoints in table order from `0xFF54` (`pod-info` `0xFF54`,
|
|
|
95
96
|
| `prov-config` | yes | standard Wi-Fi set/apply/status |
|
|
96
97
|
| `prov-ctrl` | yes | standard reset after a failed join, before a retry |
|
|
97
98
|
| `pod-info` | yes | device information |
|
|
98
|
-
| `pod-credential` | yes | credential delivery
|
|
99
|
+
| `pod-credential` | yes | LAN credential delivery/status/finish; BLE accepts only reprovision status/finish |
|
|
99
100
|
|
|
100
|
-
Custom endpoint bodies are UTF-8 JSON, at most 480 bytes
|
|
101
|
+
Custom endpoint bodies are UTF-8 JSON, at most 480 bytes over BLE. LAN requests
|
|
102
|
+
and responses are bounded to 64 KiB including encryption overhead.
|
|
101
103
|
|
|
102
104
|
`pod-info` request `{}`, response:
|
|
103
105
|
|
|
104
106
|
```json
|
|
105
107
|
{
|
|
106
108
|
"protocol": 1,
|
|
109
|
+
"env": "prod",
|
|
107
110
|
"deviceId": "a0b1c2d3e4f5",
|
|
108
111
|
"model": "S1",
|
|
109
112
|
"platform": "esp32c5",
|
|
@@ -114,6 +117,9 @@ Custom endpoint bodies are UTF-8 JSON, at most 480 bytes in each direction.
|
|
|
114
117
|
}
|
|
115
118
|
```
|
|
116
119
|
|
|
120
|
+
`env` must match the native Client runtime (`prod`, `dev`, `e2e`).
|
|
121
|
+
`podId` is included when activation is in flight or durably completed, so a
|
|
122
|
+
new session can observe completion without issuing another credential.
|
|
117
123
|
`platform` is diagnostic only. `wifiConfigured` is true when the pod kept a
|
|
118
124
|
working network across logout; the commissioner then skips the Wi-Fi steps after
|
|
119
125
|
confirming connectivity with `prov-config` status. Inside a Wi-Fi change window
|
|
@@ -146,21 +152,52 @@ Activation error codes: `time_sync_failed`, `cloud_unreachable`,
|
|
|
146
152
|
`refresh_rejected`, `auth_rejected`, `storage_failed` (the final commit, or in a
|
|
147
153
|
Wi-Fi change window saving the new network, failed).
|
|
148
154
|
|
|
149
|
-
The
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
The Pod commits Wi-Fi immediately after a successful join, before any credential
|
|
156
|
+
issuance. Activation commits only identity/binding data. Failed activation,
|
|
157
|
+
disconnection, code expiry and reboot preserve the successfully saved network.
|
|
158
|
+
A failed join rolls back only the incomplete network change.
|
|
159
|
+
|
|
160
|
+
### LAN discovery and channel
|
|
161
|
+
|
|
162
|
+
- Service: `_meow-pod-init._tcp.local.` while network-ready and uninitialized.
|
|
163
|
+
TXT: full `deviceId`, `v=1`, `env`, optional `model`; SRV/A/AAAA locate the
|
|
164
|
+
endpoint. A failed or expired pairing attempt does not remove this service.
|
|
165
|
+
- The same service works on Ethernet and Wi-Fi. BLE supplies the full device ID
|
|
166
|
+
and identity over its secure session; the short display ID is never the match
|
|
167
|
+
key. Discovery data is untrusted and is checked against encrypted `pod-info`.
|
|
168
|
+
- HTTP POST `/{endpoint}` with `Content-Type: application/octet-stream` and a
|
|
169
|
+
fresh UUID `X-Meow-Session`. Allowed endpoints: `proto-ver`, `prov-session`,
|
|
170
|
+
`pod-info`, `pod-credential`. Wi-Fi endpoints are unavailable over LAN.
|
|
171
|
+
Clients connect to the resolved socket directly, with proxies/redirects off.
|
|
172
|
+
- Run a fresh Security 2 patch-1 handshake on LAN. After BLE, reuse only the
|
|
173
|
+
temporarily held code, never the BLE session key or nonce. Ethernet setup
|
|
174
|
+
requests the code in the App UI. Business messages are AES-GCM protected.
|
|
175
|
+
- Each IP session has separate crypto state. Devices cap simultaneous sessions
|
|
176
|
+
at four; pending handshakes expire after two minutes, sessions after ten.
|
|
177
|
+
An encrypted `pod-credential {"op":"close"}` releases that temporary session.
|
|
178
|
+
- Stop init advertisement on durable completion. The existing session has a
|
|
179
|
+
short finish window for confirmation; normal business disconnection never
|
|
180
|
+
returns a completed device to initialization.
|
|
153
181
|
|
|
154
182
|
## 4. Pod state machine
|
|
155
183
|
|
|
156
184
|
```text
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
no network -> BLE secure -> join -> save Wi-Fi -> network ready
|
|
186
|
+
Ethernet / saved Wi-Fi ------------------------> network ready
|
|
187
|
+
network ready -> advertise init -> LAN secure -> authorize -> activate -> save identity
|
|
188
|
+
join failure -> end attempt; start Wi-Fi setup again
|
|
189
|
+
activation failure -> end attempt; keep network and init advertisement
|
|
190
|
+
pairing expiry -> discard temporary sessions/code; keep network; accept a new code
|
|
162
191
|
```
|
|
163
192
|
|
|
193
|
+
The App reports `discovering_lan` and `connecting_lan` during BLE handoff.
|
|
194
|
+
Discovery snapshots distinguish `scanning` (BLE), `lanBrowsing`, optional
|
|
195
|
+
`lanError`, and candidate `transport` (`ble` or `lan`). LAN candidates carry the
|
|
196
|
+
full `deviceId`; Bluetooth denial never blocks them. The App retries initialization
|
|
197
|
+
by selecting the LAN candidate, possibly re-entering the current code. It first
|
|
198
|
+
reads actual device state: `commissioned` completes immediately, `activating`
|
|
199
|
+
waits for the existing attempt, and a failed attempt may issue afresh.
|
|
200
|
+
|
|
164
201
|
Runtime states after commit:
|
|
165
202
|
|
|
166
203
|
- `commissioned`: normal operation.
|
|
@@ -316,7 +353,7 @@ and unexpired pending pods, each
|
|
|
316
353
|
|
|
317
354
|
All targets are handled by the native Client and never forwarded. Payloads are
|
|
318
355
|
domain input JSON. Clients without a usable Bluetooth adapter report
|
|
319
|
-
`adapter.state` and reject commissioning with `bluetooth_unavailable
|
|
356
|
+
`adapter.state` and reject BLE commissioning with `bluetooth_unavailable`; LAN candidates remain usable.
|
|
320
357
|
|
|
321
358
|
A rejected request uses the facade error envelope
|
|
322
359
|
`{"error","message","details":{"reason"}}`: `error` is `BAD_REQUEST` for an
|
|
@@ -394,9 +431,10 @@ example `storage_failed`) in `detail`. UIs show such a session as changing the
|
|
|
394
431
|
pod's Wi-Fi rather than adding a pod.
|
|
395
432
|
|
|
396
433
|
Session states, in order: `connecting`, `awaiting_code`, `securing`,
|
|
397
|
-
`reading_info`, `awaiting_wifi`, `joining_wifi`, `
|
|
434
|
+
`reading_info`, `awaiting_wifi`, `joining_wifi`, `discovering_lan`,
|
|
435
|
+
`connecting_lan`, `awaiting_authorization`,
|
|
398
436
|
`issuing`, `delivering`, `activating`, `completed`; terminal `failed`,
|
|
399
|
-
`timed_out`. Wi-Fi states are skipped when the pod reports a
|
|
437
|
+
`timed_out`. LAN candidates skip all Wi-Fi states. Wi-Fi states are also skipped when the pod reports a
|
|
400
438
|
configured network and `prov-config` status reports it connected; while that
|
|
401
439
|
status is `connecting` the commissioner keeps polling, and only after a failure
|
|
402
440
|
does it reset Wi-Fi (`prov-ctrl`) and offer new credentials. The commissioner
|
|
@@ -407,10 +445,11 @@ scan runs waits for it. `revision` increases on every observable change.
|
|
|
407
445
|
`delivering` on it is `activateBefore` plus 30 seconds.
|
|
408
446
|
|
|
409
447
|
Only the device's durable `commissioned` response confirms successful setup.
|
|
410
|
-
Lost
|
|
448
|
+
Lost LAN delivery confirmation or activation status fails the attempt, even
|
|
411
449
|
when Lion has already activated its credential. Expiry fails or times out the
|
|
412
450
|
attempt. The commissioner does not recover interrupted setup through Lion's
|
|
413
|
-
credential status. The user
|
|
451
|
+
credential status. The user selects the network-ready device again, establishes
|
|
452
|
+
a fresh LAN session and reads its actual activation status.
|
|
414
453
|
|
|
415
454
|
A refused `deliver` fails with `delivery_failed` carrying the pod's `reason`
|
|
416
455
|
(`invalid_payload` or `wrong_state`) in `detail`. When the pod
|
|
@@ -423,14 +462,13 @@ app is in the background (Android foreground service of type
|
|
|
423
462
|
|
|
424
463
|
Session error codes: `bluetooth_unavailable`, `device_busy`, `disconnected`,
|
|
425
464
|
`code_rejected` (retry allowed), `code_locked`, `wifi_auth_failed`,
|
|
426
|
-
`wifi_not_found`, `wifi_failed`, `issue_failed`, `delivery_failed`,
|
|
465
|
+
`wifi_not_found`, `wifi_failed`, `lan_unavailable`, `device_mismatch`, `issue_failed`, `delivery_failed`,
|
|
427
466
|
`activation_failed` (with the pod's activation code, or `not_activated`, in
|
|
428
467
|
`detail`), `session_timeout`. `code_locked` is terminal: the device shows a new
|
|
429
468
|
code once it accepts handshakes again. `failed` and `timed_out` always carry `error`. A rejected
|
|
430
|
-
code returns to `awaiting_code` with `code_rejected`; a failed join
|
|
431
|
-
`
|
|
432
|
-
without revoking credentials.
|
|
433
|
-
activation outcome; unused pending cloud credentials expire automatically.
|
|
469
|
+
code returns to `awaiting_code` with `code_rejected`; a failed Pod join ends in
|
|
470
|
+
`failed` with the `wifi_*` code. No other state carries `error`. Terminal states release the link and radio
|
|
471
|
+
without revoking credentials. Retry over LAN after an uncertain activation outcome; unused pending cloud credentials expire automatically.
|
|
434
472
|
|
|
435
473
|
The schemas are `schema/pod-discovery.v1.schema.json` and
|
|
436
474
|
`schema/pod-commission.v1.schema.json`.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"revision": 4,
|
|
3
|
+
"lanBrowsing": false,
|
|
3
4
|
"scanning": true,
|
|
4
5
|
"adapter": { "state": "ready" },
|
|
5
6
|
"candidates": [
|
|
6
7
|
{
|
|
7
8
|
"candidateId": "ble:1A2B3C4D",
|
|
8
9
|
"kind": "pod",
|
|
10
|
+
"transport": "ble",
|
|
9
11
|
"name": "MeowPod S1",
|
|
10
12
|
"model": "S1",
|
|
11
13
|
"shortId": "1A2B3C4D",
|
package/manifest/routing.v1.json
CHANGED
package/manifest/targets.v1.json
CHANGED
package/package.json
CHANGED
package/protocol.json
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"reading_info",
|
|
17
17
|
"awaiting_wifi",
|
|
18
18
|
"joining_wifi",
|
|
19
|
+
"discovering_lan",
|
|
20
|
+
"connecting_lan",
|
|
19
21
|
"awaiting_authorization",
|
|
20
22
|
"issuing",
|
|
21
23
|
"delivering",
|
|
@@ -86,6 +88,8 @@
|
|
|
86
88
|
"bluetooth_unavailable",
|
|
87
89
|
"device_busy",
|
|
88
90
|
"disconnected",
|
|
91
|
+
"lan_unavailable",
|
|
92
|
+
"device_mismatch",
|
|
89
93
|
"code_rejected",
|
|
90
94
|
"code_locked",
|
|
91
95
|
"wifi_auth_failed",
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://schemas.mhome.ai/app-facade/pod-discovery.v1.schema.json",
|
|
4
|
-
"title": "Native-client BLE discovery snapshot (pods and Hosts)",
|
|
4
|
+
"title": "Native-client BLE and LAN discovery snapshot (pods and Hosts)",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": ["revision", "scanning", "adapter", "candidates"],
|
|
7
|
+
"required": ["revision", "scanning", "lanBrowsing", "adapter", "candidates"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"revision": { "type": "integer", "minimum": 0 },
|
|
10
10
|
"scanning": { "type": "boolean" },
|
|
11
|
+
"lanBrowsing": { "type": "boolean" },
|
|
12
|
+
"lanError": { "type": "string" },
|
|
11
13
|
"adapter": { "$ref": "#/$defs/adapter" },
|
|
12
14
|
"candidates": { "type": "array", "items": { "$ref": "#/$defs/candidate" } }
|
|
13
15
|
},
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
"required": [
|
|
28
30
|
"candidateId",
|
|
29
31
|
"kind",
|
|
32
|
+
"transport",
|
|
30
33
|
"name",
|
|
31
34
|
"shortId",
|
|
32
35
|
"rssi",
|
|
@@ -37,9 +40,11 @@
|
|
|
37
40
|
"properties": {
|
|
38
41
|
"candidateId": { "type": "string", "minLength": 1 },
|
|
39
42
|
"kind": { "enum": ["pod", "host"] },
|
|
43
|
+
"transport": { "enum": ["ble", "lan"] },
|
|
44
|
+
"deviceId": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
40
45
|
"name": { "type": "string", "minLength": 1 },
|
|
41
46
|
"model": { "type": "string" },
|
|
42
|
-
"shortId": { "type": "string", "
|
|
47
|
+
"shortId": { "type": "string", "minLength": 1, "maxLength": 64 },
|
|
43
48
|
"rssi": { "type": "integer" },
|
|
44
49
|
"commissionable": { "type": "boolean" },
|
|
45
50
|
"wifiConfigured": { "type": "boolean" },
|