@@ -1,3 +1,4 @@
|
||||
// Package amf0 contains an AMF0 decoder and encoder.
|
||||
package amf0
|
||||
|
||||
import (
|
||||
@@ -28,9 +29,15 @@ const (
|
||||
|
||||
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) ([]interface{}, error) {
|
||||
var out []interface{}
|
||||
func Unmarshal(buf []byte) (Data, error) {
|
||||
var out Data
|
||||
|
||||
for len(buf) != 0 {
|
||||
var item interface{}
|
||||
@@ -210,3 +217,207 @@ func unmarshal(buf []byte) (interface{}, []byte, error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -9,7 +9,7 @@ import (
|
||||
var cases = []struct {
|
||||
name string
|
||||
enc []byte
|
||||
dec []interface{}
|
||||
dec Data
|
||||
}{
|
||||
{
|
||||
"on metadata",
|
||||
@@ -316,6 +316,16 @@ func TestUnmarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
enc, err := ca.dec.Marshal()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.enc, enc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzUnmarshal(f *testing.F) {
|
||||
for _, ca := range cases {
|
||||
f.Add(ca.enc)
|
||||
@@ -324,7 +334,7 @@ func FuzzUnmarshal(f *testing.F) {
|
||||
f.Fuzz(func(_ *testing.T, b []byte) {
|
||||
what, err := Unmarshal(b)
|
||||
if err == nil {
|
||||
Marshal(what) //nolint:errcheck
|
||||
what.Marshal() //nolint:errcheck
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
// Package amf0 contains an AMF0 marshaler and unmarshaler.
|
||||
package amf0
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Marshal encodes AMF0 data.
|
||||
func Marshal(data []interface{}) ([]byte, error) {
|
||||
n, err := marshalSize(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
n = 0
|
||||
|
||||
for _, item := range data {
|
||||
n += marshalItem(item, buf[n:])
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func marshalSize(data []interface{}) (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
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package amf0
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
enc, err := Marshal(ca.dec)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.enc, enc)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,6 @@ type Object []ObjectEntry
|
||||
// ECMAArray is an AMF0 ECMA Array.
|
||||
type ECMAArray Object
|
||||
|
||||
// StrictArray is an AMF0 Strict Array.
|
||||
type StrictArray []interface{}
|
||||
|
||||
// Get returns the value corresponding to key.
|
||||
func (o Object) Get(key string) (interface{}, bool) {
|
||||
for _, item := range o {
|
||||
|
||||
@@ -13,7 +13,7 @@ type CommandAMF0 struct {
|
||||
MessageStreamID uint32
|
||||
Name string
|
||||
CommandID int
|
||||
Arguments []interface{}
|
||||
Arguments amf0.Data
|
||||
}
|
||||
|
||||
func (m *CommandAMF0) unmarshal(raw *rawmessage.Message) error {
|
||||
@@ -47,12 +47,12 @@ func (m *CommandAMF0) unmarshal(raw *rawmessage.Message) error {
|
||||
}
|
||||
|
||||
func (m CommandAMF0) marshal() (*rawmessage.Message, error) {
|
||||
data := append([]interface{}{
|
||||
data := append(amf0.Data{
|
||||
m.Name,
|
||||
float64(m.CommandID),
|
||||
}, m.Arguments...)
|
||||
|
||||
body, err := amf0.Marshal(data)
|
||||
body, err := data.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,24 +9,24 @@ import (
|
||||
type DataAMF0 struct {
|
||||
ChunkStreamID byte
|
||||
MessageStreamID uint32
|
||||
Payload []interface{}
|
||||
Payload amf0.Data
|
||||
}
|
||||
|
||||
func (m *DataAMF0) unmarshal(raw *rawmessage.Message) error {
|
||||
m.ChunkStreamID = raw.ChunkStreamID
|
||||
m.MessageStreamID = raw.MessageStreamID
|
||||
|
||||
payload, err := amf0.Unmarshal(raw.Body)
|
||||
var err error
|
||||
m.Payload, err = amf0.Unmarshal(raw.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Payload = payload
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m DataAMF0) marshal() (*rawmessage.Message, error) {
|
||||
body, err := amf0.Marshal(m.Payload)
|
||||
body, err := m.Payload.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,25 +2,71 @@ package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
|
||||
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage"
|
||||
)
|
||||
|
||||
// ExtendedMetadata is a metadata extended message.
|
||||
type ExtendedMetadata struct {
|
||||
FourCC FourCC
|
||||
ChunkStreamID byte
|
||||
DTS time.Duration
|
||||
MessageStreamID uint32
|
||||
FourCC FourCC
|
||||
Payload amf0.Data
|
||||
}
|
||||
|
||||
func (m *ExtendedMetadata) unmarshal(raw *rawmessage.Message) error {
|
||||
if len(raw.Body) != 5 {
|
||||
if len(raw.Body) < 6 {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
m.ChunkStreamID = raw.ChunkStreamID
|
||||
m.DTS = raw.Timestamp
|
||||
m.MessageStreamID = raw.MessageStreamID
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
return fmt.Errorf("ExtendedMetadata is not implemented yet")
|
||||
var err error
|
||||
m.Payload, err = amf0.Unmarshal(raw.Body[5:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m ExtendedMetadata) marshalBodySize() (int, error) {
|
||||
ms, err := m.Payload.MarshalSize()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 5 + ms, nil
|
||||
}
|
||||
|
||||
func (m ExtendedMetadata) marshal() (*rawmessage.Message, error) {
|
||||
return nil, fmt.Errorf("TODO")
|
||||
mbs, err := m.marshalBodySize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body := make([]byte, mbs)
|
||||
|
||||
body[0] = 0b10000000 | byte(ExtendedTypeMetadata)
|
||||
body[1] = uint8(m.FourCC >> 24)
|
||||
body[2] = uint8(m.FourCC >> 16)
|
||||
body[3] = uint8(m.FourCC >> 8)
|
||||
body[4] = uint8(m.FourCC)
|
||||
|
||||
_, err = m.Payload.MarshalTo(body[5:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rawmessage.Message{
|
||||
ChunkStreamID: m.ChunkStreamID,
|
||||
Timestamp: m.DTS,
|
||||
Type: uint8(TypeVideo),
|
||||
MessageStreamID: m.MessageStreamID,
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type ExtendedMPEG2TSSequenceStart struct {
|
||||
}
|
||||
|
||||
func (m *ExtendedMPEG2TSSequenceStart) unmarshal(raw *rawmessage.Message) error {
|
||||
if len(raw.Body) != 5 {
|
||||
if len(raw.Body) < 5 {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type ExtendedSequenceEnd struct {
|
||||
}
|
||||
|
||||
func (m *ExtendedSequenceEnd) unmarshal(raw *rawmessage.Message) error {
|
||||
if len(raw.Body) != 5 {
|
||||
if len(raw.Body) < 5 {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
|
||||
@@ -277,6 +277,25 @@ var readWriterCases = []struct {
|
||||
0x31, 0x01, 0x02, 0x03,
|
||||
},
|
||||
},
|
||||
{
|
||||
"extended metadata",
|
||||
&ExtendedMetadata{
|
||||
ChunkStreamID: 0x6,
|
||||
DTS: 0,
|
||||
MessageStreamID: 0x1000000,
|
||||
FourCC: 0x68766331,
|
||||
Payload: []interface{}{"colorInfo", amf0.Object{amf0.ObjectEntry{Key: "colorConfig", Value: amf0.Object{}}}},
|
||||
},
|
||||
[]byte{
|
||||
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x09,
|
||||
0x01, 0x00, 0x00, 0x00, 0x84, 0x68, 0x76, 0x63,
|
||||
0x31, 0x02, 0x00, 0x09, 0x63, 0x6f, 0x6c, 0x6f,
|
||||
0x72, 0x49, 0x6e, 0x66, 0x6f, 0x03, 0x00, 0x0b,
|
||||
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x03, 0x00, 0x00, 0x09, 0x00,
|
||||
0x00, 0x09,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0000\x00\x00\b\t0000\x94hvc1000")
|
||||
Reference in New Issue
Block a user