This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git
commit e6e7ee06f4ea6d683ea71f5743c0d4fd1548b3fd Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:38:13 2026 +0200 nmxact/udp: Use a new RX buffer for each datagram The UDP receive loop read every datagram into the same buffer and passed a slice of it to the dispatch callback. For OMP the callback does not consume the data synchronously: the parsed CoAP message keeps referring to the buffer (its payload is a sub-slice) and is decoded later by the OMP listener goroutine. A datagram arriving in the meantime (e.g. a retransmission or another response) overwrote the payload of the previous one, so a response could be decoded from corrupted data, and the decode raced with the next read. Allocate a fresh buffer per datagram. Assisted-by: Claude:claude-opus-5-5 --- nmxact/udp/udp.go | 8 ++++++-- nmxact/udp/udp_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/nmxact/udp/udp.go b/nmxact/udp/udp.go index 8a00c14..c74475b 100644 --- a/nmxact/udp/udp.go +++ b/nmxact/udp/udp.go @@ -45,9 +45,13 @@ func Listen(peerString string, dispatchCb func(data []byte)) ( } go func() { - data := make([]byte, MAX_PACKET_SIZE) - for { + // Use a new buffer for each datagram. The callback may hand + // the data to another goroutine (e.g. a CoAP message payload + // refers to it), so it must not be overwritten by the next + // read. + data := make([]byte, MAX_PACKET_SIZE) + nr, srcAddr, err := conn.ReadFromUDP(data) if err != nil { // Connection closed or read error. diff --git a/nmxact/udp/udp_test.go b/nmxact/udp/udp_test.go index 4b1b12f..4a9efcc 100644 --- a/nmxact/udp/udp_test.go +++ b/nmxact/udp/udp_test.go @@ -444,3 +444,54 @@ func TestUdpXportStartStop(t *testing.T) { t.Errorf("BuildSesn returned %T, want *UdpSesn", s) } } + +// The dispatch callback may hand the received data to another goroutine +// (OMP responses are decoded asynchronously and the CoAP payload refers to +// the input buffer), so each datagram must get its own buffer rather than +// being overwritten by the next one. +func TestListenBufferNotReused(t *testing.T) { + dev, err := net.ListenUDP("udp", + &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) + if err != nil { + t.Fatal(err) + } + defer dev.Close() + + rx := make(chan []byte, 2) + conn, _, err := Listen(dev.LocalAddr().String(), func(data []byte) { + rx <- data + }) + if err != nil { + t.Fatalf("Listen failed: %v", err) + } + defer conn.Close() + + dst := &net.UDPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: conn.LocalAddr().(*net.UDPAddr).Port, + } + for _, s := range []string{"first", "SECND"} { + if _, err := dev.WriteToUDP([]byte(s), dst); err != nil { + t.Fatal(err) + } + } + + var bufs [][]byte + for i := 0; i < 2; i++ { + select { + case b := <-rx: + bufs = append(bufs, b) + case <-time.After(testTimeout): + t.Fatalf("timeout waiting for datagram %d", i) + } + } + + // Both datagrams have been received; the first slice must still hold + // the first datagram. + if string(bufs[0]) != "first" { + t.Errorf("first datagram = %q, want %q", bufs[0], "first") + } + if string(bufs[1]) != "SECND" { + t.Errorf("second datagram = %q", bufs[1]) + } +}
