Files
rtsp-simple-server/internal/protocols/rtmp/amf0/data.go
2025-07-26 16:44:32 +02:00

424 lines
7.1 KiB
Go

// Package amf0 contains an AMF0 decoder and encoder.
package amf0
import (
"errors"
"fmt"
"math"
)
const (
markerNumber = 0x00
markerBoolean = 0x01
markerString = 0x02
markerObject = 0x03
markerMovieclip = 0x04
markerNull = 0x05
markerUndefined = 0x06
markerReference = 0x07
markerECMAArray = 0x08
markerObjectEnd = 0x09
markerStrictArray = 0x0A
markerDate = 0x0B
markerLongString = 0x0C
markerUnsupported = 0x0D
markerRecordset = 0x0E
markerXMLDocument = 0xF
markerTypedObject = 0x10
)
var errBufferTooShort = errors.New("buffer is too short")
// StrictArray is an AMF0 Strict Array.
type StrictArray []interface{}
// Data is a list of ActionScript object graphs.
type Data []interface{}
// Unmarshal decodes AMF0 data.
func Unmarshal(buf []byte) (Data, error) {
var out Data
for len(buf) != 0 {
var item interface{}
var err error
item, buf, err = unmarshal(buf)
if err != nil {
return nil, err
}
out = append(out, item)
}
return out, nil
}
func unmarshal(buf []byte) (interface{}, []byte, error) {
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
var marker byte
marker, buf = buf[0], buf[1:]
switch marker {
case markerNumber:
if len(buf) < 8 {
return nil, nil, errBufferTooShort
}
return math.Float64frombits(uint64(buf[0])<<56 | uint64(buf[1])<<48 | uint64(buf[2])<<40 | uint64(buf[3])<<32 |
uint64(buf[4])<<24 | uint64(buf[5])<<16 | uint64(buf[6])<<8 | uint64(buf[7])), buf[8:], nil
case markerBoolean:
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
return (buf[0] != 0), buf[1:], nil
case markerString:
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
le := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if len(buf) < int(le) {
return nil, nil, errBufferTooShort
}
return string(buf[:le]), buf[le:], nil
case markerECMAArray:
if len(buf) < 4 {
return nil, nil, errBufferTooShort
}
buf = buf[4:]
out := ECMAArray{}
for {
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
keyLen := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if keyLen == 0 {
break
}
if len(buf) < int(keyLen) {
return nil, nil, errBufferTooShort
}
key := string(buf[:keyLen])
buf = buf[keyLen:]
var value interface{}
var err error
value, buf, err = unmarshal(buf)
if err != nil {
return nil, nil, err
}
out = append(out, ObjectEntry{
Key: key,
Value: value,
})
}
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
if buf[0] != markerObjectEnd {
return nil, nil, fmt.Errorf("object end not found")
}
return out, buf[1:], nil
case markerObject:
out := Object{}
for {
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
keyLen := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if keyLen == 0 {
break
}
if len(buf) < int(keyLen) {
return nil, nil, errBufferTooShort
}
key := string(buf[:keyLen])
buf = buf[keyLen:]
var value interface{}
var err error
value, buf, err = unmarshal(buf)
if err != nil {
return nil, nil, err
}
out = append(out, ObjectEntry{
Key: key,
Value: value,
})
}
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
if buf[0] != markerObjectEnd {
return nil, nil, fmt.Errorf("object end not found")
}
return out, buf[1:], nil
case markerNull:
return nil, buf, nil
case markerStrictArray:
if len(buf) < 4 {
return nil, nil, errBufferTooShort
}
arrayCount := uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3])
buf = buf[4:]
out := StrictArray{}
for i := 0; i < int(arrayCount); i++ {
var value interface{}
var err error
value, buf, err = unmarshal(buf)
if err != nil {
return nil, nil, err
}
out = append(out, value)
}
return out, buf, nil
default:
return nil, nil, fmt.Errorf("unsupported marker 0x%.2x", marker)
}
}
// Marshal encodes AMF0 data.
func (data Data) Marshal() ([]byte, error) {
n, err := data.MarshalSize()
if err != nil {
return nil, err
}
buf := make([]byte, n)
_, err = data.MarshalTo(buf)
if err != nil {
return nil, err
}
return buf, nil
}
// MarshalTo encodes AMF0 data into an existing buffer.
func (data Data) MarshalTo(buf []byte) (int, error) {
n := 0
for _, item := range data {
n += marshalItem(item, buf[n:])
}
return n, nil
}
// MarshalSize returns the size needed to encode data in AMF0.
func (data Data) MarshalSize() (int, error) {
n := 0
for _, item := range data {
in, err := marshalSizeItem(item)
if err != nil {
return 0, err
}
n += in
}
return n, nil
}
func marshalSizeItem(item interface{}) (int, error) {
switch item := item.(type) {
case float64:
return 9, nil
case bool:
return 2, nil
case string:
return 3 + len(item), nil
case ECMAArray:
n := 5
for _, entry := range item {
en, err := marshalSizeItem(entry.Value)
if err != nil {
return 0, err
}
n += 2 + len(entry.Key) + en
}
n += 3
return n, nil
case Object:
n := 1
for _, entry := range item {
en, err := marshalSizeItem(entry.Value)
if err != nil {
return 0, err
}
n += 2 + len(entry.Key) + en
}
n += 3
return n, nil
case StrictArray:
n := 5
for _, entry := range item {
en, err := marshalSizeItem(entry)
if err != nil {
return 0, err
}
n += en
}
return n, nil
case nil:
return 1, nil
default:
return 0, fmt.Errorf("unsupported data type: %T", item)
}
}
func marshalItem(item interface{}, buf []byte) int {
switch item := item.(type) {
case float64:
v := math.Float64bits(item)
buf[0] = markerNumber
buf[1] = byte(v >> 56)
buf[2] = byte(v >> 48)
buf[3] = byte(v >> 40)
buf[4] = byte(v >> 32)
buf[5] = byte(v >> 24)
buf[6] = byte(v >> 16)
buf[7] = byte(v >> 8)
buf[8] = byte(v)
return 9
case bool:
buf[0] = markerBoolean
if item {
buf[1] = 1
}
return 2
case string:
le := len(item)
buf[0] = markerString
buf[1] = byte(le >> 8)
buf[2] = byte(le)
copy(buf[3:], item)
return 3 + le
case ECMAArray:
le := len(item)
buf[0] = markerECMAArray
buf[1] = byte(le >> 24)
buf[2] = byte(le >> 16)
buf[3] = byte(le >> 8)
buf[4] = byte(le)
n := 5
for _, entry := range item {
le = len(entry.Key)
buf[n] = byte(le >> 8)
buf[n+1] = byte(le)
copy(buf[n+2:], entry.Key)
n += 2 + le
n += marshalItem(entry.Value, buf[n:])
}
buf[n] = 0
buf[n+1] = 0
buf[n+2] = markerObjectEnd
return n + 3
case Object:
buf[0] = markerObject
n := 1
for _, entry := range item {
le := len(entry.Key)
buf[n] = byte(le >> 8)
buf[n+1] = byte(le)
copy(buf[n+2:], entry.Key)
n += 2 + le
n += marshalItem(entry.Value, buf[n:])
}
buf[n] = 0
buf[n+1] = 0
buf[n+2] = markerObjectEnd
return n + 3
case StrictArray:
le := len(item)
buf[0] = markerStrictArray
buf[1] = byte(le >> 24)
buf[2] = byte(le >> 16)
buf[3] = byte(le >> 8)
buf[4] = byte(le)
n := 5
for _, entry := range item {
n += marshalItem(entry, buf[n:])
}
return n
default:
buf[0] = markerNull
return 1
}
}