Add RTMP packet formats

This commit is contained in:
Josh Deprez 2024-04-07 14:05:07 +10:00
parent ce7b360dff
commit 3001499cae
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
3 changed files with 147 additions and 0 deletions

89
atalk/rtmp/data.go Normal file
View file

@ -0,0 +1,89 @@
package rtmp
import (
"encoding/binary"
"fmt"
"github.com/sfiera/multitalk/pkg/ddp"
)
// DataPacket represents an RTMP Data packet.
type DataPacket struct {
RouterAddr ddp.Addr
NetworkTuples []NetworkTuple
}
// NetworkTuple represents routing information.
type NetworkTuple struct {
Extended bool
RangeStart ddp.Network
RangeEnd ddp.Network
Distance uint8
}
// UnmarshalDataPacket unmarshals a DataPacket.
func UnmarshalDataPacket(data []byte) (*DataPacket, error) {
if len(data) < 7 || (len(data)-4)%3 != 0 {
return nil, fmt.Errorf("invalid input length %d for RTMP Data packet", len(data))
}
if data[2] != 8 {
return nil, fmt.Errorf("unsupported node ID length %d for RTMP Data packet", data[2])
}
dp := &DataPacket{
RouterAddr: ddp.Addr{
Network: ddp.Network(binary.BigEndian.Uint16(data[:2])),
Node: ddp.Node(data[3]),
},
}
data = data[4:]
first := true
for len(data) > 0 {
if len(data) < 3 {
return nil, fmt.Errorf("insufficient remaining input length %d for RTMP Data network tuple", len(data))
}
nt := NetworkTuple{
RangeStart: ddp.Network(binary.BigEndian.Uint16(data[:2])),
Distance: data[2],
}
data = data[3:]
if nt.RangeStart == 0 {
// if non-extended, first tuple should contain version
if !first {
return nil, fmt.Errorf("invalid RTMP network tuple range start 0")
}
// initial non-extended tuple with Distance field containing version
if nt.Distance != 0x82 {
return nil, fmt.Errorf("unsupported RTMP version %x", nt.Distance)
}
first = false
continue
}
nt.Extended = nt.Distance&0x80 != 0
if !nt.Extended {
// ordinary non-extended tuple
if first && nt.RangeStart != 0 {
return nil, fmt.Errorf("first RTMP network tuple is not version tuple")
}
dp.NetworkTuples = append(dp.NetworkTuples, nt)
continue
}
// extended tuple
if len(data) < 3 {
return nil, fmt.Errorf("insufficient remaining input length %d for RTMP Data extended network tuple", len(data))
}
nt.Distance &^= 0x80
nt.RangeEnd = ddp.Network(binary.BigEndian.Uint16(data[:2]))
if first {
if data[2] != 0x82 {
return nil, fmt.Errorf("unsupported RTMP version %x", data[2])
}
}
first = false
dp.NetworkTuples = append(dp.NetworkTuples, nt)
data = data[3:]
}
return dp, nil
}

15
atalk/rtmp/request.go Normal file
View file

@ -0,0 +1,15 @@
package rtmp
import "fmt"
// RequestPacket represents an RTMP Request or RTMP Route Data Request packet.
type RequestPacket struct {
Function uint8
}
func UnmarshalRequestPacket(data []byte) (*RequestPacket, error) {
if len(data) != 1 {
return nil, fmt.Errorf("invalid data length %d for RTMP Request or RTMP RDR packet", len(data))
}
return &RequestPacket{Function: data[0]}, nil
}

43
atalk/rtmp/response.go Normal file
View file

@ -0,0 +1,43 @@
package rtmp
import (
"encoding/binary"
"fmt"
"github.com/sfiera/multitalk/pkg/ddp"
)
type ResponsePacket struct {
SenderAddr ddp.Addr
Extended bool
RangeStart ddp.Network
RangeEnd ddp.Network
}
func UnmarshalResponsePacket(data []byte) (*ResponsePacket, error) {
if len(data) != 4 && len(data) != 10 {
return nil, fmt.Errorf("invalid input length %d for RTMP Response packet", len(data))
}
if data[2] != 8 {
return nil, fmt.Errorf("unsupported node ID length %d for RTMP Response packet", data[2])
}
rp := &ResponsePacket{
SenderAddr: ddp.Addr{
Network: ddp.Network(binary.BigEndian.Uint16(data[:2])),
Node: ddp.Node(data[3]),
},
}
if len(data) == 4 {
return rp, nil
}
rp.RangeStart = ddp.Network(binary.BigEndian.Uint16(data[4:6]))
if data[6] != 0x80 {
return nil, fmt.Errorf("invalid intermediate byte %x for RTMP Response packet", data[6])
}
rp.RangeEnd = ddp.Network(binary.BigEndian.Uint16(data[7:9]))
if data[9] != 0x82 {
return nil, fmt.Errorf("unsupported version %x for RTMP Response packet", data[9])
}
return rp, nil
}