snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s

This commit is contained in:
upstream-mirror
2026-09-15 20:06:07 +05:00
commit 8acb1a3a29
394 changed files with 78746 additions and 0 deletions

107
pkg/media/rtpdump/reader.go Normal file
View File

@@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtpdump
import (
"bufio"
"errors"
"io"
"regexp"
"sync"
)
// Reader reads the RTPDump file format.
type Reader struct {
readerMu sync.Mutex
reader io.Reader
}
// NewReader opens a new Reader and immediately reads the Header from the start
// of the input stream.
func NewReader(r io.Reader) (*Reader, Header, error) {
var hdr Header
bio := bufio.NewReader(r)
// Look ahead to see if there's a valid preamble
peek, err := bio.Peek(preambleLen)
if errors.Is(err, io.EOF) {
return nil, hdr, errMalformed
}
if err != nil {
return nil, hdr, err
}
// The file starts with #!rtpplay1.0 address/port\n
preambleRegexp := regexp.MustCompile(`#\!rtpplay1\.0 \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,5}\n`)
if !preambleRegexp.Match(peek) {
return nil, hdr, errMalformed
}
// consume the preamble
_, _, err = bio.ReadLine()
if errors.Is(err, io.EOF) {
return nil, hdr, errMalformed
}
if err != nil {
return nil, hdr, err
}
hBuf := make([]byte, headerLen)
_, err = io.ReadFull(bio, hBuf)
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
return nil, hdr, errMalformed
}
if err != nil {
return nil, hdr, err
}
if err := hdr.Unmarshal(hBuf); err != nil {
return nil, hdr, err
}
return &Reader{
reader: bio,
}, hdr, nil
}
// Next returns the next Packet in the Reader input stream.
func (r *Reader) Next() (Packet, error) {
r.readerMu.Lock()
defer r.readerMu.Unlock()
hBuf := make([]byte, pktHeaderLen)
_, err := io.ReadFull(r.reader, hBuf)
if errors.Is(err, io.ErrUnexpectedEOF) {
return Packet{}, errMalformed
}
if err != nil {
return Packet{}, err
}
var header packetHeader
if err = header.Unmarshal(hBuf); err != nil {
return Packet{}, err
}
if header.Length == 0 {
return Packet{}, errMalformed
}
payload := make([]byte, header.Length-pktHeaderLen)
_, err = io.ReadFull(r.reader, payload)
if errors.Is(err, io.ErrUnexpectedEOF) {
return Packet{}, errMalformed
}
if err != nil {
return Packet{}, err
}
return Packet{
Offset: header.offset(),
IsRTCP: header.PacketLength == 0,
Payload: payload,
}, nil
}

View File

