217 lines
9.8 KiB
Markdown
217 lines
9.8 KiB
Markdown
# State Machine
|
|
|
|
> **Phase 1 note:** the gateway and voice-node do not share membership state.
|
|
> The voice node registers a client on the first UDP packet and drops idle addresses
|
|
> after 30 seconds. Diagrams that show `gateway notify voice node` describe the Phase 2 target.
|
|
|
|
Client and server maintain synchronized state machines over the LNEx connection.
|
|
This document describes the states, transitions, and what triggers them.
|
|
|
|
---
|
|
|
|
## Client states
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ DISCONNECTED │ ◄─── initial state / after disconnect
|
|
└────────┬────────┘
|
|
│ connect(address)
|
|
▼
|
|
┌─────────────────┐
|
|
│ CONNECTING │ TCP handshake in progress
|
|
└────────┬────────┘
|
|
│ TCP established
|
|
▼
|
|
┌─────────────────┐
|
|
│ HANDSHAKING │ HELLO received, sending AUTH
|
|
└────────┬────────┘
|
|
│ SESSION received
|
|
▼
|
|
┌─────────────────┐
|
|
│ CONNECTED │ ◄─── idle, no channel joined
|
|
└────────┬────────┘
|
|
│ join_channel()
|
|
▼
|
|
┌─────────────────┐
|
|
┌────►│ IN_CHANNEL │ text + voice available
|
|
│ └────────┬────────┘
|
|
│ │ leave_channel() / switch channel
|
|
└──────────────┘
|
|
│ disconnect() / TCP drop
|
|
▼
|
|
┌─────────────────┐
|
|
│ RECONNECTING │ exponential backoff (Phase 2)
|
|
└────────┬────────┘
|
|
│ max retries exceeded
|
|
▼
|
|
┌─────────────────┐
|
|
│ DISCONNECTED │
|
|
└─────────────────┘
|
|
```
|
|
|
|
Any state can transition to `DISCONNECTED` on:
|
|
- TCP connection drop
|
|
- `DISCONNECT` packet received
|
|
- auth failure (`ERR_AUTH_FAILED`)
|
|
- version mismatch (`ERR_VERSION_MISMATCH`)
|
|
|
|
---
|
|
|
|
## Server session states
|
|
|
|
Per-client session on the gateway:
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ ACCEPTING │ TCP connection received, sending HELLO
|
|
└────────┬────────┘
|
|
│ AUTH received
|
|
▼
|
|
┌─────────────────┐
|
|
│ AUTHENTICATING │ verifying Ed25519 signature
|
|
└────────┬────────┘
|
|
│ signature valid + not banned
|
|
▼
|
|
┌─────────────────┐
|
|
│ ESTABLISHED │ SESSION sent, client is authenticated
|
|
└────────┬────────┘
|
|
│ JOIN_CHANNEL received
|
|
▼
|
|
┌─────────────────┐
|
|
┌────►│ IN_CHANNEL │ forwarding messages + voice state
|
|
│ └────────┬────────┘
|
|
│ │ LEAVE_CHANNEL / JOIN different channel
|
|
└──────────────┘
|
|
│ TCP drop / DISCONNECT / idle timeout
|
|
▼
|
|
┌─────────────────┐
|
|
│ TERMINATED │ session cleaned up, resources freed
|
|
└─────────────────┘
|
|
```
|
|
|
|
### Auth failure paths
|
|
|
|
```
|
|
AUTHENTICATING
|
|
│ invalid signature → send ERR_AUTH_FAILED → TERMINATED
|
|
│ banned pubkey → send ERR_AUTH_FAILED → TERMINATED
|
|
│ unsupported version → send ERR_VERSION_MISMATCH → TERMINATED
|
|
│ rate limit on auth → send ERR_RATE_LIMITED → TERMINATED
|
|
```
|
|
|
|
---
|
|
|
|
## Voice session states (per user, on voice-node)
|
|
|
|
> **Phase 1:** transition from `INACTIVE` to `JOINING` happens when the first UDP
|
|
> packet arrives from a client address, not when the gateway sends a signal.
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ INACTIVE │ user not in a voice channel
|
|
└────────┬────────┘
|
|
│ first UDP packet from client (Phase 1)
|
|
│ gateway signals join (Phase 2, not implemented)
|
|
▼
|
|
┌─────────────────┐
|
|
│ JOINING │ UDP path being established
|
|
└────────┬────────┘
|
|
│ first UDP packet received from client
|
|
▼
|
|
┌─────────────────┐
|
|
│ SILENT │ in channel, not transmitting
|
|
└────────┬────────┘
|
|
│ voice packets arriving
|
|
▼
|
|
┌─────────────────┐
|
|
┌────►│ SPEAKING │ relaying to other channel members
|
|
│ └────────┬────────┘
|
|
│ │ DTX / push-to-talk released / silence
|
|
└──────────────┘
|
|
┌─────────────────┐
|
|
│ MUTED │ server-side mute (moderator action)
|
|
└────────┬────────┘
|
|
│ unmuted by moderator
|
|
▼
|
|
┌─────────────────┐
|
|
│ SILENT │
|
|
└─────────────────┘
|
|
```
|
|
|
|
Voice state is broadcast to all channel members as `VOICE_STATE` packets
|
|
on every transition: `SILENT → SPEAKING`, `SPEAKING → SILENT`, `MUTED`, `DEAFENED`.
|
|
|
|
---
|
|
|
|
## Channel join sequence (detailed)
|
|
|
|
Full flow from `join_channel()` call to receiving audio:
|
|
|
|
```
|
|
Client Gateway Voice Node
|
|
│ │ │
|
|
│── JOIN_CHANNEL ─────────►│ │
|
|
│ │ check permissions │
|
|
│ │ check channel exists │
|
|
│◄─ CHANNEL_STATE ─────────│ │
|
|
│ { members, │ │
|
|
│ voice_endpoint, │ (Phase 2: gateway │
|
|
│ channel_id } │ notifies voice node) │
|
|
│ │ │
|
|
│ [start UDP] │ │
|
|
│── UDP VOICE_PACKET ──────────────────────────────── ►│
|
|
│ │ │ relay to others
|
|
│◄─ UDP VOICE_PACKET ───────────────────────────────── │
|
|
│ │ │
|
|
│◄─ USER_JOIN broadcast ───│ │
|
|
│ (sent to all members) │ │
|
|
```
|
|
|
|
If `ERR_PERMISSION_DENIED` is returned on `JOIN_CHANNEL`, the client
|
|
stays in `CONNECTED` state and does not attempt UDP.
|
|
|
|
---
|
|
|
|
## Reconnect logic (Phase 2)
|
|
|
|
On unexpected TCP drop from `IN_CHANNEL` or `CONNECTED`:
|
|
|
|
```
|
|
disconnect detected
|
|
│
|
|
▼
|
|
RECONNECTING state
|
|
│
|
|
├── attempt 1: wait 1s
|
|
├── attempt 2: wait 2s
|
|
├── attempt 3: wait 4s
|
|
├── attempt 4: wait 8s
|
|
├── attempt 5: wait 16s
|
|
└── attempt 6+: wait 30s (cap)
|
|
```
|
|
|
|
On successful reconnect:
|
|
- full auth exchange (new session token)
|
|
- if user was `IN_CHANNEL` before disconnect: auto-rejoin same channel
|
|
|
|
On reconnect failure after N attempts (configurable, default 10):
|
|
- transition to `DISCONNECTED`
|
|
- notify user in UI
|
|
|
|
---
|
|
|
|
## Packet validity by state
|
|
|
|
Packets received in an unexpected state are dropped with `ERR_INVALID_PACKET`.
|
|
|
|
| Packet | Valid in states |
|
|
|--------|----------------|
|
|
| `HELLO` | server sends in `ACCEPTING` |
|
|
| `AUTH` | client sends in `HANDSHAKING` |
|
|
| `SESSION` | server sends in `AUTHENTICATING` |
|
|
| `JOIN_CHANNEL` | `CONNECTED`, `IN_CHANNEL` |
|
|
| `LEAVE_CHANNEL` | `IN_CHANNEL` |
|
|
| `CHAT_MESSAGE` | `IN_CHANNEL` |
|
|
| `VOICE_PACKET` | UDP, user in voice channel |
|
|
| `PING` / `PONG` | any authenticated state |
|
|
| `DISCONNECT` | any state |
|