av/rtmp/amf/amf.go

557 lines
11 KiB
Go

/*
NAME
amf.go
DESCRIPTION
Action Message Format (AMF) encoding/decoding functions.
See https://en.wikipedia.org/wiki/Action_Message_Format.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>
Dan Kortschak <dan@ausocean.org>
Jake Lane <jake@ausocean.org>
Alan Noble <alan@ausocean.org>
LICENSE
amf.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
Derived from librtmp under the GNU Lesser General Public License 2.1
Copyright (C) 2005-2008 Team XBMC http://www.xbmc.org
Copyright (C) 2008-2009 Andrej Stepanchuk
Copyright (C) 2009-2010 Howard Chu
*/
// amf implements Action Message Format (AMF) encoding and decoding.
// See https://en.wikipedia.org/wiki/Action_Message_Format.
package amf
import (
"encoding/binary"
"math"
)
// AMF data types.
const (
Number = iota
Boolean
String
Object
MovieClip // reserved, not implemented
Null
Undefined
Reference
EcmaArray
ObjectEnd
StrictArray
Date
LongSring
Unsupported
Recordset // reserved, not implemented
XmlDoc
TypedObject
Avmplus // reserved, not implemented
Invalid = 0xff
)
// DataType represents an AMF data type, which is simply an index into the above.
type DataType int32
// AMF represents an AMF message, which is simply a collection of properties.
type AMF struct {
Props []Property // ToDo: consider not exporting this
}
// Property represents an AMF property.
type Property struct {
name string
atype DataType
vu vu
UTCoffset int16
}
type vu struct {
number float64
aval string
obj AMF
}
var (
ObjInvalid AMF
PropInvalid = Property{atype: Invalid}
)
func DecodeInt16(data []byte) uint16 {
return uint16(binary.BigEndian.Uint16(data))
}
func DecodeInt24(data []byte) uint32 {
return uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
// return uint16(data[0])<<8 | uint16(data[1])
}
func DecodeInt32(data []byte) uint32 {
return uint32(binary.BigEndian.Uint32(data))
}
func DecodeString(data []byte) string {
n := DecodeInt16(data)
return string(data[2 : 2+n])
}
func DecodeLongString(data []byte) string {
n := DecodeInt32(data)
return string(data[2 : 2+n])
}
func DecodeNumber(data []byte) float64 {
return math.Float64frombits(binary.BigEndian.Uint64(data))
}
func DecodeBoolean(data []byte) bool {
return data[0] != 0
}
func EncodeInt24(dst []byte, val int32) []byte {
if len(dst) < 3 {
return nil
}
dst[0] = byte(val >> 16)
dst[1] = byte(val >> 8)
dst[2] = byte(val)
if len(dst) == 3 {
return nil
}
return dst[3:]
}
func EncodeInt32(dst []byte, val int32) []byte {
if len(dst) < 4 {
return nil
}
binary.BigEndian.PutUint32(dst, uint32(val))
if len(dst) == 4 {
return nil
}
return dst[4:]
}
func EncodeString(dst []byte, val string) []byte {
const typeSize = 1
if len(val) < 65536 && len(val)+typeSize+binary.Size(int16(0)) > len(dst) {
return nil
}
if len(val)+typeSize+binary.Size(int32(0)) > len(dst) {
return nil
}
if len(val) < 65536 {
dst[0] = String
dst = dst[1:]
binary.BigEndian.PutUint16(dst[:2], uint16(len(val)))
dst = dst[2:]
copy(dst, val)
if len(dst) == len(val) {
return nil
}
return dst[len(val):]
}
dst[0] = LongSring
dst = dst[1:]
binary.BigEndian.PutUint32(dst[:4], uint32(len(val)))
dst = dst[4:]
copy(dst, val)
if len(dst) == len(val) {
return nil
}
return dst[len(val):]
}
func EncodeNumber(dst []byte, val float64) []byte {
if len(dst) < 9 {
return nil
}
dst[0] = Number
dst = dst[1:]
binary.BigEndian.PutUint64(dst, math.Float64bits(val))
return dst[8:]
}
func EncodeBoolean(dst []byte, val bool) []byte {
if len(dst) < 2 {
return nil
}
dst[0] = Boolean
if val {
dst[1] = 1
}
if len(dst) == 2 {
return nil
}
return dst[2:]
}
func EncodeNamedString(dst []byte, key, val string) []byte {
if 2+len(key) > len(dst) {
return nil
}
binary.BigEndian.PutUint16(dst[:2], uint16(len(key)))
dst = dst[2:]
copy(dst, key)
if len(key) == len(dst) {
return nil
}
return EncodeString(dst[len(key):], val)
}
func EncodeNamedNumber(dst []byte, key string, val float64) []byte {
if 2+len(key) > len(dst) {
return nil
}
binary.BigEndian.PutUint16(dst[:2], uint16(len(key)))
dst = dst[2:]
copy(dst, key)
if len(key) == len(dst) {
return nil
}
return EncodeNumber(dst[len(key):], val)
}
func EncodeNamedBoolean(dst []byte, key string, val bool) []byte {
if 2+len(key) > len(dst) {
return nil
}
binary.BigEndian.PutUint16(dst[:2], uint16(len(key)))
dst = dst[2:]
copy(dst, key)
if len(key) == len(dst) {
return nil
}
return EncodeBoolean(dst[len(key):], val)
}
func PropSetName(prop *Property, name string) {
prop.name = name
}
func PropGetNumber(prop *Property) float64 {
return prop.vu.number
}
func PropGetString(prop *Property) string {
if prop.atype == String {
return prop.vu.aval
}
return ""
}
func PropGetObject(prop *Property, a *AMF) {
if prop.atype == Object {
*a = prop.vu.obj
} else {
*a = ObjInvalid
}
}
func PropEncode(p *Property, dst []byte) []byte {
if p.atype == Invalid {
return nil
}
if p.atype != Null && len(p.name)+2+1 >= len(dst) {
return nil
}
if p.atype != Null && len(p.name) != 0 {
binary.BigEndian.PutUint16(dst[:2], uint16(len(p.name)))
dst = dst[2:]
copy(dst, p.name)
dst = dst[len(p.name):]
}
switch p.atype {
case Number:
dst = EncodeNumber(dst, p.vu.number)
case Boolean:
dst = EncodeBoolean(dst, p.vu.number != 0)
case String:
dst = EncodeString(dst, p.vu.aval)
case Null:
if len(dst) < 2 {
return nil
}
dst[0] = Null
dst = dst[1:]
case Object:
dst = Encode(&p.vu.obj, dst)
case EcmaArray:
dst = EncodeEcmaArray(&p.vu.obj, dst)
case StrictArray:
dst = EncodeArray(&p.vu.obj, dst)
default:
// ??? log.Println("PropEncode: invalid type!")
dst = nil
}
return dst
}
func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 {
prop.name = ""
nOriginalSize := len(data)
if len(data) == 0 {
// TODO use new logger here
// RTMLog(RTMLOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__);
return -1
}
if bDecodeName != 0 && len(data) < 4 {
// at least name (length + at least 1 byte) and 1 byte of data
// TODO use new logger here
// RTMLog(RTMLOGDEBUG, "%s: Not enough data for decoding with name, less than 4 bytes!",__FUNCTION__);
return -1
}
if bDecodeName != 0 {
nNameSize := DecodeInt16(data[:2])
if int(nNameSize) > len(data)-2 {
// TODO use new logger here
//RTMLog(RTMLOGDEBUG, "%s: Name size out of range: namesize (%d) > len (%d) - 2",__FUNCTION__, nNameSize, nSize);
return -1
}
prop.name = DecodeString(data)
data = data[2+nNameSize:]
}
if len(data) == 0 {
return -1
}
prop.atype = DataType(data[0])
data = data[1:]
var nRes int32
switch prop.atype {
case Number:
if len(data) < 8 {
return -1
}
prop.vu.number = DecodeNumber(data[:8])
data = data[8:]
case Boolean:
panic("Boolean not supported")
case String:
nStringSize := DecodeInt16(data[:2])
if len(data) < int(nStringSize+2) {
return -1
}
prop.vu.aval = DecodeString(data)
data = data[2+nStringSize:]
case Object:
nRes := Decode(&prop.vu.obj, data, 1)
if nRes == -1 {
return -1
}
data = data[nRes:]
case MovieClip:
// TODO use new logger here
// ??? log.Println("PropDecode: MAF_MOVIECLIP reserved!")
//RTMLog(RTMLOGERROR, "MovieClip reserved!");
return -1
case Null, Undefined, Unsupported:
prop.atype = Null
case Reference:
// TODO use new logger here
// ??? log.Println("PropDecode: Reference not supported!")
//RTMLog(RTMLOGERROR, "Reference not supported!");
return -1
case EcmaArray:
// next comes the rest, mixed array has a final 0x000009 mark and names, so its an object
data = data[4:]
nRes = Decode(&prop.vu.obj, data, 1)
if nRes == -1 {
return -1
}
data = data[nRes:]
case ObjectEnd:
return -1
case StrictArray:
panic("StrictArray not supported")
case Date:
panic("Date not supported")
case LongSring, XmlDoc:
panic("LongSring, XmlDoc not supported")
case Recordset:
// TODO use new logger here
// ??? log.Println("PropDecode: Recordset reserved!")
//RTMLog(RTMLOGERROR, "Recordset reserved!");
return -1
case TypedObject:
// TODO use new logger here
// RTMLog(RTMLOGERROR, "Typed_object not supported!")
return -1
case Avmplus:
panic("Avmplus not supported")
default:
// TODO use new logger here
//RTMLog(RTMLOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__,
//prop.atype, pBuffer - 1);
return -1
}
return int32(nOriginalSize - len(data))
}
func PropReset(prop *Property) {
if prop.atype == Object || prop.atype == EcmaArray ||
prop.atype == StrictArray {
Reset(&prop.vu.obj)
} else {
prop.vu.aval = ""
}
prop.atype = Invalid
}
func Encode(a *AMF, dst []byte) []byte {
if len(dst) < 5 {
return nil
}
dst[0] = Object
dst = dst[1:]
for i := 0; i < len(a.Props); i++ {
dst = PropEncode(&a.Props[i], dst)
if dst == nil {
// ??? log.Println("Encode: failed to encode property in index")
break
}
}
if len(dst) < 4 {
return nil
}
return EncodeInt24(dst, ObjectEnd)
}
func EncodeEcmaArray(a *AMF, dst []byte) []byte {
if len(dst) < 5 {
return nil
}
dst[0] = EcmaArray
dst = dst[1:]
binary.BigEndian.PutUint32(dst[:4], uint32(len(a.Props)))
dst = dst[4:]
for i := 0; i < len(a.Props); i++ {
dst = PropEncode(&a.Props[i], dst)
if dst == nil {
// ??? log.Println("EncodeEcmaArray: failed to encode property!")
break
}
}
if len(dst) < 4 {
return nil
}
return EncodeInt24(dst, ObjectEnd)
}
// not used?
func EncodeArray(a *AMF, dst []byte) []byte {
if len(dst) < 5 {
return nil
}
dst[0] = StrictArray
dst = dst[1:]
binary.BigEndian.PutUint32(dst[:4], uint32(len(a.Props)))
dst = dst[4:]
for i := 0; i < len(a.Props); i++ {
dst = PropEncode(&a.Props[i], dst)
if dst == nil {
// ??? log.Println("EncodeArray: failed to encode property!")
break
}
}
return dst
}
func Decode(a *AMF, data []byte, bDecodeName int32) int32 {
nOriginalSize := len(data)
a.Props = a.Props[:0]
for len(data) != 0 {
if len(data) >= 3 && DecodeInt24(data[:3]) == ObjectEnd {
data = data[3:]
break
}
var prop Property
nRes := PropDecode(&prop, data, bDecodeName)
// nRes = int32(C.PropDecode(&prop, (*byte)(unsafe.Pointer(pBuffer)),
// int32(nSize), int32(bDecodeName)))
if nRes == -1 {
return -1
}
data = data[nRes:]
a.Props = append(a.Props, prop)
}
return int32(nOriginalSize - len(data))
}
func GetProp(a *AMF, name string, idx int32) *Property {
if idx >= 0 {
if idx < int32(len(a.Props)) {
return &a.Props[idx]
}
} else {
for i, p := range a.Props {
if p.name == name {
return &a.Props[i]
}
}
}
return &PropInvalid
}
func Reset(a *AMF) {
for i := range a.Props {
PropReset(&a.Props[i])
}
*a = AMF{}
}