@@ -0,0 +1,285 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtpdump
import (
"bytes"
"errors"
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReader(t *testing.T) { //nolint:maintidx
validPreamble := []byte("#!rtpplay1.0 224.2.0.1/3456\n")
for _, test := range []struct {
Name string
Data []byte
WantHeader Header
WantPackets []Packet
WantErr error
}{
{
Name: "empty",
Data: nil,
WantErr: errMalformed,
},
{
Name: "hashbang missing ip/port",
Data: append(
[]byte("#!rtpplay1.0 \n"),
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
),
WantErr: errMalformed,
},
{
Name: "hashbang missing port",
Data: append(
[]byte("#!rtpplay1.0 0.0.0.0\n"),
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
),
WantErr: errMalformed,
},
{
Name: "valid empty file",
Data: append(
validPreamble,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x01,
0x22, 0xB8, 0x00, 0x00,
),
WantHeader: Header{
Start: time.Unix(1, 0).UTC(),
Source: net.IPv4(1, 1, 1, 1),
Port: 8888,
},
},
{
Name: "malformed packet header",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header
0x00,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantErr: errMalformed,
},
{
Name: "short packet payload",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header len=1048575
0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet payload
0x00,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantErr: errMalformed,
},
{
Name: "empty packet payload",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header len=0
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantErr: errMalformed,
},
{
Name: "valid rtcp packet",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header len=20, pLen=0, off=1
0x00, 0x14, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
// packet payload (BYE)
0x81, 0xcb, 0x00, 0x0c,
0x90, 0x2f, 0x9e, 0x2e,
0x03, 0x46, 0x4f, 0x4f,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantPackets: []Packet{
{
Offset: time.Millisecond,
IsRTCP: true,
Payload: []byte{
0x81, 0xcb, 0x00, 0x0c,
0x90, 0x2f, 0x9e, 0x2e,
0x03, 0x46, 0x4f, 0x4f,
},
},
},
WantErr: nil,
},
{
Name: "truncated rtcp packet",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header len=9, pLen=0, off=1
0x00, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
// invalid payload
0x81,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantPackets: []Packet{
{
Offset: time.Millisecond,
IsRTCP: true,
Payload: []byte{0x81},
},
},
},
{
Name: "two valid packets",
Data: append(
validPreamble,
// header
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// packet header len=20, pLen=0, off=1
0x00, 0x14, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
// packet payload (BYE)
0x81, 0xcb, 0x00, 0x0c,
0x90, 0x2f, 0x9e, 0x2e,
0x03, 0x46, 0x4f, 0x4f,
// packet header len=33, pLen=0, off=2
0x00, 0x21, 0x00, 0x19,
0x00, 0x00, 0x00, 0x02,
// packet payload (RTP)
0x90, 0x60, 0x69, 0x8f,
0xd9, 0xc2, 0x93, 0xda,
0x1c, 0x64, 0x27, 0x82,
0x00, 0x01, 0x00, 0x01,
0xFF, 0xFF, 0xFF, 0xFF,
0x98, 0x36, 0xbe, 0x88,
0x9e,
),
WantHeader: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
WantPackets: []Packet{
{
Offset: time.Millisecond,
IsRTCP: true,
Payload: []byte{
0x81, 0xcb, 0x00, 0x0c,
0x90, 0x2f, 0x9e, 0x2e,
0x03, 0x46, 0x4f, 0x4f,
},
},
{
Offset: 2 * time.Millisecond,
IsRTCP: false,
Payload: []byte{
0x90, 0x60, 0x69, 0x8f,
0xd9, 0xc2, 0x93, 0xda,
0x1c, 0x64, 0x27, 0x82,
0x00, 0x01, 0x00, 0x01,
0xFF, 0xFF, 0xFF, 0xFF,
0x98, 0x36, 0xbe, 0x88,
0x9e,
},
},
},
WantErr: nil,
},
} {
reader, hdr, err := NewReader(bytes.NewReader(test.Data))
// we validate the error again. at the end of the reading loop.
if err != nil {
assert.ErrorIs(t, err, test.WantErr, test.Name)
continue
}
assert.Equal(t, test.WantHeader, hdr, test.Name)
var nextErr error
var packets []Packet
for {
pkt, err := reader.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
nextErr = err
break
}
packets = append(packets, pkt)
}
if test.WantErr != nil {
assert.ErrorIs(t, nextErr, test.WantErr, test.Name)
} else {
assert.NoError(t, nextErr, test.Name)
}
assert.Equal(t, test.WantPackets, packets, test.Name)
}
}

View File

@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package rtpdump implements the RTPDump file format documented at
// https://www.cs.columbia.edu/irt/software/rtptools/
package rtpdump
import (
"encoding/binary"
"errors"
"net"
"time"
)
const (
pktHeaderLen = 8
headerLen = 16
preambleLen = 36
)
var errMalformed = errors.New("malformed rtpdump")
// Header is the binary header at the top of the RTPDump file. It contains
// information about the source and start time of the packet stream included
// in the file.
type Header struct {
// start of recording (GMT)
Start time.Time
// network source (multicast address)
Source net.IP
// UDP port
Port uint16
}
// Marshal encodes the Header as binary.
func (h Header) Marshal() ([]byte, error) {
data := make([]byte, headerLen)
startNano := h.Start.UnixNano()
startSec := uint32(startNano / int64(time.Second)) //nolint:gosec // G115
startUsec := uint32( //nolint:gosec // G115
(startNano % int64(time.Second)) / int64(time.Microsecond),
)
binary.BigEndian.PutUint32(data[0:], startSec)
binary.BigEndian.PutUint32(data[4:], startUsec)
source := h.Source.To4()
copy(data[8:], source)
binary.BigEndian.PutUint16(data[12:], h.Port)
return data, nil
}
// Unmarshal decodes the Header from binary.
func (h *Header) Unmarshal(data []byte) error {
if len(data) < headerLen {
return errMalformed
}
// time as a `struct timeval`
startSec := binary.BigEndian.Uint32(data[0:])
startUsec := binary.BigEndian.Uint32(data[4:])
h.Start = time.Unix(int64(startSec), int64(startUsec)*1e3).UTC()
// ipv4 address
h.Source = net.IPv4(data[8], data[9], data[10], data[11])
h.Port = binary.BigEndian.Uint16(data[12:])
// 2 bytes of padding (ignored)
return nil
}
// Packet contains an RTP or RTCP packet along a time offset when it was logged
// (relative to the Start of the recording in Header). The Payload may contain
// truncated packets to support logging just the headers of RTP/RTCP packets.
type Packet struct {
// Offset is the time since the start of recording in milliseconds
Offset time.Duration
// IsRTCP is true if the payload is RTCP, false if the payload is RTP
IsRTCP bool
// Payload is the binary RTP or RTCP payload. The contents may not parse
// as a valid packet if the contents have been truncated.
Payload []byte
}
// Marshal encodes the Packet as binary.
func (p Packet) Marshal() ([]byte, error) {
packetLength := len(p.Payload)
if p.IsRTCP {
packetLength = 0
}
hdr := packetHeader{
Length: uint16(len(p.Payload)) + 8, //nolint:gosec // G115
PacketLength: uint16(packetLength), //nolint:gosec // G115
Offset: p.offsetMs(),
}
hdrData, err := hdr.Marshal()
if err != nil {
return nil, err
}
return append(hdrData, p.Payload...), nil
}
// Unmarshal decodes the Packet from binary.
func (p *Packet) Unmarshal(data []byte) error {
var hdr packetHeader
if err := hdr.Unmarshal(data); err != nil {
return err
}
p.Offset = hdr.offset()
p.IsRTCP = hdr.Length != 0 && hdr.PacketLength == 0
if hdr.Length < 8 {
return errMalformed
}
if len(data) < int(hdr.Length) {
return errMalformed
}
p.Payload = data[8:hdr.Length]
return nil
}
func (p *Packet) offsetMs() uint32 {
return uint32(p.Offset / time.Millisecond) //nolint:gosec // G115
}
type packetHeader struct {
// length of packet, including this header (may be smaller than
// plen if not whole packet recorded)
Length uint16
// Actual header+payload length for RTP, 0 for RTCP
PacketLength uint16
// milliseconds since the start of recording
Offset uint32
}
func (p packetHeader) Marshal() ([]byte, error) {
d := make([]byte, pktHeaderLen)
binary.BigEndian.PutUint16(d[0:], p.Length)
binary.BigEndian.PutUint16(d[2:], p.PacketLength)
binary.BigEndian.PutUint32(d[4:], p.Offset)
return d, nil
}
func (p *packetHeader) Unmarshal(d []byte) error {
if len(d) < pktHeaderLen {
return errMalformed
}
p.Length = binary.BigEndian.Uint16(d[0:])
p.PacketLength = binary.BigEndian.Uint16(d[2:])
p.Offset = binary.BigEndian.Uint32(d[4:])
return nil
}
func (p packetHeader) offset() time.Duration {
return time.Duration(p.Offset) * time.Millisecond
}

View File

