mirror of
https://github.com/wader/fq.git
synced 2024-12-20 11:51:58 +03:00
151 lines
3.5 KiB
Go
151 lines
3.5 KiB
Go
package rtmp
|
|
|
|
// https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf
|
|
|
|
import (
|
|
"github.com/wader/fq/format"
|
|
"github.com/wader/fq/pkg/decode"
|
|
"github.com/wader/fq/pkg/interp"
|
|
"github.com/wader/fq/pkg/scalar"
|
|
)
|
|
|
|
func init() {
|
|
interp.RegisterFormat(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 amf0DecodeValue(d *decode.D) {
|
|
typ := d.FieldU8("type", typeNames)
|
|
|
|
switch typ {
|
|
case typeNumber:
|
|
d.FieldF64("value")
|
|
case typeBoolean:
|
|
d.FieldU8("value")
|
|
case typeString:
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
case typeObject:
|
|
d.FieldArray("value", func(d *decode.D) {
|
|
var typ uint64
|
|
for typ != typeObjectEnd {
|
|
d.FieldStruct("pair", func(d *decode.D) {
|
|
d.FieldStruct("key", func(d *decode.D) {
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
})
|
|
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:
|
|
d.FieldU32("count")
|
|
d.FieldArray("value", func(d *decode.D) {
|
|
var typ uint64
|
|
for typ != typeObjectEnd {
|
|
d.FieldStruct("entry", func(d *decode.D) {
|
|
d.FieldStruct("key", func(d *decode.D) {
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
})
|
|
typ = d.PeekBits(8)
|
|
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:
|
|
l := d.FieldU32("length")
|
|
d.FieldUTF8("value", int(l))
|
|
case typeXML:
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
case typeTypedObject:
|
|
d.FieldStruct("class_name", func(d *decode.D) {
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
})
|
|
d.FieldArray("value", func(d *decode.D) {
|
|
var typ uint64
|
|
for typ != typeObjectEnd {
|
|
d.FieldStruct("pair", func(d *decode.D) {
|
|
d.FieldStruct("key", func(d *decode.D) {
|
|
l := d.FieldU16("length")
|
|
d.FieldUTF8("value", int(l))
|
|
})
|
|
typ = d.PeekBits(8)
|
|
d.FieldStruct("value", amf0DecodeValue)
|
|
})
|
|
}
|
|
})
|
|
default:
|
|
d.Fatalf("unknown type %d", typ)
|
|
}
|
|
}
|
|
|
|
func amf0Decode(d *decode.D, _ any) any {
|
|
amf0DecodeValue(d)
|
|
return nil
|
|
}
|