From 3001499cae03a79d1edc8f92f22f4d64a61af5b4 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 7 Apr 2024 14:05:07 +1000 Subject: [PATCH] Add RTMP packet formats --- atalk/rtmp/data.go | 89 ++++++++++++++++++++++++++++++++++++++++++ atalk/rtmp/request.go | 15 +++++++ atalk/rtmp/response.go | 43 ++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 atalk/rtmp/data.go create mode 100644 atalk/rtmp/request.go create mode 100644 atalk/rtmp/response.go diff --git a/atalk/rtmp/data.go b/atalk/rtmp/data.go new file mode 100644 index 0000000..77be855 --- /dev/null +++ b/atalk/rtmp/data.go @@ -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 +} diff --git a/atalk/rtmp/request.go b/atalk/rtmp/request.go new file mode 100644 index 0000000..ce9a2ae --- /dev/null +++ b/atalk/rtmp/request.go @@ -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 +} diff --git a/atalk/rtmp/response.go b/atalk/rtmp/response.go new file mode 100644 index 0000000..2cbaec6 --- /dev/null +++ b/atalk/rtmp/response.go @@ -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 +}