mirror of https://bitbucket.org/ausocean/av.git
550 lines
11 KiB
Go
550 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
|
|
*/
|
|
package rtmp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math"
|
|
)
|
|
|
|
const (
|
|
amfNumber = iota
|
|
amfBoolean
|
|
amfString
|
|
amfObject
|
|
amfMovieClip // reserved, not implemented
|
|
amfNull
|
|
amfUndefined
|
|
amfReference
|
|
amfEcmaArray
|
|
amfObjectEnd
|
|
amfStrictArray
|
|
amfDate
|
|
amfLongSring
|
|
amfUnsupported
|
|
amfRecordset // reserved, not implemented
|
|
amfXmlDoc
|
|
amfTypedObject
|
|
amfAvmplus // reserved, not implemented
|
|
amfInvalid = 0xff
|
|
)
|
|
|
|
type AMF struct {
|
|
props []AMFProperty
|
|
}
|
|
|
|
type AMFProperty struct {
|
|
name string
|
|
atype AMFDataType
|
|
vu vu
|
|
UTCoffset int16
|
|
}
|
|
|
|
type vu struct {
|
|
number float64
|
|
aval string
|
|
obj AMF
|
|
}
|
|
|
|
type AMFDataType int32
|
|
|
|
var (
|
|
amfObjInvalid AMF
|
|
amfPropInvalid = AMFProperty{atype: amfInvalid}
|
|
)
|
|
|
|
func amfDecodeInt16(data []byte) uint16 {
|
|
return uint16(binary.BigEndian.Uint16(data))
|
|
}
|
|
|
|
func amfDecodeInt24(data []byte) uint32 {
|
|
return uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
|
|
// return uint16(data[0])<<8 | uint16(data[1])
|
|
}
|
|
|
|
func amfDecodeInt32(data []byte) uint32 {
|
|
return uint32(binary.BigEndian.Uint32(data))
|
|
}
|
|
|
|
func amfDecodeString(data []byte) string {
|
|
n := amfDecodeInt16(data)
|
|
return string(data[2 : 2+n])
|
|
}
|
|
|
|
func amfDecodeLongString(data []byte) string {
|
|
n := amfDecodeInt32(data)
|
|
return string(data[2 : 2+n])
|
|
}
|
|
|
|
func amfDecodeNumber(data []byte) float64 {
|
|
return math.Float64frombits(binary.BigEndian.Uint64(data))
|
|
}
|
|
|
|
func amfDecodeBoolean(data []byte) bool {
|
|
return data[0] != 0
|
|
}
|
|
|
|
func amfEncodeInt24(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 amfEncodeInt32(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 amfEncodeString(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] = amfString
|
|
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] = amfLongSring
|
|
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 amfEncodeNumber(dst []byte, val float64) []byte {
|
|
if len(dst) < 9 {
|
|
return nil
|
|
}
|
|
dst[0] = amfNumber
|
|
dst = dst[1:]
|
|
binary.BigEndian.PutUint64(dst, math.Float64bits(val))
|
|
return dst[8:]
|
|
}
|
|
|
|
func amfEncodeBoolean(dst []byte, val bool) []byte {
|
|
if len(dst) < 2 {
|
|
return nil
|
|
}
|
|
dst[0] = amfBoolean
|
|
if val {
|
|
dst[1] = 1
|
|
}
|
|
if len(dst) == 2 {
|
|
return nil
|
|
}
|
|
return dst[2:]
|
|
|
|
}
|
|
|
|
func amfEncodeNamedString(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 amfEncodeString(dst[len(key):], val)
|
|
}
|
|
|
|
func amfEncodeNamedNumber(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 amfEncodeNumber(dst[len(key):], val)
|
|
}
|
|
|
|
func amfEncodeNamedBoolean(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 amfEncodeBoolean(dst[len(key):], val)
|
|
}
|
|
|
|
func amfPropSetName(prop *AMFProperty, name string) {
|
|
prop.name = name
|
|
}
|
|
|
|
func amfPropGetNumber(prop *AMFProperty) float64 {
|
|
return prop.vu.number
|
|
}
|
|
|
|
func amfPropGetString(prop *AMFProperty) string {
|
|
if prop.atype == amfString {
|
|
return prop.vu.aval
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func amfPropGetObject(prop *AMFProperty, a *AMF) {
|
|
if prop.atype == amfObject {
|
|
*a = prop.vu.obj
|
|
} else {
|
|
*a = amfObjInvalid
|
|
}
|
|
}
|
|
|
|
func amfPropEncode(p *AMFProperty, dst []byte) []byte {
|
|
if p.atype == amfInvalid {
|
|
return nil
|
|
}
|
|
|
|
if p.atype != amfNull && len(p.name)+2+1 >= len(dst) {
|
|
return nil
|
|
}
|
|
|
|
if p.atype != amfNull && 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 amfNumber:
|
|
dst = amfEncodeNumber(dst, p.vu.number)
|
|
case amfBoolean:
|
|
dst = amfEncodeBoolean(dst, p.vu.number != 0)
|
|
case amfString:
|
|
dst = amfEncodeString(dst, p.vu.aval)
|
|
case amfNull:
|
|
if len(dst) < 2 {
|
|
return nil
|
|
}
|
|
dst[0] = amfNull
|
|
dst = dst[1:]
|
|
case amfObject:
|
|
dst = amfEncode(&p.vu.obj, dst)
|
|
case amfEcmaArray:
|
|
dst = amfEncodeEcmaArray(&p.vu.obj, dst)
|
|
case amfStrictArray:
|
|
dst = amfEncodeArray(&p.vu.obj, dst)
|
|
default:
|
|
// ??? log.Println("amfPropEncode: invalid type!")
|
|
dst = nil
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func amfProDecode(prop *AMFProperty, 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 := amfDecodeInt16(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 = amfDecodeString(data)
|
|
data = data[2+nNameSize:]
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return -1
|
|
}
|
|
|
|
prop.atype = AMFDataType(data[0])
|
|
data = data[1:]
|
|
|
|
var nRes int32
|
|
switch prop.atype {
|
|
case amfNumber:
|
|
if len(data) < 8 {
|
|
return -1
|
|
}
|
|
prop.vu.number = amfDecodeNumber(data[:8])
|
|
data = data[8:]
|
|
|
|
case amfBoolean:
|
|
panic("amfBoolean not supported")
|
|
|
|
case amfString:
|
|
nStringSize := amfDecodeInt16(data[:2])
|
|
if len(data) < int(nStringSize+2) {
|
|
return -1
|
|
}
|
|
prop.vu.aval = amfDecodeString(data)
|
|
data = data[2+nStringSize:]
|
|
|
|
case amfObject:
|
|
nRes := amfDecode(&prop.vu.obj, data, 1)
|
|
if nRes == -1 {
|
|
return -1
|
|
}
|
|
data = data[nRes:]
|
|
|
|
case amfMovieClip:
|
|
// TODO use new logger here
|
|
// ??? log.Println("AMFProDecode: MAF_MOVIECLIP reserved!")
|
|
//RTMLog(RTMLOGERROR, "amfMovieClip reserved!");
|
|
return -1
|
|
|
|
case amfNull, amfUndefined, amfUnsupported:
|
|
prop.atype = amfNull
|
|
|
|
case amfReference:
|
|
// TODO use new logger here
|
|
// ??? log.Println("AMFProDecode: amfReference not supported!")
|
|
//RTMLog(RTMLOGERROR, "amfReference not supported!");
|
|
return -1
|
|
|
|
case amfEcmaArray:
|
|
// next comes the rest, mixed array has a final 0x000009 mark and names, so its an object
|
|
data = data[4:]
|
|
nRes = amfDecode(&prop.vu.obj, data, 1)
|
|
if nRes == -1 {
|
|
return -1
|
|
}
|
|
data = data[nRes:]
|
|
|
|
case amfObjectEnd:
|
|
return -1
|
|
|
|
case amfStrictArray:
|
|
panic("amfStrictArray not supported")
|
|
|
|
case amfDate:
|
|
panic("amfDate not supported")
|
|
|
|
case amfLongSring, amfXmlDoc:
|
|
panic("amfLongSring, amfXmlDoc not supported")
|
|
|
|
case amfRecordset:
|
|
// TODO use new logger here
|
|
// ??? log.Println("AMFProDecode: amfRecordset reserved!")
|
|
//RTMLog(RTMLOGERROR, "amfRecordset reserved!");
|
|
return -1
|
|
|
|
case amfTypedObject:
|
|
// TODO use new logger here
|
|
// RTMLog(RTMLOGERROR, "amfTyped_object not supported!")
|
|
return -1
|
|
|
|
case amfAvmplus:
|
|
panic("amfAvmplus 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 amfPropReset(prop *AMFProperty) {
|
|
if prop.atype == amfObject || prop.atype == amfEcmaArray ||
|
|
prop.atype == amfStrictArray {
|
|
amfReset(&prop.vu.obj)
|
|
} else {
|
|
prop.vu.aval = ""
|
|
}
|
|
prop.atype = amfInvalid
|
|
}
|
|
|
|
func amfEncode(a *AMF, dst []byte) []byte {
|
|
if len(dst) < 5 {
|
|
return nil
|
|
}
|
|
|
|
dst[0] = amfObject
|
|
dst = dst[1:]
|
|
|
|
for i := 0; i < len(a.props); i++ {
|
|
dst = amfPropEncode(&a.props[i], dst)
|
|
if dst == nil {
|
|
// ??? log.Println("amfEncode: failed to encode property in index")
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(dst) < 4 {
|
|
return nil
|
|
}
|
|
return amfEncodeInt24(dst, amfObjectEnd)
|
|
}
|
|
|
|
func amfEncodeEcmaArray(a *AMF, dst []byte) []byte {
|
|
if len(dst) < 5 {
|
|
return nil
|
|
}
|
|
|
|
dst[0] = amfEcmaArray
|
|
dst = dst[1:]
|
|
binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props)))
|
|
dst = dst[4:]
|
|
|
|
for i := 0; i < len(a.props); i++ {
|
|
dst = amfPropEncode(&a.props[i], dst)
|
|
if dst == nil {
|
|
// ??? log.Println("amfEncodeEcmaArray: failed to encode property!")
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(dst) < 4 {
|
|
return nil
|
|
}
|
|
return amfEncodeInt24(dst, amfObjectEnd)
|
|
}
|
|
|
|
// not used?
|
|
func amfEncodeArray(a *AMF, dst []byte) []byte {
|
|
if len(dst) < 5 {
|
|
return nil
|
|
}
|
|
|
|
dst[0] = amfStrictArray
|
|
dst = dst[1:]
|
|
binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props)))
|
|
dst = dst[4:]
|
|
|
|
for i := 0; i < len(a.props); i++ {
|
|
dst = amfPropEncode(&a.props[i], dst)
|
|
if dst == nil {
|
|
// ??? log.Println("amfEncodeArray: failed to encode property!")
|
|
break
|
|
}
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
func amfDecode(a *AMF, data []byte, bDecodeName int32) int32 {
|
|
nOriginalSize := len(data)
|
|
|
|
a.props = a.props[:0]
|
|
for len(data) != 0 {
|
|
if len(data) >= 3 && amfDecodeInt24(data[:3]) == amfObjectEnd {
|
|
data = data[3:]
|
|
break
|
|
}
|
|
|
|
var prop AMFProperty
|
|
nRes := amfProDecode(&prop, data, bDecodeName)
|
|
// nRes = int32(C.AMFProDecode(&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 amfGetProp(a *AMF, name string, idx int32) *AMFProperty {
|
|
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 &amfPropInvalid
|
|
}
|
|
|
|
func amfReset(a *AMF) {
|
|
for i := range a.props {
|
|
amfPropReset(&a.props[i])
|
|
}
|
|
*a = AMF{}
|
|
}
|