Skip to content

frame: fix UDP datagram boundary recovery after parse errors#246

Open
gavrilaf wants to merge 1 commit intobluenviron:mainfrom
gavrilaf:fix/udp-client-resync
Open

frame: fix UDP datagram boundary recovery after parse errors#246
gavrilaf wants to merge 1 commit intobluenviron:mainfrom
gavrilaf:fix/udp-client-resync

Conversation

@gavrilaf
Copy link
Copy Markdown

Problem

When using EndpointUDPClient over a real UDP link (e.g. a radio-link tunnel), a single malformed or out-of-order datagram produces dozens of spurious parse error invalid magic byte errors:

2026/04/11 15:55:46 parse error invalid magic byte: 0
2026/04/11 15:55:46 parse error invalid magic byte: ff
2026/04/11 15:55:46 parse error invalid magic byte: 2b

The root cause: bufio.Reader treats the UDP connection as a byte stream. After a parse error it continues scanning byte-by-byte inside the same datagram, generating one error per remaining byte. The next datagram then starts mid-buffer, corrupting further frames.

This does not affect TCP or serial endpoints because those are true byte streams where byte-by-byte scanning is the correct recovery strategy.

Solution

Add EndpointUDPClientPacket — identical to EndpointUDPClient but marks the channel as packet-oriented. After any ReadError, DiscardBuffered() drops all bytes still buffered from the current datagram so the next Read() begins on a fresh UDP packet boundary.

One bad datagram → exactly one EventParseError → normal parsing resumes.

Changes

  • pkg/frame: add Reader.DiscardBuffered() to flush the bufio buffer
  • pkg/frame: ReadWriter allocates a 65536-byte buffer in packet-oriented mode so one bufio fill always covers exactly one datagram
  • pkg/frame/v2_frame: unmarshal header with Peek + Discard instead of peekAndDiscard so an incompatibility-flag error leaves header bytes in the buffer for DiscardBuffered to clean up
  • channel: call DiscardBuffered() on ReadError when PacketOriented
  • channel_provider: detect packetOrientedEndpoint interface and set the flag
  • endpoint_udp_client_packet: new EndpointUDPClientPacket endpoint type

Tests

  • TestEndpointUDPClientPacketRecoverAfterMalformedDatagrams: sends 2 junk datagrams followed by a valid one — verifies exactly 2 EventParseError events and correct frame received after
  • TestEndpointUDPClientRecoverAfterMalformedDatagram: existing-style test in endpoint_client_test.go
  • TestReaderRecoverAfterInvalidV2IncompatibilityFlag: unit test for the Peek+Discard header change

On a packet-oriented transport (UDP), a single malformed datagram used
to produce dozens of spurious "invalid magic byte" errors because bufio
scanned the bad datagram byte-by-byte looking for the next magic byte.

Add EndpointUDPClientPacket: identical to EndpointUDPClient but marks
the channel as PacketOriented. After any ReadError, DiscardBuffered()
drops all bytes still buffered from the current datagram so the next
Read() begins on a fresh UDP packet boundary.

Key changes:
- pkg/frame: Reader.DiscardBuffered() discards remaining buffered bytes
- pkg/frame: ReadWriter uses a 65536-byte buffer in PacketOriented mode
  so a single bufio fill always covers exactly one datagram
- pkg/frame/v2_frame: unmarshal header with Peek+Discard instead of
  peekAndDiscard so an incompatibility-flag error leaves the header
  bytes in the buffer for DiscardBuffered to clean up
- channel: call DiscardBuffered on ReadError when PacketOriented
- channel_provider: detect packetOrientedEndpoint and set the flag
- endpoint_udp_client_packet: new EndpointUDPClientPacket type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant