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

  1. Inspect the wrapped cause with errors.Unwrap(err) or errors.Is(err, net.ErrClosed) / os.IsTimeout to identify the transport failure
  2. Check network reachability and firewall/NAT rules between client and the cs2 endpoint
  3. Recreate the cs2 connection (the worker has exited; c.err is terminal) and resume, replaying any missed sequence numbers
  4. 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

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)