Daemon:
- Per-network download directories (-download-dir flag, set_download_dir IPC)
- File transfer resume after disconnection: .tmp.meta sidecars survive
interruption; resume_offset in file-accept lets sender seek and continue
- set_download_dir IPC command; download_dir reported in state_snapshot
Protocol:
- PeerMessage.ResumeOffset (EXT-006) for file transfer resume
- IpcMessage.ShareNetworks json tag changed from "networks" to "network_ids"
to fix duplicate json tag collision with Networks []NetworkInfo
Desktop app (cmd/app):
- Wails v2 shell embedding daemon logic directly (no subprocess)
- System tray on Linux/Windows via getlantern/systray; macOS hides to Dock
- OS notifications for message_received and file_complete via Wails events
- notray build tag for headless/CI builds without GTK tray headers
- build-app.sh: builds web frontend, copies dist, runs wails build
Web / PWA:
- manifest.json + Apple touch icon meta tags for mobile "Add to Home Screen"
- PNG icons (192px, 512px, 180px) generated from SVG
- Wails EventsOn("notify") hook in App.tsx for native OS notifications
CI:
- .gitea/workflows/build.yml: server binaries cross-compiled for 5 platforms,
desktop app for Linux amd64, release artifacts published on v* tags
Docs:
- README: download dir, file transfer resume, desktop app, PWA, CI sections
- EXTENSIONS.md: EXT-004 daemon mode marked shipped; EXT-006 resume added
- FUTURE.md: roadmap updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
227 lines
7.2 KiB
Markdown
227 lines
7.2 KiB
Markdown
# waste-go Protocol Extensions
|
|
|
|
These are additive extensions to [YAW/2](PROTOCOL.md) implemented by waste-go.
|
|
They do **not** break compatibility — YAW/2-only peers silently ignore all new
|
|
fields. Where a waste-go peer connects to a YAW/2-only peer, the extension
|
|
simply has no effect on that peer.
|
|
|
|
---
|
|
|
|
## EXT-001 — Signed Invites
|
|
|
|
**Status:** implemented
|
|
**Affects:** `waste:` invite format, `hello` DataChannel message
|
|
|
|
### Motivation
|
|
|
|
The base YAW/2 network model is open to anyone who knows the anchor URL and
|
|
network name (or hash). This extension adds opt-in cryptographic membership
|
|
gating: invites are signed by an existing peer, and peers that enforce
|
|
`RequireInvite` reject hellos that carry no valid signed invite.
|
|
|
|
### Invite format changes
|
|
|
|
The `waste:` invite payload (base64-encoded JSON) gains two optional fields:
|
|
|
|
```json
|
|
{
|
|
"anchor": "wss://...",
|
|
"network": "friends",
|
|
"net": "<64-hex SHA-256(yaw2-net:name)>",
|
|
"inviter": "<64-hex Ed25519 pubkey of signing peer>",
|
|
"sig": "<hex Ed25519 signature>"
|
|
}
|
|
```
|
|
|
|
The signature covers the following bytes (null-separated):
|
|
|
|
```
|
|
anchor \x00 network \x00 net \x00 inviter
|
|
```
|
|
|
|
Unsigned invites (`inviter`/`sig` absent) remain valid for backward compat.
|
|
|
|
### Hello message extension
|
|
|
|
The YAW/2 §6 hello message gains one optional field:
|
|
|
|
```json
|
|
{
|
|
"type": "hello",
|
|
"id": "<hex pubkey>",
|
|
"nick": "alice",
|
|
"caps": ["chat", "file"],
|
|
"sig": "<DTLS binding sig>",
|
|
"invite": "waste:eyJ..."
|
|
}
|
|
```
|
|
|
|
`invite` carries the full `waste:` string the connecting peer used to join.
|
|
YAW/2-only peers ignore this field.
|
|
|
|
### Enforcement
|
|
|
|
Per-network flag `RequireInvite` (set via `join_network` IPC command).
|
|
When enabled:
|
|
|
|
1. A peer that presents no `invite` in hello is disconnected immediately.
|
|
2. A peer that presents an invite with no signature is disconnected.
|
|
3. A peer whose invite signature is invalid is disconnected.
|
|
4. A peer whose invite was signed by an unknown peer ID (not in the store or
|
|
currently connected) is disconnected.
|
|
|
|
The inviter's key must be a **known peer** — i.e. previously connected and
|
|
stored in the per-network SQLite store, or currently connected. This forms a
|
|
chain of trust: Alice (founder) invites Bob; Bob's key is now known; Bob can
|
|
invite Carol, whose invite Alice will also accept.
|
|
|
|
**Default:** off. Networks opt in. Existing networks with no RequireInvite
|
|
behave exactly as before.
|
|
|
|
---
|
|
|
|
## EXT-002 — Hash-based Hang Link
|
|
|
|
**Status:** implemented
|
|
**Affects:** web UI URL handling only, no wire changes
|
|
|
|
### Motivation
|
|
|
|
A shareable URL that pre-fills the join form without conveying cryptographic
|
|
membership. Suitable for public announcements ("come hang out here"). The
|
|
fragment is never sent to the server, keeping the network name opaque to
|
|
server logs and HTTP intermediaries.
|
|
|
|
### Format
|
|
|
|
```
|
|
https://host/#waste:eyJ...
|
|
```
|
|
|
|
The fragment payload is the standard `waste:` base64 JSON with only `network`
|
|
and `anchor` fields — no `inviter`, no `sig`. This does **not** grant
|
|
membership on networks with `RequireInvite` enabled; it only pre-fills the
|
|
join form.
|
|
|
|
The web UI generates hang links via the 🔗 button in the Networks sidebar
|
|
section. Arriving users see the join form pre-populated and still need a
|
|
proper signed invite (if the network enforces it) to be accepted by peers.
|
|
|
|
---
|
|
|
|
## EXT-003 — Multi-Share Configuration
|
|
|
|
**Status:** implemented
|
|
**Affects:** IPC protocol only, no peer-to-peer wire changes
|
|
|
|
### New IPC commands
|
|
|
|
```jsonc
|
|
{"type":"add_share","path":"/home/alice/Music"} // global
|
|
{"type":"add_share","path":"/home/alice/Docs","network_ids":["abc123"]} // scoped
|
|
{"type":"remove_share","path":"/home/alice/Music"}
|
|
{"type":"list_shares"}
|
|
```
|
|
|
|
### New IPC event
|
|
|
|
```jsonc
|
|
{"type":"shares_list","shares":[{"path":"...","networks":["*"]}]}
|
|
```
|
|
|
|
### Persistence
|
|
|
|
`shares.json` in the data directory (next to `identity.json`). Each entry:
|
|
|
|
```json
|
|
{ "path": "/absolute/path", "networks": ["*"] }
|
|
```
|
|
|
|
`networks: ["*"]` = global (all networks). Specific network IDs = scoped.
|
|
Coexists with the legacy `set_share_dir` single-dir mechanism.
|
|
|
|
File listings returned by `get_file_list` and `MsgFileListReq` include
|
|
entries from all applicable share roots, with relative `path` fields
|
|
(e.g. `"path": "docs/report.pdf"`).
|
|
|
|
---
|
|
|
|
## EXT-004 — TURN Relay (browser mode)
|
|
|
|
**Status:** implemented (browser mode + daemon mode)
|
|
**Affects:** ICE server configuration only, no wire changes
|
|
|
|
The browser adapter reads `WASTE_CONFIG.turnURL` and `WASTE_CONFIG.turnSecret`
|
|
and adds a TURN server to the WebRTC `ICEServers` list. Credentials are
|
|
generated using HMAC-SHA1 of the username (coturn `use-auth-secret` scheme).
|
|
|
|
YAW/2 §0 explicitly declines TURN ("No relay (TURN)"). This extension is
|
|
opt-in via server configuration and does not affect peers that omit it.
|
|
|
|
---
|
|
|
|
## EXT-005 — Per-Network Path in FileEntry
|
|
|
|
**Status:** implemented
|
|
**Affects:** `MsgFileListResp` wire message (additive field)
|
|
|
|
`FileEntry` gains an optional `path` field carrying the file's relative path
|
|
within its share root (e.g. `"docs/report.pdf"`). Peers that don't understand
|
|
this field continue to use `name` for display and download requests.
|
|
|
|
`MsgFileListReq` / `get` requests use `path` as the lookup key when present,
|
|
falling back to `name` for backward compat with peers that don't send `path`.
|
|
|
|
---
|
|
|
|
## EXT-006 — File Transfer Resume
|
|
|
|
**Status:** implemented (daemon mode)
|
|
**Affects:** `file-accept` wire message (additive field)
|
|
|
|
### Motivation
|
|
|
|
A transfer interrupted mid-stream (peer disconnect, network drop) can be
|
|
continued from where it left off on the next offer of the same file, avoiding
|
|
a full re-download.
|
|
|
|
### Protocol change
|
|
|
|
`file-accept` gains one optional field:
|
|
|
|
```json
|
|
{ "type": "file-accept", "xid": "...", "resume_offset": 65536 }
|
|
```
|
|
|
|
`resume_offset` is the number of bytes the receiver already has on disk.
|
|
When non-zero, the sender seeks to that byte position before streaming.
|
|
Peers that don't understand this field ignore it and send from the start —
|
|
the receiver detects this by comparing incoming data to expected offset and
|
|
will still verify the final SHA-256, but the partial bytes from the interrupted
|
|
session will be overwritten (YAW/2-only interop degrades gracefully to a full
|
|
re-download, not corruption).
|
|
|
|
### Receiver behaviour
|
|
|
|
1. On `file-offer`, the receiver scans its download directory for a `.tmp.meta`
|
|
sidecar whose `sha256` matches the offer.
|
|
2. If found, the corresponding `.tmp` file's size is the resume offset. This is
|
|
sent back in `file-accept`.
|
|
3. On DC open, the receiver opens the existing `.tmp` in append mode and
|
|
re-hashes its existing bytes to restore the SHA-256 state.
|
|
4. On DC close with all bytes received, SHA-256 is verified. Success → sidecar
|
|
removed, file renamed to final path. Hash mismatch → both files removed.
|
|
5. On DC close with fewer bytes than expected (interrupted again) → both files
|
|
kept for the next resume attempt.
|
|
|
|
### Sidecar format
|
|
|
|
Each in-progress `.tmp` file has a corresponding `.tmp.meta` JSON sidecar:
|
|
|
|
```json
|
|
{ "name": "archive.zip", "sha256": "abc...", "from": "<peer-id>", "size": 1048576 }
|
|
```
|
|
|
|
The sidecar is written when the transfer starts and removed on completion or
|
|
corruption. Interrupted transfers keep the sidecar indefinitely.
|