fix: harden resume/shutdown paths, tighten Intent-18 and PSK handshake handling
Client (Go) — resume correctness - C1: completeResume now re-parks the stream when replay fails mid-conn-loss. parked was cleared before the replay loop, so the dying conn's teardown would start a second resumeLoop and the two loops could strand the stream with neither alive. Resume stats are counted only after the replay lands. - C2: RST(ALREADY_BOUND) is retryable instead of terminating the loop. With C1 fixed there is never a genuine second attempt, so "already bound" means the hub still holds the stream on a half-open conn; the retry waits out that bind (bounded by the grace deadline, teardown on expiry) instead of returning and leaving the destination socket hung forever. Client (Go) — shutdown semantics - C3: Close() sets a closing flag and cancels an internal context; dialSession takes a ctx (DialContext + AfterFunc so shutdown aborts in-flight handshakes); the worker pool refuses new conns after closeAll (Allocate, background growth, cond waiters); serveControl's reconnect loop is gated by closing so Close works even when the caller's Start context is not cancelled; conn-loss teardown closes streams outright during shutdown instead of parking them for a reattach that will never come. Client (Go) — hygiene - C4: pingInterval() clamps at the single point a duration is derived, so a hand-built Config with PingIntervalMs <= 0 can no longer panic time.NewTicker (added DefaultPingIntervalMs). - E6: shaperStall is sampled right after shaper.Acquire, before the socket write, so a hub that is not reading is no longer charged to the bandwidth cap in the stats. - E7: stream log lines now carry conn%d/sid%d (leg.String()), making streams traceable across reattaches. - P5: mirror constants IntentReserved/RegisterOk/RegisterErrPattern added; RegisterAck dispatch logs rejection reasons via the named codes. Hub (Java) + PROTOCOL.md - P3: Intent 18 replies with a Minecraft status-response packet ([Len: VarInt][0x00][JSON: String]) and closes (socket.end, so the write always lands) instead of closing silently; documented in PROTOCOL.md §2. - P4: PSK address check is strict equality with the lowercase hex address; an uppercase/case-folded variant is now rejected per PROTOCOL.md §2. - P7: PROTOCOL.md §5 SessionReady row lists its real fields (Flags/RecvWindow/ResumeGraceMs) instead of "(none)". Verified: go vet, go test -race ./client/..., gradle test, full e2e suite (twice), resume e2e 3x, plus live probes of the hub with the real client codec (Intent-18 status reply, strict-lowercase PSK acceptance/rejection).%
This commit is contained in:
+14
-1
@@ -74,6 +74,19 @@ The hub reads exactly one Handshake packet and dispatches on `Intent`:
|
||||
| `18` | Reserved for redapricot management/status. Never matched against patterns. The reference hub replies with a status line and closes. |
|
||||
| anything else | **Player** connection. `ServerAddress` is matched against the registered **regex** PATTERNs (§5.1). |
|
||||
|
||||
For `Intent == 18` the hub replies with a Minecraft status-response packet and
|
||||
closes — the same shape a player receives for a status query (Intent 1), so the
|
||||
port can be probed with ordinary tooling:
|
||||
|
||||
```
|
||||
[Len: VarInt][PacketID 0x00][JSON: String]
|
||||
```
|
||||
|
||||
The JSON is a minimal status payload, e.g.
|
||||
`{"description":{"text":"redapricot hub"},"version":{"name":"redapricot","protocol":767},"players":{"max":0,"online":0}}`.
|
||||
It is the one reply the hub sends in plaintext: Intent 18 never negotiates
|
||||
encryption or any other redapricot state.
|
||||
|
||||
For `Intent == 17` the hub additionally requires
|
||||
`ServerAddress == lowercase_hex(SHA3-224(PSK))` — a 56-character hex string.
|
||||
This is the first (cheap) proof that the peer knows the PSK. A mismatch closes
|
||||
@@ -211,7 +224,7 @@ Type : u8
|
||||
|
||||
| Type | Name | Direction | Fields |
|
||||
|--------|----------------|-----------|--------|
|
||||
| `0x00` | SessionReady | S → C | *(none)* — the confirmation frame from §4 |
|
||||
| `0x00` | SessionReady | S → C | `Flags: VarInt`, `RecvWindow: VarInt`, `ResumeGraceMs: VarInt` (only when the hub accepted `STREAM_RESUME`) — the confirmation frame from §4 |
|
||||
| `0x01` | Register | C → S | `Pattern: String` |
|
||||
| `0x02` | Unregister | C → S | `Pattern: String` |
|
||||
| `0x03` | RegisterAck | S → C | `Pattern: String`, `Status: u8` (0 = ok, 1 = invalid pattern) |
|
||||
|
||||
Reference in New Issue
Block a user