/* NAME amf.go DESCRIPTION See Readme.md AUTHORS Saxon Nelson-Milton Dan Kortschak Jake Lane LICENSE amf.go is Copyright (C) 2017 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" "log" "math" ) var ( AMFObj_Invalid C_AMFObject AMFProp_Invalid = C_AMFObjectProperty{p_type: AMF_INVALID} ) const ( AMF3_INTEGER_MAX = 268435455 AMF3_INTEGER_MIN = -268435456 ) // unsigned short AMF_DecodeInt16(const char* data); // amf.c +41 func C_AMF_DecodeInt16(data []byte) uint16 { return uint16(data[0])<<8 | uint16(data[1]) } // unsigned int AMF_DecodeInt24(const char* data); // amf.c +50 func C_AMF_DecodeInt24(data []byte) uint32 { return uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2]) } // unsigned int AMF_DeocdeInt32(const char* data); // amf.c +59 func C_AMF_DecodeInt32(data []byte) uint32 { return uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]) } // void AMF_DecodeString(const char* data, C_AVal* bv); // amf.c +68 func C_AMF_DecodeString(data []byte) string { n := C_AMF_DecodeInt16(data) return string(data[2 : 2+n]) } // void AMF_DecodeLongString(const char *data, AVal *bv); // amf.c +75 func C_AMF_DecodeLongString(data []byte) string { n := C_AMF_DecodeInt32(data) return string(data[2 : 2+n]) } // double AMF_DecodeNumber(const char* data); // amf.c +82 func C_AMF_DecodeNumber(data []byte) float64 { return math.Float64frombits(binary.BigEndian.Uint64(data)) } // int AMF_DecodeBoolean(const char *data); // amf.c +132 func C_AMF_DecodeBoolean(data []byte) bool { return data[0] != 0 } // char* AMF_EncodeInt24(char* output, char* outend, int nVal); // amf.c +149 func C_AMF_EncodeInt24(dst []byte, val int32) []byte { if len(dst) < 3 { return nil } _ = dst[2] dst[0] = byte(val >> 16) dst[1] = byte(val >> 8) dst[2] = byte(val) if len(dst) == 3 { return nil } return dst[3:] } // char* AMF_EncodeInt32(char* output, char* outend, int nVal); // amf.c +160 func C_AMF_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:] } // char* AMF_EncodeString(char* output, char* outend, const C_AVal* bv); // amf.c +173 func C_AMF_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] = AMF_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] = AMF_LONG_STRING 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):] } // char* AMF_EncodeNumber(char* output, char* outend, double dVal); // amf.c +199 func C_AMF_EncodeNumber(dst []byte, val float64) []byte { if len(dst) < 9 { return nil } dst[0] = AMF_NUMBER dst = dst[1:] binary.BigEndian.PutUint64(dst, math.Float64bits(val)) return dst[8:] } // char* AMF_EncodeBoolean(char* output, char* outend, int bVal); // amf.c +260 func C_AMF_EncodeBoolean(dst []byte, val bool) []byte { if len(dst) < 2 { return nil } dst[0] = AMF_BOOLEAN if val { dst[1] = 1 } if len(dst) == 2 { return nil } return dst[2:] } // char* AMF_EncodeNamedString(char* output, char* outend, const C_AVal* strName, const C_AVal* strValue); // amf.c +273 func C_AMF_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 C_AMF_EncodeString(dst[len(key):], val) } // char* AMF_EncodeNamedNumber(char* output, char* outend, const C_AVal* strName, double dVal); // amf.c +286 func C_AMF_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 C_AMF_EncodeNumber(dst[len(key):], val) } // char* AMF_EncodeNamedBoolean(char* output, char* outend, const C_AVal* strname, int bVal); // amf.c +299 func C_AMF_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 C_AMF_EncodeBoolean(dst[len(key):], val) } // void AMFProp_SetName(AMFObjectProperty *prop, AVal *name); // amf.c +318 func C_AMFProp_SetName(prop *C_AMFObjectProperty, name string) { prop.p_name = name } // double AMFProp_GetNumber(AMFObjectProperty* prop); // amf.c +330 func C_AMFProp_GetNumber(prop *C_AMFObjectProperty) float64 { return prop.p_vu.p_number } // void AMFProp_GetString(AMFObjectProperty* prop, AVal* str); // amf.c +341 func C_AMFProp_GetString(prop *C_AMFObjectProperty) string { if prop.p_type == AMF_STRING { return prop.p_vu.p_aval } return "" } // void AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj); // amf.c +351 func C_AMFProp_GetObject(prop *C_AMFObjectProperty, obj *C_AMFObject) { if prop.p_type == AMF_OBJECT { *obj = prop.p_vu.p_object } else { *obj = AMFObj_Invalid } } // char* AMFPropEncode(AMFOBjectProperty* prop, char* pBufer, char* pBufEnd); // amf.c +366 func C_AMF_PropEncode(p *C_AMFObjectProperty, dst []byte) []byte { if p.p_type == AMF_INVALID { return nil } if p.p_type != AMF_NULL && len(p.p_name)+2+1 >= len(dst) { return nil } if p.p_type != AMF_NULL && len(p.p_name) != 0 { binary.BigEndian.PutUint16(dst[:2], uint16(len(p.p_name))) dst = dst[2:] copy(dst, p.p_name) dst = dst[len(p.p_name):] } switch p.p_type { case AMF_NUMBER: dst = C_AMF_EncodeNumber(dst, p.p_vu.p_number) case AMF_BOOLEAN: dst = C_AMF_EncodeBoolean(dst, p.p_vu.p_number != 0) case AMF_STRING: dst = C_AMF_EncodeString(dst, p.p_vu.p_aval) case AMF_NULL: if len(dst) < 2 { return nil } dst[0] = AMF_NULL dst = dst[1:] case AMF_OBJECT: dst = C_AMF_Encode(&p.p_vu.p_object, dst) case AMF_ECMA_ARRAY: dst = C_AMF_EncodeEcmaArray(&p.p_vu.p_object, dst) case AMF_STRICT_ARRAY: dst = C_AMF_EncodeArray(&p.p_vu.p_object, dst) default: log.Println("C_AMF_PropEncode: invalid type!") dst = nil } return dst } // int AMFProp_Decode(C_AMFObjectProperty* prop, const char* pBuffer, int nSize, int bDecodeName); // amf.c +619 func C_AMFProp_Decode(prop *C_AMFObjectProperty, data []byte, bDecodeName int32) int32 { prop.p_name = "" nOriginalSize := len(data) if len(data) == 0 { // TODO use new logger here // RTMP_Log(RTMP_LOGDEBUG, "%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 // RTMP_Log(RTMP_LOGDEBUG, "%s: Not enough data for decoding with name, less than 4 bytes!",__FUNCTION__); return -1 } if bDecodeName != 0 { nNameSize := C_AMF_DecodeInt16(data[:2]) if int(nNameSize) > len(data)-2 { // TODO use new logger here //RTMP_Log(RTMP_LOGDEBUG, "%s: Name size out of range: namesize (%d) > len (%d) - 2",__FUNCTION__, nNameSize, nSize); return -1 } prop.p_name = C_AMF_DecodeString(data) data = data[2+nNameSize:] } if len(data) == 0 { return -1 } prop.p_type = C_AMFDataType(data[0]) data = data[1:] var nRes int32 switch prop.p_type { case AMF_NUMBER: if len(data) < 8 { return -1 } prop.p_vu.p_number = C_AMF_DecodeNumber(data[:8]) data = data[8:] case AMF_BOOLEAN: panic("AMF_BOOLEAN not supported") case AMF_STRING: nStringSize := C_AMF_DecodeInt16(data[:2]) if len(data) < int(nStringSize+2) { return -1 } prop.p_vu.p_aval = C_AMF_DecodeString(data) data = data[2+nStringSize:] case AMF_OBJECT: nRes := C_AMF_Decode(&prop.p_vu.p_object, data, 1) if nRes == -1 { return -1 } data = data[nRes:] case AMF_MOVIECLIP: // TODO use new logger here log.Println("AMFProp_Decode: MAF_MOVIECLIP reserved!") //RTMP_Log(RTMP_LOGERROR, "AMF_MOVIECLIP reserved!"); return -1 case AMF_NULL, AMF_UNDEFINED, AMF_UNSUPPORTED: prop.p_type = AMF_NULL case AMF_REFERENCE: // TODO use new logger here log.Println("AMFProp_Decode: AMF_REFERENCE not supported!") //RTMP_Log(RTMP_LOGERROR, "AMF_REFERENCE not supported!"); return -1 case AMF_ECMA_ARRAY: // next comes the rest, mixed array has a final 0x000009 mark and names, so its an object data = data[4:] nRes = C_AMF_Decode(&prop.p_vu.p_object, data, 1) if nRes == -1 { return -1 } data = data[nRes:] case AMF_OBJECT_END: return -1 case AMF_STRICT_ARRAY: panic("AMF_STRICT_ARRAY not supported") case AMF_DATE: panic("AMF_DATE not supported") case AMF_LONG_STRING, AMF_XML_DOC: panic("AMF_LONG_STRING, AMF_XML_DOC not supported") case AMF_RECORDSET: // TODO use new logger here log.Println("AMFProp_Decode: AMF_RECORDSET reserved!") //RTMP_Log(RTMP_LOGERROR, "AMF_RECORDSET reserved!"); return -1 case AMF_TYPED_OBJECT: // TODO use new logger here // RTMP_Log(RTMP_LOGERROR, "AMF_TYPED_OBJECT not supported!") return -1 case AMF_AVMPLUS: panic("AMF_AVMPLUS not supported") default: // TODO use new logger here //RTMP_Log(RTMP_LOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, //prop.p_type, pBuffer - 1); return -1 } return int32(nOriginalSize - len(data)) } // void AMFProp_Reset(AMFObjectProperty* prop); // amf.c +875 func C_AMFProp_Reset(prop *C_AMFObjectProperty) { if prop.p_type == AMF_OBJECT || prop.p_type == AMF_ECMA_ARRAY || prop.p_type == AMF_STRICT_ARRAY { C_AMF_Reset(&prop.p_vu.p_object) } else { prop.p_vu.p_aval = "" } prop.p_type = AMF_INVALID } // char* AMF_Encode(AMFObject* obj, char* pBuffer, char* pBufEnd); // amf.c +891 func C_AMF_Encode(obj *C_AMFObject, dst []byte) []byte { if len(dst) < 5 { return nil } dst[0] = AMF_OBJECT dst = dst[1:] for i := 0; i < len(obj.o_props); i++ { dst = C_AMF_PropEncode(&obj.o_props[i], dst) if dst == nil { log.Println("C_AMF_Encode: failed to encode property in index") break } } if len(dst) < 4 { return nil } return C_AMF_EncodeInt24(dst, AMF_OBJECT_END) } // char* AMF_EncodeEcmaArray(AMFObject* obj, char* pBuffer, char* pBufEnd); // amf.c +924 func C_AMF_EncodeEcmaArray(obj *C_AMFObject, dst []byte) []byte { if len(dst) < 5 { return nil } dst[0] = AMF_ECMA_ARRAY dst = dst[1:] binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.o_props))) dst = dst[4:] for i := 0; i < len(obj.o_props); i++ { dst = C_AMF_PropEncode(&obj.o_props[i], dst) if dst == nil { log.Println("C_AMF_EncodeEcmaArray: failed to encode property!") break } } if len(dst) < 4 { return nil } return C_AMF_EncodeInt24(dst, AMF_OBJECT_END) } // char* AMF_EncodeArray(AMFObject* obj, char* pBuffer, char* pBufEnd); // amf.c +959 func C_AMF_EncodeArray(obj *C_AMFObject, dst []byte) []byte { if len(dst) < 5 { return nil } dst[0] = AMF_STRICT_ARRAY dst = dst[1:] binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.o_props))) dst = dst[4:] for i := 0; i < len(obj.o_props); i++ { dst = C_AMF_PropEncode(&obj.o_props[i], dst) if dst == nil { log.Println("C_AMF_EncodeArray: failed to encode property!") break } } return dst } // int AMF_Decode(AMFObject *obj, const char* pBuffer, int nSize, int bDecodeName); // amf.c +1180 func C_AMF_Decode(obj *C_AMFObject, data []byte, bDecodeName int32) int32 { nOriginalSize := len(data) obj.o_props = obj.o_props[:0] for len(data) != 0 { if len(data) >= 3 && C_AMF_DecodeInt24(data[:3]) == AMF_OBJECT_END { data = data[3:] break } var prop C_AMFObjectProperty nRes := C_AMFProp_Decode(&prop, data, bDecodeName) // nRes = int32(C.AMFProp_Decode(&prop, (*byte)(unsafe.Pointer(pBuffer)), // int32(nSize), int32(bDecodeName))) if nRes == -1 { return -1 } data = data[nRes:] obj.o_props = append(obj.o_props, prop) } return int32(nOriginalSize - len(data)) } // AMFObjectProperty* AMF_GetProp(AMFObject *obj, const AVal* name, int nIndex); // amf.c + 1249 func C_AMF_GetProp(obj *C_AMFObject, name string, idx int32) *C_AMFObjectProperty { if idx >= 0 { if idx < int32(len(obj.o_props)) { return &obj.o_props[idx] } } else { for i, p := range obj.o_props { if p.p_name == name { return &obj.o_props[i] } } } return &AMFProp_Invalid } // void AMF_Reset(AMFObject* obj); // amf.c +1282 func C_AMF_Reset(obj *C_AMFObject) { for i := range obj.o_props { C_AMFProp_Reset(&obj.o_props[i]) } obj.o_props = obj.o_props[:0] } /* // void AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop); // amf.c +1298 func AMF3CD_AddProp(cd *C.AMF3ClassDef, prop *C_AVal) { if cd.cd_num&0x0f == 0 { cd.cd_props = (*C_AVal)(realloc(unsafe.Pointer(cd.cd_props), int(uintptr(cd.cd_num+16)*unsafe.Sizeof(C_AVal{})))) } *(*C_AVal)(incPtr(unsafe.Pointer(cd.cd_props), int(cd.cd_num), int(unsafe.Sizeof(C_AVal{})))) = *prop cd.cd_num++ } */