Previously the client latched on the first epoch it saw, which could
be another client in the room. Now it accepts frames from all epochs
until onData delivers the first KCP message (handshake welcome),
then locks to that epoch and ignores others.
Fixes#67
In single-peer mode (client), frames from unknown epochs are now
silently dropped instead of triggering a reconnect loop. This
prevents the client from mistaking another client's VP8 track
for a server restart.
Part of #67
Implement PeerTransport interface (SendTo/SupportsPeerRouting) so the
server can route KCP traffic to individual peers by their epoch.
When OnPeerData is set (server mode), each remote epoch gets its own
KCP runtime instead of triggering a reconnect loop.
Also add DNS retry in protect.NewHTTPClient to handle transient
resolver failures.
Fixes#67
Address 25 issues reported by golangci-lint following the structural
refactor:
- cyclop: split common.Reassembler.Push into upsert/storeChunk/deliver
helpers (12→5). Move seichannel option-default fill into Options.
withDefaults so New stays under the limit.
- exhaustive: enumerate ResultPartial / ResultIgnore explicitly in
seichannel and videochannel switches over common.Result.
- gosec G115: annotate the test-fixture int→uint16/uint32 conversions
in common_test.go with //nolint:gosec.
- lll: break up the 130+ character one-liners in transport
unit/integration tests and the videochannel track-ID construction.
- nolintlint: drop the stale //nolint:cyclop in mobile_test.go where
the underlying complexity already cleared the limit.
- wrapcheck: wrap errors returned from internal/framing and
internal/runtime in their public callers (handshake, control,
server.setupCipher, client.setupCipher) so they carry the layer name.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
internal/transport/vp8channel/transport.go opened with a 24-line block
of Russian profanity addressed at Yandex SFU engineers (the engine
vp8channel targets to evade). It served no engineering purpose and is
not suitable for an open-source codebase.
Replace with a one-paragraph package doc summarising what the package
actually does (KCP-over-VP8-keyframes byte transport for SFUs that
validate VP8 conformance).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
videochannel, seichannel and vp8channel each carried independent copies of
randomID(), fragmentPayload(), inboundMessage + upsertInbound +
assembleMessage + ackWaiters/ackMu. The reassembly logic was almost
byte-identical across videochannel and seichannel; vp8channel only needed
randomID. Three copies of the same idea.
Add internal/transport/common with:
- RandomID(): 8-char hex per-peer ID (Jitsi msid uniqueness requirement).
- FragmentPayload(): split bytes into max-size chunks.
- Reassembler: stores in-flight messages keyed by Seq, validates CRC, and
reports Partial / Delivered / Duplicate / Ignore via a Result enum.
- AckRegistry: Register/Unregister/Resolve for ack waiters.
videochannel and seichannel now hold *common.AckRegistry and
*common.Reassembler instead of raw maps + mutexes. Their Send paths route
through acks.Register/Unregister; their handleInboundFrame is a 20-line
switch over reassembler.Push. vp8channel keeps its KCP framing but reuses
common.RandomID.
Tests that constructed raw streamTransport with inbound/delivered/ackWaiters
maps are updated to instantiate the new common types instead. Two now-
redundant low-level tests (upsertInbound out-of-range, assembleMessage)
collapse into the new TestInboundRejectsBadCRC.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
internal/carrier and internal/carrier/builtin sat between transports and
engines, wrapping every engine.Session in carrier.Session +
engineByteStream/engineVideoTrack adapters that mechanically proxied every
method. That layer existed solely to translate Capabilities/AddTrack names;
no behaviour lived above engine.
Replace with internal/engine/builtin: a name-keyed registry that calls
auth.Issue and engine.New directly. Transports look up engine.Session via
enginebuiltin.Open, then type-assert engine.VideoTrackCapable for video
transports. A small per-transport engineVideoSession adapter unifies the
reconnect callback signature (engine uses func(*webrtc.DataChannel); the
transports want func()).
Updates:
- internal/engine/builtin/builtin.go: new Register/Open registry + auth
pass-through ("none") + auth-driven factories for jazz/telemost/wbstream/jitsi.
- internal/transport/datachannel/transport.go: uses engine.Session directly
via Capabilities().ByteStream check.
- internal/transport/{seichannel,videochannel,vp8channel}: each gains an
engineVideoSession adapter and routes Connect/Send/Close/AddTrack through
the engine session.
- internal/app/session: imports enginebuiltin; carrier.Available() →
enginebuiltin.Available().
- pkg/olcrtc/olcrtc.go: switches to enginebuiltin.RegisterDefaults.
- internal/carrier and internal/carrier/builtin: deleted.
- Tests rewritten to register a fakeEngineSession (implements engine.Session
+ engine.VideoTrackCapable) through enginebuiltin.Register. The e2e
memoryStream gains the same dual interface so memorySession is gone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
transport.Config used to carry a flat union of video+vp8+sei tuning fields
that every transport ignored except its own. Replace with an opaque
transport.Options marker interface and per-transport Options structs
(videochannel.Options, vp8channel.Options, seichannel.Options). Datachannel
keeps an unset Options.
link.Config gains TransportOptions and drops the 16 transport-specific
fields. server.Config and client.Config follow suit. session.Config is
left untouched in this commit — buildTransportOptions packs its existing
flat fields into the typed Options bundle before calling server/client
(session.Config is rebuilt in a later commit when YAML config moves to
typed sections).
Tests that synthesized link/server/client/transport configs are updated
to pass typed Options bundles. The shared e2eTransportOptions helper
replaces three copies of the flat field bundle in e2e/tunnel_test.go.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Break CLI backwards compatibility as planned for refactor/universal-carrier:
- Drop -carrier flag; add -auth (auth provider name), -engine (engine
name for -auth none), -url and -token (SFU endpoint + access token for
direct/none auth mode).
- session.Config.Carrier → Auth + Engine + URL + Token.
- session.Gen() is now generic: auth.Get(cfg.Auth).(auth.RoomCreator)
replaces the hard-coded switch on carrier names.
- Register a "none" carrier in builtin (registerDirect) that bypasses
auth and connects directly to any engine with caller-supplied URL+Token.
- auth/telemost.Provider.Issue now accepts a raw room-ID hash in addition
to a full https://telemost.yandex.ru/j/<id> URL.
- Plumb Engine/URL/Token from session.Config through server.Run,
client.Run/RunWithReady, bringUpLink, link.Config, transport.Config, and
carrier.Config so the "none" carrier has access to them end-to-end.
- Update all tests and mobile.go call sites.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>