AlexxIT/go2rtc · error
%s: %w
Error message
%s: %w
What it means
This error wraps any error returned by the underlying TCP Conn.Read inside the cs2 connection's worker read loop. The library prefixes it with "cs2" so the wrapped net.Error (e.g. connection reset, EOF, timeout) remains inspectable via errors.Unwrap. It terminates the worker loop and is stored in c.err, surfacing to any caller reading from the connection afterward.
Solutions
- Inspect the wrapped cause with errors.Unwrap(err) or errors.Is(err, net.ErrClosed) / os.IsTimeout to identify the transport failure
- Check network reachability and firewall/NAT rules between client and the cs2 endpoint
- Recreate the cs2 connection (the worker has exited; c.err is terminal) and resume, replaying any missed sequence numbers
- Enable periodic pings (msgPing/msgPong are supported) to keep NAT mappings alive and detect dead peers early
Example fix
// before
n, err := c.Conn.Read(buf)
if err != nil {
c.err = fmt.Errorf("%s: %w", "cs2", err)
return
}
// after
n, err := c.Conn.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, syscall.ECONNRESET) {
err = c.reconnect() // reconnect with backoff instead of surfacing raw error
} else {
c.err = fmt.Errorf("%s: %w", "cs2", err)
}
return
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: check connection liveness before read-heavy work
func connAlive(c *net.TCPConn) bool {
if c == nil { return false }
return c.SetReadDeadline(time.Now().Add(time.Second)) == nil
} Type guard
func isConnErr(err error) bool {
var ne net.Error
return errors.As(err, &ne) || errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF)
} Try / catch
n, err := conn.Read(buf)
if err != nil {
if isConnErr(err) {
// reconnect with backoff
err = reconnect()
}
return err
} Prevention
- Set read/write deadlines on the connection to detect dead peers
- Send periodic pings to keep NAT mappings alive
- Monitor c.err and reconnect proactively
- Log the unwrapped cause for diagnosis
When it happens
Trigger: The remote cs2 endpoint closes the TCP connection (EOF), resets it, times out, or the network drops while the worker goroutine is blocked in c.Conn.Read inside the read loop.
Common situations: Device/peer powers off or reboots mid-session; NAT or firewall silently drops an idle connection; keep-alive pings not being answered; server-side connection limit reached; mobile/unstable networks.
Related errors
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/4d3218f8d0232388.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/xiaomi/miss/cs2/conn.go:122
}
return conn, nil
}
func (c *Conn) worker() {
defer func() {
c.channels[0].Close()
c.channels[2].Close()
}()
var keepaliveTS time.Time // only for TCP
buf := make([]byte, 1200)
for {
n, err := c.Conn.Read(buf)
if err != nil {
c.err = fmt.Errorf("%s: %w", "cs2", err)
return
}
// 0 f1d0 magic
// 2 005d size = total size + 4
// 4 d1 magic
// 5 00 channel
// 6 0000 seq
switch buf[1] {
case msgDrw:
ch := buf[5]
channel := c.channels[ch]
if c.isTCP {
// For TCP we should send ping every second to keep connection alive.
// Based on PCAP analysis: official Mi Home app sends PING every ~1s.
if now := time.Now(); now.After(keepaliveTS) {
_, _ = c.Conn.Write([]byte{magic, msgPing, 0, 0})View on GitHub (pinned to c245815e75)