fix(nestsClient): more bugs from review — IETF parser, leaks, IPv6, URL encoding, structured concurrency
- Unknown IETF control message types now skip just the unknown frame
instead of dropping the entire merge buffer (a peer sending a
draft-17 message we don't enumerate — FETCH, GOAWAY, MAX_SUBSCRIBE_ID
— would otherwise wedge the pump). Adds MoqUnknownTypeException
carrying bytesConsumed so runControlPump can advance past it.
- pumpUniStreams + pumpInboundBidis wrap their inner collect in
coroutineScope so per-stream drains are children of the pump's job
(was: launched on the outer scope as siblings, leaking past
cancelAndJoin until the transport's own flow errored out).
- parseEndpoint handles IPv6 authorities ([::1], [2001:db8::1]:4443).
Naive lastIndexOf(':') used to find a colon inside the address.
- buildRelayConnectTarget percent-encodes the namespace path so a
malicious / careless `d` tag containing `?`, `#`, `&`, ` ` etc.
can't truncate the URL and shove the JWT into the wrong slot.
- Reconnect orchestrators (listener + speaker) replace
`runCatching { openOnce / close / startBroadcasting }` with explicit
try/catch that rethrows CancellationException, so cooperative
cancellation from the parent scope dies promptly instead of running
one more iteration after the cancel.
Adds three regression tests:
- parseEndpoint_handles_IPv6_authorities
- buildRelayConnectTarget_percent_encodes_path_unsafe_chars
- unknown_message_type_throws_typed_exception_with_full_frame_size
215 tests pass, 0 failures.
This commit is contained in:
@@ -167,6 +167,41 @@ class NestsConnectTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseEndpoint_handles_IPv6_authorities() {
|
||||
// No port — naive `lastIndexOf(':')` would find a colon inside
|
||||
// the address; the bracket-aware path keeps `[::1]` whole.
|
||||
assertEquals(
|
||||
"[::1]" to "/moq",
|
||||
parseEndpoint("https://[::1]/moq"),
|
||||
)
|
||||
assertEquals(
|
||||
"[2001:db8::1]:4443" to "/moq",
|
||||
parseEndpoint("https://[2001:db8::1]:4443/moq"),
|
||||
)
|
||||
// Default-port stripping still applies on bracketed authorities.
|
||||
assertEquals(
|
||||
"[::1]" to "/",
|
||||
parseEndpoint("https://[::1]:443/"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildRelayConnectTarget_percent_encodes_path_unsafe_chars() {
|
||||
// A malicious or careless `d` tag containing `?`, `#`, ` ` etc.
|
||||
// would otherwise truncate the URL path and shove the JWT into
|
||||
// the wrong slot. Encoding keeps the namespace literal in path
|
||||
// position; the relay URL-decodes before comparing to claim root.
|
||||
val (authority, path) =
|
||||
buildRelayConnectTarget(
|
||||
endpoint = "https://relay.example.com/moq",
|
||||
namespace = "nests/30312:0123:room?weird#frag",
|
||||
token = "tok-abc",
|
||||
)
|
||||
assertEquals("relay.example.com", authority)
|
||||
assertEquals("/nests/30312:0123:room%3Fweird%23frag?jwt=tok-abc", path)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- fakes
|
||||
|
||||
private class FakeNestsClient(
|
||||
|
||||
+10
-4
@@ -100,12 +100,18 @@ class MoqCodecTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unknown_message_type_is_rejected() {
|
||||
// Craft a frame with message type 0xFFFF (large varint), length 0, no payload.
|
||||
fun unknown_message_type_throws_typed_exception_with_full_frame_size() {
|
||||
// Craft a frame with message type 0xFFFF (large varint), length 4,
|
||||
// four bytes of payload. The pump uses bytesConsumed to skip past
|
||||
// the unknown frame instead of dropping the whole buffer.
|
||||
val bogus = MoqWriter()
|
||||
bogus.writeVarint(0xFFFFL)
|
||||
bogus.writeVarint(0L)
|
||||
assertFailsWith<MoqCodecException> { MoqCodec.decode(bogus.toByteArray()) }
|
||||
bogus.writeVarint(4L)
|
||||
bogus.writeBytes(byteArrayOf(0x01, 0x02, 0x03, 0x04))
|
||||
val encoded = bogus.toByteArray()
|
||||
val ex = assertFailsWith<MoqUnknownTypeException> { MoqCodec.decode(encoded) }
|
||||
assertEquals(0xFFFFL, ex.typeCode)
|
||||
assertEquals(encoded.size, ex.bytesConsumed)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user