Skip to content

Commit 3436288

Browse files
authored
Binary formatters in packetdump (#304)
* Support for binary formatters in packetdump * Tests, validation, format
1 parent 6022ad6 commit 3436288

File tree

6 files changed

+353
-19
lines changed

6 files changed

+353
-19
lines changed

pkg/packetdump/filter.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ type RTPFilterCallback func(pkt *rtp.Packet) bool
1414

1515
// RTCPFilterCallback can be used to filter RTCP packets to dump.
1616
// The callback returns whether or not to print dump the packet's content.
17+
// Deprecated: prefer RTCPPerPacketFilterCallback
1718
type RTCPFilterCallback func(pkt []rtcp.Packet) bool
19+
20+
// RTCPPerPacketFilterCallback can be used to filter RTCP packets to dump.
21+
// It's called once per every packet opposing to RTCPFilterCallback which is called once per packet batch
22+
type RTCPPerPacketFilterCallback func(pkt rtcp.Packet) bool

pkg/packetdump/format.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,29 @@ import (
1414
// RTPFormatCallback can be used to apply custom formatting to each dumped RTP
1515
// packet. If new lines should be added after each packet, they must be included
1616
// in the returned format.
17+
// Deprecated: prefer RTPBinaryFormatCallback
1718
type RTPFormatCallback func(*rtp.Packet, interceptor.Attributes) string
1819

1920
// RTCPFormatCallback can be used to apply custom formatting to each dumped RTCP
2021
// packet. If new lines should be added after each packet, they must be included
2122
// in the returned format.
23+
// Deprecated: prefer RTCPBinaryFormatCallback
2224
type RTCPFormatCallback func([]rtcp.Packet, interceptor.Attributes) string
2325

2426
// DefaultRTPFormatter returns the default log format for RTP packets
27+
// Deprecated: useless export since set by default
2528
func DefaultRTPFormatter(pkt *rtp.Packet, _ interceptor.Attributes) string {
2629
return fmt.Sprintf("%s\n", pkt)
2730
}
2831

2932
// DefaultRTCPFormatter returns the default log format for RTCP packets
33+
// Deprecated: useless export since set by default
3034
func DefaultRTCPFormatter(pkts []rtcp.Packet, _ interceptor.Attributes) string {
3135
return fmt.Sprintf("%s\n", pkts)
3236
}
37+
38+
// RTPBinaryFormatCallback can be used to apply custom formatting or marshaling to each dumped RTP packet
39+
type RTPBinaryFormatCallback func(*rtp.Packet, interceptor.Attributes) ([]byte, error)
40+
41+
// RTCPBinaryFormatCallback can be used to apply custom formatting or marshaling to each dumped RTCP packet
42+
type RTCPBinaryFormatCallback func(rtcp.Packet, interceptor.Attributes) ([]byte, error)

pkg/packetdump/option.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func RTCPWriter(w io.Writer) PacketDumperOption {
3737
}
3838

3939
// RTPFormatter sets the RTP format
40+
// Deprecated: prefer RTPBinaryFormatter
4041
func RTPFormatter(f RTPFormatCallback) PacketDumperOption {
4142
return func(d *PacketDumper) error {
4243
d.rtpFormat = f
@@ -45,13 +46,30 @@ func RTPFormatter(f RTPFormatCallback) PacketDumperOption {
4546
}
4647

4748
// RTCPFormatter sets the RTCP format
49+
// Deprecated: prefer RTCPBinaryFormatter
4850
func RTCPFormatter(f RTCPFormatCallback) PacketDumperOption {
4951
return func(d *PacketDumper) error {
5052
d.rtcpFormat = f
5153
return nil
5254
}
5355
}
5456

57+
// RTPBinaryFormatter sets the RTP binary formatter
58+
func RTPBinaryFormatter(f RTPBinaryFormatCallback) PacketDumperOption {
59+
return func(d *PacketDumper) error {
60+
d.rtpFormatBinary = f
61+
return nil
62+
}
63+
}
64+
65+
// RTCPBinaryFormatter sets the RTCP binary formatter
66+
func RTCPBinaryFormatter(f RTCPBinaryFormatCallback) PacketDumperOption {
67+
return func(d *PacketDumper) error {
68+
d.rtcpFormatBinary = f
69+
return nil
70+
}
71+
}
72+
5573
// RTPFilter sets the RTP filter.
5674
func RTPFilter(callback RTPFilterCallback) PacketDumperOption {
5775
return func(d *PacketDumper) error {
@@ -61,9 +79,18 @@ func RTPFilter(callback RTPFilterCallback) PacketDumperOption {
6179
}
6280

6381
// RTCPFilter sets the RTCP filter.
82+
// Deprecated: prefer RTCPPerPacketFilter
6483
func RTCPFilter(callback RTCPFilterCallback) PacketDumperOption {
6584
return func(d *PacketDumper) error {
6685
d.rtcpFilter = callback
6786
return nil
6887
}
6988
}
89+
90+
// RTCPPerPacketFilter sets the RTCP per-packet filter.
91+
func RTCPPerPacketFilter(callback RTCPPerPacketFilterCallback) PacketDumperOption {
92+
return func(d *PacketDumper) error {
93+
d.rtcpPerPacketFilter = callback
94+
return nil
95+
}
96+
}

pkg/packetdump/packet_dumper.go

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"github.com/pion/rtp"
1616
)
1717

18+
// ErrBothBinaryAndDeprecatedFormat is returned when both binary and deprecated format callbacks are set
19+
var ErrBothBinaryAndDeprecatedFormat = fmt.Errorf("both binary and deprecated format callbacks are set")
20+
1821
// PacketDumper dumps packet to a io.Writer
1922
type PacketDumper struct {
2023
log logging.LeveledLogger
@@ -28,31 +31,44 @@ type PacketDumper struct {
2831
rtpStream io.Writer
2932
rtcpStream io.Writer
3033

34+
rtpFormatBinary RTPBinaryFormatCallback
35+
rtcpFormatBinary RTCPBinaryFormatCallback
36+
3137
rtpFormat RTPFormatCallback
3238
rtcpFormat RTCPFormatCallback
3339

34-
rtpFilter RTPFilterCallback
35-
rtcpFilter RTCPFilterCallback
40+
rtpFilter RTPFilterCallback
41+
rtcpFilter RTCPFilterCallback
42+
rtcpPerPacketFilter RTCPPerPacketFilterCallback
3643
}
3744

3845
// NewPacketDumper creates a new PacketDumper
3946
func NewPacketDumper(opts ...PacketDumperOption) (*PacketDumper, error) {
4047
d := &PacketDumper{
41-
log: logging.NewDefaultLoggerFactory().NewLogger("packet_dumper"),
42-
wg: sync.WaitGroup{},
43-
close: make(chan struct{}),
44-
rtpChan: make(chan *rtpDump),
45-
rtcpChan: make(chan *rtcpDump),
46-
rtpStream: os.Stdout,
47-
rtcpStream: os.Stdout,
48-
rtpFormat: DefaultRTPFormatter,
49-
rtcpFormat: DefaultRTCPFormatter,
48+
log: logging.NewDefaultLoggerFactory().NewLogger("packet_dumper"),
49+
wg: sync.WaitGroup{},
50+
close: make(chan struct{}),
51+
rtpChan: make(chan *rtpDump),
52+
rtcpChan: make(chan *rtcpDump),
53+
rtpStream: os.Stdout,
54+
rtcpStream: os.Stdout,
55+
rtpFormat: nil,
56+
rtcpFormat: nil,
57+
rtpFormatBinary: nil,
58+
rtcpFormatBinary: nil,
5059
rtpFilter: func(*rtp.Packet) bool {
5160
return true
5261
},
5362
rtcpFilter: func([]rtcp.Packet) bool {
5463
return true
5564
},
65+
rtcpPerPacketFilter: func(rtcp.Packet) bool {
66+
return true
67+
},
68+
}
69+
70+
if d.rtpFormat != nil && d.rtpFormatBinary != nil {
71+
return nil, ErrBothBinaryAndDeprecatedFormat
5672
}
5773

5874
for _, opt := range opts {
@@ -61,6 +77,14 @@ func NewPacketDumper(opts ...PacketDumperOption) (*PacketDumper, error) {
6177
}
6278
}
6379

80+
if d.rtpFormat == nil && d.rtpFormatBinary == nil {
81+
d.rtpFormat = DefaultRTPFormatter
82+
}
83+
84+
if d.rtcpFormat == nil && d.rtcpFormatBinary == nil {
85+
d.rtcpFormat = DefaultRTCPFormatter
86+
}
87+
6488
d.wg.Add(1)
6589
go d.loop()
6690

@@ -117,17 +141,72 @@ func (d *PacketDumper) loop() {
117141
case <-d.close:
118142
return
119143
case dump := <-d.rtpChan:
120-
if d.rtpFilter(dump.packet) {
121-
if _, err := fmt.Fprint(d.rtpStream, d.rtpFormat(dump.packet, dump.attributes)); err != nil {
122-
d.log.Errorf("could not dump RTP packet %v", err)
123-
}
144+
err := d.writeDumpedRTP(dump)
145+
if err != nil {
146+
d.log.Errorf("could not dump RTP packet: %v", err)
124147
}
125148
case dump := <-d.rtcpChan:
126-
if d.rtcpFilter(dump.packets) {
127-
if _, err := fmt.Fprint(d.rtcpStream, d.rtcpFormat(dump.packets, dump.attributes)); err != nil {
128-
d.log.Errorf("could not dump RTCP packet %v", err)
129-
}
149+
err := d.writeDumpedRTCP(dump)
150+
if err != nil {
151+
d.log.Errorf("could not dump RTCP packets: %v", err)
152+
}
153+
}
154+
}
155+
}
156+
157+
func (d *PacketDumper) writeDumpedRTP(dump *rtpDump) error {
158+
if !d.rtpFilter(dump.packet) {
159+
return nil
160+
}
161+
162+
if d.rtpFormatBinary != nil {
163+
dumped, err := d.rtpFormatBinary(dump.packet, dump.attributes)
164+
if err != nil {
165+
return fmt.Errorf("rtp format binary: %w", err)
166+
}
167+
_, err = d.rtpStream.Write(dumped)
168+
if err != nil {
169+
return fmt.Errorf("rtp stream write: %w", err)
170+
}
171+
}
172+
173+
if d.rtpFormat != nil {
174+
if _, err := fmt.Fprint(d.rtpStream, d.rtpFormat(dump.packet, dump.attributes)); err != nil {
175+
return fmt.Errorf("rtp stream Fprint: %w", err)
176+
}
177+
}
178+
179+
return nil
180+
}
181+
182+
func (d *PacketDumper) writeDumpedRTCP(dump *rtcpDump) error {
183+
if !d.rtcpFilter(dump.packets) {
184+
return nil
185+
}
186+
187+
for _, pkt := range dump.packets {
188+
if !d.rtcpPerPacketFilter(pkt) {
189+
continue
190+
}
191+
192+
if d.rtcpFormatBinary != nil {
193+
dumped, err := d.rtcpFormatBinary(pkt, dump.attributes)
194+
if err != nil {
195+
return fmt.Errorf("rtcp format binary: %w", err)
196+
}
197+
198+
_, err = d.rtcpStream.Write(dumped)
199+
if err != nil {
200+
return fmt.Errorf("rtcp stream write: %w", err)
130201
}
131202
}
132203
}
204+
205+
if d.rtcpFormat != nil {
206+
if _, err := fmt.Fprint(d.rtcpStream, d.rtcpFormat(dump.packets, dump.attributes)); err != nil {
207+
return fmt.Errorf("rtсp stream Fprint: %w", err)
208+
}
209+
}
210+
211+
return nil
133212
}

pkg/packetdump/receiver_interceptor_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,106 @@ func TestReceiverFilterNothing(t *testing.T) {
108108

109109
assert.NotZero(t, buf.Len())
110110
}
111+
112+
func TestReceiverCustomBinaryFormatter(t *testing.T) {
113+
rtpBuf := bytes.Buffer{}
114+
rtcpBuf := bytes.Buffer{}
115+
116+
factory, err := NewReceiverInterceptor(
117+
RTPWriter(&rtpBuf),
118+
RTCPWriter(&rtcpBuf),
119+
Log(logging.NewDefaultLoggerFactory().NewLogger("test")),
120+
// custom binary formatter to dump only seqno mod 256
121+
RTPBinaryFormatter(func(p *rtp.Packet, _ interceptor.Attributes) ([]byte, error) {
122+
return []byte{byte(p.SequenceNumber)}, nil
123+
}),
124+
// custom binary formatter to dump only DestinationSSRCs mod 256
125+
RTCPBinaryFormatter(func(p rtcp.Packet, _ interceptor.Attributes) ([]byte, error) {
126+
b := make([]byte, 0)
127+
for _, ssrc := range p.DestinationSSRC() {
128+
b = append(b, byte(ssrc))
129+
}
130+
return b, nil
131+
}),
132+
)
133+
assert.NoError(t, err)
134+
135+
i, err := factory.NewInterceptor("")
136+
assert.NoError(t, err)
137+
138+
assert.EqualValues(t, 0, rtpBuf.Len())
139+
assert.EqualValues(t, 0, rtcpBuf.Len())
140+
141+
stream := test.NewMockStream(&interceptor.StreamInfo{
142+
SSRC: 123456,
143+
ClockRate: 90000,
144+
}, i)
145+
defer func() {
146+
assert.NoError(t, stream.Close())
147+
}()
148+
149+
stream.ReceiveRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{
150+
SenderSSRC: 123,
151+
MediaSSRC: 45,
152+
}})
153+
stream.ReceiveRTP(&rtp.Packet{Header: rtp.Header{
154+
SequenceNumber: uint16(123),
155+
}})
156+
157+
// Give time for packets to be handled and stream written to.
158+
time.Sleep(50 * time.Millisecond)
159+
160+
err = i.Close()
161+
assert.NoError(t, err)
162+
163+
// check that there is custom formatter results in buffer
164+
assert.Equal(t, []byte{123}, rtpBuf.Bytes())
165+
assert.Equal(t, []byte{45}, rtcpBuf.Bytes())
166+
}
167+
168+
func TestReceiverRTCPPerPacketFilter(t *testing.T) {
169+
buf := bytes.Buffer{}
170+
171+
factory, err := NewReceiverInterceptor(
172+
RTCPWriter(&buf),
173+
Log(logging.NewDefaultLoggerFactory().NewLogger("test")),
174+
RTCPPerPacketFilter(func(packet rtcp.Packet) bool {
175+
_, isPli := packet.(*rtcp.PictureLossIndication)
176+
return isPli
177+
}),
178+
RTCPBinaryFormatter(func(p rtcp.Packet, _ interceptor.Attributes) ([]byte, error) {
179+
assert.IsType(t, &rtcp.PictureLossIndication{}, p)
180+
return []byte{123}, nil
181+
}),
182+
)
183+
assert.NoError(t, err)
184+
185+
i, err := factory.NewInterceptor("")
186+
assert.NoError(t, err)
187+
188+
assert.EqualValues(t, 0, buf.Len())
189+
190+
stream := test.NewMockStream(&interceptor.StreamInfo{
191+
SSRC: 123456,
192+
ClockRate: 90000,
193+
}, i)
194+
defer func() {
195+
assert.NoError(t, stream.Close())
196+
}()
197+
198+
stream.ReceiveRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{
199+
SenderSSRC: 123,
200+
MediaSSRC: 456,
201+
}})
202+
stream.ReceiveRTCP([]rtcp.Packet{&rtcp.ReceiverReport{
203+
SSRC: 789,
204+
}})
205+
// Give time for packets to be handled and stream written to.
206+
time.Sleep(50 * time.Millisecond)
207+
208+
err = i.Close()
209+
assert.NoError(t, err)
210+
211+
// Only single PictureLossIndication should have been written.
212+
assert.Equal(t, []byte{123}, buf.Bytes())
213+
}

0 commit comments

Comments
 (0)