1
1
mirror of https://github.com/wader/fq.git synced 2024-11-26 21:55:57 +03:00
fq/format/rtmp/amf0.go
Mattias Wadman d8aaf30345 rtmp,amf0: Add decoders
Basic RTMP support, used via tcp_stream group (pcap etc) or manually.
Basic AMF0 support, mostly what is used in RTMP.
2022-04-01 13:07:20 +02:00

137 lines
3.3 KiB
Go

package rtmp
// https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf
import (
"github.com/wader/fq/format"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
func init() {
registry.MustRegister(decode.Format{
Name: format.AMF0,
Description: "Action Message Format 0",
DecodeFn: amf0Decode,
})
}
const (
typeNumber = 0x0
typeBoolean = 0x1
typeString = 0x2
typeObject = 0x3
typeMovieClip = 0x4
typeNull = 0x5
typeUndefined = 0x6
typeReference = 0x7
typeECMAArray = 0x8
typeObjectEnd = 0x9
typeStrictArray = 0xa
typeDate = 0xb
typeLongString = 0xc
typeUnsupported = 0xd
typeRecordSet = 0xe
typeXML = 0xf
typeTypedObject = 0x10
)
var typeNames = scalar.UToSymStr{
typeNumber: "number",
typeBoolean: "boolean",
typeString: "string",
typeObject: "object",
typeMovieClip: "movie_clip",
typeNull: "null",
typeUndefined: "undefined",
typeReference: "reference",
typeECMAArray: "ecma_array",
typeObjectEnd: "object_end",
typeStrictArray: "strict_array",
typeDate: "date",
typeLongString: "long_string",
typeUnsupported: "unsupported",
typeRecordSet: "record_set",
typeXML: "xml",
typeTypedObject: "typed_object",
}
func amf0DecodeString(d *decode.D) string { return d.UTF8(int(d.U16())) }
func amf0DecodeStringLong(d *decode.D) string { return d.UTF8(int(d.U32())) }
func amf0DecodeValue(d *decode.D) {
typ := d.FieldU8("type", typeNames)
switch typ {
case typeNumber:
d.FieldF64("value")
case typeBoolean:
d.FieldU8("value")
case typeString:
d.FieldStrFn("value", amf0DecodeString)
case typeObject:
d.FieldArray("value", func(d *decode.D) {
var typ uint64
for typ != typeObjectEnd {
d.FieldStruct("pair", func(d *decode.D) {
d.FieldStrFn("key", amf0DecodeString)
typ = d.PeekBits(8)
d.FieldStruct("value", amf0DecodeValue)
})
}
})
case typeNull:
d.FieldValueNil("value")
case typeUndefined:
d.FieldValueNil("value") // TODO: ?
case typeReference:
d.FieldU16("value") // TODO: index pointer
case typeECMAArray:
count := d.FieldU32("count")
d.FieldArray("value", func(d *decode.D) {
d.FieldStruct("entry", func(d *decode.D) {
for i := uint64(0); i < count; i++ {
d.FieldStrFn("key", amf0DecodeString)
d.FieldStruct("value", amf0DecodeValue)
}
})
})
case typeObjectEnd:
// nop
case typeStrictArray:
length := d.FieldU32("count")
d.FieldArray("value", func(d *decode.D) {
for i := uint64(0); i < length; i++ {
d.FieldStruct("entry", amf0DecodeValue)
}
})
case typeDate:
d.FieldF64("date_time")
d.FieldS16("local_data_time_offset")
case typeLongString:
d.FieldStrFn("value", amf0DecodeStringLong)
case typeXML:
d.FieldStrFn("value", amf0DecodeStringLong)
case typeTypedObject:
d.FieldStrFn("class_name", amf0DecodeString)
d.FieldArray("value", func(d *decode.D) {
var typ uint64
for typ != typeObjectEnd {
d.FieldStruct("pair", func(d *decode.D) {
d.FieldStrFn("key", amf0DecodeString)
typ = d.PeekBits(8)
d.FieldStruct("value", amf0DecodeValue)
})
}
})
default:
d.Fatalf("unknown type %d", typ)
}
}
func amf0Decode(d *decode.D, in interface{}) interface{} {
amf0DecodeValue(d)
return nil
}