Skip to content

Error handling is broken for short reads in NextMessage #22

@juhaszp-uhu

Description

@juhaszp-uhu

The NextMessage function is not equipped to handle short reads, it just assumes that the entire payload can be read with one syscall, and what's worse, it has a subtle bug when it attempts to report this error case.

The issue can be demonstrated with the following strace extract:

/tmp/strace.out.94:16:42:00.052698 read(7, "\20\1@", 3)    = 3
/tmp/strace.out.94:16:42:00.052804 read(7, "\10\0\370\377\370\377\10\0\370\377\370\377\370\377\370\377\370\377\10\0\370\377\10\0\370\377\10\0\370\377\370\377"..., 320) = 103
/tmp/strace.out.94:16:42:00.053075 read(7, 0xc00041abe9, 3) = -1 EAGAIN (Resource temporarily unavailable)
/tmp/strace.out.94:16:42:00.094725 read(7, "\377\370\377", 3) = 3
/tmp/strace.out.94:16:42:00.095028 read(7, "\10\0\370\377\10\0\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377\370\377"..., 63743) = 860
/tmp/strace.out.94:16:42:00.095367 read(7, 0xc00041abf8, 3) = -1 EAGAIN (Resource temporarily unavailable)

The go code that produced this trace is very similar to the example code around here, it calls NextMessage in a loop, checks for errors, and then attempts to process the received packet in a switch, depending on the packet's Kind().
In this case it first reads a proper packet header, learns that the payload should be 320 bytes long and then attempts to read that many bytes. However, it only reads 103 bytes, and then it returns. The next call to NextMessage then interprets whatever audio sample that was in the buffer as the header of the next packet.

What makes this worse is a subtle bug in errors.Wrapf, as used in NextMessage:

	if n != int(payloadLen) {
		return nil, errors.Wrapf(err, "read wrong number of bytes (%d) for payload", n)
	}

If err is nil, as in this case, the value returned by errors.Wrapf is also nil! This can be examined in the following playground link: https://go.dev/play/p/5zHdS1_Crkr

Proposed steps to fix:

  • handle short reads properly, by calling Read() in a loop until error or enough bytes were read
  • don't use errors.Wrapf when err is nil, or even better, don't use github.com/pkg/errors at all, it's considered deprecated nowadays

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions