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
108 lines
2.2 KiB
Go
108 lines
2.2 KiB
Go
// 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
|
|
}
|