@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtpdump
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestHeaderRoundTrip(t *testing.T) {
for _, test := range []struct {
Header Header
}{
{
Header: Header{
Start: time.Unix(0, 0).UTC(),
Source: net.IPv4(0, 0, 0, 0),
Port: 0,
},
},
{
Header: Header{
Start: time.Date(2019, 3, 25, 1, 1, 1, 0, time.UTC),
Source: net.IPv4(1, 2, 3, 4),
Port: 8080,
},
},
} {
d, err := test.Header.Marshal()
assert.NoError(t, err)
var hdr Header
assert.NoError(t, hdr.Unmarshal(d))
assert.Equal(t, test.Header, hdr)
}
}
func TestMarshalHeader(t *testing.T) {
for _, test := range []struct {
Name string
Header Header
Want []byte
WantErr error
}{
{
Name: "nil source",
Header: Header{
Start: time.Unix(0, 0).UTC(),
Source: nil,
Port: 0,
},
Want: []byte{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
},
} {
data, err := test.Header.Marshal()
assert.ErrorIs(t, err, test.WantErr)
assert.Equal(t, test.Want, data)
}
}
func TestPacketRoundTrip(t *testing.T) {
for _, test := range []struct {
Packet Packet
}{
{
Packet: Packet{
Offset: 0,
IsRTCP: false,
Payload: []byte{0},
},
},
{
Packet: Packet{
Offset: 0,
IsRTCP: true,
Payload: []byte{0},
},
},
{
Packet: Packet{
Offset: 123 * time.Millisecond,
IsRTCP: false,
Payload: []byte{1, 2, 3, 4},
},
},
} {
packet, err := test.Packet.Marshal()
assert.NoError(t, err)
var pkt Packet
assert.NoError(t, pkt.Unmarshal(packet))
assert.Equal(t, test.Packet, pkt)
}
}

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtpdump
import (
"fmt"
"io"
"sync"
)
// Writer writes the RTPDump file format.
type Writer struct {
writerMu sync.Mutex
writer io.Writer
}
// NewWriter makes a new Writer and immediately writes the given Header
// to begin the file.
func NewWriter(w io.Writer, hdr Header) (*Writer, error) {
preamble := fmt.Sprintf(
"#!rtpplay1.0 %s/%d\n",
hdr.Source.To4().String(),
hdr.Port)
if _, err := w.Write([]byte(preamble)); err != nil {
return nil, err
}
hData, err := hdr.Marshal()
if err != nil {
return nil, err
}
if _, err := w.Write(hData); err != nil {
return nil, err
}
return &Writer{writer: w}, nil
}
// WritePacket writes a Packet to the output.
func (w *Writer) WritePacket(p Packet) error {
w.writerMu.Lock()
defer w.writerMu.Unlock()
data, err := p.Marshal()
if err != nil {
return err
}
if _, err := w.writer.Write(data); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtpdump
import (
"bytes"
"errors"
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWriter(t *testing.T) {
buf := bytes.NewBuffer(nil)
writer, err := NewWriter(buf, Header{
Start: time.Unix(9, 0),
Source: net.IPv4(2, 2, 2, 2),
Port: 2222,
})
assert.NoError(t, err)
assert.NoError(t, writer.WritePacket(Packet{
Offset: time.Millisecond,
IsRTCP: false,
Payload: []byte{9},
}))
expected := append(
[]byte("#!rtpplay1.0 2.2.2.2/2222\n"),
// header
0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x00, 0x00,
0x02, 0x02, 0x02, 0x02,
0x08, 0xae, 0x00, 0x00,
// packet header
0x00, 0x09, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0x09,
)
assert.Equal(t, expected, buf.Bytes())
}
func TestRoundTrip(t *testing.T) {
buf := bytes.NewBuffer(nil)
packets := []Packet{
{
Offset: time.Millisecond,
IsRTCP: false,
Payload: []byte{9},
},
{
Offset: 999 * time.Millisecond,
IsRTCP: true,
Payload: []byte{9},
},
}
hdr := Header{
Start: time.Unix(9, 0).UTC(),
Source: net.IPv4(2, 2, 2, 2),
Port: 2222,
}
writer, err := NewWriter(buf, hdr)
assert.NoError(t, err)
for _, pkt := range packets {
assert.NoError(t, writer.WritePacket(pkt))
}
reader, hdr2, err := NewReader(buf)
assert.NoError(t, err)
assert.Equal(t, hdr, hdr2, "round trip: header")
var packets2 []Packet
for {
pkt, err := reader.Next()
if errors.Is(err, io.EOF) {
break
}
assert.NoError(t, err)
packets2 = append(packets2, pkt)
}
assert.Equal(t, packets, packets2, "round trip: packets")
}