/* NAME amf.go DESCRIPTION Action Message Format (AMF) encoding/decoding functions. See https://en.wikipedia.org/wiki/Action_Message_Format. AUTHORS Saxon Nelson-Milton Dan Kortschak Jake Lane Alan Noble 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{} }