diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_NODE_PROTOCOL.md | 52 |
1 files changed, 48 insertions, 4 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md index f3fcbb9..47c3636 100644 --- a/docs/MESHBAY_NODE_PROTOCOL.md +++ b/docs/MESHBAY_NODE_PROTOCOL.md @@ -1,6 +1,6 @@ # MeshBay Node Protocol (MNP) -**Wire version:** `3.0` — `meshbay_common/__init__.py` (`MNP_VERSION`) +**Wire version:** `3.1` — `meshbay_common/__init__.py` (`MNP_VERSION`) **Oldest peer accepted:** `3.0` — `handshake.py` (`MNP_MIN_SUPPORTED`) **Normative implementation:** `meshbay-common` (`protocol.py`, `handshake.py`, `groupbox.py`, `chatbox.py`, `adminop.py`, `join.py`, `device.py`, `crypto.py`, @@ -620,6 +620,46 @@ nodes. operator who cracks the copy on their own disk gets a key that opens nothing anywhere else. +### 7.1a Per-account blobs (MNP 3.1) + +The same shape as a keypair bundle with a different payload — playlists today +(`docs/playlists.md` §8). The node stores bytes it cannot read for an account it +already holds a bundle for, so this adds **no new trust boundary**. + +``` + C N + |-- user_blob_list {v} ---------------------------->| which kinds exist here + |<- user_blob_list_resp {v, blobs: [{kind, rev}]} --| revisions only, no payload + | | + |-- user_blob_fetch {v, kind} ---------------------->| + |<- user_blob_resp {v, kind, rev|null, | + | blob_enc|null} -------------------------------| + | | + |-- user_blob_store {v, kind, rev, blob_enc} ------->| + |<- ack {v, detail: "user_blob_stored"} ------------| + | | + |-- user_blob_delete {v, kind} --------------------->| +``` + +* **`kind` is a namespace, validated against a pattern**: `playlists` is the + manifest, `playlist:<id>` is one playlist's tracks. That is what lets one + playlist be rewritten without re-uploading the whole collection, and it is a + pattern rather than "anything" so the table does not become a key/value store + for whatever a client feels like writing. +* **`blob_enc` is msgpack `bin`, not base64.** These run to hundreds of + kilobytes, where base64 is a third of every write. +* **`user_id` comes from the authenticated session, never from the message.** A + `user_id` in the body would let any member read or overwrite any other + member's blob. +* **Caps refuse, never truncate**: 64 KB for the manifest, 1 MB for one body, + 8 MB per account per node, each with a stated reason. A truncating cap loses + tracks silently, which is the failure the design exists to prevent. +* A `fetch` for a kind never written answers `null`, not an error: that is the + ordinary state of a node the reader has just joined. +* The node keeps **no history**. The client is the authority on which revision + is current and holds its own copy; a node keeping older revisions would mean + the node deciding, which is exactly what it must not do. + ### 7.2 Wrapped group key ``` @@ -1840,6 +1880,10 @@ it back (§3.5). | `keypair_bundle_fetch` / `_resp` | C→N / N→C | pre | the caller's encrypted identity bundle | | `keypair_bundle_store` | C→N | auth | back up (or re-wrap) that bundle | | `keypair_bundle_delete` | C→N | auth | withdraw the backup | +| `user_blob_store` | C→N | auth | write one per-account blob (playlists) | +| `user_blob_fetch` / `user_blob_resp` | C→N / N→C | auth | read one, or `null` | +| `user_blob_list` / `user_blob_list_resp` | C→N / N→C | auth | which kinds, at what revision — never a payload | +| `user_blob_delete` | C→N | auth | drop one | | `gek_bundle_fetch` / `_resp` | C→N / N→C | pre | the caller's wrapped GEK | | `join_request` / `join_result` | C→N / N→C | pre | pin or recognise an identity; wrap the GEK | | `invite_create` / `invite_result` | C→N / N→C | signed | issue a one-time pairing code | @@ -1942,8 +1986,8 @@ message: ## 13. Versioning and compatibility -MNP versions independently of the package version. Current: **`3.0`**; oldest peer -accepted: **`3.0`**. +MNP versions independently of the package version. Current: **`3.1`**; oldest peer +accepted: **`3.0`** — 3.1 is additive, so the floor does not move with it. The two numbers are separate on purpose. `MNP_VERSION` says what this build speaks; `MNP_MIN_SUPPORTED` says what it will talk to, and moving the second is a decision about @@ -2133,7 +2177,7 @@ LP(x) = uint32be(len(x)) || x every field, no exceptions | Constant | Value | Source | |---|---|---| -| `MNP_VERSION` | `3.0` | `meshbay_common/__init__.py` | +| `MNP_VERSION` | `3.1` | `meshbay_common/__init__.py` | | `MNP_MIN_SUPPORTED` | `3.0` | `handshake.py` | | `NONCE_LEN` | 32 bytes (both handshake nonces) | `handshake.py` | | `ADMIN_CHALLENGE_TTL` | 120 s | `adminop.py` | |