From e78cc9343249f92e154c912cdd577e982fcc2f04 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 11 Jan 2019 16:00:00 +1030 Subject: [PATCH 01/52] minDataSize is out and flvTagheaderSize is in! --- rtmp/rtmp.go | 3 +-- rtmp/session.go | 10 +++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 4b334587..a5a8e82b 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -46,7 +46,6 @@ import ( const ( pkg = "rtmp:" - minDataSize = 11 // ToDo: this should be the same as fullHeaderSize signatureSize = 1536 fullHeaderSize = 12 ) @@ -159,7 +158,7 @@ var ( errConnStream = errors.New("rtmp: connection stream error") errInvalidHeader = errors.New("rtmp: invalid header") errInvalidBody = errors.New("rtmp: invalid body") - errTinyPacket = errors.New("rtmp: packet too small") + errInvalidFlvTag = errors.New("rtmp: invalid FLV tag") errEncoding = errors.New("rtmp: encoding error") errDecoding = errors.New("rtmp: decoding error") errUnimplemented = errors.New("rtmp: unimplemented feature") diff --git a/rtmp/session.go b/rtmp/session.go index eb97c938..be6b8945 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -106,6 +106,10 @@ const ( FatalLevel int8 = 5 ) +// flvTagheaderSize is the FLV header size we expect. +// NB: We don't accept extended headers. +const flvTagheaderSize = 11 + // NewSession returns a new Session. func NewSession(url string, timeout uint, log Log) *Session { return &Session{ @@ -174,8 +178,8 @@ func (s *Session) Write(data []byte) (int, error) { if !s.isConnected() { return 0, errNotConnected } - if len(data) < minDataSize { - return 0, errTinyPacket + if len(data) < flvTagheaderSize { + return 0, errInvalidFlvTag } if data[0] == packetTypeInfo || (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') { return 0, errUnimplemented @@ -190,7 +194,7 @@ func (s *Session) Write(data []byte) (int, error) { } pkt.resize(pkt.bodySize, headerSizeAuto) - copy(pkt.body, data[minDataSize:minDataSize+pkt.bodySize]) + copy(pkt.body, data[flvTagheaderSize:flvTagheaderSize+pkt.bodySize]) err := pkt.write(s, false) if err != nil { return 0, err From 19f245ae7602168e0846e40e4312bf2771d97260 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 06:42:34 +1030 Subject: [PATCH 02/52] First cut at AMF refactoring; use idiomatic names. --- rtmp/amf.go | 421 ++++++++++++++++++++------------------------ rtmp/amf_headers.go | 47 ----- rtmp/packet.go | 18 +- rtmp/rtmp.go | 126 ++++++------- rtmp/session.go | 6 +- 5 files changed, 270 insertions(+), 348 deletions(-) diff --git a/rtmp/amf.go b/rtmp/amf.go index d46ea16e..1977254c 100644 --- a/rtmp/amf.go +++ b/rtmp/amf.go @@ -3,15 +3,16 @@ NAME amf.go DESCRIPTION - See Readme.md + RMTP encoding/decoding functions. AUTHORS Saxon Nelson-Milton Dan Kortschak Jake Lane + Alan Noble LICENSE - amf.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean) + 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 @@ -35,71 +36,90 @@ 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 + 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 ) -// 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]) +type AMF struct { + props []AMFProperty } -// unsigned int AMF_DecodeInt24(const char* data); -// amf.c +50 -func C_AMF_DecodeInt24(data []byte) uint32 { +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]) } -// 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]) +func amfDecodeInt32(data []byte) uint32 { + return uint32(binary.BigEndian.Uint32(data)) } -// void AMF_DecodeString(const char* data, C_AVal* bv); -// amf.c +68 -func C_AMF_DecodeString(data []byte) string { - n := C_AMF_DecodeInt16(data) +func amfDecodeString(data []byte) string { + n := amfDecodeInt16(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) +func amfDecodeLongString(data []byte) string { + n := amfDecodeInt32(data) return string(data[2 : 2+n]) } -// double AMF_DecodeNumber(const char* data); -// amf.c +82 -func C_AMF_DecodeNumber(data []byte) float64 { +func amfDecodeNumber(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 { +func amfDecodeBoolean(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 { +func amfEncodeInt24(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) @@ -109,9 +129,7 @@ func C_AMF_EncodeInt24(dst []byte, val int32) []byte { return dst[3:] } -// char* AMF_EncodeInt32(char* output, char* outend, int nVal); -// amf.c +160 -func C_AMF_EncodeInt32(dst []byte, val int32) []byte { +func amfEncodeInt32(dst []byte, val int32) []byte { if len(dst) < 4 { return nil } @@ -122,9 +140,7 @@ func C_AMF_EncodeInt32(dst []byte, val int32) []byte { 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 { +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 @@ -134,7 +150,7 @@ func C_AMF_EncodeString(dst []byte, val string) []byte { } if len(val) < 65536 { - dst[0] = AMF_STRING + dst[0] = amfString dst = dst[1:] binary.BigEndian.PutUint16(dst[:2], uint16(len(val))) dst = dst[2:] @@ -144,7 +160,7 @@ func C_AMF_EncodeString(dst []byte, val string) []byte { } return dst[len(val):] } - dst[0] = AMF_LONG_STRING + dst[0] = amfLongSring dst = dst[1:] binary.BigEndian.PutUint32(dst[:4], uint32(len(val))) dst = dst[4:] @@ -155,25 +171,21 @@ func C_AMF_EncodeString(dst []byte, val string) []byte { 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 { +func amfEncodeNumber(dst []byte, val float64) []byte { if len(dst) < 9 { return nil } - dst[0] = AMF_NUMBER + dst[0] = amfNumber 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 { +func amfEncodeBoolean(dst []byte, val bool) []byte { if len(dst) < 2 { return nil } - dst[0] = AMF_BOOLEAN + dst[0] = amfBoolean if val { dst[1] = 1 } @@ -184,9 +196,7 @@ func C_AMF_EncodeBoolean(dst []byte, val bool) []byte { } -// 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 { +func amfEncodeNamedString(dst []byte, key, val string) []byte { if 2+len(key) > len(dst) { return nil } @@ -196,12 +206,10 @@ func C_AMF_EncodeNamedString(dst []byte, key, val string) []byte { if len(key) == len(dst) { return nil } - return C_AMF_EncodeString(dst[len(key):], val) + return amfEncodeString(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 { +func amfEncodeNamedNumber(dst []byte, key string, val float64) []byte { if 2+len(key) > len(dst) { return nil } @@ -211,12 +219,10 @@ func C_AMF_EncodeNamedNumber(dst []byte, key string, val float64) []byte { if len(key) == len(dst) { return nil } - return C_AMF_EncodeNumber(dst[len(key):], val) + return amfEncodeNumber(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 { +func amfEncodeNamedBoolean(dst []byte, key string, val bool) []byte { if 2+len(key) > len(dst) { return nil } @@ -226,112 +232,100 @@ func C_AMF_EncodeNamedBoolean(dst []byte, key string, val bool) []byte { if len(key) == len(dst) { return nil } - return C_AMF_EncodeBoolean(dst[len(key):], val) + return amfEncodeBoolean(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 +func amfPropSetName(prop *AMFProperty, name string) { + prop.name = name } -// double AMFProp_GetNumber(AMFObjectProperty* prop); -// amf.c +330 -func C_AMFProp_GetNumber(prop *C_AMFObjectProperty) float64 { - return prop.p_vu.p_number +func amfPropGetNumber(prop *AMFProperty) float64 { + return prop.vu.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 +func amfPropGetString(prop *AMFProperty) string { + if prop.atype == amfString { + return prop.vu.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 +func amfPropGetObject(prop *AMFProperty, a *AMF) { + if prop.atype == amfObject { + *a = prop.vu.obj } else { - *obj = AMFObj_Invalid + *a = amfObjInvalid } } -// 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 { +func amfPropEncode(p *AMFProperty, dst []byte) []byte { + if p.atype == amfInvalid { return nil } - if p.p_type != AMF_NULL && len(p.p_name)+2+1 >= len(dst) { + if p.atype != amfNull && len(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))) + if p.atype != amfNull && len(p.name) != 0 { + binary.BigEndian.PutUint16(dst[:2], uint16(len(p.name))) dst = dst[2:] - copy(dst, p.p_name) - dst = dst[len(p.p_name):] + copy(dst, p.name) + dst = dst[len(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: + 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] = AMF_NULL + dst[0] = amfNull 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) + 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("C_AMF_PropEncode: invalid type!") + // ??? log.Println("amfPropEncode: 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 = "" +func amfProDecode(prop *AMFProperty, data []byte, bDecodeName int32) int32 { + prop.name = "" nOriginalSize := len(data) if len(data) == 0 { // TODO use new logger here - // RTMP_Log(RTMP_LOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__); + // 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 - // RTMP_Log(RTMP_LOGDEBUG, "%s: Not enough data for decoding with name, less than 4 bytes!",__FUNCTION__); + // RTMLog(RTMLOGDEBUG, "%s: Not enough data for decoding with name, less than 4 bytes!",__FUNCTION__); return -1 } if bDecodeName != 0 { - nNameSize := C_AMF_DecodeInt16(data[:2]) + nNameSize := amfDecodeInt16(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); + //RTMLog(RTMLOGDEBUG, "%s: Name size out of range: namesize (%d) > len (%d) - 2",__FUNCTION__, nNameSize, nSize); return -1 } - prop.p_name = C_AMF_DecodeString(data) + prop.name = amfDecodeString(data) data = data[2+nNameSize:] } @@ -339,122 +333,118 @@ func C_AMFProp_Decode(prop *C_AMFObjectProperty, data []byte, bDecodeName int32) return -1 } - prop.p_type = C_AMFDataType(data[0]) + prop.atype = AMFDataType(data[0]) data = data[1:] var nRes int32 - switch prop.p_type { - case AMF_NUMBER: + switch prop.atype { + case amfNumber: if len(data) < 8 { return -1 } - prop.p_vu.p_number = C_AMF_DecodeNumber(data[:8]) + prop.vu.number = amfDecodeNumber(data[:8]) data = data[8:] - case AMF_BOOLEAN: - panic("AMF_BOOLEAN not supported") + case amfBoolean: + panic("amfBoolean not supported") - case AMF_STRING: - nStringSize := C_AMF_DecodeInt16(data[:2]) + case amfString: + nStringSize := amfDecodeInt16(data[:2]) if len(data) < int(nStringSize+2) { return -1 } - prop.p_vu.p_aval = C_AMF_DecodeString(data) + prop.vu.aval = amfDecodeString(data) data = data[2+nStringSize:] - case AMF_OBJECT: - nRes := C_AMF_Decode(&prop.p_vu.p_object, data, 1) + case amfObject: + nRes := amfDecode(&prop.vu.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case AMF_MOVIECLIP: + case amfMovieClip: // TODO use new logger here - log.Println("AMFProp_Decode: MAF_MOVIECLIP reserved!") - //RTMP_Log(RTMP_LOGERROR, "AMF_MOVIECLIP reserved!"); + // ??? log.Println("AMFProDecode: MAF_MOVIECLIP reserved!") + //RTMLog(RTMLOGERROR, "amfMovieClip reserved!"); return -1 - case AMF_NULL, AMF_UNDEFINED, AMF_UNSUPPORTED: - prop.p_type = AMF_NULL + case amfNull, amfUndefined, amfUnsupported: + prop.atype = amfNull - case AMF_REFERENCE: + case amfReference: // TODO use new logger here - log.Println("AMFProp_Decode: AMF_REFERENCE not supported!") - //RTMP_Log(RTMP_LOGERROR, "AMF_REFERENCE not supported!"); + // ??? log.Println("AMFProDecode: amfReference not supported!") + //RTMLog(RTMLOGERROR, "amfReference not supported!"); return -1 - case AMF_ECMA_ARRAY: + case amfEcmaArray: // 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) + nRes = amfDecode(&prop.vu.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case AMF_OBJECT_END: + case amfObjectEnd: return -1 - case AMF_STRICT_ARRAY: - panic("AMF_STRICT_ARRAY not supported") + case amfStrictArray: + panic("amfStrictArray not supported") - case AMF_DATE: - panic("AMF_DATE not supported") + case amfDate: + panic("amfDate not supported") - case AMF_LONG_STRING, AMF_XML_DOC: - panic("AMF_LONG_STRING, AMF_XML_DOC not supported") + case amfLongSring, amfXmlDoc: + panic("amfLongSring, amfXmlDoc not supported") - case AMF_RECORDSET: + case amfRecordset: // TODO use new logger here - log.Println("AMFProp_Decode: AMF_RECORDSET reserved!") - //RTMP_Log(RTMP_LOGERROR, "AMF_RECORDSET reserved!"); + // ??? log.Println("AMFProDecode: amfRecordset reserved!") + //RTMLog(RTMLOGERROR, "amfRecordset reserved!"); return -1 - case AMF_TYPED_OBJECT: + case amfTypedObject: // TODO use new logger here - // RTMP_Log(RTMP_LOGERROR, "AMF_TYPED_OBJECT not supported!") + // RTMLog(RTMLOGERROR, "amfTyped_object not supported!") return -1 - case AMF_AVMPLUS: - panic("AMF_AVMPLUS not supported") + case amfAvmplus: + panic("amfAvmplus not supported") default: // TODO use new logger here - //RTMP_Log(RTMP_LOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, - //prop.p_type, pBuffer - 1); + //RTMLog(RTMLOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, + //prop.atype, 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) +func amfPropReset(prop *AMFProperty) { + if prop.atype == amfObject || prop.atype == amfEcmaArray || + prop.atype == amfStrictArray { + amfReset(&prop.vu.obj) } else { - prop.p_vu.p_aval = "" + prop.vu.aval = "" } - prop.p_type = AMF_INVALID + prop.atype = amfInvalid } -// char* AMF_Encode(AMFObject* obj, char* pBuffer, char* pBufEnd); -// amf.c +891 -func C_AMF_Encode(obj *C_AMFObject, dst []byte) []byte { +func amfEncode(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = AMF_OBJECT + dst[0] = amfObject dst = dst[1:] - for i := 0; i < len(obj.o_props); i++ { - dst = C_AMF_PropEncode(&obj.o_props[i], dst) + for i := 0; i < len(a.props); i++ { + dst = amfPropEncode(&a.props[i], dst) if dst == nil { - log.Println("C_AMF_Encode: failed to encode property in index") + // ??? log.Println("amfEncode: failed to encode property in index") break } } @@ -462,25 +452,23 @@ func C_AMF_Encode(obj *C_AMFObject, dst []byte) []byte { if len(dst) < 4 { return nil } - return C_AMF_EncodeInt24(dst, AMF_OBJECT_END) + return amfEncodeInt24(dst, amfObjectEnd) } -// char* AMF_EncodeEcmaArray(AMFObject* obj, char* pBuffer, char* pBufEnd); -// amf.c +924 -func C_AMF_EncodeEcmaArray(obj *C_AMFObject, dst []byte) []byte { +func amfEncodeEcmaArray(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = AMF_ECMA_ARRAY + dst[0] = amfEcmaArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.o_props))) + binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props))) dst = dst[4:] - for i := 0; i < len(obj.o_props); i++ { - dst = C_AMF_PropEncode(&obj.o_props[i], dst) + for i := 0; i < len(a.props); i++ { + dst = amfPropEncode(&a.props[i], dst) if dst == nil { - log.Println("C_AMF_EncodeEcmaArray: failed to encode property!") + // ??? log.Println("amfEncodeEcmaArray: failed to encode property!") break } } @@ -488,25 +476,24 @@ func C_AMF_EncodeEcmaArray(obj *C_AMFObject, dst []byte) []byte { if len(dst) < 4 { return nil } - return C_AMF_EncodeInt24(dst, AMF_OBJECT_END) + return amfEncodeInt24(dst, amfObjectEnd) } -// char* AMF_EncodeArray(AMFObject* obj, char* pBuffer, char* pBufEnd); -// amf.c +959 -func C_AMF_EncodeArray(obj *C_AMFObject, dst []byte) []byte { +// not used? +func amfEncodeArray(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = AMF_STRICT_ARRAY + dst[0] = amfStrictArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.o_props))) + binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props))) dst = dst[4:] - for i := 0; i < len(obj.o_props); i++ { - dst = C_AMF_PropEncode(&obj.o_props[i], dst) + for i := 0; i < len(a.props); i++ { + dst = amfPropEncode(&a.props[i], dst) if dst == nil { - log.Println("C_AMF_EncodeArray: failed to encode property!") + // ??? log.Println("amfEncodeArray: failed to encode property!") break } } @@ -514,66 +501,48 @@ func C_AMF_EncodeArray(obj *C_AMFObject, dst []byte) []byte { 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 { +func amfDecode(a *AMF, data []byte, bDecodeName int32) int32 { nOriginalSize := len(data) - obj.o_props = obj.o_props[:0] + a.props = a.props[:0] for len(data) != 0 { - if len(data) >= 3 && C_AMF_DecodeInt24(data[:3]) == AMF_OBJECT_END { + if len(data) >= 3 && amfDecodeInt24(data[:3]) == amfObjectEnd { 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)), + 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:] - obj.o_props = append(obj.o_props, prop) + a.props = append(a.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 { +func amfGetProp(a *AMF, name string, idx int32) *AMFProperty { if idx >= 0 { - if idx < int32(len(obj.o_props)) { - return &obj.o_props[idx] + if idx < int32(len(a.props)) { + return &a.props[idx] } } else { - for i, p := range obj.o_props { - if p.p_name == name { - return &obj.o_props[i] + for i, p := range a.props { + if p.name == name { + return &a.props[i] } } } - return &AMFProp_Invalid + return &amfPropInvalid } -// 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]) +func amfReset(a *AMF) { + for i := range a.props { + amfPropReset(&a.props[i]) } - obj.o_props = obj.o_props[:0] + *a = AMF{} } - -/* -// 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++ -} -*/ diff --git a/rtmp/amf_headers.go b/rtmp/amf_headers.go index 453b38de..61a7904b 100644 --- a/rtmp/amf_headers.go +++ b/rtmp/amf_headers.go @@ -31,51 +31,4 @@ LICENSE */ package rtmp -const ( - AMF_NUMBER = iota - AMF_BOOLEAN - AMF_STRING - AMF_OBJECT - AMF_MOVIECLIP /* reserved, not used */ - AMF_NULL - AMF_UNDEFINED - AMF_REFERENCE - AMF_ECMA_ARRAY - AMF_OBJECT_END - AMF_STRICT_ARRAY - AMF_DATE - AMF_LONG_STRING - AMF_UNSUPPORTED - AMF_RECORDSET /* reserved, not used */ - AMF_XML_DOC - AMF_TYPED_OBJECT - AMF_AVMPLUS /* switch to AMF3 */ - AMF_INVALID = 0xff -) -// typedef enum -// amf.h +40 -type C_AMFDataType int32 - -// typedef struct AMF_Object -// amf.h +67 -type C_AMFObject struct { - o_props []C_AMFObjectProperty -} - -// typedef struct P_vu -// amf.h +73 -type P_vu struct { - p_number float64 - p_aval string - p_object C_AMFObject -} - -// typedef struct AMFObjectProperty -// amf.h +79 -type C_AMFObjectProperty struct { - p_name string - p_type C_AMFDataType - p_vu P_vu - p_UTCoffset int16 -} diff --git a/rtmp/packet.go b/rtmp/packet.go index dac3d803..a61d7bc3 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -182,9 +182,9 @@ func (pkt *packet) read(s *Session) error { hSize := len(hbuf) - len(header) + size if size >= 3 { - pkt.timestamp = C_AMF_DecodeInt24(header[:3]) + pkt.timestamp = amfDecodeInt24(header[:3]) if size >= 6 { - pkt.bodySize = C_AMF_DecodeInt24(header[3:6]) + pkt.bodySize = amfDecodeInt24(header[3:6]) pkt.bytesRead = 0 if size > 6 { @@ -205,7 +205,7 @@ func (pkt *packet) read(s *Session) error { return err } // TODO: port this - pkt.timestamp = C_AMF_DecodeInt32(header[size : size+4]) + pkt.timestamp = amfDecodeInt32(header[size : size+4]) hSize += 4 } @@ -392,12 +392,12 @@ func (pkt *packet) write(s *Session, queue bool) error { if ts > 0xffffff { res = 0xffffff } - C_AMF_EncodeInt24(headBytes[headerIdx:], int32(res)) + amfEncodeInt24(headBytes[headerIdx:], int32(res)) headerIdx += 3 // 24bits } if headerSizes[pkt.headerType] > 4 { - C_AMF_EncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) + amfEncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) headerIdx += 3 // 24bits headBytes[headerIdx] = pkt.packetType headerIdx++ @@ -409,7 +409,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } if ts >= 0xffffff { - C_AMF_EncodeInt32(headBytes[headerIdx:], int32(ts)) + amfEncodeInt32(headBytes[headerIdx:], int32(ts)) headerIdx += 4 // 32bits } @@ -479,7 +479,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } if ts >= 0xffffff { extendedTimestamp := headBytes[origIdx+1+cSize:] - C_AMF_EncodeInt32(extendedTimestamp[:4], int32(ts)) + amfEncodeInt32(extendedTimestamp[:4], int32(ts)) } } } @@ -487,12 +487,12 @@ func (pkt *packet) write(s *Session, queue bool) error { // We invoked a remote method if pkt.packetType == packetTypeInvoke { buf := pkt.body[1:] - meth := C_AMF_DecodeString(buf) + meth := amfDecodeString(buf) s.log(DebugLevel, pkg+"invoking method "+meth) // keep it in call queue till result arrives if queue { buf = buf[3+len(meth):] - txn := int32(C_AMF_DecodeNumber(buf[:8])) + txn := int32(amfDecodeNumber(buf[:8])) s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index a5a8e82b..c530ed38 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -7,7 +7,7 @@ DESCRIPTION AUTHORS Saxon Nelson-Milton - Dan Kortschak + Dan Kortschak ! Alan Noble LICENSE @@ -252,20 +252,20 @@ func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { case packetTypeChunkSize: if pkt.bodySize >= 4 { - s.inChunkSize = int32(C_AMF_DecodeInt32(pkt.body[:4])) + s.inChunkSize = int32(amfDecodeInt32(pkt.body[:4])) } case packetTypeBytesReadReport: - s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) + s.serverBW = int32(amfDecodeInt32(pkt.body[:4])) case packetTypeControl: s.log(FatalLevel, pkg+"unsupported packet type packetTypeControl") case packetTypeServerBW: - s.serverBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) + s.serverBW = int32(amfDecodeInt32(pkt.body[:4])) case packetTypeClientBW: - s.clientBW = int32(C_AMF_DecodeInt32(pkt.body[:4])) + s.clientBW = int32(amfDecodeInt32(pkt.body[:4])) if pkt.bodySize > 4 { s.clientBW2 = pkt.body[4] } else { @@ -312,72 +312,72 @@ func sendConnectPacket(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avConnect) + enc = amfEncodeString(enc, avConnect) if enc == nil { return errEncoding } s.numInvokes += 1 - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_OBJECT + enc[0] = amfObject enc = enc[1:] - enc = C_AMF_EncodeNamedString(enc, avApp, s.link.app) + enc = amfEncodeNamedString(enc, avApp, s.link.app) if enc == nil { return errEncoding } if s.link.protocol&featureWrite != 0 { - enc = C_AMF_EncodeNamedString(enc, avType, avNonprivate) + enc = amfEncodeNamedString(enc, avType, avNonprivate) if enc == nil { return errEncoding } } if s.link.flashVer != "" { - enc = C_AMF_EncodeNamedString(enc, avFlashver, s.link.flashVer) + enc = amfEncodeNamedString(enc, avFlashver, s.link.flashVer) if enc == nil { return errEncoding } } if s.link.swfUrl != "" { - enc = C_AMF_EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) + enc = amfEncodeNamedString(enc, avSwfUrl, s.link.swfUrl) if enc == nil { return errEncoding } } if s.link.tcUrl != "" { - enc = C_AMF_EncodeNamedString(enc, avTcUrl, s.link.tcUrl) + enc = amfEncodeNamedString(enc, avTcUrl, s.link.tcUrl) if enc == nil { return errEncoding } } if s.link.protocol&featureWrite == 0 { - enc = C_AMF_EncodeNamedBoolean(enc, avFpad, false) + enc = amfEncodeNamedBoolean(enc, avFpad, false) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, avCapabilities, 15) + enc = amfEncodeNamedNumber(enc, avCapabilities, 15) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) + enc = amfEncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) + enc = amfEncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) if enc == nil { return errEncoding } - enc = C_AMF_EncodeNamedNumber(enc, avVideoFunction, 1) + enc = amfEncodeNamedNumber(enc, avVideoFunction, 1) if enc == nil { return errEncoding } if s.link.pageUrl != "" { - enc = C_AMF_EncodeNamedString(enc, avPageUrl, s.link.pageUrl) + enc = amfEncodeNamedString(enc, avPageUrl, s.link.pageUrl) if enc == nil { return errEncoding } @@ -385,29 +385,29 @@ func sendConnectPacket(s *Session) error { } if s.encoding != 0.0 || s.sendEncoding { - enc = C_AMF_EncodeNamedNumber(enc, avObjectEncoding, s.encoding) + enc = amfEncodeNamedNumber(enc, avObjectEncoding, s.encoding) if enc == nil { return errEncoding } } - copy(enc, []byte{0, 0, AMF_OBJECT_END}) + copy(enc, []byte{0, 0, amfObjectEnd}) enc = enc[3:] // add auth string if s.link.auth != "" { - enc = C_AMF_EncodeBoolean(enc, s.link.flags&linkAuth != 0) + enc = amfEncodeBoolean(enc, s.link.flags&linkAuth != 0) if enc == nil { return errEncoding } - enc = C_AMF_EncodeString(enc, s.link.auth) + enc = amfEncodeString(enc, s.link.auth) if enc == nil { return errEncoding } } - for i := range s.link.extras.o_props { - enc = C_AMF_PropEncode(&s.link.extras.o_props[i], enc) + for i := range s.link.extras.props { + enc = amfPropEncode(&s.link.extras.props[i], enc) if enc == nil { return errEncoding } @@ -429,16 +429,16 @@ func sendCreateStream(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avCreatestream) + enc = amfEncodeString(enc, avCreatestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -457,18 +457,18 @@ func sendReleaseStream(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avReleasestream) + enc = amfEncodeString(enc, avReleasestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] - enc = C_AMF_EncodeString(enc, s.link.playpath) + enc = amfEncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -488,18 +488,18 @@ func sendFCPublish(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avFCPublish) + enc = amfEncodeString(enc, avFCPublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] - enc = C_AMF_EncodeString(enc, s.link.playpath) + enc = amfEncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -520,18 +520,18 @@ func sendFCUnpublish(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avFCUnpublish) + enc = amfEncodeString(enc, avFCUnpublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] - enc = C_AMF_EncodeString(enc, s.link.playpath) + enc = amfEncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -552,22 +552,22 @@ func sendPublish(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avPublish) + enc = amfEncodeString(enc, avPublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] - enc = C_AMF_EncodeString(enc, s.link.playpath) + enc = amfEncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } - enc = C_AMF_EncodeString(enc, avLive) + enc = amfEncodeString(enc, avLive) if enc == nil { return errEncoding } @@ -588,18 +588,18 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, avDeletestream) + enc = amfEncodeString(enc, avDeletestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] - enc = C_AMF_EncodeNumber(enc, dStreamId) + enc = amfEncodeNumber(enc, dStreamId) if enc == nil { return errEncoding } @@ -621,7 +621,7 @@ func sendBytesReceived(s *Session) error { enc := pkt.body s.nBytesInSent = s.nBytesIn - enc = C_AMF_EncodeInt32(enc, s.nBytesIn) + enc = amfEncodeInt32(enc, s.nBytesIn) if enc == nil { return errEncoding } @@ -641,16 +641,16 @@ func sendCheckBW(s *Session) error { } enc := pkt.body - enc = C_AMF_EncodeString(enc, av_checkbw) + enc = amfEncodeString(enc, av_checkbw) if enc == nil { return errEncoding } s.numInvokes++ - enc = C_AMF_EncodeNumber(enc, float64(s.numInvokes)) + enc = amfEncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = AMF_NULL + enc[0] = amfNull enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -670,15 +670,15 @@ func handleInvoke(s *Session, body []byte) error { if body[0] != 0x02 { return errInvalidBody } - var obj C_AMFObject - nRes := C_AMF_Decode(&obj, body, 0) + var obj AMF + nRes := amfDecode(&obj, body, 0) if nRes < 0 { return errDecoding } - meth := C_AMFProp_GetString(C_AMF_GetProp(&obj, "", 0)) + meth := amfPropGetString(amfGetProp(&obj, "", 0)) s.log(DebugLevel, pkg+"invoking method "+meth) - txn := C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 1)) + txn := amfPropGetNumber(amfGetProp(&obj, "", 1)) switch meth { case av_result: @@ -718,7 +718,7 @@ func handleInvoke(s *Session, body []byte) error { } case avCreatestream: - s.streamID = int32(C_AMFProp_GetNumber(C_AMF_GetProp(&obj, "", 3))) + s.streamID = int32(amfPropGetNumber(amfGetProp(&obj, "", 3))) if s.link.protocol&featureWrite == 0 { return errNotWritable } @@ -755,10 +755,10 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - var obj2 C_AMFObject - C_AMFProp_GetObject(C_AMF_GetProp(&obj, "", 3), &obj2) - code := C_AMFProp_GetString(C_AMF_GetProp(&obj2, avCode, -1)) - level := C_AMFProp_GetString(C_AMF_GetProp(&obj2, avLevel, -1)) + var obj2 AMF + amfPropGetObject(amfGetProp(&obj, "", 3), &obj2) + code := amfPropGetString(amfGetProp(&obj2, avCode, -1)) + level := amfPropGetString(amfGetProp(&obj2, avLevel, -1)) s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { @@ -797,7 +797,7 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unknown method "+meth) } leave: - C_AMF_Reset(&obj) + amfReset(&obj) return nil } diff --git a/rtmp/session.go b/rtmp/session.go index be6b8945..aaba6e85 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -79,7 +79,7 @@ type link struct { auth string flashVer string token string - extras C_AMFObject + extras AMF flags int32 swfAge int32 protocol int32 @@ -187,8 +187,8 @@ func (s *Session) Write(data []byte) (int, error) { pkt := packet{ packetType: data[0], - bodySize: C_AMF_DecodeInt24(data[1:4]), - timestamp: C_AMF_DecodeInt24(data[4:7]) | uint32(data[7])<<24, + bodySize: amfDecodeInt24(data[1:4]), + timestamp: amfDecodeInt24(data[4:7]) | uint32(data[7])<<24, channel: chanSource, info: s.streamID, } From de8c3ab64d841319e5d784b04201efbea83a80f5 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 06:45:27 +1030 Subject: [PATCH 03/52] Remove unused file. --- rtmp/amf_headers.go | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 rtmp/amf_headers.go diff --git a/rtmp/amf_headers.go b/rtmp/amf_headers.go deleted file mode 100644 index 61a7904b..00000000 --- a/rtmp/amf_headers.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -NAME - amf_headers.go - -DESCRIPTION - See Readme.md - -AUTHORS - Saxon Nelson-Milton - -LICENSE - amf_headers.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 - - From 355e069b5bbe3af239551d21782d0a09c4df14f8 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 06:48:33 +1030 Subject: [PATCH 04/52] Improved description. --- rtmp/amf.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rtmp/amf.go b/rtmp/amf.go index 1977254c..c0212c5f 100644 --- a/rtmp/amf.go +++ b/rtmp/amf.go @@ -3,7 +3,8 @@ NAME amf.go DESCRIPTION - RMTP encoding/decoding functions. + Action Message Format (AMF) encoding/decoding functions. + See https://en.wikipedia.org/wiki/Action_Message_Format. AUTHORS Saxon Nelson-Milton From f13d4010cca466c59e4de4251f971b3ef0e6ca20 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 07:13:27 +1030 Subject: [PATCH 05/52] Moved AMF functions into a new rtmp/amf package; names otherwise unchanged. --- rtmp/{ => amf}/amf.go | 315 +++++++++++++++++++++--------------------- rtmp/packet.go | 20 +-- rtmp/rtmp.go | 126 ++++++++--------- rtmp/session.go | 8 +- 4 files changed, 241 insertions(+), 228 deletions(-) rename rtmp/{ => amf}/amf.go (52%) diff --git a/rtmp/amf.go b/rtmp/amf/amf.go similarity index 52% rename from rtmp/amf.go rename to rtmp/amf/amf.go index c0212c5f..18199cf3 100644 --- a/rtmp/amf.go +++ b/rtmp/amf/amf.go @@ -33,42 +33,51 @@ LICENSE Copyright (C) 2008-2009 Andrej Stepanchuk Copyright (C) 2009-2010 Howard Chu */ -package rtmp + +// 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 ( - 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 + 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 []AMFProperty + Props []Property // ToDo: consider not exporting this } -type AMFProperty struct { +// Property represents an AMF property. +type Property struct { name string - atype AMFDataType + atype DataType vu vu UTCoffset int16 } @@ -79,45 +88,43 @@ type vu struct { obj AMF } -type AMFDataType int32 - var ( - amfObjInvalid AMF - amfPropInvalid = AMFProperty{atype: amfInvalid} + ObjInvalid AMF + PropInvalid = Property{atype: Invalid} ) -func amfDecodeInt16(data []byte) uint16 { +func DecodeInt16(data []byte) uint16 { return uint16(binary.BigEndian.Uint16(data)) } -func amfDecodeInt24(data []byte) uint32 { +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 amfDecodeInt32(data []byte) uint32 { +func DecodeInt32(data []byte) uint32 { return uint32(binary.BigEndian.Uint32(data)) } -func amfDecodeString(data []byte) string { - n := amfDecodeInt16(data) +func DecodeString(data []byte) string { + n := DecodeInt16(data) return string(data[2 : 2+n]) } -func amfDecodeLongString(data []byte) string { - n := amfDecodeInt32(data) +func DecodeLongString(data []byte) string { + n := DecodeInt32(data) return string(data[2 : 2+n]) } -func amfDecodeNumber(data []byte) float64 { +func DecodeNumber(data []byte) float64 { return math.Float64frombits(binary.BigEndian.Uint64(data)) } -func amfDecodeBoolean(data []byte) bool { +func DecodeBoolean(data []byte) bool { return data[0] != 0 } -func amfEncodeInt24(dst []byte, val int32) []byte { +func EncodeInt24(dst []byte, val int32) []byte { if len(dst) < 3 { return nil } @@ -130,7 +137,7 @@ func amfEncodeInt24(dst []byte, val int32) []byte { return dst[3:] } -func amfEncodeInt32(dst []byte, val int32) []byte { +func EncodeInt32(dst []byte, val int32) []byte { if len(dst) < 4 { return nil } @@ -141,7 +148,7 @@ func amfEncodeInt32(dst []byte, val int32) []byte { return dst[4:] } -func amfEncodeString(dst []byte, val string) []byte { +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 @@ -151,7 +158,7 @@ func amfEncodeString(dst []byte, val string) []byte { } if len(val) < 65536 { - dst[0] = amfString + dst[0] = String dst = dst[1:] binary.BigEndian.PutUint16(dst[:2], uint16(len(val))) dst = dst[2:] @@ -161,7 +168,7 @@ func amfEncodeString(dst []byte, val string) []byte { } return dst[len(val):] } - dst[0] = amfLongSring + dst[0] = LongSring dst = dst[1:] binary.BigEndian.PutUint32(dst[:4], uint32(len(val))) dst = dst[4:] @@ -172,21 +179,21 @@ func amfEncodeString(dst []byte, val string) []byte { return dst[len(val):] } -func amfEncodeNumber(dst []byte, val float64) []byte { +func EncodeNumber(dst []byte, val float64) []byte { if len(dst) < 9 { return nil } - dst[0] = amfNumber + dst[0] = Number dst = dst[1:] binary.BigEndian.PutUint64(dst, math.Float64bits(val)) return dst[8:] } -func amfEncodeBoolean(dst []byte, val bool) []byte { +func EncodeBoolean(dst []byte, val bool) []byte { if len(dst) < 2 { return nil } - dst[0] = amfBoolean + dst[0] = Boolean if val { dst[1] = 1 } @@ -197,7 +204,7 @@ func amfEncodeBoolean(dst []byte, val bool) []byte { } -func amfEncodeNamedString(dst []byte, key, val string) []byte { +func EncodeNamedString(dst []byte, key, val string) []byte { if 2+len(key) > len(dst) { return nil } @@ -207,10 +214,10 @@ func amfEncodeNamedString(dst []byte, key, val string) []byte { if len(key) == len(dst) { return nil } - return amfEncodeString(dst[len(key):], val) + return EncodeString(dst[len(key):], val) } -func amfEncodeNamedNumber(dst []byte, key string, val float64) []byte { +func EncodeNamedNumber(dst []byte, key string, val float64) []byte { if 2+len(key) > len(dst) { return nil } @@ -220,10 +227,10 @@ func amfEncodeNamedNumber(dst []byte, key string, val float64) []byte { if len(key) == len(dst) { return nil } - return amfEncodeNumber(dst[len(key):], val) + return EncodeNumber(dst[len(key):], val) } -func amfEncodeNamedBoolean(dst []byte, key string, val bool) []byte { +func EncodeNamedBoolean(dst []byte, key string, val bool) []byte { if 2+len(key) > len(dst) { return nil } @@ -233,42 +240,42 @@ func amfEncodeNamedBoolean(dst []byte, key string, val bool) []byte { if len(key) == len(dst) { return nil } - return amfEncodeBoolean(dst[len(key):], val) + return EncodeBoolean(dst[len(key):], val) } -func amfPropSetName(prop *AMFProperty, name string) { +func PropSetName(prop *Property, name string) { prop.name = name } -func amfPropGetNumber(prop *AMFProperty) float64 { +func PropGetNumber(prop *Property) float64 { return prop.vu.number } -func amfPropGetString(prop *AMFProperty) string { - if prop.atype == amfString { +func PropGetString(prop *Property) string { + if prop.atype == String { return prop.vu.aval } return "" } -func amfPropGetObject(prop *AMFProperty, a *AMF) { - if prop.atype == amfObject { +func PropGetObject(prop *Property, a *AMF) { + if prop.atype == Object { *a = prop.vu.obj } else { - *a = amfObjInvalid + *a = ObjInvalid } } -func amfPropEncode(p *AMFProperty, dst []byte) []byte { - if p.atype == amfInvalid { +func PropEncode(p *Property, dst []byte) []byte { + if p.atype == Invalid { return nil } - if p.atype != amfNull && len(p.name)+2+1 >= len(dst) { + if p.atype != Null && len(p.name)+2+1 >= len(dst) { return nil } - if p.atype != amfNull && len(p.name) != 0 { + if p.atype != Null && len(p.name) != 0 { binary.BigEndian.PutUint16(dst[:2], uint16(len(p.name))) dst = dst[2:] copy(dst, p.name) @@ -276,32 +283,32 @@ func amfPropEncode(p *AMFProperty, dst []byte) []byte { } 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: + 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] = amfNull + dst[0] = Null 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) + 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("amfPropEncode: invalid type!") + // ??? log.Println("PropEncode: invalid type!") dst = nil } return dst } -func amfProDecode(prop *AMFProperty, data []byte, bDecodeName int32) int32 { +func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 { prop.name = "" nOriginalSize := len(data) @@ -319,14 +326,14 @@ func amfProDecode(prop *AMFProperty, data []byte, bDecodeName int32) int32 { } if bDecodeName != 0 { - nNameSize := amfDecodeInt16(data[:2]) + 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 = amfDecodeString(data) + prop.name = DecodeString(data) data = data[2+nNameSize:] } @@ -334,85 +341,85 @@ func amfProDecode(prop *AMFProperty, data []byte, bDecodeName int32) int32 { return -1 } - prop.atype = AMFDataType(data[0]) + prop.atype = DataType(data[0]) data = data[1:] var nRes int32 switch prop.atype { - case amfNumber: + case Number: if len(data) < 8 { return -1 } - prop.vu.number = amfDecodeNumber(data[:8]) + prop.vu.number = DecodeNumber(data[:8]) data = data[8:] - case amfBoolean: - panic("amfBoolean not supported") + case Boolean: + panic("Boolean not supported") - case amfString: - nStringSize := amfDecodeInt16(data[:2]) + case String: + nStringSize := DecodeInt16(data[:2]) if len(data) < int(nStringSize+2) { return -1 } - prop.vu.aval = amfDecodeString(data) + prop.vu.aval = DecodeString(data) data = data[2+nStringSize:] - case amfObject: - nRes := amfDecode(&prop.vu.obj, data, 1) + case Object: + nRes := Decode(&prop.vu.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case amfMovieClip: + case MovieClip: // TODO use new logger here - // ??? log.Println("AMFProDecode: MAF_MOVIECLIP reserved!") - //RTMLog(RTMLOGERROR, "amfMovieClip reserved!"); + // ??? log.Println("PropDecode: MAF_MOVIECLIP reserved!") + //RTMLog(RTMLOGERROR, "MovieClip reserved!"); return -1 - case amfNull, amfUndefined, amfUnsupported: - prop.atype = amfNull + case Null, Undefined, Unsupported: + prop.atype = Null - case amfReference: + case Reference: // TODO use new logger here - // ??? log.Println("AMFProDecode: amfReference not supported!") - //RTMLog(RTMLOGERROR, "amfReference not supported!"); + // ??? log.Println("PropDecode: Reference not supported!") + //RTMLog(RTMLOGERROR, "Reference not supported!"); return -1 - case amfEcmaArray: + case EcmaArray: // 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) + nRes = Decode(&prop.vu.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case amfObjectEnd: + case ObjectEnd: return -1 - case amfStrictArray: - panic("amfStrictArray not supported") + case StrictArray: + panic("StrictArray not supported") - case amfDate: - panic("amfDate not supported") + case Date: + panic("Date not supported") - case amfLongSring, amfXmlDoc: - panic("amfLongSring, amfXmlDoc not supported") + case LongSring, XmlDoc: + panic("LongSring, XmlDoc not supported") - case amfRecordset: + case Recordset: // TODO use new logger here - // ??? log.Println("AMFProDecode: amfRecordset reserved!") - //RTMLog(RTMLOGERROR, "amfRecordset reserved!"); + // ??? log.Println("PropDecode: Recordset reserved!") + //RTMLog(RTMLOGERROR, "Recordset reserved!"); return -1 - case amfTypedObject: + case TypedObject: // TODO use new logger here - // RTMLog(RTMLOGERROR, "amfTyped_object not supported!") + // RTMLog(RTMLOGERROR, "Typed_object not supported!") return -1 - case amfAvmplus: - panic("amfAvmplus not supported") + case Avmplus: + panic("Avmplus not supported") default: // TODO use new logger here @@ -424,28 +431,28 @@ func amfProDecode(prop *AMFProperty, data []byte, bDecodeName int32) int32 { return int32(nOriginalSize - len(data)) } -func amfPropReset(prop *AMFProperty) { - if prop.atype == amfObject || prop.atype == amfEcmaArray || - prop.atype == amfStrictArray { - amfReset(&prop.vu.obj) +func PropReset(prop *Property) { + if prop.atype == Object || prop.atype == EcmaArray || + prop.atype == StrictArray { + Reset(&prop.vu.obj) } else { prop.vu.aval = "" } - prop.atype = amfInvalid + prop.atype = Invalid } -func amfEncode(a *AMF, dst []byte) []byte { +func Encode(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = amfObject + dst[0] = Object dst = dst[1:] - for i := 0; i < len(a.props); i++ { - dst = amfPropEncode(&a.props[i], dst) + for i := 0; i < len(a.Props); i++ { + dst = PropEncode(&a.Props[i], dst) if dst == nil { - // ??? log.Println("amfEncode: failed to encode property in index") + // ??? log.Println("Encode: failed to encode property in index") break } } @@ -453,23 +460,23 @@ func amfEncode(a *AMF, dst []byte) []byte { if len(dst) < 4 { return nil } - return amfEncodeInt24(dst, amfObjectEnd) + return EncodeInt24(dst, ObjectEnd) } -func amfEncodeEcmaArray(a *AMF, dst []byte) []byte { +func EncodeEcmaArray(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = amfEcmaArray + dst[0] = EcmaArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props))) + 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) + for i := 0; i < len(a.Props); i++ { + dst = PropEncode(&a.Props[i], dst) if dst == nil { - // ??? log.Println("amfEncodeEcmaArray: failed to encode property!") + // ??? log.Println("EncodeEcmaArray: failed to encode property!") break } } @@ -477,24 +484,24 @@ func amfEncodeEcmaArray(a *AMF, dst []byte) []byte { if len(dst) < 4 { return nil } - return amfEncodeInt24(dst, amfObjectEnd) + return EncodeInt24(dst, ObjectEnd) } // not used? -func amfEncodeArray(a *AMF, dst []byte) []byte { +func EncodeArray(a *AMF, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = amfStrictArray + dst[0] = StrictArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(a.props))) + 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) + for i := 0; i < len(a.Props); i++ { + dst = PropEncode(&a.Props[i], dst) if dst == nil { - // ??? log.Println("amfEncodeArray: failed to encode property!") + // ??? log.Println("EncodeArray: failed to encode property!") break } } @@ -502,48 +509,48 @@ func amfEncodeArray(a *AMF, dst []byte) []byte { return dst } -func amfDecode(a *AMF, data []byte, bDecodeName int32) int32 { +func Decode(a *AMF, data []byte, bDecodeName int32) int32 { nOriginalSize := len(data) - a.props = a.props[:0] + a.Props = a.Props[:0] for len(data) != 0 { - if len(data) >= 3 && amfDecodeInt24(data[:3]) == amfObjectEnd { + if len(data) >= 3 && DecodeInt24(data[:3]) == ObjectEnd { data = data[3:] break } - var prop AMFProperty - nRes := amfProDecode(&prop, data, bDecodeName) - // nRes = int32(C.AMFProDecode(&prop, (*byte)(unsafe.Pointer(pBuffer)), + 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) + a.Props = append(a.Props, prop) } return int32(nOriginalSize - len(data)) } -func amfGetProp(a *AMF, name string, idx int32) *AMFProperty { +func GetProp(a *AMF, name string, idx int32) *Property { if idx >= 0 { - if idx < int32(len(a.props)) { - return &a.props[idx] + if idx < int32(len(a.Props)) { + return &a.Props[idx] } } else { - for i, p := range a.props { + for i, p := range a.Props { if p.name == name { - return &a.props[i] + return &a.Props[i] } } } - return &amfPropInvalid + return &PropInvalid } -func amfReset(a *AMF) { - for i := range a.props { - amfPropReset(&a.props[i]) +func Reset(a *AMF) { + for i := range a.Props { + PropReset(&a.Props[i]) } *a = AMF{} } diff --git a/rtmp/packet.go b/rtmp/packet.go index a61d7bc3..adbe32f3 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -37,6 +37,8 @@ package rtmp import ( "encoding/binary" "io" + + "bitbucket.org/ausocean/av/rtmp/amf" ) // Packet types. @@ -182,9 +184,9 @@ func (pkt *packet) read(s *Session) error { hSize := len(hbuf) - len(header) + size if size >= 3 { - pkt.timestamp = amfDecodeInt24(header[:3]) + pkt.timestamp = amf.DecodeInt24(header[:3]) if size >= 6 { - pkt.bodySize = amfDecodeInt24(header[3:6]) + pkt.bodySize = amf.DecodeInt24(header[3:6]) pkt.bytesRead = 0 if size > 6 { @@ -205,7 +207,7 @@ func (pkt *packet) read(s *Session) error { return err } // TODO: port this - pkt.timestamp = amfDecodeInt32(header[size : size+4]) + pkt.timestamp = amf.DecodeInt32(header[size : size+4]) hSize += 4 } @@ -392,12 +394,12 @@ func (pkt *packet) write(s *Session, queue bool) error { if ts > 0xffffff { res = 0xffffff } - amfEncodeInt24(headBytes[headerIdx:], int32(res)) + amf.EncodeInt24(headBytes[headerIdx:], int32(res)) headerIdx += 3 // 24bits } if headerSizes[pkt.headerType] > 4 { - amfEncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) + amf.EncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) headerIdx += 3 // 24bits headBytes[headerIdx] = pkt.packetType headerIdx++ @@ -409,7 +411,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } if ts >= 0xffffff { - amfEncodeInt32(headBytes[headerIdx:], int32(ts)) + amf.EncodeInt32(headBytes[headerIdx:], int32(ts)) headerIdx += 4 // 32bits } @@ -479,7 +481,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } if ts >= 0xffffff { extendedTimestamp := headBytes[origIdx+1+cSize:] - amfEncodeInt32(extendedTimestamp[:4], int32(ts)) + amf.EncodeInt32(extendedTimestamp[:4], int32(ts)) } } } @@ -487,12 +489,12 @@ func (pkt *packet) write(s *Session, queue bool) error { // We invoked a remote method if pkt.packetType == packetTypeInvoke { buf := pkt.body[1:] - meth := amfDecodeString(buf) + meth := amf.DecodeString(buf) s.log(DebugLevel, pkg+"invoking method "+meth) // keep it in call queue till result arrives if queue { buf = buf[3+len(meth):] - txn := int32(amfDecodeNumber(buf[:8])) + txn := int32(amf.DecodeNumber(buf[:8])) s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index c530ed38..0088613d 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -42,6 +42,8 @@ import ( "net" "strconv" "time" + + "bitbucket.org/ausocean/av/rtmp/amf" ) const ( @@ -252,20 +254,20 @@ func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { case packetTypeChunkSize: if pkt.bodySize >= 4 { - s.inChunkSize = int32(amfDecodeInt32(pkt.body[:4])) + s.inChunkSize = int32(amf.DecodeInt32(pkt.body[:4])) } case packetTypeBytesReadReport: - s.serverBW = int32(amfDecodeInt32(pkt.body[:4])) + s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) case packetTypeControl: s.log(FatalLevel, pkg+"unsupported packet type packetTypeControl") case packetTypeServerBW: - s.serverBW = int32(amfDecodeInt32(pkt.body[:4])) + s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) case packetTypeClientBW: - s.clientBW = int32(amfDecodeInt32(pkt.body[:4])) + s.clientBW = int32(amf.DecodeInt32(pkt.body[:4])) if pkt.bodySize > 4 { s.clientBW2 = pkt.body[4] } else { @@ -312,72 +314,72 @@ func sendConnectPacket(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avConnect) + enc = amf.EncodeString(enc, avConnect) if enc == nil { return errEncoding } s.numInvokes += 1 - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfObject + enc[0] = amf.Object enc = enc[1:] - enc = amfEncodeNamedString(enc, avApp, s.link.app) + enc = amf.EncodeNamedString(enc, avApp, s.link.app) if enc == nil { return errEncoding } if s.link.protocol&featureWrite != 0 { - enc = amfEncodeNamedString(enc, avType, avNonprivate) + enc = amf.EncodeNamedString(enc, avType, avNonprivate) if enc == nil { return errEncoding } } if s.link.flashVer != "" { - enc = amfEncodeNamedString(enc, avFlashver, s.link.flashVer) + enc = amf.EncodeNamedString(enc, avFlashver, s.link.flashVer) if enc == nil { return errEncoding } } if s.link.swfUrl != "" { - enc = amfEncodeNamedString(enc, avSwfUrl, s.link.swfUrl) + enc = amf.EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) if enc == nil { return errEncoding } } if s.link.tcUrl != "" { - enc = amfEncodeNamedString(enc, avTcUrl, s.link.tcUrl) + enc = amf.EncodeNamedString(enc, avTcUrl, s.link.tcUrl) if enc == nil { return errEncoding } } if s.link.protocol&featureWrite == 0 { - enc = amfEncodeNamedBoolean(enc, avFpad, false) + enc = amf.EncodeNamedBoolean(enc, avFpad, false) if enc == nil { return errEncoding } - enc = amfEncodeNamedNumber(enc, avCapabilities, 15) + enc = amf.EncodeNamedNumber(enc, avCapabilities, 15) if enc == nil { return errEncoding } - enc = amfEncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) + enc = amf.EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) if enc == nil { return errEncoding } - enc = amfEncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) + enc = amf.EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) if enc == nil { return errEncoding } - enc = amfEncodeNamedNumber(enc, avVideoFunction, 1) + enc = amf.EncodeNamedNumber(enc, avVideoFunction, 1) if enc == nil { return errEncoding } if s.link.pageUrl != "" { - enc = amfEncodeNamedString(enc, avPageUrl, s.link.pageUrl) + enc = amf.EncodeNamedString(enc, avPageUrl, s.link.pageUrl) if enc == nil { return errEncoding } @@ -385,29 +387,29 @@ func sendConnectPacket(s *Session) error { } if s.encoding != 0.0 || s.sendEncoding { - enc = amfEncodeNamedNumber(enc, avObjectEncoding, s.encoding) + enc = amf.EncodeNamedNumber(enc, avObjectEncoding, s.encoding) if enc == nil { return errEncoding } } - copy(enc, []byte{0, 0, amfObjectEnd}) + copy(enc, []byte{0, 0, amf.ObjectEnd}) enc = enc[3:] // add auth string if s.link.auth != "" { - enc = amfEncodeBoolean(enc, s.link.flags&linkAuth != 0) + enc = amf.EncodeBoolean(enc, s.link.flags&linkAuth != 0) if enc == nil { return errEncoding } - enc = amfEncodeString(enc, s.link.auth) + enc = amf.EncodeString(enc, s.link.auth) if enc == nil { return errEncoding } } - for i := range s.link.extras.props { - enc = amfPropEncode(&s.link.extras.props[i], enc) + for i := range s.link.extras.Props { + enc = amf.PropEncode(&s.link.extras.Props[i], enc) if enc == nil { return errEncoding } @@ -429,16 +431,16 @@ func sendCreateStream(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avCreatestream) + enc = amf.EncodeString(enc, avCreatestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -457,18 +459,18 @@ func sendReleaseStream(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avReleasestream) + enc = amf.EncodeString(enc, avReleasestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] - enc = amfEncodeString(enc, s.link.playpath) + enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -488,18 +490,18 @@ func sendFCPublish(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avFCPublish) + enc = amf.EncodeString(enc, avFCPublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] - enc = amfEncodeString(enc, s.link.playpath) + enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -520,18 +522,18 @@ func sendFCUnpublish(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avFCUnpublish) + enc = amf.EncodeString(enc, avFCUnpublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] - enc = amfEncodeString(enc, s.link.playpath) + enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } @@ -552,22 +554,22 @@ func sendPublish(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, avPublish) + enc = amf.EncodeString(enc, avPublish) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] - enc = amfEncodeString(enc, s.link.playpath) + enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { return errEncoding } - enc = amfEncodeString(enc, avLive) + enc = amf.EncodeString(enc, avLive) if enc == nil { return errEncoding } @@ -588,18 +590,18 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } enc := pkt.body - enc = amfEncodeString(enc, avDeletestream) + enc = amf.EncodeString(enc, avDeletestream) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] - enc = amfEncodeNumber(enc, dStreamId) + enc = amf.EncodeNumber(enc, dStreamId) if enc == nil { return errEncoding } @@ -621,7 +623,7 @@ func sendBytesReceived(s *Session) error { enc := pkt.body s.nBytesInSent = s.nBytesIn - enc = amfEncodeInt32(enc, s.nBytesIn) + enc = amf.EncodeInt32(enc, s.nBytesIn) if enc == nil { return errEncoding } @@ -641,16 +643,16 @@ func sendCheckBW(s *Session) error { } enc := pkt.body - enc = amfEncodeString(enc, av_checkbw) + enc = amf.EncodeString(enc, av_checkbw) if enc == nil { return errEncoding } s.numInvokes++ - enc = amfEncodeNumber(enc, float64(s.numInvokes)) + enc = amf.EncodeNumber(enc, float64(s.numInvokes)) if enc == nil { return errEncoding } - enc[0] = amfNull + enc[0] = amf.Null enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -670,15 +672,15 @@ func handleInvoke(s *Session, body []byte) error { if body[0] != 0x02 { return errInvalidBody } - var obj AMF - nRes := amfDecode(&obj, body, 0) + var obj amf.AMF + nRes := amf.Decode(&obj, body, 0) if nRes < 0 { return errDecoding } - meth := amfPropGetString(amfGetProp(&obj, "", 0)) + meth := amf.PropGetString(amf.GetProp(&obj, "", 0)) s.log(DebugLevel, pkg+"invoking method "+meth) - txn := amfPropGetNumber(amfGetProp(&obj, "", 1)) + txn := amf.PropGetNumber(amf.GetProp(&obj, "", 1)) switch meth { case av_result: @@ -718,7 +720,7 @@ func handleInvoke(s *Session, body []byte) error { } case avCreatestream: - s.streamID = int32(amfPropGetNumber(amfGetProp(&obj, "", 3))) + s.streamID = int32(amf.PropGetNumber(amf.GetProp(&obj, "", 3))) if s.link.protocol&featureWrite == 0 { return errNotWritable } @@ -755,10 +757,10 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - var obj2 AMF - amfPropGetObject(amfGetProp(&obj, "", 3), &obj2) - code := amfPropGetString(amfGetProp(&obj2, avCode, -1)) - level := amfPropGetString(amfGetProp(&obj2, avLevel, -1)) + var obj2 amf.AMF + amf.PropGetObject(amf.GetProp(&obj, "", 3), &obj2) + code := amf.PropGetString(amf.GetProp(&obj2, avCode, -1)) + level := amf.PropGetString(amf.GetProp(&obj2, avLevel, -1)) s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { @@ -797,7 +799,7 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unknown method "+meth) } leave: - amfReset(&obj) + amf.Reset(&obj) return nil } diff --git a/rtmp/session.go b/rtmp/session.go index aaba6e85..62496921 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -37,6 +37,8 @@ import ( "io" "net" "time" + + "bitbucket.org/ausocean/av/rtmp/amf" ) // Session holds the state for an RTMP session. @@ -79,7 +81,7 @@ type link struct { auth string flashVer string token string - extras AMF + extras amf.AMF flags int32 swfAge int32 protocol int32 @@ -187,8 +189,8 @@ func (s *Session) Write(data []byte) (int, error) { pkt := packet{ packetType: data[0], - bodySize: amfDecodeInt24(data[1:4]), - timestamp: amfDecodeInt24(data[4:7]) | uint32(data[7])<<24, + bodySize: amf.DecodeInt24(data[1:4]), + timestamp: amf.DecodeInt24(data[4:7]) | uint32(data[7])<<24, channel: chanSource, info: s.streamID, } From 255464d85afc857baef9f24d63247dc506aaa6d2 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 08:56:57 +1030 Subject: [PATCH 06/52] amf.AMF now amf.Object, amf type consts now prefixed with type and vu struct merged into Property. --- rtmp/amf/amf.go | 255 +++++++++++++++++++++++------------------------- rtmp/rtmp.go | 22 ++--- rtmp/session.go | 2 +- 3 files changed, 134 insertions(+), 145 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 18199cf3..e5ec3a08 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -44,62 +44,49 @@ import ( ) // AMF data types. +// NB: we export these sparingly. 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 + typeNumber = iota + typeBoolean + typeString + TypeObject // ToDo: consider not exporting this + typeMovieClip // reserved, not implemented + TypeNull // ToDo: consider not exporting this + typeUndefined + typeReference + typeEcmaArray + TypeObjectEnd // ToDo: consider not exporting this + typeStrictArray + typeDate + typeLongString + typeUnsupported + typeRecordset // reserved, not implemented + typeXmlDoc + typeTypedObject + typeAvmplus // reserved, not implemented + typeInvalid = 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 { +// AMF represents an AMF message (object), which is simply a collection of properties. +type Object 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 { + name string + dtype uint8 number float64 - aval string - obj AMF + str string + obj Object } -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 { @@ -153,12 +140,13 @@ func EncodeString(dst []byte, val string) []byte { 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[0] = typeString dst = dst[1:] binary.BigEndian.PutUint16(dst[:2], uint16(len(val))) dst = dst[2:] @@ -168,7 +156,8 @@ func EncodeString(dst []byte, val string) []byte { } return dst[len(val):] } - dst[0] = LongSring + + dst[0] = typeLongString dst = dst[1:] binary.BigEndian.PutUint32(dst[:4], uint32(len(val))) dst = dst[4:] @@ -183,7 +172,7 @@ func EncodeNumber(dst []byte, val float64) []byte { if len(dst) < 9 { return nil } - dst[0] = Number + dst[0] = typeNumber dst = dst[1:] binary.BigEndian.PutUint64(dst, math.Float64bits(val)) return dst[8:] @@ -193,7 +182,7 @@ func EncodeBoolean(dst []byte, val bool) []byte { if len(dst) < 2 { return nil } - dst[0] = Boolean + dst[0] = typeBoolean if val { dst[1] = 1 } @@ -248,59 +237,59 @@ func PropSetName(prop *Property, name string) { } func PropGetNumber(prop *Property) float64 { - return prop.vu.number + return prop.number } func PropGetString(prop *Property) string { - if prop.atype == String { - return prop.vu.aval + if prop.dtype == typeString { + return prop.str } return "" } -func PropGetObject(prop *Property, a *AMF) { - if prop.atype == Object { - *a = prop.vu.obj +func PropGetObject(prop *Property, obj *Object) { + if prop.dtype == TypeObject { + *obj = prop.obj } else { - *a = ObjInvalid + *obj = Object{} } } func PropEncode(p *Property, dst []byte) []byte { - if p.atype == Invalid { + if p.dtype == typeInvalid { return nil } - if p.atype != Null && len(p.name)+2+1 >= len(dst) { + if p.dtype != TypeNull && len(p.name)+2+1 >= len(dst) { return nil } - if p.atype != Null && len(p.name) != 0 { + if p.dtype != TypeNull && 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: + switch p.dtype { + case typeNumber: + dst = EncodeNumber(dst, p.number) + case typeBoolean: + dst = EncodeBoolean(dst, p.number != 0) + case typeString: + dst = EncodeString(dst, p.str) + case TypeNull: if len(dst) < 2 { return nil } - dst[0] = Null + dst[0] = TypeNull 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) + case TypeObject: + dst = Encode(&p.obj, dst) + case typeEcmaArray: + dst = EncodeEcmaArray(&p.obj, dst) + case typeStrictArray: + dst = EncodeArray(&p.obj, dst) default: // ??? log.Println("PropEncode: invalid type!") dst = nil @@ -308,7 +297,7 @@ func PropEncode(p *Property, dst []byte) []byte { return dst } -func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 { +func PropDecode(prop *Property, data []byte, decodeName int32) int32 { prop.name = "" nOriginalSize := len(data) @@ -318,14 +307,14 @@ func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 { return -1 } - if bDecodeName != 0 && len(data) < 4 { + if decodeName != 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 { + if decodeName != 0 { nNameSize := DecodeInt16(data[:2]) if int(nNameSize) > len(data)-2 { // TODO use new logger here @@ -341,90 +330,90 @@ func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 { return -1 } - prop.atype = DataType(data[0]) + prop.dtype = uint8(data[0]) data = data[1:] var nRes int32 - switch prop.atype { - case Number: + switch prop.dtype { + case typeNumber: if len(data) < 8 { return -1 } - prop.vu.number = DecodeNumber(data[:8]) + prop.number = DecodeNumber(data[:8]) data = data[8:] - case Boolean: - panic("Boolean not supported") + case typeBoolean: + panic("typeBoolean not supported") - case String: + case typeString: nStringSize := DecodeInt16(data[:2]) if len(data) < int(nStringSize+2) { return -1 } - prop.vu.aval = DecodeString(data) + prop.str = DecodeString(data) data = data[2+nStringSize:] - case Object: - nRes := Decode(&prop.vu.obj, data, 1) + case TypeObject: + nRes := Decode(&prop.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case MovieClip: + case typeMovieClip: // 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 TypeNull, typeUndefined, typeUnsupported: + prop.dtype = TypeNull - case Reference: + case typeReference: // TODO use new logger here // ??? log.Println("PropDecode: Reference not supported!") //RTMLog(RTMLOGERROR, "Reference not supported!"); return -1 - case EcmaArray: + case typeEcmaArray: // 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) + nRes = Decode(&prop.obj, data, 1) if nRes == -1 { return -1 } data = data[nRes:] - case ObjectEnd: + case TypeObjectEnd: return -1 - case StrictArray: + case typeStrictArray: panic("StrictArray not supported") - case Date: + case typeDate: panic("Date not supported") - case LongSring, XmlDoc: - panic("LongSring, XmlDoc not supported") + case typeLongString, typeXmlDoc: + panic("typeLongString, XmlDoc not supported") - case Recordset: + case typeRecordset: // TODO use new logger here // ??? log.Println("PropDecode: Recordset reserved!") //RTMLog(RTMLOGERROR, "Recordset reserved!"); return -1 - case TypedObject: + case typeTypedObject: // TODO use new logger here // RTMLog(RTMLOGERROR, "Typed_object not supported!") return -1 - case Avmplus: + case typeAvmplus: panic("Avmplus not supported") default: // TODO use new logger here //RTMLog(RTMLOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, - //prop.atype, pBuffer - 1); + //prop.dtype, pBuffer - 1); return -1 } @@ -432,25 +421,25 @@ func PropDecode(prop *Property, data []byte, bDecodeName int32) int32 { } func PropReset(prop *Property) { - if prop.atype == Object || prop.atype == EcmaArray || - prop.atype == StrictArray { - Reset(&prop.vu.obj) + if prop.dtype == TypeObject || prop.dtype == typeEcmaArray || + prop.dtype == typeStrictArray { + Reset(&prop.obj) } else { - prop.vu.aval = "" + prop.str = "" } - prop.atype = Invalid + prop.dtype = typeInvalid } -func Encode(a *AMF, dst []byte) []byte { +func Encode(obj *Object, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = Object + dst[0] = TypeObject dst = dst[1:] - for i := 0; i < len(a.Props); i++ { - dst = PropEncode(&a.Props[i], dst) + for i := 0; i < len(obj.Props); i++ { + dst = PropEncode(&obj.Props[i], dst) if dst == nil { // ??? log.Println("Encode: failed to encode property in index") break @@ -460,21 +449,21 @@ func Encode(a *AMF, dst []byte) []byte { if len(dst) < 4 { return nil } - return EncodeInt24(dst, ObjectEnd) + return EncodeInt24(dst, TypeObjectEnd) } -func EncodeEcmaArray(a *AMF, dst []byte) []byte { +func EncodeEcmaArray(obj *Object, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = EcmaArray + dst[0] = typeEcmaArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(a.Props))) + binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.Props))) dst = dst[4:] - for i := 0; i < len(a.Props); i++ { - dst = PropEncode(&a.Props[i], dst) + for i := 0; i < len(obj.Props); i++ { + dst = PropEncode(&obj.Props[i], dst) if dst == nil { // ??? log.Println("EncodeEcmaArray: failed to encode property!") break @@ -484,22 +473,22 @@ func EncodeEcmaArray(a *AMF, dst []byte) []byte { if len(dst) < 4 { return nil } - return EncodeInt24(dst, ObjectEnd) + return EncodeInt24(dst, TypeObjectEnd) } // not used? -func EncodeArray(a *AMF, dst []byte) []byte { +func EncodeArray(obj *Object, dst []byte) []byte { if len(dst) < 5 { return nil } - dst[0] = StrictArray + dst[0] = typeStrictArray dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(a.Props))) + binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.Props))) dst = dst[4:] - for i := 0; i < len(a.Props); i++ { - dst = PropEncode(&a.Props[i], dst) + for i := 0; i < len(obj.Props); i++ { + dst = PropEncode(&obj.Props[i], dst) if dst == nil { // ??? log.Println("EncodeArray: failed to encode property!") break @@ -509,48 +498,48 @@ func EncodeArray(a *AMF, dst []byte) []byte { return dst } -func Decode(a *AMF, data []byte, bDecodeName int32) int32 { +func Decode(obj *Object, data []byte, decodeName int32) int32 { nOriginalSize := len(data) - a.Props = a.Props[:0] + obj.Props = obj.Props[:0] for len(data) != 0 { - if len(data) >= 3 && DecodeInt24(data[:3]) == ObjectEnd { + if len(data) >= 3 && DecodeInt24(data[:3]) == TypeObjectEnd { data = data[3:] break } var prop Property - nRes := PropDecode(&prop, data, bDecodeName) + nRes := PropDecode(&prop, data, decodeName) // nRes = int32(C.PropDecode(&prop, (*byte)(unsafe.Pointer(pBuffer)), - // int32(nSize), int32(bDecodeName))) + // int32(nSize), int32(decodeName))) if nRes == -1 { return -1 } data = data[nRes:] - a.Props = append(a.Props, prop) + obj.Props = append(obj.Props, prop) } return int32(nOriginalSize - len(data)) } -func GetProp(a *AMF, name string, idx int32) *Property { +func GetProp(obj *Object, name string, idx int32) *Property { if idx >= 0 { - if idx < int32(len(a.Props)) { - return &a.Props[idx] + if idx < int32(len(obj.Props)) { + return &obj.Props[idx] } } else { - for i, p := range a.Props { + for i, p := range obj.Props { if p.name == name { - return &a.Props[i] + return &obj.Props[i] } } } - return &PropInvalid + return &Property{} } -func Reset(a *AMF) { - for i := range a.Props { - PropReset(&a.Props[i]) +func Reset(obj *Object) { + for i := range obj.Props { + PropReset(&obj.Props[i]) } - *a = AMF{} + *obj = Object{} } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 0088613d..d0aad407 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -323,7 +323,7 @@ func sendConnectPacket(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Object + enc[0] = amf.TypeObject enc = enc[1:] enc = amf.EncodeNamedString(enc, avApp, s.link.app) @@ -393,7 +393,7 @@ func sendConnectPacket(s *Session) error { } } - copy(enc, []byte{0, 0, amf.ObjectEnd}) + copy(enc, []byte{0, 0, amf.TypeObjectEnd}) enc = enc[3:] // add auth string @@ -440,7 +440,7 @@ func sendCreateStream(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -468,7 +468,7 @@ func sendReleaseStream(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { @@ -499,7 +499,7 @@ func sendFCPublish(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { @@ -531,7 +531,7 @@ func sendFCUnpublish(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { @@ -563,7 +563,7 @@ func sendPublish(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] enc = amf.EncodeString(enc, s.link.playpath) if enc == nil { @@ -599,7 +599,7 @@ func sendDeleteStream(s *Session, dStreamId float64) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] enc = amf.EncodeNumber(enc, dStreamId) if enc == nil { @@ -652,7 +652,7 @@ func sendCheckBW(s *Session) error { if enc == nil { return errEncoding } - enc[0] = amf.Null + enc[0] = amf.TypeNull enc = enc[1:] pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -672,7 +672,7 @@ func handleInvoke(s *Session, body []byte) error { if body[0] != 0x02 { return errInvalidBody } - var obj amf.AMF + var obj amf.Object nRes := amf.Decode(&obj, body, 0) if nRes < 0 { return errDecoding @@ -757,7 +757,7 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - var obj2 amf.AMF + var obj2 amf.Object amf.PropGetObject(amf.GetProp(&obj, "", 3), &obj2) code := amf.PropGetString(amf.GetProp(&obj2, avCode, -1)) level := amf.PropGetString(amf.GetProp(&obj2, avLevel, -1)) diff --git a/rtmp/session.go b/rtmp/session.go index 62496921..ed40be8c 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -81,7 +81,7 @@ type link struct { auth string flashVer string token string - extras amf.AMF + extras amf.Object flags int32 swfAge int32 protocol int32 From c2d4e0b4a245c9f1e7854e611d75bab3d16b256c Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 08:57:53 +1030 Subject: [PATCH 07/52] Initial revision. --- rtmp/amf/amf_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 rtmp/amf/amf_test.go diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go new file mode 100644 index 00000000..0a5a56ac --- /dev/null +++ b/rtmp/amf/amf_test.go @@ -0,0 +1,83 @@ +/* +NAME + amf_test.go + +DESCRIPTION + AMF tests + +AUTHORS + Saxon Nelson-Milton + Dan Kortschak + Alan Noble + +LICENSE + amf_test.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. +*/ + +package amf + +import ( + "testing" +) + +// TestStrings tests string encoding and decoding. +func TestStrings(t *testing.T) { + var testStrings = [...]string{ + "foo", + "bar", + } + + for _, s := range testStrings { + // Short string encoding is as follows + // enc[0] = data type (typeString) + // end[1:3] = size + // enc[3:] = data + buf := make([]byte, len(s)+5) + enc := EncodeString(buf, s) + if enc == nil { + t.Errorf("EncodeString failed") + } + if buf[0] != typeString { + t.Errorf("Expected typeString, got %v", buf[0]) + } + ds := DecodeString(buf[1:]) + if s != ds { + t.Errorf("DecodeString did not produce original string, got %v", ds) + } + } +} + +// TestNumbers tests 24-bit encoding and encoding. +// We don't test the others as they are just wrappers for standard functions in encoding/binary. +func TestNumbers(t *testing.T) { + var testNumbers = [...]int32{ + 0x0, + 0xababab, + 0xffffff, + } + + for _, n := range testNumbers { + buf := make([]byte, 4) // NB: encoder requires an extra byte for some reason + enc := EncodeInt24(buf, n) + if enc == nil { + t.Errorf("EncodeInt24 failed") + } + dn := int32(DecodeInt24(buf)) + if n != dn { + t.Errorf("DecodeInt24 did not produce original number, got %v", dn) + } + } +} From f0b98ab3714c67a92d2f4653f2505d6a20053c3d Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 13:46:21 +1030 Subject: [PATCH 08/52] Added TestProperties and TestObjects. --- rtmp/amf/amf_test.go | 151 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 138 insertions(+), 13 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 0a5a56ac..8ea11ba8 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -3,7 +3,7 @@ NAME amf_test.go DESCRIPTION - AMF tests + AMF test suite. AUTHORS Saxon Nelson-Milton @@ -33,15 +33,32 @@ import ( "testing" ) +// Test data. +var testStrings = [...]string{ + "", + "foo", + "bar", + "bazz", +} + +var testNumbers = [...]int32{ + 0, + 1, + 0xababab, + 0xffffff, +} + +// TestSanity checks that we haven't accidentally changed constants. +func TestSanity(t *testing.T) { + if TypeObjectEnd != 0x09 { + t.Errorf("TypeObjectEnd has wrong value; got %d, expected %d", TypeObjectEnd, 0x09) + } +} + // TestStrings tests string encoding and decoding. func TestStrings(t *testing.T) { - var testStrings = [...]string{ - "foo", - "bar", - } - for _, s := range testStrings { - // Short string encoding is as follows + // Short string encoding is as follows: // enc[0] = data type (typeString) // end[1:3] = size // enc[3:] = data @@ -63,12 +80,6 @@ func TestStrings(t *testing.T) { // TestNumbers tests 24-bit encoding and encoding. // We don't test the others as they are just wrappers for standard functions in encoding/binary. func TestNumbers(t *testing.T) { - var testNumbers = [...]int32{ - 0x0, - 0xababab, - 0xffffff, - } - for _, n := range testNumbers { buf := make([]byte, 4) // NB: encoder requires an extra byte for some reason enc := EncodeInt24(buf, n) @@ -81,3 +92,117 @@ func TestNumbers(t *testing.T) { } } } + +// TestProperties tests encoding and decoding of properties. +func TestProperties(t *testing.T) { + var buf [1024]byte + + // Encode/decode number properties. + enc := buf[:] + for i, _ := range testNumbers { + enc = PropEncode(&Property{dtype: typeNumber, number: float64(testNumbers[i])}, enc) + if enc == nil { + t.Errorf("PropEncode of number failed") + } + + } + var n int32 + prop := Property{} + dec := buf[:] + for i, _ := range testNumbers { + n = PropDecode(&prop, dec, 0) + if n < 0 { + t.Errorf("PropDecode of number failed") + } + if int32(prop.number) != testNumbers[i] { + t.Errorf("PropEncode/PropDecode returned wrong number; got %v, expected %v", int32(prop.number), testNumbers[i]) + } + dec = dec[n:] + } + + // Encode/decode string properties. + enc = buf[:] + for i, _ := range testStrings { + enc = PropEncode(&Property{dtype: typeString, str: testStrings[i]}, enc) + if enc == nil { + t.Errorf("PropEncode of string failed") + } + + } + prop = Property{} + dec = buf[:] + for i, _ := range testStrings { + n = PropDecode(&prop, dec, 0) + if n < 0 { + t.Errorf("PropDecode of string failed") + } + if prop.str != testStrings[i] { + t.Errorf("PropEncode/PropDecode returned wrong string; got %s, expected %s", prop.str, testStrings[i]) + } + dec = dec[n:] + } + +} + +// TestObject tests encoding and decoding of objects. +func TestObject(t *testing.T) { + var buf [1024]byte + + // Construct a simple object that has one property, the number 42. + prop1 := Property{dtype: typeNumber, number: 42} + obj1 := Object{} + obj1.Props = append(obj1.Props, prop1) + + // Encode it + enc := buf[:] + enc = Encode(&obj1, enc) + if enc == nil { + t.Errorf("Encode of object failed") + } + + // Check the encoding + if uint8(buf[0]) != TypeObject { + t.Errorf("Encoded wrong type; expected %d, got %v", TypeObject, uint8(buf[0])) + } + if uint8(buf[1]) != typeNumber { + t.Errorf("Encoded wrong type; expected %d, got %v", typeNumber, uint8(buf[0])) + } + num := DecodeNumber(buf[2:10]) + if num != 42 { + t.Errorf("Encoded wrong number") + } + end := int32(DecodeInt24(buf[10:13])) + if end != TypeObjectEnd { + t.Errorf("Did not encode TypeObjectEnd") + } + + // Decode it + dec := buf[1:] + var dobj1 Object + n := Decode(&dobj1, dec, 0) + if n < 0 { + t.Errorf("Decode of object failed") + } + + // Construct a more complicated object. + var obj2 Object + for i, _ := range testStrings { + obj2.Props = append(obj2.Props, Property{dtype: typeString, str: testStrings[i]}) + obj2.Props = append(obj2.Props, Property{dtype: typeNumber, number: float64(testNumbers[i])}) + } + + // Encode it. + enc = buf[:] + enc = Encode(&obj2, enc) + if enc == nil { + t.Errorf("Encode of object failed") + } + + // Decode it. + dec = buf[1:] + var dobj2 Object + n = Decode(&dobj2, dec, 0) + if n < 0 { + t.Errorf("Decode of object failed") + } +} From b79a035d0f027a568acab28359ccc441aed0a350 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 14:18:50 +1030 Subject: [PATCH 09/52] Added error return value to all encoding functions. --- rtmp/amf/amf.go | 150 +++++++++++++++------------ rtmp/amf/amf_test.go | 26 ++--- rtmp/rtmp.go | 239 ++++++++++++++++++++++--------------------- 3 files changed, 222 insertions(+), 193 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index e5ec3a08..745c39af 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -40,6 +40,7 @@ package amf import ( "encoding/binary" + "errors" "math" ) @@ -81,6 +82,17 @@ type Property struct { obj Object } +// AMF errors. +var ( + ErrShortBuffer = errors.New("amf: short buffer") + ErrEndOfBuffer = errors.New("amf: end of buffer") + ErrInvalidType = errors.New("amf: invalid type") + ErrUnimplemented = errors.New("amf: unimplemented feature") + ErrDecodingName = errors.New("amf: name decoding error") + ErrDecodingString = errors.New("amf: string decoding error") +) + +// Basic decoding funcions. func DecodeInt16(data []byte) uint16 { return uint16(binary.BigEndian.Uint16(data)) } @@ -111,38 +123,39 @@ func DecodeBoolean(data []byte) bool { return data[0] != 0 } -func EncodeInt24(dst []byte, val int32) []byte { +// Basic encoding functions. +func EncodeInt24(dst []byte, val int32) ([]byte, error) { if len(dst) < 3 { - return nil + return nil, ErrShortBuffer } dst[0] = byte(val >> 16) dst[1] = byte(val >> 8) dst[2] = byte(val) if len(dst) == 3 { - return nil + return nil, ErrEndOfBuffer } - return dst[3:] + return dst[3:], nil } -func EncodeInt32(dst []byte, val int32) []byte { +func EncodeInt32(dst []byte, val int32) ([]byte, error) { if len(dst) < 4 { - return nil + return nil, ErrShortBuffer } binary.BigEndian.PutUint32(dst, uint32(val)) if len(dst) == 4 { - return nil + return nil, ErrEndOfBuffer } - return dst[4:] + return dst[4:], nil } -func EncodeString(dst []byte, val string) []byte { +func EncodeString(dst []byte, val string) ([]byte, error) { const typeSize = 1 if len(val) < 65536 && len(val)+typeSize+binary.Size(int16(0)) > len(dst) { - return nil + return nil, ErrShortBuffer } if len(val)+typeSize+binary.Size(int32(0)) > len(dst) { - return nil + return nil, ErrShortBuffer } if len(val) < 65536 { @@ -152,9 +165,9 @@ func EncodeString(dst []byte, val string) []byte { dst = dst[2:] copy(dst, val) if len(dst) == len(val) { - return nil + return nil, ErrEndOfBuffer } - return dst[len(val):] + return dst[len(val):], nil } dst[0] = typeLongString @@ -163,75 +176,77 @@ func EncodeString(dst []byte, val string) []byte { dst = dst[4:] copy(dst, val) if len(dst) == len(val) { - return nil + return nil, ErrEndOfBuffer } - return dst[len(val):] + return dst[len(val):], nil } -func EncodeNumber(dst []byte, val float64) []byte { +func EncodeNumber(dst []byte, val float64) ([]byte, error) { if len(dst) < 9 { - return nil + return nil, ErrShortBuffer } dst[0] = typeNumber dst = dst[1:] binary.BigEndian.PutUint64(dst, math.Float64bits(val)) - return dst[8:] + return dst[8:], nil } -func EncodeBoolean(dst []byte, val bool) []byte { +func EncodeBoolean(dst []byte, val bool) ([]byte, error) { if len(dst) < 2 { - return nil + return nil, ErrShortBuffer } dst[0] = typeBoolean if val { dst[1] = 1 } if len(dst) == 2 { - return nil + return nil, ErrEndOfBuffer } - return dst[2:] + return dst[2:], nil } -func EncodeNamedString(dst []byte, key, val string) []byte { +func EncodeNamedString(dst []byte, key, val string) ([]byte, error) { if 2+len(key) > len(dst) { - return nil + return nil, ErrShortBuffer } binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) dst = dst[2:] copy(dst, key) if len(key) == len(dst) { - return nil + return nil, ErrEndOfBuffer } return EncodeString(dst[len(key):], val) } -func EncodeNamedNumber(dst []byte, key string, val float64) []byte { +func EncodeNamedNumber(dst []byte, key string, val float64) ([]byte, error) { if 2+len(key) > len(dst) { - return nil + return nil, ErrShortBuffer } binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) dst = dst[2:] copy(dst, key) if len(key) == len(dst) { - return nil + return nil, ErrEndOfBuffer } return EncodeNumber(dst[len(key):], val) } -func EncodeNamedBoolean(dst []byte, key string, val bool) []byte { +func EncodeNamedBoolean(dst []byte, key string, val bool) ([]byte, error) { if 2+len(key) > len(dst) { - return nil + return nil, ErrShortBuffer } binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) dst = dst[2:] copy(dst, key) if len(key) == len(dst) { - return nil + return nil, ErrEndOfBuffer } return EncodeBoolean(dst[len(key):], val) } +// Property functions. + func PropSetName(prop *Property, name string) { prop.name = name } @@ -255,13 +270,15 @@ func PropGetObject(prop *Property, obj *Object) { } } -func PropEncode(p *Property, dst []byte) []byte { +// PropEncode encodes a property. + +func PropEncode(p *Property, dst []byte) ([]byte, error) { if p.dtype == typeInvalid { - return nil + return nil, ErrShortBuffer } if p.dtype != TypeNull && len(p.name)+2+1 >= len(dst) { - return nil + return nil, ErrShortBuffer } if p.dtype != TypeNull && len(p.name) != 0 { @@ -271,32 +288,33 @@ func PropEncode(p *Property, dst []byte) []byte { dst = dst[len(p.name):] } + var err error switch p.dtype { case typeNumber: - dst = EncodeNumber(dst, p.number) + dst, err = EncodeNumber(dst, p.number) case typeBoolean: - dst = EncodeBoolean(dst, p.number != 0) + dst, err = EncodeBoolean(dst, p.number != 0) case typeString: - dst = EncodeString(dst, p.str) + dst, err = EncodeString(dst, p.str) case TypeNull: if len(dst) < 2 { - return nil + return nil, ErrShortBuffer } dst[0] = TypeNull dst = dst[1:] case TypeObject: - dst = Encode(&p.obj, dst) + dst, err = Encode(&p.obj, dst) case typeEcmaArray: - dst = EncodeEcmaArray(&p.obj, dst) + dst, err = EncodeEcmaArray(&p.obj, dst) case typeStrictArray: - dst = EncodeArray(&p.obj, dst) + dst, err = EncodeArray(&p.obj, dst) default: - // ??? log.Println("PropEncode: invalid type!") - dst = nil + dst, err = nil, ErrInvalidType } - return dst + return dst, err } +// PropDecode decodes a property, returning the number of bytes consumed from the data buffer. func PropDecode(prop *Property, data []byte, decodeName int32) int32 { prop.name = "" @@ -430,31 +448,32 @@ func PropReset(prop *Property) { prop.dtype = typeInvalid } -func Encode(obj *Object, dst []byte) []byte { +// Encode serializes an Object into its AMF representation. +func Encode(obj *Object, dst []byte) ([]byte, error) { if len(dst) < 5 { - return nil + return nil, ErrShortBuffer } dst[0] = TypeObject dst = dst[1:] for i := 0; i < len(obj.Props); i++ { - dst = PropEncode(&obj.Props[i], dst) - if dst == nil { - // ??? log.Println("Encode: failed to encode property in index") - break + var err error + dst, err = PropEncode(&obj.Props[i], dst) + if err != nil { + return nil, err } } if len(dst) < 4 { - return nil + return nil, ErrShortBuffer } return EncodeInt24(dst, TypeObjectEnd) } -func EncodeEcmaArray(obj *Object, dst []byte) []byte { +func EncodeEcmaArray(obj *Object, dst []byte) ([]byte, error) { if len(dst) < 5 { - return nil + return nil, ErrShortBuffer } dst[0] = typeEcmaArray @@ -463,23 +482,22 @@ func EncodeEcmaArray(obj *Object, dst []byte) []byte { dst = dst[4:] for i := 0; i < len(obj.Props); i++ { - dst = PropEncode(&obj.Props[i], dst) - if dst == nil { - // ??? log.Println("EncodeEcmaArray: failed to encode property!") - break + var err error + dst, err = PropEncode(&obj.Props[i], dst) + if err != nil { + return nil, err } } if len(dst) < 4 { - return nil + return nil, ErrShortBuffer } return EncodeInt24(dst, TypeObjectEnd) } -// not used? -func EncodeArray(obj *Object, dst []byte) []byte { +func EncodeArray(obj *Object, dst []byte) ([]byte, error) { if len(dst) < 5 { - return nil + return nil, ErrShortBuffer } dst[0] = typeStrictArray @@ -488,14 +506,14 @@ func EncodeArray(obj *Object, dst []byte) []byte { dst = dst[4:] for i := 0; i < len(obj.Props); i++ { - dst = PropEncode(&obj.Props[i], dst) - if dst == nil { - // ??? log.Println("EncodeArray: failed to encode property!") - break + var err error + dst, err = PropEncode(&obj.Props[i], dst) + if err != nil { + return nil, err } } - return dst + return dst, nil } func Decode(obj *Object, data []byte, decodeName int32) int32 { diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 8ea11ba8..2d3d306c 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -63,8 +63,8 @@ func TestStrings(t *testing.T) { // end[1:3] = size // enc[3:] = data buf := make([]byte, len(s)+5) - enc := EncodeString(buf, s) - if enc == nil { + _, err := EncodeString(buf, s) + if err != nil { t.Errorf("EncodeString failed") } if buf[0] != typeString { @@ -82,8 +82,8 @@ func TestStrings(t *testing.T) { func TestNumbers(t *testing.T) { for _, n := range testNumbers { buf := make([]byte, 4) // NB: encoder requires an extra byte for some reason - enc := EncodeInt24(buf, n) - if enc == nil { + _, err := EncodeInt24(buf, n) + if err != nil { t.Errorf("EncodeInt24 failed") } dn := int32(DecodeInt24(buf)) @@ -99,9 +99,10 @@ func TestProperties(t *testing.T) { // Encode/decode number properties. enc := buf[:] + var err error for i, _ := range testNumbers { - enc = PropEncode(&Property{dtype: typeNumber, number: float64(testNumbers[i])}, enc) - if enc == nil { + enc, err = PropEncode(&Property{dtype: typeNumber, number: float64(testNumbers[i])}, enc) + if err != nil { t.Errorf("PropEncode of number failed") } @@ -123,8 +124,8 @@ func TestProperties(t *testing.T) { // Encode/decode string properties. enc = buf[:] for i, _ := range testStrings { - enc = PropEncode(&Property{dtype: typeString, str: testStrings[i]}, enc) - if enc == nil { + enc, err = PropEncode(&Property{dtype: typeString, str: testStrings[i]}, enc) + if err != nil { t.Errorf("PropEncode of string failed") } @@ -155,8 +156,9 @@ func TestObject(t *testing.T) { // Encode it enc := buf[:] - enc = Encode(&obj1, enc) - if enc == nil { + var err error + enc, err = Encode(&obj1, enc) + if err != nil { t.Errorf("Encode of object failed") } @@ -193,8 +195,8 @@ func TestObject(t *testing.T) { // Encode it. enc = buf[:] - enc = Encode(&obj2, enc) - if enc == nil { + enc, err = Encode(&obj2, enc) + if err != nil { t.Errorf("Encode of object failed") } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index d0aad407..c433b63a 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -161,7 +161,6 @@ var ( errInvalidHeader = errors.New("rtmp: invalid header") errInvalidBody = errors.New("rtmp: invalid body") errInvalidFlvTag = errors.New("rtmp: invalid FLV tag") - errEncoding = errors.New("rtmp: encoding error") errDecoding = errors.New("rtmp: decoding error") errUnimplemented = errors.New("rtmp: unimplemented feature") ) @@ -314,82 +313,84 @@ func sendConnectPacket(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avConnect) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avConnect) + if err != nil { + return err } s.numInvokes += 1 - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeObject enc = enc[1:] - enc = amf.EncodeNamedString(enc, avApp, s.link.app) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedString(enc, avApp, s.link.app) + if err != nil { + return err } if s.link.protocol&featureWrite != 0 { - enc = amf.EncodeNamedString(enc, avType, avNonprivate) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedString(enc, avType, avNonprivate) + if err != nil { + return err } } if s.link.flashVer != "" { - enc = amf.EncodeNamedString(enc, avFlashver, s.link.flashVer) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedString(enc, avFlashver, s.link.flashVer) + if err != nil { + return err } } if s.link.swfUrl != "" { - enc = amf.EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) + if err != nil { + return err } } if s.link.tcUrl != "" { - enc = amf.EncodeNamedString(enc, avTcUrl, s.link.tcUrl) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.tcUrl) + if err != nil { + return err } } if s.link.protocol&featureWrite == 0 { - enc = amf.EncodeNamedBoolean(enc, avFpad, false) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedBoolean(enc, avFpad, false) + if err != nil { + return err } - enc = amf.EncodeNamedNumber(enc, avCapabilities, 15) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedNumber(enc, avCapabilities, 15) + if err != nil { + return err } - enc = amf.EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) + if err != nil { + return err } - enc = amf.EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) + if err != nil { + return err } - enc = amf.EncodeNamedNumber(enc, avVideoFunction, 1) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedNumber(enc, avVideoFunction, 1) + if err != nil { + return err } if s.link.pageUrl != "" { - enc = amf.EncodeNamedString(enc, avPageUrl, s.link.pageUrl) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeNamedString(enc, avPageUrl, s.link.pageUrl) + if err != nil { + return err } } } if s.encoding != 0.0 || s.sendEncoding { - enc = amf.EncodeNamedNumber(enc, avObjectEncoding, s.encoding) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNamedNumber(enc, avObjectEncoding, s.encoding) + if err != nil { + return err } } @@ -398,20 +399,20 @@ func sendConnectPacket(s *Session) error { // add auth string if s.link.auth != "" { - enc = amf.EncodeBoolean(enc, s.link.flags&linkAuth != 0) - if enc == nil { - return errEncoding + enc, err = amf.EncodeBoolean(enc, s.link.flags&linkAuth != 0) + if err != nil { + return err } - enc = amf.EncodeString(enc, s.link.auth) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, s.link.auth) + if err != nil { + return err } } for i := range s.link.extras.Props { - enc = amf.PropEncode(&s.link.extras.Props[i], enc) - if enc == nil { - return errEncoding + enc, err = amf.PropEncode(&s.link.extras.Props[i], enc) + if err != nil { + return err } } @@ -431,14 +432,15 @@ func sendCreateStream(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avCreatestream) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avCreatestream) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] @@ -459,20 +461,21 @@ func sendReleaseStream(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avReleasestream) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avReleasestream) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] - enc = amf.EncodeString(enc, s.link.playpath) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, s.link.playpath) + if err != nil { + return err } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -490,20 +493,21 @@ func sendFCPublish(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avFCPublish) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avFCPublish) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] - enc = amf.EncodeString(enc, s.link.playpath) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, s.link.playpath) + if err != nil { + return err } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -522,20 +526,21 @@ func sendFCUnpublish(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avFCUnpublish) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avFCUnpublish) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] - enc = amf.EncodeString(enc, s.link.playpath) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, s.link.playpath) + if err != nil { + return err } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -554,24 +559,25 @@ func sendPublish(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, avPublish) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avPublish) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] - enc = amf.EncodeString(enc, s.link.playpath) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, s.link.playpath) + if err != nil { + return err } - enc = amf.EncodeString(enc, avLive) - if enc == nil { - return errEncoding + enc, err = amf.EncodeString(enc, avLive) + if err != nil { + return err } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -590,20 +596,21 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } enc := pkt.body - enc = amf.EncodeString(enc, avDeletestream) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, avDeletestream) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] - enc = amf.EncodeNumber(enc, dStreamId) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, dStreamId) + if err != nil { + return err } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) @@ -623,9 +630,10 @@ func sendBytesReceived(s *Session) error { enc := pkt.body s.nBytesInSent = s.nBytesIn - enc = amf.EncodeInt32(enc, s.nBytesIn) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeInt32(enc, s.nBytesIn) + if err != nil { + return err } pkt.bodySize = 4 @@ -643,14 +651,15 @@ func sendCheckBW(s *Session) error { } enc := pkt.body - enc = amf.EncodeString(enc, av_checkbw) - if enc == nil { - return errEncoding + var err error + enc, err = amf.EncodeString(enc, av_checkbw) + if err != nil { + return err } s.numInvokes++ - enc = amf.EncodeNumber(enc, float64(s.numInvokes)) - if enc == nil { - return errEncoding + enc, err = amf.EncodeNumber(enc, float64(s.numInvokes)) + if err != nil { + return err } enc[0] = amf.TypeNull enc = enc[1:] From 1fe18493932ca98aa3860c818e79b9bcec2c7ec0 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 14:55:12 +1030 Subject: [PATCH 10/52] amf.Decode and amf.PropDecode now return an error. --- rtmp/amf/amf.go | 125 +++++++++++++------------------------------ rtmp/amf/amf_test.go | 18 +++---- rtmp/rtmp.go | 7 ++- 3 files changed, 50 insertions(+), 100 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 745c39af..0564b04c 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -90,6 +90,7 @@ var ( ErrUnimplemented = errors.New("amf: unimplemented feature") ErrDecodingName = errors.New("amf: name decoding error") ErrDecodingString = errors.New("amf: string decoding error") + ErrUnexpectedEnd = errors.New("amf: unexpected end") ) // Basic decoding funcions. @@ -271,7 +272,6 @@ func PropGetObject(prop *Property, obj *Object) { } // PropEncode encodes a property. - func PropEncode(p *Property, dst []byte) ([]byte, error) { if p.dtype == typeInvalid { return nil, ErrShortBuffer @@ -314,128 +314,81 @@ func PropEncode(p *Property, dst []byte) ([]byte, error) { return dst, err } -// PropDecode decodes a property, returning the number of bytes consumed from the data buffer. -func PropDecode(prop *Property, data []byte, decodeName int32) int32 { +// PropDecode decodes a property, returning the number of bytes consumed from the supplied buffer. +func PropDecode(prop *Property, data []byte, decodeName bool) (int, error) { prop.name = "" - nOriginalSize := len(data) + sz := len(data) if len(data) == 0 { - // TODO use new logger here - // RTMLog(RTMLOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__); - return -1 + return 0, ErrEndOfBuffer } - if decodeName != 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 decodeName != 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 + if decodeName { + if len(data) < 4 { + return 0, ErrShortBuffer + } + n := DecodeInt16(data[:2]) + if int(n) > len(data)-2 { + return 0, ErrDecodingName } prop.name = DecodeString(data) - data = data[2+nNameSize:] + data = data[2+n:] } if len(data) == 0 { - return -1 + return 0, ErrEndOfBuffer } prop.dtype = uint8(data[0]) data = data[1:] - var nRes int32 switch prop.dtype { case typeNumber: if len(data) < 8 { - return -1 + return 0, ErrShortBuffer } prop.number = DecodeNumber(data[:8]) data = data[8:] case typeBoolean: - panic("typeBoolean not supported") + return 0, ErrUnimplemented case typeString: - nStringSize := DecodeInt16(data[:2]) - if len(data) < int(nStringSize+2) { - return -1 + n := DecodeInt16(data[:2]) + if len(data) < int(n+2) { + return 0, ErrShortBuffer } prop.str = DecodeString(data) - data = data[2+nStringSize:] + data = data[2+n:] case TypeObject: - nRes := Decode(&prop.obj, data, 1) - if nRes == -1 { - return -1 + n, err := Decode(&prop.obj, data, true) + if err != nil { + return 0, err } - data = data[nRes:] + data = data[n:] - case typeMovieClip: - // TODO use new logger here - // ??? log.Println("PropDecode: MAF_MOVIECLIP reserved!") - //RTMLog(RTMLOGERROR, "MovieClip reserved!"); - return -1 case TypeNull, typeUndefined, typeUnsupported: prop.dtype = TypeNull - case typeReference: - // TODO use new logger here - // ??? log.Println("PropDecode: Reference not supported!") - //RTMLog(RTMLOGERROR, "Reference not supported!"); - return -1 - case typeEcmaArray: - // next comes the rest, mixed array has a final 0x000009 mark and names, so its an object data = data[4:] - nRes = Decode(&prop.obj, data, 1) - if nRes == -1 { - return -1 + n, err := Decode(&prop.obj, data, true) + if err != nil { + return 0, err } - data = data[nRes:] + data = data[n:] case TypeObjectEnd: - return -1 - - case typeStrictArray: - panic("StrictArray not supported") - - case typeDate: - panic("Date not supported") - - case typeLongString, typeXmlDoc: - panic("typeLongString, XmlDoc not supported") - - case typeRecordset: - // TODO use new logger here - // ??? log.Println("PropDecode: Recordset reserved!") - //RTMLog(RTMLOGERROR, "Recordset reserved!"); - return -1 - - case typeTypedObject: - // TODO use new logger here - // RTMLog(RTMLOGERROR, "Typed_object not supported!") - return -1 - - case typeAvmplus: - panic("Avmplus not supported") + return 0, ErrUnexpectedEnd default: - // TODO use new logger here - //RTMLog(RTMLOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, - //prop.dtype, pBuffer - 1); - return -1 + return 0, ErrUnimplemented } - return int32(nOriginalSize - len(data)) + return sz - len(data), nil } func PropReset(prop *Property) { @@ -516,8 +469,8 @@ func EncodeArray(obj *Object, dst []byte) ([]byte, error) { return dst, nil } -func Decode(obj *Object, data []byte, decodeName int32) int32 { - nOriginalSize := len(data) +func Decode(obj *Object, data []byte, decodeName bool) (int, error) { + sz := len(data) obj.Props = obj.Props[:0] for len(data) != 0 { @@ -527,17 +480,15 @@ func Decode(obj *Object, data []byte, decodeName int32) int32 { } var prop Property - nRes := PropDecode(&prop, data, decodeName) - // nRes = int32(C.PropDecode(&prop, (*byte)(unsafe.Pointer(pBuffer)), - // int32(nSize), int32(decodeName))) - if nRes == -1 { - return -1 + n, err := PropDecode(&prop, data, decodeName) + if err != nil { + return 0, err } - data = data[nRes:] + data = data[n:] obj.Props = append(obj.Props, prop) } - return int32(nOriginalSize - len(data)) + return sz - len(data), nil } func GetProp(obj *Object, name string, idx int32) *Property { diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 2d3d306c..66972129 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -107,12 +107,12 @@ func TestProperties(t *testing.T) { } } - var n int32 + prop := Property{} dec := buf[:] for i, _ := range testNumbers { - n = PropDecode(&prop, dec, 0) - if n < 0 { + n, err := PropDecode(&prop, dec, false) + if err != nil { t.Errorf("PropDecode of number failed") } if int32(prop.number) != testNumbers[i] { @@ -133,8 +133,8 @@ func TestProperties(t *testing.T) { prop = Property{} dec = buf[:] for i, _ := range testStrings { - n = PropDecode(&prop, dec, 0) - if n < 0 { + n, err := PropDecode(&prop, dec, false) + if err != nil { t.Errorf("PropDecode of string failed") } if prop.str != testStrings[i] { @@ -181,8 +181,8 @@ func TestObject(t *testing.T) { // Decode it dec := buf[1:] var dobj1 Object - n := Decode(&dobj1, dec, 0) - if n < 0 { + _, err = Decode(&dobj1, dec, false) + if err != nil { t.Errorf("Decode of object failed") } @@ -203,8 +203,8 @@ func TestObject(t *testing.T) { // Decode it. dec = buf[1:] var dobj2 Object - n = Decode(&dobj2, dec, 0) - if n < 0 { + _, err = Decode(&dobj2, dec, false) + if err != nil { t.Errorf("Decode of object failed") } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index c433b63a..e78a89f0 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -161,7 +161,6 @@ var ( errInvalidHeader = errors.New("rtmp: invalid header") errInvalidBody = errors.New("rtmp: invalid body") errInvalidFlvTag = errors.New("rtmp: invalid FLV tag") - errDecoding = errors.New("rtmp: decoding error") errUnimplemented = errors.New("rtmp: unimplemented feature") ) @@ -682,9 +681,9 @@ func handleInvoke(s *Session, body []byte) error { return errInvalidBody } var obj amf.Object - nRes := amf.Decode(&obj, body, 0) - if nRes < 0 { - return errDecoding + _, err := amf.Decode(&obj, body, false) + if err != nil { + return err } meth := amf.PropGetString(amf.GetProp(&obj, "", 0)) From f7de9526c84055709cf1a2827a4ffd0ba6033924 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 15:29:58 +1030 Subject: [PATCH 11/52] Removed Reset and PropReset which are not required. --- rtmp/amf/amf.go | 17 ----------------- rtmp/rtmp.go | 4 +--- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 0564b04c..a7e41afb 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -391,16 +391,6 @@ func PropDecode(prop *Property, data []byte, decodeName bool) (int, error) { return sz - len(data), nil } -func PropReset(prop *Property) { - if prop.dtype == TypeObject || prop.dtype == typeEcmaArray || - prop.dtype == typeStrictArray { - Reset(&prop.obj) - } else { - prop.str = "" - } - prop.dtype = typeInvalid -} - // Encode serializes an Object into its AMF representation. func Encode(obj *Object, dst []byte) ([]byte, error) { if len(dst) < 5 { @@ -505,10 +495,3 @@ func GetProp(obj *Object, name string, idx int32) *Property { } return &Property{} } - -func Reset(obj *Object) { - for i := range obj.Props { - PropReset(&obj.Props[i]) - } - *obj = Object{} -} diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index e78a89f0..fc34809c 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -702,7 +702,7 @@ func handleInvoke(s *Session, body []byte) error { } if methodInvoked == "" { s.log(WarnLevel, pkg+"received result without matching request", "id", txn) - goto leave + return nil } s.log(DebugLevel, pkg+"received result for "+methodInvoked) @@ -806,8 +806,6 @@ func handleInvoke(s *Session, body []byte) error { default: s.log(FatalLevel, pkg+"unknown method "+meth) } -leave: - amf.Reset(&obj) return nil } From 396c809424b70c1b45b8b0633423a47d6c7117f7 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 15:33:14 +1030 Subject: [PATCH 12/52] Documented methods and standardized on 'buf' for parameter name. --- rtmp/amf/amf.go | 348 +++++++++++++++++++++++++----------------------- 1 file changed, 183 insertions(+), 165 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index a7e41afb..8b552f5e 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -34,7 +34,7 @@ LICENSE Copyright (C) 2009-2010 Howard Chu */ -// amf implements Action Message Format (AMF) encoding and decoding. +// Package amf implements Action Message Format (AMF) encoding and decoding. // See https://en.wikipedia.org/wiki/Action_Message_Format. package amf @@ -50,21 +50,21 @@ const ( typeNumber = iota typeBoolean typeString - TypeObject // ToDo: consider not exporting this - typeMovieClip // reserved, not implemented - TypeNull // ToDo: consider not exporting this + TypeObject + typeMovieClip + TypeNull typeUndefined typeReference typeEcmaArray - TypeObjectEnd // ToDo: consider not exporting this + TypeObjectEnd typeStrictArray typeDate typeLongString typeUnsupported - typeRecordset // reserved, not implemented + typeRecordset typeXmlDoc typeTypedObject - typeAvmplus // reserved, not implemented + typeAvmplus typeInvalid = 0xff ) @@ -93,157 +93,170 @@ var ( ErrUnexpectedEnd = errors.New("amf: unexpected end") ) -// Basic decoding funcions. -func DecodeInt16(data []byte) uint16 { - return uint16(binary.BigEndian.Uint16(data)) +// DecodeInt16 decodes a 16-bit integer. +func DecodeInt16(buf []byte) uint16 { + return uint16(binary.BigEndian.Uint16(buf)) } -func DecodeInt24(data []byte) uint32 { - return uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2]) +// DecodeInt24 decodes a 24-bit integer. +func DecodeInt24(buf []byte) uint32 { + return uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2]) } -func DecodeInt32(data []byte) uint32 { - return uint32(binary.BigEndian.Uint32(data)) +// DecodeInt32 decodes a 32-bit integer. +func DecodeInt32(buf []byte) uint32 { + return uint32(binary.BigEndian.Uint32(buf)) } -func DecodeString(data []byte) string { - n := DecodeInt16(data) - return string(data[2 : 2+n]) +// DecodeString decodes a string that is less than 2^16 bytes long. +func DecodeString(buf []byte) string { + n := DecodeInt16(buf) + return string(buf[2 : 2+n]) } -func DecodeLongString(data []byte) string { - n := DecodeInt32(data) - return string(data[2 : 2+n]) +// DecodeLongString decodes a long string. +func DecodeLongString(buf []byte) string { + n := DecodeInt32(buf) + return string(buf[2 : 2+n]) } -func DecodeNumber(data []byte) float64 { - return math.Float64frombits(binary.BigEndian.Uint64(data)) +// DecodeNumber decodes a 64-bit floating-point number. +func DecodeNumber(buf []byte) float64 { + return math.Float64frombits(binary.BigEndian.Uint64(buf)) } -func DecodeBoolean(data []byte) bool { - return data[0] != 0 +// DecodeBoolean decodes a boolean. +func DecodeBoolean(buf []byte) bool { + return buf[0] != 0 } -// Basic encoding functions. -func EncodeInt24(dst []byte, val int32) ([]byte, error) { - if len(dst) < 3 { +// EncodeInt24 encodes a 24-bit integer. +func EncodeInt24(buf []byte, val int32) ([]byte, error) { + if len(buf) < 3 { return nil, ErrShortBuffer } - dst[0] = byte(val >> 16) - dst[1] = byte(val >> 8) - dst[2] = byte(val) - if len(dst) == 3 { + buf[0] = byte(val >> 16) + buf[1] = byte(val >> 8) + buf[2] = byte(val) + if len(buf) == 3 { return nil, ErrEndOfBuffer } - return dst[3:], nil + return buf[3:], nil } -func EncodeInt32(dst []byte, val int32) ([]byte, error) { - if len(dst) < 4 { +// EncodeInt32 encodes a 32-bit integer. +func EncodeInt32(buf []byte, val int32) ([]byte, error) { + if len(buf) < 4 { return nil, ErrShortBuffer } - binary.BigEndian.PutUint32(dst, uint32(val)) - if len(dst) == 4 { + binary.BigEndian.PutUint32(buf, uint32(val)) + if len(buf) == 4 { return nil, ErrEndOfBuffer } - return dst[4:], nil + return buf[4:], nil } -func EncodeString(dst []byte, val string) ([]byte, error) { +// EncodeString encodes a string. +func EncodeString(buf []byte, val string) ([]byte, error) { const typeSize = 1 - if len(val) < 65536 && len(val)+typeSize+binary.Size(int16(0)) > len(dst) { + if len(val) < 65536 && len(val)+typeSize+binary.Size(int16(0)) > len(buf) { return nil, ErrShortBuffer } - if len(val)+typeSize+binary.Size(int32(0)) > len(dst) { + if len(val)+typeSize+binary.Size(int32(0)) > len(buf) { return nil, ErrShortBuffer } if len(val) < 65536 { - dst[0] = typeString - dst = dst[1:] - binary.BigEndian.PutUint16(dst[:2], uint16(len(val))) - dst = dst[2:] - copy(dst, val) - if len(dst) == len(val) { + buf[0] = typeString + buf = buf[1:] + binary.BigEndian.PutUint16(buf[:2], uint16(len(val))) + buf = buf[2:] + copy(buf, val) + if len(buf) == len(val) { return nil, ErrEndOfBuffer } - return dst[len(val):], nil + return buf[len(val):], nil } - dst[0] = typeLongString - dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(val))) - dst = dst[4:] - copy(dst, val) - if len(dst) == len(val) { + buf[0] = typeLongString + buf = buf[1:] + binary.BigEndian.PutUint32(buf[:4], uint32(len(val))) + buf = buf[4:] + copy(buf, val) + if len(buf) == len(val) { return nil, ErrEndOfBuffer } - return dst[len(val):], nil + return buf[len(val):], nil } -func EncodeNumber(dst []byte, val float64) ([]byte, error) { - if len(dst) < 9 { +// EncodeNumber encodes a 64-bit floating-point number. +func EncodeNumber(buf []byte, val float64) ([]byte, error) { + if len(buf) < 9 { return nil, ErrShortBuffer } - dst[0] = typeNumber - dst = dst[1:] - binary.BigEndian.PutUint64(dst, math.Float64bits(val)) - return dst[8:], nil + buf[0] = typeNumber + buf = buf[1:] + binary.BigEndian.PutUint64(buf, math.Float64bits(val)) + return buf[8:], nil } -func EncodeBoolean(dst []byte, val bool) ([]byte, error) { - if len(dst) < 2 { +// EncodeBoolean encodes a boolean. +func EncodeBoolean(buf []byte, val bool) ([]byte, error) { + if len(buf) < 2 { return nil, ErrShortBuffer } - dst[0] = typeBoolean + buf[0] = typeBoolean if val { - dst[1] = 1 + buf[1] = 1 } - if len(dst) == 2 { + if len(buf) == 2 { return nil, ErrEndOfBuffer } - return dst[2:], nil + return buf[2:], nil } -func EncodeNamedString(dst []byte, key, val string) ([]byte, error) { - if 2+len(key) > len(dst) { +// EncodeNamedString encodes a named string, where key is the name and val is the string value. +func EncodeNamedString(buf []byte, key, val string) ([]byte, error) { + if 2+len(key) > len(buf) { return nil, ErrShortBuffer } - binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) - dst = dst[2:] - copy(dst, key) - if len(key) == len(dst) { + binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) + buf = buf[2:] + copy(buf, key) + if len(key) == len(buf) { return nil, ErrEndOfBuffer } - return EncodeString(dst[len(key):], val) + return EncodeString(buf[len(key):], val) } -func EncodeNamedNumber(dst []byte, key string, val float64) ([]byte, error) { - if 2+len(key) > len(dst) { +// EncodeNamedNumber encodes a named number, where key is the name and val is the number value. +func EncodeNamedNumber(buf []byte, key string, val float64) ([]byte, error) { + if 2+len(key) > len(buf) { return nil, ErrShortBuffer } - binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) - dst = dst[2:] - copy(dst, key) - if len(key) == len(dst) { + binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) + buf = buf[2:] + copy(buf, key) + if len(key) == len(buf) { return nil, ErrEndOfBuffer } - return EncodeNumber(dst[len(key):], val) + return EncodeNumber(buf[len(key):], val) } -func EncodeNamedBoolean(dst []byte, key string, val bool) ([]byte, error) { - if 2+len(key) > len(dst) { +// EncodeNamedNumber encodes a named boolean, where key is the name and val is the booelean value. +func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { + if 2+len(key) > len(buf) { return nil, ErrShortBuffer } - binary.BigEndian.PutUint16(dst[:2], uint16(len(key))) - dst = dst[2:] - copy(dst, key) - if len(key) == len(dst) { + binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) + buf = buf[2:] + copy(buf, key) + if len(key) == len(buf) { return nil, ErrEndOfBuffer } - return EncodeBoolean(dst[len(key):], val) + return EncodeBoolean(buf[len(key):], val) } // Property functions. @@ -272,114 +285,114 @@ func PropGetObject(prop *Property, obj *Object) { } // PropEncode encodes a property. -func PropEncode(p *Property, dst []byte) ([]byte, error) { +func PropEncode(p *Property, buf []byte) ([]byte, error) { if p.dtype == typeInvalid { return nil, ErrShortBuffer } - if p.dtype != TypeNull && len(p.name)+2+1 >= len(dst) { + if p.dtype != TypeNull && len(p.name)+2+1 >= len(buf) { return nil, ErrShortBuffer } if p.dtype != TypeNull && 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):] + binary.BigEndian.PutUint16(buf[:2], uint16(len(p.name))) + buf = buf[2:] + copy(buf, p.name) + buf = buf[len(p.name):] } var err error switch p.dtype { case typeNumber: - dst, err = EncodeNumber(dst, p.number) + buf, err = EncodeNumber(buf, p.number) case typeBoolean: - dst, err = EncodeBoolean(dst, p.number != 0) + buf, err = EncodeBoolean(buf, p.number != 0) case typeString: - dst, err = EncodeString(dst, p.str) + buf, err = EncodeString(buf, p.str) case TypeNull: - if len(dst) < 2 { + if len(buf) < 2 { return nil, ErrShortBuffer } - dst[0] = TypeNull - dst = dst[1:] + buf[0] = TypeNull + buf = buf[1:] case TypeObject: - dst, err = Encode(&p.obj, dst) + buf, err = Encode(&p.obj, buf) case typeEcmaArray: - dst, err = EncodeEcmaArray(&p.obj, dst) + buf, err = EncodeEcmaArray(&p.obj, buf) case typeStrictArray: - dst, err = EncodeArray(&p.obj, dst) + buf, err = EncodeArray(&p.obj, buf) default: - dst, err = nil, ErrInvalidType + buf, err = nil, ErrInvalidType } - return dst, err + return buf, err } // PropDecode decodes a property, returning the number of bytes consumed from the supplied buffer. -func PropDecode(prop *Property, data []byte, decodeName bool) (int, error) { +func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { prop.name = "" - sz := len(data) - if len(data) == 0 { + sz := len(buf) + if len(buf) == 0 { return 0, ErrEndOfBuffer } if decodeName { - if len(data) < 4 { + if len(buf) < 4 { return 0, ErrShortBuffer } - n := DecodeInt16(data[:2]) - if int(n) > len(data)-2 { + n := DecodeInt16(buf[:2]) + if int(n) > len(buf)-2 { return 0, ErrDecodingName } - prop.name = DecodeString(data) - data = data[2+n:] + prop.name = DecodeString(buf) + buf = buf[2+n:] } - if len(data) == 0 { + if len(buf) == 0 { return 0, ErrEndOfBuffer } - prop.dtype = uint8(data[0]) - data = data[1:] + prop.dtype = uint8(buf[0]) + buf = buf[1:] switch prop.dtype { case typeNumber: - if len(data) < 8 { + if len(buf) < 8 { return 0, ErrShortBuffer } - prop.number = DecodeNumber(data[:8]) - data = data[8:] + prop.number = DecodeNumber(buf[:8]) + buf = buf[8:] case typeBoolean: return 0, ErrUnimplemented case typeString: - n := DecodeInt16(data[:2]) - if len(data) < int(n+2) { + n := DecodeInt16(buf[:2]) + if len(buf) < int(n+2) { return 0, ErrShortBuffer } - prop.str = DecodeString(data) - data = data[2+n:] + prop.str = DecodeString(buf) + buf = buf[2+n:] case TypeObject: - n, err := Decode(&prop.obj, data, true) + n, err := Decode(&prop.obj, buf, true) if err != nil { return 0, err } - data = data[n:] + buf = buf[n:] case TypeNull, typeUndefined, typeUnsupported: prop.dtype = TypeNull case typeEcmaArray: - data = data[4:] - n, err := Decode(&prop.obj, data, true) + buf = buf[4:] + n, err := Decode(&prop.obj, buf, true) if err != nil { return 0, err } - data = data[n:] + buf = buf[n:] case TypeObjectEnd: return 0, ErrUnexpectedEnd @@ -388,102 +401,107 @@ func PropDecode(prop *Property, data []byte, decodeName bool) (int, error) { return 0, ErrUnimplemented } - return sz - len(data), nil + return sz - len(buf), nil } -// Encode serializes an Object into its AMF representation. -func Encode(obj *Object, dst []byte) ([]byte, error) { - if len(dst) < 5 { +// Encode encodes an Object into its AMF representation. +func Encode(obj *Object, buf []byte) ([]byte, error) { + if len(buf) < 5 { return nil, ErrShortBuffer } - dst[0] = TypeObject - dst = dst[1:] + buf[0] = TypeObject + buf = buf[1:] for i := 0; i < len(obj.Props); i++ { var err error - dst, err = PropEncode(&obj.Props[i], dst) + buf, err = PropEncode(&obj.Props[i], buf) if err != nil { return nil, err } } - if len(dst) < 4 { + if len(buf) < 4 { return nil, ErrShortBuffer } - return EncodeInt24(dst, TypeObjectEnd) + return EncodeInt24(buf, TypeObjectEnd) } -func EncodeEcmaArray(obj *Object, dst []byte) ([]byte, error) { - if len(dst) < 5 { +// EncodeEcmaArray encodes an ECMA array. +func EncodeEcmaArray(obj *Object, buf []byte) ([]byte, error) { + if len(buf) < 5 { return nil, ErrShortBuffer } - dst[0] = typeEcmaArray - dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.Props))) - dst = dst[4:] + buf[0] = typeEcmaArray + buf = buf[1:] + binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Props))) + buf = buf[4:] for i := 0; i < len(obj.Props); i++ { var err error - dst, err = PropEncode(&obj.Props[i], dst) + buf, err = PropEncode(&obj.Props[i], buf) if err != nil { return nil, err } } - if len(dst) < 4 { + if len(buf) < 4 { return nil, ErrShortBuffer } - return EncodeInt24(dst, TypeObjectEnd) + return EncodeInt24(buf, TypeObjectEnd) } -func EncodeArray(obj *Object, dst []byte) ([]byte, error) { - if len(dst) < 5 { +// EncodeArray encodes an array. +func EncodeArray(obj *Object, buf []byte) ([]byte, error) { + if len(buf) < 5 { return nil, ErrShortBuffer } - dst[0] = typeStrictArray - dst = dst[1:] - binary.BigEndian.PutUint32(dst[:4], uint32(len(obj.Props))) - dst = dst[4:] + buf[0] = typeStrictArray + buf = buf[1:] + binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Props))) + buf = buf[4:] for i := 0; i < len(obj.Props); i++ { var err error - dst, err = PropEncode(&obj.Props[i], dst) + buf, err = PropEncode(&obj.Props[i], buf) if err != nil { return nil, err } } - return dst, nil + return buf, nil } -func Decode(obj *Object, data []byte, decodeName bool) (int, error) { - sz := len(data) +// Decode decodes an object. Property names are only decoded if decodeName is true. +func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { + sz := len(buf) obj.Props = obj.Props[:0] - for len(data) != 0 { - if len(data) >= 3 && DecodeInt24(data[:3]) == TypeObjectEnd { - data = data[3:] - break + for len(buf) != 0 { + if len(buf) >= 3 && DecodeInt24(buf[:3]) == TypeObjectEnd { + buf = buf[3:] + break } var prop Property - n, err := PropDecode(&prop, data, decodeName) + n, err := PropDecode(&prop, buf, decodeName) if err != nil { return 0, err } - data = data[n:] + buf = buf[n:] obj.Props = append(obj.Props, prop) } - return sz - len(data), nil + return sz - len(buf), nil } -func GetProp(obj *Object, name string, idx int32) *Property { +// GetProp returns an object's property, either by its index when idx is non-negative, or by its name when name otherwise. +// If the matching property is not found nil is returned. +func GetProp(obj *Object, name string, idx int) *Property { if idx >= 0 { - if idx < int32(len(obj.Props)) { + if idx < len(obj.Props) { return &obj.Props[idx] } } else { @@ -493,5 +511,5 @@ func GetProp(obj *Object, name string, idx int32) *Property { } } } - return &Property{} + return nil } From 210533965773895cd959d7efa862c5abc41ccbbb Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 16:10:09 +1030 Subject: [PATCH 13/52] Export Property members so getters are no longer required and made amf.GetProp a method on Object. --- rtmp/amf/amf.go | 115 +++++++++++++++++-------------------------- rtmp/amf/amf_test.go | 52 +++++++++++++------ rtmp/rtmp.go | 39 ++++++++++++--- 3 files changed, 113 insertions(+), 93 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 8b552f5e..0369ad82 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -70,27 +70,28 @@ const ( // AMF represents an AMF message (object), which is simply a collection of properties. type Object struct { - Props []Property // ToDo: consider not exporting this + Props []Property } // Property represents an AMF property. type Property struct { - name string - dtype uint8 - number float64 - str string - obj Object + Name string + Type uint8 + Number float64 + String string + Object Object } // AMF errors. var ( - ErrShortBuffer = errors.New("amf: short buffer") - ErrEndOfBuffer = errors.New("amf: end of buffer") - ErrInvalidType = errors.New("amf: invalid type") - ErrUnimplemented = errors.New("amf: unimplemented feature") - ErrDecodingName = errors.New("amf: name decoding error") - ErrDecodingString = errors.New("amf: string decoding error") - ErrUnexpectedEnd = errors.New("amf: unexpected end") + ErrShortBuffer = errors.New("amf: short buffer") + ErrEndOfBuffer = errors.New("amf: end of buffer") + ErrInvalidType = errors.New("amf: invalid type") + ErrUnimplemented = errors.New("amf: unimplemented feature") + ErrDecodingName = errors.New("amf: name decoding error") + ErrDecodingString = errors.New("amf: string decoding error") + ErrUnexpectedEnd = errors.New("amf: unexpected end") + ErrPropertyNotFound = errors.New("amf: property not found") ) // DecodeInt16 decodes a 16-bit integer. @@ -259,56 +260,31 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { return EncodeBoolean(buf[len(key):], val) } -// Property functions. - -func PropSetName(prop *Property, name string) { - prop.name = name -} - -func PropGetNumber(prop *Property) float64 { - return prop.number -} - -func PropGetString(prop *Property) string { - if prop.dtype == typeString { - return prop.str - } - return "" -} - -func PropGetObject(prop *Property, obj *Object) { - if prop.dtype == TypeObject { - *obj = prop.obj - } else { - *obj = Object{} - } -} - // PropEncode encodes a property. func PropEncode(p *Property, buf []byte) ([]byte, error) { - if p.dtype == typeInvalid { + if p.Type == typeInvalid { return nil, ErrShortBuffer } - if p.dtype != TypeNull && len(p.name)+2+1 >= len(buf) { + if p.Type != TypeNull && len(p.Name)+2+1 >= len(buf) { return nil, ErrShortBuffer } - if p.dtype != TypeNull && len(p.name) != 0 { - binary.BigEndian.PutUint16(buf[:2], uint16(len(p.name))) + if p.Type != TypeNull && len(p.Name) != 0 { + binary.BigEndian.PutUint16(buf[:2], uint16(len(p.Name))) buf = buf[2:] - copy(buf, p.name) - buf = buf[len(p.name):] + copy(buf, p.Name) + buf = buf[len(p.Name):] } var err error - switch p.dtype { + switch p.Type { case typeNumber: - buf, err = EncodeNumber(buf, p.number) + buf, err = EncodeNumber(buf, p.Number) case typeBoolean: - buf, err = EncodeBoolean(buf, p.number != 0) + buf, err = EncodeBoolean(buf, p.Number != 0) case typeString: - buf, err = EncodeString(buf, p.str) + buf, err = EncodeString(buf, p.String) case TypeNull: if len(buf) < 2 { return nil, ErrShortBuffer @@ -316,11 +292,11 @@ func PropEncode(p *Property, buf []byte) ([]byte, error) { buf[0] = TypeNull buf = buf[1:] case TypeObject: - buf, err = Encode(&p.obj, buf) + buf, err = Encode(&p.Object, buf) case typeEcmaArray: - buf, err = EncodeEcmaArray(&p.obj, buf) + buf, err = EncodeEcmaArray(&p.Object, buf) case typeStrictArray: - buf, err = EncodeArray(&p.obj, buf) + buf, err = EncodeArray(&p.Object, buf) default: buf, err = nil, ErrInvalidType } @@ -329,8 +305,6 @@ func PropEncode(p *Property, buf []byte) ([]byte, error) { // PropDecode decodes a property, returning the number of bytes consumed from the supplied buffer. func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { - prop.name = "" - sz := len(buf) if len(buf) == 0 { return 0, ErrEndOfBuffer @@ -345,23 +319,25 @@ func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { return 0, ErrDecodingName } - prop.name = DecodeString(buf) + prop.Name = DecodeString(buf) buf = buf[2+n:] + } else { + prop.Name = "" } if len(buf) == 0 { return 0, ErrEndOfBuffer } - prop.dtype = uint8(buf[0]) + prop.Type = uint8(buf[0]) buf = buf[1:] - switch prop.dtype { + switch prop.Type { case typeNumber: if len(buf) < 8 { return 0, ErrShortBuffer } - prop.number = DecodeNumber(buf[:8]) + prop.Number = DecodeNumber(buf[:8]) buf = buf[8:] case typeBoolean: @@ -372,23 +348,22 @@ func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { if len(buf) < int(n+2) { return 0, ErrShortBuffer } - prop.str = DecodeString(buf) + prop.String = DecodeString(buf) buf = buf[2+n:] case TypeObject: - n, err := Decode(&prop.obj, buf, true) + n, err := Decode(&prop.Object, buf, true) if err != nil { return 0, err } buf = buf[n:] - case TypeNull, typeUndefined, typeUnsupported: - prop.dtype = TypeNull + prop.Type = TypeNull case typeEcmaArray: buf = buf[4:] - n, err := Decode(&prop.obj, buf, true) + n, err := Decode(&prop.Object, buf, true) if err != nil { return 0, err } @@ -482,7 +457,7 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { for len(buf) != 0 { if len(buf) >= 3 && DecodeInt24(buf[:3]) == TypeObjectEnd { buf = buf[3:] - break + break } var prop Property @@ -497,19 +472,19 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { return sz - len(buf), nil } -// GetProp returns an object's property, either by its index when idx is non-negative, or by its name when name otherwise. -// If the matching property is not found nil is returned. -func GetProp(obj *Object, name string, idx int) *Property { +// GetProp returns an object's property, either by its index when idx is non-negative, or by its name otherwise. +// If the requested property is not found an ErrPropertyNotFound error is returned. +func (obj *Object)GetProp(name string, idx int) (*Property, error) { if idx >= 0 { if idx < len(obj.Props) { - return &obj.Props[idx] + return &obj.Props[idx], nil } } else { for i, p := range obj.Props { - if p.name == name { - return &obj.Props[i] + if p.Name == name { + return &obj.Props[i], nil } } } - return nil + return nil, ErrPropertyNotFound } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 66972129..9d19427b 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -88,7 +88,7 @@ func TestNumbers(t *testing.T) { } dn := int32(DecodeInt24(buf)) if n != dn { - t.Errorf("DecodeInt24 did not produce original number, got %v", dn) + t.Errorf("DecodeInt24 did not produce original Number, got %v", dn) } } } @@ -97,13 +97,13 @@ func TestNumbers(t *testing.T) { func TestProperties(t *testing.T) { var buf [1024]byte - // Encode/decode number properties. + // Encode/decode Number properties. enc := buf[:] var err error for i, _ := range testNumbers { - enc, err = PropEncode(&Property{dtype: typeNumber, number: float64(testNumbers[i])}, enc) + enc, err = PropEncode(&Property{Type: typeNumber, Number: float64(testNumbers[i])}, enc) if err != nil { - t.Errorf("PropEncode of number failed") + t.Errorf("PropEncode of Number failed") } } @@ -113,10 +113,10 @@ func TestProperties(t *testing.T) { for i, _ := range testNumbers { n, err := PropDecode(&prop, dec, false) if err != nil { - t.Errorf("PropDecode of number failed") + t.Errorf("PropDecode of Number failed") } - if int32(prop.number) != testNumbers[i] { - t.Errorf("PropEncode/PropDecode returned wrong number; got %v, expected %v", int32(prop.number), testNumbers[i]) + if int32(prop.Number) != testNumbers[i] { + t.Errorf("PropEncode/PropDecode returned wrong Number; got %v, expected %v", int32(prop.Number), testNumbers[i]) } dec = dec[n:] } @@ -124,7 +124,7 @@ func TestProperties(t *testing.T) { // Encode/decode string properties. enc = buf[:] for i, _ := range testStrings { - enc, err = PropEncode(&Property{dtype: typeString, str: testStrings[i]}, enc) + enc, err = PropEncode(&Property{Type: typeString, String: testStrings[i]}, enc) if err != nil { t.Errorf("PropEncode of string failed") } @@ -137,8 +137,8 @@ func TestProperties(t *testing.T) { if err != nil { t.Errorf("PropDecode of string failed") } - if prop.str != testStrings[i] { - t.Errorf("PropEncode/PropDecode returned wrong string; got %s, expected %s", prop.str, testStrings[i]) + if prop.String != testStrings[i] { + t.Errorf("PropEncode/PropDecode returned wrong string; got %s, expected %s", prop.String, testStrings[i]) } dec = dec[n:] } @@ -149,8 +149,8 @@ func TestProperties(t *testing.T) { func TestObject(t *testing.T) { var buf [1024]byte - // Construct a simple object that has one property, the number 42. - prop1 := Property{dtype: typeNumber, number: 42} + // Construct a simple object that has one property, the Number 42. + prop1 := Property{Type: typeNumber, Number: 42} obj1 := Object{} obj1.Props = append(obj1.Props, prop1) @@ -171,7 +171,7 @@ func TestObject(t *testing.T) { } num := DecodeNumber(buf[2:10]) if num != 42 { - t.Errorf("Encoded wrong number") + t.Errorf("Encoded wrong Number") } end := int32(DecodeInt24(buf[10:13])) if end != TypeObjectEnd { @@ -189,8 +189,8 @@ func TestObject(t *testing.T) { // Construct a more complicated object. var obj2 Object for i, _ := range testStrings { - obj2.Props = append(obj2.Props, Property{dtype: typeString, str: testStrings[i]}) - obj2.Props = append(obj2.Props, Property{dtype: typeNumber, number: float64(testNumbers[i])}) + obj2.Props = append(obj2.Props, Property{Type: typeString, String: testStrings[i]}) + obj2.Props = append(obj2.Props, Property{Type: typeNumber, Number: float64(testNumbers[i])}) } // Encode it. @@ -207,4 +207,26 @@ func TestObject(t *testing.T) { if err != nil { t.Errorf("Decode of object failed") } + + // Find some properties that exist. + prop, err := obj2.GetProp("", 2) + if err != nil { + t.Errorf("GetProp(2) failed") + } + if prop.String != "foo" { + t.Errorf("GetProp(2) returned wrong Property") + } + prop, err = obj2.GetProp("", 3) + if err != nil { + t.Errorf("GetProp(1) failed") + } + if prop.Number != 1 { + t.Errorf("GetProp(1) returned wrong Property") + } + + // Try to find one that doesn't exist. + prop, err = obj2.GetProp("", 10) + if err != ErrPropertyNotFound { + t.Errorf("GetProp(10) failed") + } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index fc34809c..7795c216 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -686,10 +686,18 @@ func handleInvoke(s *Session, body []byte) error { return err } - meth := amf.PropGetString(amf.GetProp(&obj, "", 0)) - s.log(DebugLevel, pkg+"invoking method "+meth) - txn := amf.PropGetNumber(amf.GetProp(&obj, "", 1)) + prop, err := obj.GetProp("", 0) + if err != nil { + return err + } + meth := prop.String + prop, err = obj.GetProp("", 1) + if err != nil { + return err + } + txn := prop.Number + s.log(DebugLevel, pkg+"invoking method "+meth) switch meth { case av_result: var methodInvoked string @@ -728,7 +736,11 @@ func handleInvoke(s *Session, body []byte) error { } case avCreatestream: - s.streamID = int32(amf.PropGetNumber(amf.GetProp(&obj, "", 3))) + prop, err = obj.GetProp("", 3) + if err != nil { + return err + } + s.streamID = int32(prop.Number) if s.link.protocol&featureWrite == 0 { return errNotWritable } @@ -765,10 +777,21 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - var obj2 amf.Object - amf.PropGetObject(amf.GetProp(&obj, "", 3), &obj2) - code := amf.PropGetString(amf.GetProp(&obj2, avCode, -1)) - level := amf.PropGetString(amf.GetProp(&obj2, avLevel, -1)) + prop, err = obj.GetProp("", 3) + if err != nil { + return err + } + obj2 := prop.Object + prop, err = obj2.GetProp(avCode, -1) + if err != nil { + return err + } + code := prop.String + prop, err = obj2.GetProp(avLevel, -1) + if err != nil { + return err + } + level := prop.String s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { From b31c65001e343421d6c30e5a48d34f7bcf74d171 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 17:39:32 +1030 Subject: [PATCH 14/52] Implemented boolean properties (although our rtmp implementation does not seem to require it at present). --- rtmp/amf/amf.go | 25 +++++++++++++++---------- rtmp/amf/amf_test.go | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 0369ad82..c6806ad9 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -210,6 +210,8 @@ func EncodeBoolean(buf []byte, val bool) ([]byte, error) { buf[0] = typeBoolean if val { buf[1] = 1 + } else { + buf[1] = 0 } if len(buf) == 2 { return nil, ErrEndOfBuffer @@ -277,14 +279,13 @@ func PropEncode(p *Property, buf []byte) ([]byte, error) { buf = buf[len(p.Name):] } - var err error switch p.Type { case typeNumber: - buf, err = EncodeNumber(buf, p.Number) + return EncodeNumber(buf, p.Number) case typeBoolean: - buf, err = EncodeBoolean(buf, p.Number != 0) + return EncodeBoolean(buf, p.Number != 0) case typeString: - buf, err = EncodeString(buf, p.String) + return EncodeString(buf, p.String) case TypeNull: if len(buf) < 2 { return nil, ErrShortBuffer @@ -292,15 +293,15 @@ func PropEncode(p *Property, buf []byte) ([]byte, error) { buf[0] = TypeNull buf = buf[1:] case TypeObject: - buf, err = Encode(&p.Object, buf) + return Encode(&p.Object, buf) case typeEcmaArray: - buf, err = EncodeEcmaArray(&p.Object, buf) + return EncodeEcmaArray(&p.Object, buf) case typeStrictArray: - buf, err = EncodeArray(&p.Object, buf) + return EncodeArray(&p.Object, buf) default: - buf, err = nil, ErrInvalidType + return nil, ErrInvalidType } - return buf, err + return buf, nil } // PropDecode decodes a property, returning the number of bytes consumed from the supplied buffer. @@ -341,7 +342,11 @@ func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { buf = buf[8:] case typeBoolean: - return 0, ErrUnimplemented + if len(buf) < 1 { + return 0, ErrShortBuffer + } + prop.Number = float64(uint8(buf[0])) + buf = buf[1:] case typeString: n := DecodeInt16(buf[:2]) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 9d19427b..cfca16c8 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -186,12 +186,35 @@ func TestObject(t *testing.T) { t.Errorf("Decode of object failed") } + // Change the object's property to a boolean. + obj1.Props[0].Type = typeBoolean + obj1.Props[0].Number = 1 + + // Re-encode it + enc = buf[:] + enc, err = Encode(&obj1, enc) + if err != nil { + t.Errorf("Encode of object failed") + } + + // Re-decode it. + dec = buf[1:] + _, err = Decode(&dobj1, dec, false) + if err != nil { + t.Errorf("Decode of object failed with error: %v", err) + } + if dobj1.Props[0].Number != 1 { + t.Errorf("Decoded wrong boolean value") + } + // Construct a more complicated object. var obj2 Object for i, _ := range testStrings { obj2.Props = append(obj2.Props, Property{Type: typeString, String: testStrings[i]}) obj2.Props = append(obj2.Props, Property{Type: typeNumber, Number: float64(testNumbers[i])}) } + obj2.Props = append(obj2.Props, Property{Type: typeBoolean, Number: 0}) + obj2.Props = append(obj2.Props, Property{Type: typeBoolean, Number: 1}) // Encode it. enc = buf[:] @@ -205,7 +228,7 @@ func TestObject(t *testing.T) { var dobj2 Object _, err = Decode(&dobj2, dec, false) if err != nil { - t.Errorf("Decode of object failed") + t.Errorf("Decode of object failed with error: %v", err) } // Find some properties that exist. @@ -223,6 +246,14 @@ func TestObject(t *testing.T) { if prop.Number != 1 { t.Errorf("GetProp(1) returned wrong Property") } + prop, err = obj2.GetProp("", 9) + if err != nil { + t.Errorf("GetProp(9) failed") + return + } + if prop.Type != typeBoolean && prop.Number != 1 { + t.Errorf("GetProp(9) returned wrong Property") + } // Try to find one that doesn't exist. prop, err = obj2.GetProp("", 10) From cb2ea08fff5c0c1912ca20b34745a4359fb1c22a Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 17:48:17 +1030 Subject: [PATCH 15/52] Use literal values for data type consts per the AMF spec, rather than iota. --- rtmp/amf/amf.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index c6806ad9..55a656fc 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -44,28 +44,28 @@ import ( "math" ) -// AMF data types. +// AMF data types, as defined by the AMF specification. // NB: we export these sparingly. const ( - typeNumber = iota - typeBoolean - typeString - TypeObject - typeMovieClip - TypeNull - typeUndefined - typeReference - typeEcmaArray - TypeObjectEnd - typeStrictArray - typeDate - typeLongString - typeUnsupported - typeRecordset - typeXmlDoc - typeTypedObject - typeAvmplus - typeInvalid = 0xff + typeNumber = 0x00 + typeBoolean = 0x01 + typeString = 0x02 + TypeObject = 0x03 + typeMovieClip = 0x04 + TypeNull = 0x05 + typeUndefined = 0x06 + typeReference = 0x07 + typeEcmaArray = 0x08 + TypeObjectEnd = 0x09 + typeStrictArray = 0x0A + typeDate = 0x0B + typeLongString = 0x0C + typeUnsupported = 0x0D + typeRecordset = 0x0E + typeXmlDoc = 0x0F + typeTypedObject = 0x10 + typeAvmplus = 0x11 + typeInvalid = 0xff ) // AMF represents an AMF message (object), which is simply a collection of properties. @@ -479,7 +479,7 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { // GetProp returns an object's property, either by its index when idx is non-negative, or by its name otherwise. // If the requested property is not found an ErrPropertyNotFound error is returned. -func (obj *Object)GetProp(name string, idx int) (*Property, error) { +func (obj *Object) GetProp(name string, idx int) (*Property, error) { if idx >= 0 { if idx < len(obj.Props) { return &obj.Props[idx], nil From 5505edab5a12fb44e762d1297f3d23f984f27e9f Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 17:53:02 +1030 Subject: [PATCH 16/52] Remove reference to deprecated invalidType data type. --- rtmp/amf/amf.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 55a656fc..9f410e69 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -264,10 +264,6 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { // PropEncode encodes a property. func PropEncode(p *Property, buf []byte) ([]byte, error) { - if p.Type == typeInvalid { - return nil, ErrShortBuffer - } - if p.Type != TypeNull && len(p.Name)+2+1 >= len(buf) { return nil, ErrShortBuffer } From 5b94ddfbe9e00d95aea3eb09937394bb789957c6 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 18:04:04 +1030 Subject: [PATCH 17/52] Renamed Object.Props to Properties. --- rtmp/amf/amf.go | 30 +++++++++++++++--------------- rtmp/amf/amf_test.go | 16 ++++++++-------- rtmp/rtmp.go | 4 ++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 9f410e69..0112eec4 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -70,7 +70,7 @@ const ( // AMF represents an AMF message (object), which is simply a collection of properties. type Object struct { - Props []Property + Properties []Property } // Property represents an AMF property. @@ -389,9 +389,9 @@ func Encode(obj *Object, buf []byte) ([]byte, error) { buf[0] = TypeObject buf = buf[1:] - for i := 0; i < len(obj.Props); i++ { + for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Props[i], buf) + buf, err = PropEncode(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -411,12 +411,12 @@ func EncodeEcmaArray(obj *Object, buf []byte) ([]byte, error) { buf[0] = typeEcmaArray buf = buf[1:] - binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Props))) + binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Properties))) buf = buf[4:] - for i := 0; i < len(obj.Props); i++ { + for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Props[i], buf) + buf, err = PropEncode(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -436,12 +436,12 @@ func EncodeArray(obj *Object, buf []byte) ([]byte, error) { buf[0] = typeStrictArray buf = buf[1:] - binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Props))) + binary.BigEndian.PutUint32(buf[:4], uint32(len(obj.Properties))) buf = buf[4:] - for i := 0; i < len(obj.Props); i++ { + for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Props[i], buf) + buf, err = PropEncode(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -454,7 +454,7 @@ func EncodeArray(obj *Object, buf []byte) ([]byte, error) { func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { sz := len(buf) - obj.Props = obj.Props[:0] + obj.Properties = obj.Properties[:0] for len(buf) != 0 { if len(buf) >= 3 && DecodeInt24(buf[:3]) == TypeObjectEnd { buf = buf[3:] @@ -467,7 +467,7 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { return 0, err } buf = buf[n:] - obj.Props = append(obj.Props, prop) + obj.Properties = append(obj.Properties, prop) } return sz - len(buf), nil @@ -477,13 +477,13 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { // If the requested property is not found an ErrPropertyNotFound error is returned. func (obj *Object) GetProp(name string, idx int) (*Property, error) { if idx >= 0 { - if idx < len(obj.Props) { - return &obj.Props[idx], nil + if idx < len(obj.Properties) { + return &obj.Properties[idx], nil } } else { - for i, p := range obj.Props { + for i, p := range obj.Properties { if p.Name == name { - return &obj.Props[i], nil + return &obj.Properties[i], nil } } } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index cfca16c8..4c7e7f92 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -152,7 +152,7 @@ func TestObject(t *testing.T) { // Construct a simple object that has one property, the Number 42. prop1 := Property{Type: typeNumber, Number: 42} obj1 := Object{} - obj1.Props = append(obj1.Props, prop1) + obj1.Properties = append(obj1.Properties, prop1) // Encode it enc := buf[:] @@ -187,8 +187,8 @@ func TestObject(t *testing.T) { } // Change the object's property to a boolean. - obj1.Props[0].Type = typeBoolean - obj1.Props[0].Number = 1 + obj1.Properties[0].Type = typeBoolean + obj1.Properties[0].Number = 1 // Re-encode it enc = buf[:] @@ -203,18 +203,18 @@ func TestObject(t *testing.T) { if err != nil { t.Errorf("Decode of object failed with error: %v", err) } - if dobj1.Props[0].Number != 1 { + if dobj1.Properties[0].Number != 1 { t.Errorf("Decoded wrong boolean value") } // Construct a more complicated object. var obj2 Object for i, _ := range testStrings { - obj2.Props = append(obj2.Props, Property{Type: typeString, String: testStrings[i]}) - obj2.Props = append(obj2.Props, Property{Type: typeNumber, Number: float64(testNumbers[i])}) + obj2.Properties = append(obj2.Properties, Property{Type: typeString, String: testStrings[i]}) + obj2.Properties = append(obj2.Properties, Property{Type: typeNumber, Number: float64(testNumbers[i])}) } - obj2.Props = append(obj2.Props, Property{Type: typeBoolean, Number: 0}) - obj2.Props = append(obj2.Props, Property{Type: typeBoolean, Number: 1}) + obj2.Properties = append(obj2.Properties, Property{Type: typeBoolean, Number: 0}) + obj2.Properties = append(obj2.Properties, Property{Type: typeBoolean, Number: 1}) // Encode it. enc = buf[:] diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 7795c216..f1ff5f3b 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -408,8 +408,8 @@ func sendConnectPacket(s *Session) error { } } - for i := range s.link.extras.Props { - enc, err = amf.PropEncode(&s.link.extras.Props[i], enc) + for i := range s.link.extras.Properties { + enc, err = amf.PropEncode(&s.link.extras.Properties[i], enc) if err != nil { return err } From 32c281d2dcab5369b647c201fba9a5c968e95486 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 18:43:51 +1030 Subject: [PATCH 18/52] Added type-specific wrappers for amf.Object.GetProperty, namely GetNumber, GetString and GetObject. --- rtmp/amf/amf.go | 44 ++++++++++++++++++++++++++++++++++++++------ rtmp/amf/amf_test.go | 24 ++++++++++++------------ rtmp/rtmp.go | 21 ++++++++------------- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 0112eec4..1ee86a66 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -473,19 +473,51 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { return sz - len(buf), nil } -// GetProp returns an object's property, either by its index when idx is non-negative, or by its name otherwise. -// If the requested property is not found an ErrPropertyNotFound error is returned. -func (obj *Object) GetProp(name string, idx int) (*Property, error) { +// GetProperty returns a property, either by its index when idx is non-negative, or by its name otherwise. +// If the requested property is not found or the type does not match, an ErrPropertyNotFound error is returned. +func (obj *Object) GetProperty(name string, idx int, typ uint8) (*Property, error) { + var prop *Property if idx >= 0 { if idx < len(obj.Properties) { - return &obj.Properties[idx], nil + prop = &obj.Properties[idx] } } else { for i, p := range obj.Properties { if p.Name == name { - return &obj.Properties[i], nil + prop = &obj.Properties[i] + break } } } - return nil, ErrPropertyNotFound + if prop == nil || prop.Type != typ { + return nil, ErrPropertyNotFound + } + return prop, nil +} + +// GetNumber is a wrapper for GetProperty that returns a Number property's value, if any. +func (obj *Object) GetNumber(name string, idx int) (float64, error) { + prop, err := obj.GetProperty(name, idx, typeNumber) + if err != nil { + return 0, err + } + return prop.Number, nil +} + +// GetString is a wrapper for GetProperty that returns a String property's value, if any. +func (obj *Object) GetString(name string, idx int) (string, error) { + prop, err := obj.GetProperty(name, idx, typeString) + if err != nil { + return "", err + } + return prop.String, nil +} + +// GetObject is a wrapper for GetProperty that returns an Object property's value, if any. +func (obj *Object) GetObject(name string, idx int) (*Object, error) { + prop, err := obj.GetProperty(name, idx, TypeObject) + if err != nil { + return nil, err + } + return &prop.Object, nil } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 4c7e7f92..ef676e7e 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -232,32 +232,32 @@ func TestObject(t *testing.T) { } // Find some properties that exist. - prop, err := obj2.GetProp("", 2) + prop, err := obj2.GetProperty("", 2, typeString) if err != nil { - t.Errorf("GetProp(2) failed") + t.Errorf("GetProperty(2) failed") } if prop.String != "foo" { - t.Errorf("GetProp(2) returned wrong Property") + t.Errorf("GetProperty(2) returned wrong Property") } - prop, err = obj2.GetProp("", 3) + prop, err = obj2.GetProperty("", 3, typeNumber) if err != nil { - t.Errorf("GetProp(1) failed") + t.Errorf("GetProperty(1) failed") } if prop.Number != 1 { - t.Errorf("GetProp(1) returned wrong Property") + t.Errorf("GetProperty(1) returned wrong Property") } - prop, err = obj2.GetProp("", 9) + prop, err = obj2.GetProperty("", 9, typeBoolean) if err != nil { - t.Errorf("GetProp(9) failed") + t.Errorf("GetProperty(9) failed") return } - if prop.Type != typeBoolean && prop.Number != 1 { - t.Errorf("GetProp(9) returned wrong Property") + if prop.Number != 1 { + t.Errorf("GetProperty(9) returned wrong Property") } // Try to find one that doesn't exist. - prop, err = obj2.GetProp("", 10) + prop, err = obj2.GetProperty("", 10, TypeObject) if err != ErrPropertyNotFound { - t.Errorf("GetProp(10) failed") + t.Errorf("GetProperty(10) failed") } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index f1ff5f3b..93d07328 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -686,16 +686,14 @@ func handleInvoke(s *Session, body []byte) error { return err } - prop, err := obj.GetProp("", 0) + meth, err := obj.GetString("", 0) if err != nil { return err } - meth := prop.String - prop, err = obj.GetProp("", 1) + txn, err := obj.GetNumber("", 1) if err != nil { return err } - txn := prop.Number s.log(DebugLevel, pkg+"invoking method "+meth) switch meth { @@ -736,15 +734,15 @@ func handleInvoke(s *Session, body []byte) error { } case avCreatestream: - prop, err = obj.GetProp("", 3) + n, err := obj.GetNumber("", 3) if err != nil { return err } - s.streamID = int32(prop.Number) + s.streamID = int32(n) if s.link.protocol&featureWrite == 0 { return errNotWritable } - err := sendPublish(s) + err = sendPublish(s) if err != nil { return err } @@ -777,21 +775,18 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - prop, err = obj.GetProp("", 3) + obj2, err := obj.GetObject("", 3) if err != nil { return err } - obj2 := prop.Object - prop, err = obj2.GetProp(avCode, -1) + code, err := obj2.GetString(avCode, -1) if err != nil { return err } - code := prop.String - prop, err = obj2.GetProp(avLevel, -1) + level, err := obj2.GetString(avLevel, -1) if err != nil { return err } - level := prop.String s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) switch code { From e0a4c39c8bbb6ffa2d3225a9a7693a8c3fb797e6 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 12 Jan 2019 20:07:56 +1030 Subject: [PATCH 19/52] Fix typo in comment. --- rtmp/amf/amf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 1ee86a66..dd09dd18 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -248,7 +248,7 @@ func EncodeNamedNumber(buf []byte, key string, val float64) ([]byte, error) { return EncodeNumber(buf[len(key):], val) } -// EncodeNamedNumber encodes a named boolean, where key is the name and val is the booelean value. +// EncodeNamedNumber encodes a named boolean, where key is the name and val is the boolean value. func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { if 2+len(key) > len(buf) { return nil, ErrShortBuffer From 40806d4de7a5a02f859a98ed5569fbed805ce62e Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 07:11:25 +1030 Subject: [PATCH 20/52] Removed unused extras from rtmp.link. --- rtmp/rtmp.go | 7 ------- rtmp/session.go | 1 - 2 files changed, 8 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 93d07328..6e57a452 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -408,13 +408,6 @@ func sendConnectPacket(s *Session) error { } } - for i := range s.link.extras.Properties { - enc, err = amf.PropEncode(&s.link.extras.Properties[i], enc) - if err != nil { - return err - } - } - pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) return pkt.write(s, true) // response expected diff --git a/rtmp/session.go b/rtmp/session.go index ed40be8c..cbe27c8f 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -81,7 +81,6 @@ type link struct { auth string flashVer string token string - extras amf.Object flags int32 swfAge int32 protocol int32 From 2219a26890168ccc7d392fe751a0aefe2e7a6a38 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 07:29:36 +1030 Subject: [PATCH 21/52] Removed unused Session.encoding and sendEncoding. --- rtmp/rtmp.go | 19 ++++++------------- rtmp/session.go | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 6e57a452..9ec21d9e 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -312,8 +312,7 @@ func sendConnectPacket(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avConnect) + enc, err := amf.EncodeString(enc, avConnect) if err != nil { return err } @@ -348,7 +347,6 @@ func sendConnectPacket(s *Session) error { return err } } - if s.link.tcUrl != "" { enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.tcUrl) if err != nil { @@ -378,7 +376,6 @@ func sendConnectPacket(s *Session) error { return err } if s.link.pageUrl != "" { - var err error enc, err = amf.EncodeNamedString(enc, avPageUrl, s.link.pageUrl) if err != nil { return err @@ -386,17 +383,13 @@ func sendConnectPacket(s *Session) error { } } - if s.encoding != 0.0 || s.sendEncoding { - enc, err = amf.EncodeNamedNumber(enc, avObjectEncoding, s.encoding) - if err != nil { - return err - } + // terminate the AMF object + enc, err = amf.EncodeInt24(enc, amf.TypeObjectEnd) + if err != nil { + return err } - copy(enc, []byte{0, 0, amf.TypeObjectEnd}) - enc = enc[3:] - - // add auth string + // add auth string, if any if s.link.auth != "" { enc, err = amf.EncodeBoolean(enc, s.link.flags&linkAuth != 0) if err != nil { diff --git a/rtmp/session.go b/rtmp/session.go index cbe27c8f..c00018cd 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -54,7 +54,6 @@ type Session struct { clientBW int32 clientBW2 uint8 isPlaying bool - sendEncoding bool numInvokes int32 methodCalls []method channelsAllocatedIn int32 @@ -64,7 +63,6 @@ type Session struct { channelTimestamp []int32 audioCodecs float64 videoCodecs float64 - encoding float64 deferred []byte link link log Log From 9ca1a491782257c97fc41f17119a2cdaac8bd7cf Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 07:46:47 +1030 Subject: [PATCH 22/52] Removed unused chunk type and associated unused code. --- rtmp/packet.go | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index adbe32f3..70ead13a 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -91,19 +91,11 @@ type packet struct { info int32 bodySize uint32 bytesRead uint32 - chunk *chunk header []byte body []byte } -// chunk defines an RTMP packet chunk. -type chunk struct { - headerSize int32 - data []byte - header [fullHeaderSize]byte -} - -// read reads a packet. +// read reads an RTMP packet. func (pkt *packet) read(s *Session) error { var hbuf [fullHeaderSize]byte header := hbuf[:] @@ -222,13 +214,6 @@ func (pkt *packet) read(s *Session) error { chunkSize = toRead } - if pkt.chunk != nil { - panic("non-nil chunk") - pkt.chunk.headerSize = int32(hSize) - copy(pkt.chunk.header[:], hbuf[:hSize]) - pkt.chunk.data = pkt.body[pkt.bytesRead : pkt.bytesRead+uint32(chunkSize)] - } - _, err = s.read(pkt.body[pkt.bytesRead:][:chunkSize]) if err != nil { s.log(DebugLevel, pkg+"failed to read packet body", "error", err.Error()) @@ -237,7 +222,7 @@ func (pkt *packet) read(s *Session) error { pkt.bytesRead += uint32(chunkSize) - // keep the packet as ref for other packets on this channel + // keep the packet as a reference for other packets on this channel if s.channelsIn[pkt.channel] == nil { s.channelsIn[pkt.channel] = &packet{} } @@ -264,6 +249,7 @@ func (pkt *packet) read(s *Session) error { } // resize adjusts the packet's storage to accommodate a body of the given size and header type. +// When headerSizeAuto is specified, the header type is computed based on packet type. func (pkt *packet) resize(size uint32, ht uint8) { buf := make([]byte, fullHeaderSize+size) pkt.header = buf @@ -287,7 +273,9 @@ func (pkt *packet) resize(size uint32, ht uint8) { } } -// write sends a packet. +// write sends an RTMP packet. +// Packets are written in chunks which are Session.chunkSize in length (128 bytes in length). +// We defers sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. // When queue is true, we expect a response to this request and cache the method on s.methodCalls. func (pkt *packet) write(s *Session, queue bool) error { if pkt.body == nil { @@ -438,7 +426,6 @@ func (pkt *packet) write(s *Session, queue bool) error { } // TODO(kortschak): Rewrite this horrific peice of premature optimisation. - // NB: RTMP wants packets in chunks which are 128 bytes by default, but the server may request a different size. s.log(DebugLevel, pkg+"sending packet", "la", s.link.conn.LocalAddr(), "ra", s.link.conn.RemoteAddr(), "size", size) for size+hSize != 0 { if chunkSize > size { @@ -461,6 +448,7 @@ func (pkt *packet) write(s *Session, queue bool) error { hSize = 0 if size > 0 { + // We are writing the 2nd or subsequent chunk. origIdx -= 1 + cSize hSize = 1 + cSize @@ -486,7 +474,7 @@ func (pkt *packet) write(s *Session, queue bool) error { } } - // We invoked a remote method + // We invoked a remote method, if pkt.packetType == packetTypeInvoke { buf := pkt.body[1:] meth := amf.DecodeString(buf) From 8a68cbca2fef4499b38e8d2df76eb9c3abc1332e Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 07:51:35 +1030 Subject: [PATCH 23/52] PropEncode/PropDecode -> EncodeProperty/DecodeProperty. --- rtmp/amf/amf.go | 17 ++++++++--------- rtmp/amf/amf_test.go | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index dd09dd18..3dbc87f7 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -262,8 +262,8 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { return EncodeBoolean(buf[len(key):], val) } -// PropEncode encodes a property. -func PropEncode(p *Property, buf []byte) ([]byte, error) { +// EncodeProperty encodes a property. +func EncodeProperty(p *Property, buf []byte) ([]byte, error) { if p.Type != TypeNull && len(p.Name)+2+1 >= len(buf) { return nil, ErrShortBuffer } @@ -300,8 +300,8 @@ func PropEncode(p *Property, buf []byte) ([]byte, error) { return buf, nil } -// PropDecode decodes a property, returning the number of bytes consumed from the supplied buffer. -func PropDecode(prop *Property, buf []byte, decodeName bool) (int, error) { +// DecodeProperty decodes a property, returning the number of bytes consumed from the supplied buffer. +func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { sz := len(buf) if len(buf) == 0 { return 0, ErrEndOfBuffer @@ -391,7 +391,7 @@ func Encode(obj *Object, buf []byte) ([]byte, error) { for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Properties[i], buf) + buf, err = EncodeProperty(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -416,7 +416,7 @@ func EncodeEcmaArray(obj *Object, buf []byte) ([]byte, error) { for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Properties[i], buf) + buf, err = EncodeProperty(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -441,7 +441,7 @@ func EncodeArray(obj *Object, buf []byte) ([]byte, error) { for i := 0; i < len(obj.Properties); i++ { var err error - buf, err = PropEncode(&obj.Properties[i], buf) + buf, err = EncodeProperty(&obj.Properties[i], buf) if err != nil { return nil, err } @@ -460,9 +460,8 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { buf = buf[3:] break } - var prop Property - n, err := PropDecode(&prop, buf, decodeName) + n, err := DecodeProperty(&prop, buf, decodeName) if err != nil { return 0, err } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index ef676e7e..0eb8d1a4 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -101,9 +101,9 @@ func TestProperties(t *testing.T) { enc := buf[:] var err error for i, _ := range testNumbers { - enc, err = PropEncode(&Property{Type: typeNumber, Number: float64(testNumbers[i])}, enc) + enc, err = EncodeProperty(&Property{Type: typeNumber, Number: float64(testNumbers[i])}, enc) if err != nil { - t.Errorf("PropEncode of Number failed") + t.Errorf("EncodeProperty of Number failed") } } @@ -111,12 +111,12 @@ func TestProperties(t *testing.T) { prop := Property{} dec := buf[:] for i, _ := range testNumbers { - n, err := PropDecode(&prop, dec, false) + n, err := DecodeProperty(&prop, dec, false) if err != nil { - t.Errorf("PropDecode of Number failed") + t.Errorf("DecodeProperty of Number failed") } if int32(prop.Number) != testNumbers[i] { - t.Errorf("PropEncode/PropDecode returned wrong Number; got %v, expected %v", int32(prop.Number), testNumbers[i]) + t.Errorf("EncodeProperty/DecodeProperty returned wrong Number; got %v, expected %v", int32(prop.Number), testNumbers[i]) } dec = dec[n:] } @@ -124,21 +124,21 @@ func TestProperties(t *testing.T) { // Encode/decode string properties. enc = buf[:] for i, _ := range testStrings { - enc, err = PropEncode(&Property{Type: typeString, String: testStrings[i]}, enc) + enc, err = EncodeProperty(&Property{Type: typeString, String: testStrings[i]}, enc) if err != nil { - t.Errorf("PropEncode of string failed") + t.Errorf("EncodeProperty of string failed") } } prop = Property{} dec = buf[:] for i, _ := range testStrings { - n, err := PropDecode(&prop, dec, false) + n, err := DecodeProperty(&prop, dec, false) if err != nil { - t.Errorf("PropDecode of string failed") + t.Errorf("DecodeProperty of string failed") } if prop.String != testStrings[i] { - t.Errorf("PropEncode/PropDecode returned wrong string; got %s, expected %s", prop.String, testStrings[i]) + t.Errorf("EncodeProperty/DecodeProperty returned wrong string; got %s, expected %s", prop.String, testStrings[i]) } dec = dec[n:] } From 8f2a8ced9d5a502c5f8e36e1e305c4de83b83b51 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 08:09:54 +1030 Subject: [PATCH 24/52] More idiomatic names for Object's property getters. --- rtmp/amf/amf.go | 52 +++++++++++++++++++++++--------------------- rtmp/amf/amf_test.go | 26 +++++++++++----------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 3dbc87f7..ba1897aa 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -68,7 +68,7 @@ const ( typeInvalid = 0xff ) -// AMF represents an AMF message (object), which is simply a collection of properties. +// AMF represents an AMF object, which is simply a collection of properties. type Object struct { Properties []Property } @@ -263,25 +263,25 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { } // EncodeProperty encodes a property. -func EncodeProperty(p *Property, buf []byte) ([]byte, error) { - if p.Type != TypeNull && len(p.Name)+2+1 >= len(buf) { +func EncodeProperty(prop *Property, buf []byte) ([]byte, error) { + if prop.Type != TypeNull && len(prop.Name)+2+1 >= len(buf) { return nil, ErrShortBuffer } - if p.Type != TypeNull && len(p.Name) != 0 { - binary.BigEndian.PutUint16(buf[:2], uint16(len(p.Name))) + if prop.Type != TypeNull && len(prop.Name) != 0 { + binary.BigEndian.PutUint16(buf[:2], uint16(len(prop.Name))) buf = buf[2:] - copy(buf, p.Name) - buf = buf[len(p.Name):] + copy(buf, prop.Name) + buf = buf[len(prop.Name):] } - switch p.Type { + switch prop.Type { case typeNumber: - return EncodeNumber(buf, p.Number) + return EncodeNumber(buf, prop.Number) case typeBoolean: - return EncodeBoolean(buf, p.Number != 0) + return EncodeBoolean(buf, prop.Number != 0) case typeString: - return EncodeString(buf, p.String) + return EncodeString(buf, prop.String) case TypeNull: if len(buf) < 2 { return nil, ErrShortBuffer @@ -289,11 +289,11 @@ func EncodeProperty(p *Property, buf []byte) ([]byte, error) { buf[0] = TypeNull buf = buf[1:] case TypeObject: - return Encode(&p.Object, buf) + return Encode(&prop.Object, buf) case typeEcmaArray: - return EncodeEcmaArray(&p.Object, buf) + return EncodeEcmaArray(&prop.Object, buf) case typeStrictArray: - return EncodeArray(&p.Object, buf) + return EncodeArray(&prop.Object, buf) default: return nil, ErrInvalidType } @@ -472,9 +472,11 @@ func Decode(obj *Object, buf []byte, decodeName bool) (int, error) { return sz - len(buf), nil } -// GetProperty returns a property, either by its index when idx is non-negative, or by its name otherwise. +// Object methods: + +// Property returns a property, either by its index when idx is non-negative, or by its name otherwise. // If the requested property is not found or the type does not match, an ErrPropertyNotFound error is returned. -func (obj *Object) GetProperty(name string, idx int, typ uint8) (*Property, error) { +func (obj *Object) Property(name string, idx int, typ uint8) (*Property, error) { var prop *Property if idx >= 0 { if idx < len(obj.Properties) { @@ -494,27 +496,27 @@ func (obj *Object) GetProperty(name string, idx int, typ uint8) (*Property, erro return prop, nil } -// GetNumber is a wrapper for GetProperty that returns a Number property's value, if any. -func (obj *Object) GetNumber(name string, idx int) (float64, error) { - prop, err := obj.GetProperty(name, idx, typeNumber) +// NumberProperty is a wrapper for Property that returns a Number property's value, if any. +func (obj *Object) NumberProperty(name string, idx int) (float64, error) { + prop, err := obj.Property(name, idx, typeNumber) if err != nil { return 0, err } return prop.Number, nil } -// GetString is a wrapper for GetProperty that returns a String property's value, if any. -func (obj *Object) GetString(name string, idx int) (string, error) { - prop, err := obj.GetProperty(name, idx, typeString) +// StringProperty is a wrapper for Property that returns a String property's value, if any. +func (obj *Object) StringProperty(name string, idx int) (string, error) { + prop, err := obj.Property(name, idx, typeString) if err != nil { return "", err } return prop.String, nil } -// GetObject is a wrapper for GetProperty that returns an Object property's value, if any. -func (obj *Object) GetObject(name string, idx int) (*Object, error) { - prop, err := obj.GetProperty(name, idx, TypeObject) +// ObjectProperty is a wrapper for Property that returns an Object property's value, if any. +func (obj *Object) ObjectProperty(name string, idx int) (*Object, error) { + prop, err := obj.Property(name, idx, TypeObject) if err != nil { return nil, err } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 0eb8d1a4..32d2f0a5 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -232,32 +232,32 @@ func TestObject(t *testing.T) { } // Find some properties that exist. - prop, err := obj2.GetProperty("", 2, typeString) + s, err := obj2.StringProperty("", 2) if err != nil { - t.Errorf("GetProperty(2) failed") + t.Errorf("Property(2) failed") } - if prop.String != "foo" { - t.Errorf("GetProperty(2) returned wrong Property") + if s != "foo" { + t.Errorf("Property(2) returned wrong Property") } - prop, err = obj2.GetProperty("", 3, typeNumber) + n, err := obj2.NumberProperty("", 3) if err != nil { - t.Errorf("GetProperty(1) failed") + t.Errorf("Property(3) failed") } - if prop.Number != 1 { - t.Errorf("GetProperty(1) returned wrong Property") + if n != 1 { + t.Errorf("Property(3) returned wrong Property") } - prop, err = obj2.GetProperty("", 9, typeBoolean) + prop, err := obj2.Property("", 9, typeBoolean) if err != nil { - t.Errorf("GetProperty(9) failed") + t.Errorf("Property(9) failed") return } if prop.Number != 1 { - t.Errorf("GetProperty(9) returned wrong Property") + t.Errorf("Property(9) returned wrong Property") } // Try to find one that doesn't exist. - prop, err = obj2.GetProperty("", 10, TypeObject) + prop, err = obj2.Property("", 10, TypeObject) if err != ErrPropertyNotFound { - t.Errorf("GetProperty(10) failed") + t.Errorf("Property(10) failed") } } From f1e46461c317cc15bcec39141e5abb0276080198 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 09:12:41 +1030 Subject: [PATCH 25/52] Removed unused swfUrl, pageUrl, swfAge and flashVer from link and renamed tcUrl to url. --- rtmp/rtmp.go | 58 ++++++++++++++----------------------------------- rtmp/session.go | 13 +---------- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 9ec21d9e..ac74a3ec 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -152,33 +152,25 @@ var rtmpProtocolStrings = [...]string{ // RTMP errors. var ( errUnknownScheme = errors.New("rtmp: unknown scheme") + errInvalidURL = errors.New("rtmp: invalid URL") errConnected = errors.New("rtmp: already connected") errNotConnected = errors.New("rtmp: not connected") errNotWritable = errors.New("rtmp: connection not writable") - errHandshake = errors.New("rtmp: handshake failed") - errConnSend = errors.New("rtmp: connection send error") - errConnStream = errors.New("rtmp: connection stream error") errInvalidHeader = errors.New("rtmp: invalid header") errInvalidBody = errors.New("rtmp: invalid body") errInvalidFlvTag = errors.New("rtmp: invalid FLV tag") errUnimplemented = errors.New("rtmp: unimplemented feature") ) -// setupURL parses the RTMP URL. +// init initialises the Session link func setupURL(s *Session) (err error) { s.link.protocol, s.link.host, s.link.port, s.link.app, s.link.playpath, err = parseURL(s.url) if err != nil { return err } - - if s.link.tcUrl == "" { - if s.link.app != "" { - s.link.tcUrl = rtmpProtocolStrings[s.link.protocol] + "://" + s.link.host + ":" + strconv.Itoa(int(s.link.port)) + "/" + s.link.app - } else { - s.link.tcUrl = s.url - } + if s.link.app == "" { + return errInvalidURL } - if s.link.port == 0 { switch { case (s.link.protocol & featureSSL) != 0: @@ -190,6 +182,8 @@ func setupURL(s *Session) (err error) { s.link.port = 1935 } } + s.link.url = rtmpProtocolStrings[s.link.protocol] + "://" + s.link.host + ":" + strconv.Itoa(int(s.link.port)) + "/" + s.link.app + s.link.protocol |= featureWrite return nil } @@ -208,13 +202,13 @@ func connect(s *Session) error { err = handshake(s) if err != nil { s.log(WarnLevel, pkg+"handshake failed", "error", err.Error()) - return errHandshake + return err } s.log(DebugLevel, pkg+"handshaked") err = sendConnectPacket(s) if err != nil { s.log(WarnLevel, pkg+"sendConnect failed", "error", err.Error()) - return errConnSend + return err } return nil } @@ -334,26 +328,12 @@ func sendConnectPacket(s *Session) error { return err } } - - if s.link.flashVer != "" { - enc, err = amf.EncodeNamedString(enc, avFlashver, s.link.flashVer) + if s.link.url != "" { + enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.url) if err != nil { return err } } - if s.link.swfUrl != "" { - enc, err = amf.EncodeNamedString(enc, avSwfUrl, s.link.swfUrl) - if err != nil { - return err - } - } - if s.link.tcUrl != "" { - enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.tcUrl) - if err != nil { - return err - } - } - if s.link.protocol&featureWrite == 0 { enc, err = amf.EncodeNamedBoolean(enc, avFpad, false) if err != nil { @@ -375,12 +355,6 @@ func sendConnectPacket(s *Session) error { if err != nil { return err } - if s.link.pageUrl != "" { - enc, err = amf.EncodeNamedString(enc, avPageUrl, s.link.pageUrl) - if err != nil { - return err - } - } } // terminate the AMF object @@ -672,11 +646,11 @@ func handleInvoke(s *Session, body []byte) error { return err } - meth, err := obj.GetString("", 0) + meth, err := obj.StringProperty("", 0) if err != nil { return err } - txn, err := obj.GetNumber("", 1) + txn, err := obj.NumberProperty("", 1) if err != nil { return err } @@ -720,7 +694,7 @@ func handleInvoke(s *Session, body []byte) error { } case avCreatestream: - n, err := obj.GetNumber("", 3) + n, err := obj.NumberProperty("", 3) if err != nil { return err } @@ -761,15 +735,15 @@ func handleInvoke(s *Session, body []byte) error { s.log(FatalLevel, pkg+"unsupported method avClose") case avOnStatus: - obj2, err := obj.GetObject("", 3) + obj2, err := obj.ObjectProperty("", 3) if err != nil { return err } - code, err := obj2.GetString(avCode, -1) + code, err := obj2.StringProperty(avCode, -1) if err != nil { return err } - level, err := obj2.GetString(avLevel, -1) + level, err := obj2.StringProperty(avLevel, -1) if err != nil { return err } diff --git a/rtmp/session.go b/rtmp/session.go index c00018cd..ca66a6ef 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -72,15 +72,11 @@ type Session struct { type link struct { host string playpath string - tcUrl string - swfUrl string - pageUrl string + url string app string auth string - flashVer string token string flags int32 - swfAge int32 protocol int32 timeout uint port uint16 @@ -123,7 +119,6 @@ func NewSession(url string, timeout uint, log Log) *Session { log: log, link: link{ timeout: timeout, - swfAge: 30, }, } } @@ -138,8 +133,6 @@ func (s *Session) Open() error { if err != nil { return err } - - s.enableWrite() err = connect(s) if err != nil { s.Close() @@ -246,7 +239,3 @@ func (s *Session) isConnected() bool { return s.link.conn != nil } -// enableWrite enables the current session for writing. -func (s *Session) enableWrite() { - s.link.protocol |= featureWrite -} From 8898ee3addb71fa99b624224e20ef1778fd384fa Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 09:18:52 +1030 Subject: [PATCH 26/52] setupURL() now a method on Session and renamed init(). --- rtmp/rtmp.go | 2 +- rtmp/rtmp_test.go | 17 ++++++++++------- rtmp/session.go | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index ac74a3ec..1693a7f1 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -163,7 +163,7 @@ var ( ) // init initialises the Session link -func setupURL(s *Session) (err error) { +func (s *Session) init() (err error) { s.link.protocol, s.link.host, s.link.port, s.link.app, s.link.playpath, err = parseURL(s.url) if err != nil { return err diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 01dad10f..4e8daf5f 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -116,7 +116,7 @@ func TestKey(t *testing.T) { testLog(0, "Testing against URL "+testBaseURL+testKey) } -// TestSetupURL tests URL parsing. +// TestSetupURL tests URL parsing and link initialization. func TestSetupURL(t *testing.T) { testLog(0, "TestSetupURL") // test with just the base URL @@ -124,19 +124,22 @@ func TestSetupURL(t *testing.T) { if s.url != testBaseURL && s.link.timeout != testTimeout { t.Errorf("NewSession failed") } - err := setupURL(s) + err := s.init() if err != nil { - t.Errorf("setupURL(testBaseURL) failed with error: %v", err) + t.Errorf("setupURL: failed with error: %v", err) } // test the parts are as expected - if rtmpProtocolStrings[s.link.protocol] != rtmpProtocol { - t.Errorf("setupURL returned wrong protocol: %v", s.link.protocol) + if s.link.protocol&featureWrite == 0 { + t.Errorf("setupURL: link not writable") + } + if rtmpProtocolStrings[s.link.protocol&^featureWrite] != rtmpProtocol { + t.Errorf("setupURL: wrong protocol: %v", s.link.protocol) } if s.link.host != testHost { - t.Errorf("setupURL returned wrong host: %v", s.link.host) + t.Errorf("setupURL: wrong host: %v", s.link.host) } if s.link.app != testApp { - t.Errorf("setupURL returned wrong app: %v", s.link.app) + t.Errorf("setupURL: wrong app: %v", s.link.app) } } diff --git a/rtmp/session.go b/rtmp/session.go index ca66a6ef..7452b9db 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -129,7 +129,7 @@ func (s *Session) Open() error { if s.isConnected() { return errConnected } - err := setupURL(s) + err := s.init() if err != nil { return err } From 40ce3357eb1b1c975048b9e86c69a1a4c332bd07 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 09:47:04 +1030 Subject: [PATCH 27/52] Removed unused code for unwritable links, and collapsed unhandled cases in switch statments. --- rtmp/rtmp.go | 130 +++++++++++---------------------------------------- 1 file changed, 27 insertions(+), 103 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 1693a7f1..16300f4c 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -241,20 +241,19 @@ func connectStream(s *Session) error { } // handlePacket handles a packet that the client has received. -// NB: cases have been commented out that are not currently used by AusOcean +// NB: Unsupported packet types are logged fatally. func handlePacket(s *Session, pkt *packet) error { + if pkt.bodySize < 4 { + return errInvalidBody + } + switch pkt.packetType { case packetTypeChunkSize: - if pkt.bodySize >= 4 { - s.inChunkSize = int32(amf.DecodeInt32(pkt.body[:4])) - } + s.inChunkSize = int32(amf.DecodeInt32(pkt.body[:4])) case packetTypeBytesReadReport: s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) - case packetTypeControl: - s.log(FatalLevel, pkg+"unsupported packet type packetTypeControl") - case packetTypeServerBW: s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) @@ -266,18 +265,6 @@ func handlePacket(s *Session, pkt *packet) error { s.clientBW2 = 0xff } - case packetTypeAudio: - s.log(FatalLevel, pkg+"unsupported packet type packetTypeAudio") - - case packetTypeVideo: - s.log(FatalLevel, pkg+"unsupported packet type packetTypeVideo") - - case packetTypeFlexMessage: - s.log(FatalLevel, pkg+"unsupported packet type packetTypeFlexMessage") - - case packetTypeInfo: - s.log(FatalLevel, pkg+"unsupported packet type packetTypeInfo") - case packetTypeInvoke: err := handleInvoke(s, pkt.body[:pkt.bodySize]) if err != nil { @@ -286,8 +273,8 @@ func handlePacket(s *Session, pkt *packet) error { return err } - case packetTypeFlashVideo: - s.log(FatalLevel, pkg+"unsupported packet type packetType_FLASHVideo") + case packetTypeControl, packetTypeAudio, packetTypeVideo, packetTypeFlashVideo, packetTypeFlexMessage, packetTypeInfo: + s.log(FatalLevel, pkg+"unsupported packet type "+strconv.Itoa(int(pkt.packetType))) default: s.log(WarnLevel, pkg+"unknown packet type", "type", pkt.packetType) @@ -315,49 +302,21 @@ func sendConnectPacket(s *Session) error { if err != nil { return err } + enc[0] = amf.TypeObject enc = enc[1:] - enc, err = amf.EncodeNamedString(enc, avApp, s.link.app) if err != nil { return err } - if s.link.protocol&featureWrite != 0 { - enc, err = amf.EncodeNamedString(enc, avType, avNonprivate) - if err != nil { - return err - } + enc, err = amf.EncodeNamedString(enc, avType, avNonprivate) + if err != nil { + return err } - if s.link.url != "" { - enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.url) - if err != nil { - return err - } + enc, err = amf.EncodeNamedString(enc, avTcUrl, s.link.url) + if err != nil { + return err } - if s.link.protocol&featureWrite == 0 { - enc, err = amf.EncodeNamedBoolean(enc, avFpad, false) - if err != nil { - return err - } - enc, err = amf.EncodeNamedNumber(enc, avCapabilities, 15) - if err != nil { - return err - } - enc, err = amf.EncodeNamedNumber(enc, avAudioCodecs, s.audioCodecs) - if err != nil { - return err - } - enc, err = amf.EncodeNamedNumber(enc, avVideoCodecs, s.videoCodecs) - if err != nil { - return err - } - enc, err = amf.EncodeNamedNumber(enc, avVideoFunction, 1) - if err != nil { - return err - } - } - - // terminate the AMF object enc, err = amf.EncodeInt24(enc, amf.TypeObjectEnd) if err != nil { return err @@ -707,8 +666,8 @@ func handleInvoke(s *Session, body []byte) error { return err } - case avPlay, avPublish: - s.log(FatalLevel, pkg+"unsupported method avPlay/avPublish") + default: + s.log(FatalLevel, pkg+"unexpected method invoked"+methodInvoked) } case avOnBWDone: @@ -719,21 +678,6 @@ func handleInvoke(s *Session, body []byte) error { } } - case avOnFCUnsubscribe, avOnFCSubscribe: - s.log(FatalLevel, pkg+"unsupported method avOnFCUnsubscribe/avOonfcsubscribe") - - case avPing: - s.log(FatalLevel, pkg+"unsupported method avPing") - - case av_onbwcheck: - s.log(FatalLevel, pkg+"unsupported method av_onbwcheck") - - case av_onbwdone: - s.log(FatalLevel, pkg+"unsupported method av_onbwdone") - - case avClose: - s.log(FatalLevel, pkg+"unsupported method avClose") - case avOnStatus: obj2, err := obj.ObjectProperty("", 3) if err != nil { @@ -749,40 +693,20 @@ func handleInvoke(s *Session, body []byte) error { } s.log(DebugLevel, pkg+"onStatus", "code", code, "level", level) - switch code { - case avNetStreamFailed, avNetStreamPlayFailed, - avNetStreamPlayStreamNotFound, avNetConnectionConnectInvalidApp: - s.log(FatalLevel, pkg+"unsupported method avNetStream/avNetStreamPlayFailed/avNetstream_play_streamnotfound/av_netConnection_Connect_invalidApp") - - case avNetStreamPlayStart, avNetStreamPlayPublishNotify: - s.log(FatalLevel, pkg+"unsupported method avNetStreamPlayStart/avNetStreamPlayPublishNotify") - - case avNetStreamPublish_Start: - s.log(DebugLevel, pkg+"playing") - s.isPlaying = true - for i, m := range s.methodCalls { - if m.name == avPublish { - s.methodCalls = eraseMethod(s.methodCalls, i) - break - } + if code != avNetStreamPublish_Start { + s.log(ErrorLevel, pkg+"unexpected response "+code) + return errUnimplemented + } + s.log(DebugLevel, pkg+"playing") + s.isPlaying = true + for i, m := range s.methodCalls { + if m.name == avPublish { + s.methodCalls = eraseMethod(s.methodCalls, i) } - // ToDo: handle case when avPublish method not found - - case avNetStreamPlayComplete, avNetStreamPlayStop, avNetStreamPlayUnpublishNotify: - s.log(FatalLevel, pkg+"unsupported method avNetStreamPlayComplete/avNetStreamPlayStop/avNetStreamPlayUnpublishNotify") - - case avNetStreamSeekNotify: - s.log(FatalLevel, pkg+"unsupported method avNetstream_seek_notify") - - case avNetStreamPauseNotify: - s.log(FatalLevel, pkg+"unsupported method avNetStreamPauseNotify") } - case avPlaylist_ready: - s.log(FatalLevel, pkg+"unsupported method avPlaylist_ready") - default: - s.log(FatalLevel, pkg+"unknown method "+meth) + s.log(FatalLevel, pkg+"unsuppoted method "+meth) } return nil } From e8002582da20e68c99447e2d9fe1bc002a71dae8 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 09:59:57 +1030 Subject: [PATCH 28/52] Added DecodeInt32LE. --- rtmp/amf/amf.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index ba1897aa..ddbcc8f1 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -35,6 +35,7 @@ LICENSE */ // Package amf implements Action Message Format (AMF) encoding and decoding. +// In AMF, encoding of numbers is big endian by default, unless specified otherwise. // See https://en.wikipedia.org/wiki/Action_Message_Format. package amf @@ -109,6 +110,11 @@ func DecodeInt32(buf []byte) uint32 { return uint32(binary.BigEndian.Uint32(buf)) } +// DecodeInt32LE decodes a 32-bit little-endian integer. +func DecodeInt32LE(buf []byte) uint32 { + return uint32(binary.LittleEndian.Uint32(buf)) +} + // DecodeString decodes a string that is less than 2^16 bytes long. func DecodeString(buf []byte) string { n := DecodeInt16(buf) From 9ff10dbbacba92f5a0f3447162fafadf2835c1c4 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 10:01:03 +1030 Subject: [PATCH 29/52] Use amf.DecodeInt32LE() instead of decodeInt32LE(). --- rtmp/packet.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 70ead13a..8c0fc0f3 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -183,9 +183,8 @@ func (pkt *packet) read(s *Session) error { if size > 6 { pkt.packetType = header[6] - if size == 11 { - pkt.info = decodeInt32LE(header[7:11]) + pkt.info = int32(amf.DecodeInt32LE(header[7:11])) } } } @@ -232,10 +231,6 @@ func (pkt *packet) read(s *Session) error { s.channelsIn[pkt.channel].timestamp = 0xffffff } - if pkt.bytesRead != pkt.bodySize { - panic("readPacket: bytesRead != bodySize") - } - if !pkt.hasAbsTimestamp { // timestamps seem to always be relative pkt.timestamp += uint32(s.channelTimestamp[pkt.channel]) @@ -494,12 +489,3 @@ func (pkt *packet) write(s *Session, queue bool) error { return nil } - -func decodeInt32LE(data []byte) int32 { - return int32(data[3])<<24 | int32(data[2])<<16 | int32(data[1])<<8 | int32(data[0]) -} - -func encodeInt32LE(dst []byte, v int32) int32 { - binary.LittleEndian.PutUint32(dst, uint32(v)) - return 4 -} From f8b8d06b2e06cc6c5f1de298cd6f46ee1258bf6d Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 10:10:43 +1030 Subject: [PATCH 30/52] Removed ErrEndOfBuffer checks which are not required. --- rtmp/amf/amf.go | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index ddbcc8f1..3d150e03 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -86,11 +86,7 @@ type Property struct { // AMF errors. var ( ErrShortBuffer = errors.New("amf: short buffer") - ErrEndOfBuffer = errors.New("amf: end of buffer") ErrInvalidType = errors.New("amf: invalid type") - ErrUnimplemented = errors.New("amf: unimplemented feature") - ErrDecodingName = errors.New("amf: name decoding error") - ErrDecodingString = errors.New("amf: string decoding error") ErrUnexpectedEnd = errors.New("amf: unexpected end") ErrPropertyNotFound = errors.New("amf: property not found") ) @@ -145,9 +141,6 @@ func EncodeInt24(buf []byte, val int32) ([]byte, error) { buf[0] = byte(val >> 16) buf[1] = byte(val >> 8) buf[2] = byte(val) - if len(buf) == 3 { - return nil, ErrEndOfBuffer - } return buf[3:], nil } @@ -157,9 +150,6 @@ func EncodeInt32(buf []byte, val int32) ([]byte, error) { return nil, ErrShortBuffer } binary.BigEndian.PutUint32(buf, uint32(val)) - if len(buf) == 4 { - return nil, ErrEndOfBuffer - } return buf[4:], nil } @@ -180,9 +170,6 @@ func EncodeString(buf []byte, val string) ([]byte, error) { binary.BigEndian.PutUint16(buf[:2], uint16(len(val))) buf = buf[2:] copy(buf, val) - if len(buf) == len(val) { - return nil, ErrEndOfBuffer - } return buf[len(val):], nil } @@ -191,9 +178,6 @@ func EncodeString(buf []byte, val string) ([]byte, error) { binary.BigEndian.PutUint32(buf[:4], uint32(len(val))) buf = buf[4:] copy(buf, val) - if len(buf) == len(val) { - return nil, ErrEndOfBuffer - } return buf[len(val):], nil } @@ -219,9 +203,6 @@ func EncodeBoolean(buf []byte, val bool) ([]byte, error) { } else { buf[1] = 0 } - if len(buf) == 2 { - return nil, ErrEndOfBuffer - } return buf[2:], nil } @@ -234,9 +215,6 @@ func EncodeNamedString(buf []byte, key, val string) ([]byte, error) { binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) buf = buf[2:] copy(buf, key) - if len(key) == len(buf) { - return nil, ErrEndOfBuffer - } return EncodeString(buf[len(key):], val) } @@ -248,9 +226,6 @@ func EncodeNamedNumber(buf []byte, key string, val float64) ([]byte, error) { binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) buf = buf[2:] copy(buf, key) - if len(key) == len(buf) { - return nil, ErrEndOfBuffer - } return EncodeNumber(buf[len(key):], val) } @@ -262,9 +237,6 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { binary.BigEndian.PutUint16(buf[:2], uint16(len(key))) buf = buf[2:] copy(buf, key) - if len(key) == len(buf) { - return nil, ErrEndOfBuffer - } return EncodeBoolean(buf[len(key):], val) } @@ -309,9 +281,6 @@ func EncodeProperty(prop *Property, buf []byte) ([]byte, error) { // DecodeProperty decodes a property, returning the number of bytes consumed from the supplied buffer. func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { sz := len(buf) - if len(buf) == 0 { - return 0, ErrEndOfBuffer - } if decodeName { if len(buf) < 4 { @@ -319,7 +288,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { } n := DecodeInt16(buf[:2]) if int(n) > len(buf)-2 { - return 0, ErrDecodingName + return 0, ErrShortBuffer } prop.Name = DecodeString(buf) @@ -328,10 +297,6 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { prop.Name = "" } - if len(buf) == 0 { - return 0, ErrEndOfBuffer - } - prop.Type = uint8(buf[0]) buf = buf[1:] @@ -380,7 +345,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { return 0, ErrUnexpectedEnd default: - return 0, ErrUnimplemented + return 0, ErrInvalidType } return sz - len(buf), nil From 8cd5627974b4aa3cf0fc193e443cc6394244b01a Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 12:09:06 +1030 Subject: [PATCH 31/52] Test nested objects. --- rtmp/amf/amf_test.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 32d2f0a5..7c8149d2 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -207,15 +207,27 @@ func TestObject(t *testing.T) { t.Errorf("Decoded wrong boolean value") } - // Construct a more complicated object. + // Construct a more complicated object that includes a nested object. var obj2 Object for i, _ := range testStrings { obj2.Properties = append(obj2.Properties, Property{Type: typeString, String: testStrings[i]}) obj2.Properties = append(obj2.Properties, Property{Type: typeNumber, Number: float64(testNumbers[i])}) } - obj2.Properties = append(obj2.Properties, Property{Type: typeBoolean, Number: 0}) + obj2.Properties = append(obj2.Properties, Property{Type: TypeObject, Object: obj1}) obj2.Properties = append(obj2.Properties, Property{Type: typeBoolean, Number: 1}) + // Retrieve nested object + obj, err := obj2.ObjectProperty("", 8) + if err != err { + t.Errorf("Failed to retrieve object") + } + if len(obj.Properties) < 1 { + t.Errorf("Properties missing for nested object") + } + if obj.Properties[0].Type != typeBoolean { + t.Errorf("Wrong property type for nested object") + } + // Encode it. enc = buf[:] enc, err = Encode(&obj2, enc) @@ -234,25 +246,33 @@ func TestObject(t *testing.T) { // Find some properties that exist. s, err := obj2.StringProperty("", 2) if err != nil { - t.Errorf("Property(2) failed") + t.Errorf("StringProperty failed") } if s != "foo" { - t.Errorf("Property(2) returned wrong Property") + t.Errorf("StringProperty returned wrong String") } n, err := obj2.NumberProperty("", 3) if err != nil { - t.Errorf("Property(3) failed") + t.Errorf("NumberProperty failed") } if n != 1 { - t.Errorf("Property(3) returned wrong Property") + t.Errorf("NumberProperty returned wrong Number") + } + obj, err = obj2.ObjectProperty("", 8) + if err != nil { + t.Errorf("ObjectProperty failed") + return + } + if obj.Properties[0].Type != typeBoolean { + t.Errorf("ObjectProperty returned object with wrong property") } prop, err := obj2.Property("", 9, typeBoolean) if err != nil { - t.Errorf("Property(9) failed") + t.Errorf("Property failed") return } if prop.Number != 1 { - t.Errorf("Property(9) returned wrong Property") + t.Errorf("Property returned wrong Property") } // Try to find one that doesn't exist. From b78aae6a1998b80141c426ebea4f498671d3fb94 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 12:09:40 +1030 Subject: [PATCH 32/52] Respect decodeName param when recursively decoding objects. --- rtmp/amf/amf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 3d150e03..d6cb34a4 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -324,7 +324,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { buf = buf[2+n:] case TypeObject: - n, err := Decode(&prop.Object, buf, true) + n, err := Decode(&prop.Object, buf, decodeName) if err != nil { return 0, err } From 60af77017d890d12f9b4fa75ad7bfc3f77c915be Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 12:52:06 +1030 Subject: [PATCH 33/52] Remove superfluous error declarations. --- rtmp/rtmp.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 16300f4c..7e75ff91 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -268,7 +268,6 @@ func handlePacket(s *Session, pkt *packet) error { case packetTypeInvoke: err := handleInvoke(s, pkt.body[:pkt.bodySize]) if err != nil { - // This will never happen with the methods we implement. s.log(WarnLevel, pkg+"unexpected error from handleInvoke", "error", err.Error()) return err } @@ -350,8 +349,7 @@ func sendCreateStream(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avCreatestream) + enc, err := amf.EncodeString(enc, avCreatestream) if err != nil { return err } @@ -379,8 +377,7 @@ func sendReleaseStream(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avReleasestream) + enc, err := amf.EncodeString(enc, avReleasestream) if err != nil { return err } @@ -411,8 +408,7 @@ func sendFCPublish(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avFCPublish) + enc, err := amf.EncodeString(enc, avFCPublish) if err != nil { return err } @@ -444,8 +440,7 @@ func sendFCUnpublish(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avFCUnpublish) + enc, err := amf.EncodeString(enc, avFCUnpublish) if err != nil { return err } @@ -477,8 +472,7 @@ func sendPublish(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avPublish) + enc, err := amf.EncodeString(enc, avPublish) if err != nil { return err } @@ -514,8 +508,7 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, avDeletestream) + enc, err := amf.EncodeString(enc, avDeletestream) if err != nil { return err } @@ -548,8 +541,8 @@ func sendBytesReceived(s *Session) error { enc := pkt.body s.nBytesInSent = s.nBytesIn - var err error - enc, err = amf.EncodeInt32(enc, s.nBytesIn) + + enc, err := amf.EncodeInt32(enc, s.nBytesIn) if err != nil { return err } @@ -569,8 +562,7 @@ func sendCheckBW(s *Session) error { } enc := pkt.body - var err error - enc, err = amf.EncodeString(enc, av_checkbw) + enc, err := amf.EncodeString(enc, av_checkbw) if err != nil { return err } From bef7177c5a7166e81483e8ea6d6ca852915a89cf Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 13:04:05 +1030 Subject: [PATCH 34/52] Removed used Session.checkCounter and link.token. --- rtmp/rtmp.go | 20 ++++++-------------- rtmp/session.go | 2 -- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 7e75ff91..e10657ef 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -609,6 +609,9 @@ func handleInvoke(s *Session, body []byte) error { s.log(DebugLevel, pkg+"invoking method "+meth) switch meth { case av_result: + if (s.link.protocol & featureWrite) == 0 { + return errNotWritable + } var methodInvoked string for i, m := range s.methodCalls { if float64(m.num) == txn { @@ -625,12 +628,6 @@ func handleInvoke(s *Session, body []byte) error { switch methodInvoked { case avConnect: - if s.link.token != "" { - s.log(FatalLevel, pkg+"no support for link token") - } - if (s.link.protocol & featureWrite) == 0 { - return errNotWritable - } err := sendReleaseStream(s) if err != nil { return err @@ -650,9 +647,6 @@ func handleInvoke(s *Session, body []byte) error { return err } s.streamID = int32(n) - if s.link.protocol&featureWrite == 0 { - return errNotWritable - } err = sendPublish(s) if err != nil { return err @@ -663,11 +657,9 @@ func handleInvoke(s *Session, body []byte) error { } case avOnBWDone: - if s.checkCounter == 0 { // ToDo: why is this always zero? - err := sendCheckBW(s) - if err != nil { - return err - } + err := sendCheckBW(s) + if err != nil { + return err } case avOnStatus: diff --git a/rtmp/session.go b/rtmp/session.go index 7452b9db..610cb484 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -46,7 +46,6 @@ type Session struct { url string inChunkSize int32 outChunkSize int32 - checkCounter int32 nBytesIn int32 nBytesInSent int32 streamID int32 @@ -75,7 +74,6 @@ type link struct { url string app string auth string - token string flags int32 protocol int32 timeout uint From 82c010b6f7944f2d5bb050c73704bd0eb1a6a0c5 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 13:09:29 +1030 Subject: [PATCH 35/52] packet.read/write -> readFrom/writeTo. --- rtmp/packet.go | 5 ++--- rtmp/rtmp.go | 20 ++++++++++---------- rtmp/session.go | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 8c0fc0f3..2822c7ac 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -96,7 +96,7 @@ type packet struct { } // read reads an RTMP packet. -func (pkt *packet) read(s *Session) error { +func (pkt *packet) readFrom(s *Session) error { var hbuf [fullHeaderSize]byte header := hbuf[:] @@ -197,7 +197,6 @@ func (pkt *packet) read(s *Session) error { s.log(DebugLevel, pkg+"failed to read extended timestamp", "error", err.Error()) return err } - // TODO: port this pkt.timestamp = amf.DecodeInt32(header[size : size+4]) hSize += 4 } @@ -272,7 +271,7 @@ func (pkt *packet) resize(size uint32, ht uint8) { // Packets are written in chunks which are Session.chunkSize in length (128 bytes in length). // We defers sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. // When queue is true, we expect a response to this request and cache the method on s.methodCalls. -func (pkt *packet) write(s *Session, queue bool) error { +func (pkt *packet) writeTo(s *Session, queue bool) error { if pkt.body == nil { return errInvalidBody } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index e10657ef..d0a34e77 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -218,7 +218,7 @@ func connectStream(s *Session) error { var err error for !s.isPlaying { pkt := packet{} - err = pkt.read(s) + err = pkt.readFrom(s) if err != nil { break } @@ -335,7 +335,7 @@ func sendConnectPacket(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, true) // response expected + return pkt.writeTo(s, true) // response expected } func sendCreateStream(s *Session) error { @@ -363,7 +363,7 @@ func sendCreateStream(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, true) // response expected + return pkt.writeTo(s, true) // response expected } func sendReleaseStream(s *Session) error { @@ -394,7 +394,7 @@ func sendReleaseStream(s *Session) error { } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, false) + return pkt.writeTo(s, false) } func sendFCPublish(s *Session) error { @@ -426,7 +426,7 @@ func sendFCPublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, false) + return pkt.writeTo(s, false) } func sendFCUnpublish(s *Session) error { @@ -458,7 +458,7 @@ func sendFCUnpublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, false) + return pkt.writeTo(s, false) } func sendPublish(s *Session) error { @@ -494,7 +494,7 @@ func sendPublish(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, true) // response expected + return pkt.writeTo(s, true) // response expected } func sendDeleteStream(s *Session, dStreamId float64) error { @@ -525,7 +525,7 @@ func sendDeleteStream(s *Session, dStreamId float64) error { } pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, false) + return pkt.writeTo(s, false) } // sendBytesReceived tells the server how many bytes the client has received. @@ -548,7 +548,7 @@ func sendBytesReceived(s *Session) error { } pkt.bodySize = 4 - return pkt.write(s, false) + return pkt.writeTo(s, false) } func sendCheckBW(s *Session) error { @@ -576,7 +576,7 @@ func sendCheckBW(s *Session) error { pkt.bodySize = uint32((len(pbuf) - fullHeaderSize) - len(enc)) - return pkt.write(s, false) + return pkt.writeTo(s, false) } func eraseMethod(m []method, i int) []method { diff --git a/rtmp/session.go b/rtmp/session.go index 610cb484..f555d00e 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -185,7 +185,7 @@ func (s *Session) Write(data []byte) (int, error) { pkt.resize(pkt.bodySize, headerSizeAuto) copy(pkt.body, data[flvTagheaderSize:flvTagheaderSize+pkt.bodySize]) - err := pkt.write(s, false) + err := pkt.writeTo(s, false) if err != nil { return 0, err } From 21090a65956f3f44e477c9683d1d5ef741cc2d44 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 13:14:12 +1030 Subject: [PATCH 36/52] Update doc comments to reflect new method names. --- rtmp/packet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 2822c7ac..8b5d9a83 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -95,7 +95,7 @@ type packet struct { body []byte } -// read reads an RTMP packet. +// readFrom reads an packet from the RTMP connection. func (pkt *packet) readFrom(s *Session) error { var hbuf [fullHeaderSize]byte header := hbuf[:] @@ -267,7 +267,7 @@ func (pkt *packet) resize(size uint32, ht uint8) { } } -// write sends an RTMP packet. +// writeTo writes a packet to the RTMP connection. // Packets are written in chunks which are Session.chunkSize in length (128 bytes in length). // We defers sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. // When queue is true, we expect a response to this request and cache the method on s.methodCalls. From dd562f1a28f37681c9dd81a81ebf30ecc49a2b9f Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 13:15:03 +1030 Subject: [PATCH 37/52] Update comment to reflect new method names. --- rtmp/packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 8b5d9a83..bf16b3cf 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -95,7 +95,7 @@ type packet struct { body []byte } -// readFrom reads an packet from the RTMP connection. +// readFrom reads a packet from the RTMP connection. func (pkt *packet) readFrom(s *Session) error { var hbuf [fullHeaderSize]byte header := hbuf[:] From 9f3d49faa558e258087088e52ee946aac4fd0991 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 13:28:34 +1030 Subject: [PATCH 38/52] Tidied up some comments and simplified queuing logic in writeTo(). --- rtmp/packet.go | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index bf16b3cf..3c2c5d09 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -220,7 +220,7 @@ func (pkt *packet) readFrom(s *Session) error { pkt.bytesRead += uint32(chunkSize) - // keep the packet as a reference for other packets on this channel + // Keep the packet as a reference for other packets on this channel. if s.channelsIn[pkt.channel] == nil { s.channelsIn[pkt.channel] = &packet{} } @@ -231,7 +231,7 @@ func (pkt *packet) readFrom(s *Session) error { } if !pkt.hasAbsTimestamp { - // timestamps seem to always be relative + // Timestamps seem to always be relative. pkt.timestamp += uint32(s.channelTimestamp[pkt.channel]) } s.channelTimestamp[pkt.channel] = int32(pkt.timestamp) @@ -269,7 +269,7 @@ func (pkt *packet) resize(size uint32, ht uint8) { // writeTo writes a packet to the RTMP connection. // Packets are written in chunks which are Session.chunkSize in length (128 bytes in length). -// We defers sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. +// We defer sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. // When queue is true, we expect a response to this request and cache the method on s.methodCalls. func (pkt *packet) writeTo(s *Session, queue bool) error { if pkt.body == nil { @@ -298,7 +298,7 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { prevPkt := s.channelsOut[pkt.channel] var last int if prevPkt != nil && pkt.headerType != headerSizeLarge { - // compress a bit by using the prev packet's attributes + // Compress header by using the previous packet's attributes. if prevPkt.bodySize == pkt.bodySize && prevPkt.packetType == pkt.packetType && pkt.headerType == headerSizeMedium { pkt.headerType = headerSizeSmall } @@ -321,7 +321,7 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { hSize := headerSizes[pkt.headerType] origIdx := fullHeaderSize - hSize - // adjust 1 or 2 bytes for the channel + // Adjust 1 or 2 bytes depending on the channel. cSize := 0 switch { case pkt.channel > 319: @@ -335,7 +335,7 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { hSize += cSize } - // adjust 4 bytes for the timestamp + // Adjust 4 bytes for the timestamp. var ts uint32 if prevPkt != nil { ts = uint32(int(pkt.timestamp) - last) @@ -372,11 +372,11 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { } if headerSizes[pkt.headerType] > 1 { - res := ts + tmp := ts if ts > 0xffffff { - res = 0xffffff + tmp = 0xffffff } - amf.EncodeInt24(headBytes[headerIdx:], int32(res)) + amf.EncodeInt24(headBytes[headerIdx:], int32(tmp)) headerIdx += 3 // 24bits } @@ -468,17 +468,14 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { } } - // We invoked a remote method, - if pkt.packetType == packetTypeInvoke { + // If we invoked a remote method and queue is true, we queue the method until the result arrives. + if pkt.packetType == packetTypeInvoke && queue { buf := pkt.body[1:] meth := amf.DecodeString(buf) - s.log(DebugLevel, pkg+"invoking method "+meth) - // keep it in call queue till result arrives - if queue { - buf = buf[3+len(meth):] - txn := int32(amf.DecodeNumber(buf[:8])) - s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) - } + s.log(DebugLevel, pkg+"queuing method "+meth) + buf = buf[3+len(meth):] + txn := int32(amf.DecodeNumber(buf[:8])) + s.methodCalls = append(s.methodCalls, method{name: meth, num: txn}) } if s.channelsOut[pkt.channel] == nil { From 4c6c549f989a67ff4e4c9caae917f311af6b5262 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 14:28:24 +1030 Subject: [PATCH 39/52] packet.writeTo now defends against a zero pkt.bodySize. --- rtmp/packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 3c2c5d09..138dd917 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -272,7 +272,7 @@ func (pkt *packet) resize(size uint32, ht uint8) { // We defer sending small audio packets and combine consecutive small audio packets where possible to reduce I/O. // When queue is true, we expect a response to this request and cache the method on s.methodCalls. func (pkt *packet) writeTo(s *Session, queue bool) error { - if pkt.body == nil { + if pkt.body == nil || pkt.bodySize == 0 { return errInvalidBody } From fb36a2dccf72fd20d8eac22609c786f7b4f882d9 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 14:30:40 +1030 Subject: [PATCH 40/52] Added TestErrorHandling which subsumes TestOpenClose. --- rtmp/rtmp_test.go | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 4e8daf5f..0dd95c9e 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -143,18 +143,48 @@ func TestSetupURL(t *testing.T) { } } -// TestOpenClose tests opening an closing an RTMP connection. -func TestOpenClose(t *testing.T) { - testLog(0, "TestOpenClose") +// TestErrorHandling tests error handling +func TestErorHandling(t *testing.T) { + testLog(0, "TestErrorHandling") if testKey == "" { - t.Skip("Skipping TestOpenClose since no RTMP_TEST_KEY") + t.Skip("Skipping TestErrorHandling since no RTMP_TEST_KEY") } s := NewSession(testBaseURL+testKey, testTimeout, testLog) - err := s.Open() + + // test errNotConnected + var buf [1024]byte + tag := buf[:0] + _, err := s.Write(tag) + if err == nil { + t.Errorf("Write did not return errNotConnected") + } + + err = s.Open() if err != nil { t.Errorf("Open failed with error: %v", err) - return } + + // test errInvalidFlvTag + _, err = s.Write(tag) + if err == nil { + t.Errorf("Write did not return errInvalidFlvTag") + } + + // test errUnimplemented + copy(tag, []byte("FLV")) + _, err = s.Write(tag) + if err == nil { + t.Errorf("Write did not return errUnimplemented") + } + + // test errInvalidBody + tag = buf[:11] + _, err = s.Write(tag) + fmt.Printf("err = %v\n", err) + if err == nil { + t.Errorf("Write did not return errInvalidBody") + } + err = s.Close() if err != nil { t.Errorf("Close failed with error: %v", err) From aa789f7e78e19a75a941a95dd21738da73628cfc Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 14:33:14 +1030 Subject: [PATCH 41/52] TestSetupURL renamed TestInit. --- rtmp/rtmp_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index 0dd95c9e..4de2daff 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -116,9 +116,9 @@ func TestKey(t *testing.T) { testLog(0, "Testing against URL "+testBaseURL+testKey) } -// TestSetupURL tests URL parsing and link initialization. -func TestSetupURL(t *testing.T) { - testLog(0, "TestSetupURL") +// TestInit tests session construction and link initialization. +func TestInit(t *testing.T) { + testLog(0, "TestInit") // test with just the base URL s := NewSession(testBaseURL, testTimeout, testLog) if s.url != testBaseURL && s.link.timeout != testTimeout { @@ -180,7 +180,6 @@ func TestErorHandling(t *testing.T) { // test errInvalidBody tag = buf[:11] _, err = s.Write(tag) - fmt.Printf("err = %v\n", err) if err == nil { t.Errorf("Write did not return errInvalidBody") } From f7c90e1093fed7e4784bdfd39b2f6ee1b3538ab5 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 15:24:38 +1030 Subject: [PATCH 42/52] Tested decoding of named properties. --- rtmp/amf/amf_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 7c8149d2..9bac593d 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -93,6 +93,7 @@ func TestNumbers(t *testing.T) { } } +/* // TestProperties tests encoding and decoding of properties. func TestProperties(t *testing.T) { var buf [1024]byte @@ -144,6 +145,7 @@ func TestProperties(t *testing.T) { } } +*/ // TestObject tests encoding and decoding of objects. func TestObject(t *testing.T) { @@ -186,7 +188,8 @@ func TestObject(t *testing.T) { t.Errorf("Decode of object failed") } - // Change the object's property to a boolean. + // Change the object's property to a named boolean. + obj1.Properties[0].Name = "on" obj1.Properties[0].Type = typeBoolean obj1.Properties[0].Number = 1 @@ -197,9 +200,9 @@ func TestObject(t *testing.T) { t.Errorf("Encode of object failed") } - // Re-decode it. + // Decode it, this time with named set to true. dec = buf[1:] - _, err = Decode(&dobj1, dec, false) + _, err = Decode(&dobj1, dec, true) if err != nil { t.Errorf("Decode of object failed with error: %v", err) } From 5cf880761e4e5fd1b88849f621ed68f182226005 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 15:44:54 +1030 Subject: [PATCH 43/52] Further simlified EncodeProperty and improved some comments. --- rtmp/amf/amf.go | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index d6cb34a4..190f8f74 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -83,12 +83,12 @@ type Property struct { Object Object } -// AMF errors. +// AMF errors: var ( - ErrShortBuffer = errors.New("amf: short buffer") - ErrInvalidType = errors.New("amf: invalid type") - ErrUnexpectedEnd = errors.New("amf: unexpected end") - ErrPropertyNotFound = errors.New("amf: property not found") + ErrShortBuffer = errors.New("amf: short buffer") // The supplied buffer was too short. + ErrInvalidType = errors.New("amf: invalid type") // An invalid type was supplied to the encoder. + ErrUnexpectedType = errors.New("amf: unexpected end") // An unexpected type was encountered while decoding. + ErrPropertyNotFound = errors.New("amf: property not found") // The requested property was not found. ) // DecodeInt16 decodes a 16-bit integer. @@ -242,11 +242,10 @@ func EncodeNamedBoolean(buf []byte, key string, val bool) ([]byte, error) { // EncodeProperty encodes a property. func EncodeProperty(prop *Property, buf []byte) ([]byte, error) { - if prop.Type != TypeNull && len(prop.Name)+2+1 >= len(buf) { - return nil, ErrShortBuffer - } - - if prop.Type != TypeNull && len(prop.Name) != 0 { + if prop.Type != TypeNull && prop.Name != "" { + if len(buf) < 2+len(prop.Name) { + return nil, ErrShortBuffer + } binary.BigEndian.PutUint16(buf[:2], uint16(len(prop.Name))) buf = buf[2:] copy(buf, prop.Name) @@ -324,7 +323,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { buf = buf[2+n:] case TypeObject: - n, err := Decode(&prop.Object, buf, decodeName) + n, err := Decode(&prop.Object, buf, true) if err != nil { return 0, err } @@ -341,17 +340,15 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { } buf = buf[n:] - case TypeObjectEnd: - return 0, ErrUnexpectedEnd - default: - return 0, ErrInvalidType + return 0, ErrUnexpectedType } return sz - len(buf), nil } // Encode encodes an Object into its AMF representation. +// This is the top-level encoding function and is typically the only function callers will need to use. func Encode(obj *Object, buf []byte) ([]byte, error) { if len(buf) < 5 { return nil, ErrShortBuffer @@ -368,7 +365,7 @@ func Encode(obj *Object, buf []byte) ([]byte, error) { } } - if len(buf) < 4 { + if len(buf) < 3 { return nil, ErrShortBuffer } return EncodeInt24(buf, TypeObjectEnd) @@ -393,7 +390,7 @@ func EncodeEcmaArray(obj *Object, buf []byte) ([]byte, error) { } } - if len(buf) < 4 { + if len(buf) < 3 { return nil, ErrShortBuffer } return EncodeInt24(buf, TypeObjectEnd) From a8572722b5cd9b517bbb2965db0d7196503cf939 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 15:55:02 +1030 Subject: [PATCH 44/52] Uncomment accidentally commented-out code. --- rtmp/amf/amf_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 9bac593d..4a0d19e1 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -93,7 +93,6 @@ func TestNumbers(t *testing.T) { } } -/* // TestProperties tests encoding and decoding of properties. func TestProperties(t *testing.T) { var buf [1024]byte @@ -145,7 +144,6 @@ func TestProperties(t *testing.T) { } } -*/ // TestObject tests encoding and decoding of objects. func TestObject(t *testing.T) { From 137ff7990aa6e1a5e063adec6aaadd317cb9fcf8 Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 16:18:25 +1030 Subject: [PATCH 45/52] Removed unnecessary conversions. --- rtmp/amf/amf.go | 10 +++++----- rtmp/amf/amf_test.go | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 190f8f74..196dac10 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -93,7 +93,7 @@ var ( // DecodeInt16 decodes a 16-bit integer. func DecodeInt16(buf []byte) uint16 { - return uint16(binary.BigEndian.Uint16(buf)) + return binary.BigEndian.Uint16(buf) } // DecodeInt24 decodes a 24-bit integer. @@ -103,12 +103,12 @@ func DecodeInt24(buf []byte) uint32 { // DecodeInt32 decodes a 32-bit integer. func DecodeInt32(buf []byte) uint32 { - return uint32(binary.BigEndian.Uint32(buf)) + return binary.BigEndian.Uint32(buf) } // DecodeInt32LE decodes a 32-bit little-endian integer. func DecodeInt32LE(buf []byte) uint32 { - return uint32(binary.LittleEndian.Uint32(buf)) + return binary.LittleEndian.Uint32(buf) } // DecodeString decodes a string that is less than 2^16 bytes long. @@ -296,7 +296,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { prop.Name = "" } - prop.Type = uint8(buf[0]) + prop.Type = buf[0] buf = buf[1:] switch prop.Type { @@ -311,7 +311,7 @@ func DecodeProperty(prop *Property, buf []byte, decodeName bool) (int, error) { if len(buf) < 1 { return 0, ErrShortBuffer } - prop.Number = float64(uint8(buf[0])) + prop.Number = float64(buf[0]) buf = buf[1:] case typeString: diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 4a0d19e1..04cc1ebc 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -163,11 +163,11 @@ func TestObject(t *testing.T) { } // Check the encoding - if uint8(buf[0]) != TypeObject { - t.Errorf("Encoded wrong type; expected %d, got %v", TypeObject, uint8(buf[0])) + if buf[0] != TypeObject { + t.Errorf("Encoded wrong type; expected %d, got %v", TypeObject, buf[0]) } - if uint8(buf[1]) != typeNumber { - t.Errorf("Encoded wrong type; expected %d, got %v", typeNumber, uint8(buf[0])) + if buf[1] != typeNumber { + t.Errorf("Encoded wrong type; expected %d, got %v", typeNumber, buf[0]) } num := DecodeNumber(buf[2:10]) if num != 42 { From ffcd011220e0cd62d2287e6398ca79aa79b3690c Mon Sep 17 00:00:00 2001 From: scruzin Date: Sun, 13 Jan 2019 19:06:04 +1030 Subject: [PATCH 46/52] Added more number and string encoding/decoding tests. --- rtmp/amf/amf_test.go | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 04cc1ebc..437e49c9 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -57,12 +57,12 @@ func TestSanity(t *testing.T) { // TestStrings tests string encoding and decoding. func TestStrings(t *testing.T) { + // Short string encoding is as follows: + // enc[0] = data type (typeString) + // end[1:3] = size + // enc[3:] = data for _, s := range testStrings { - // Short string encoding is as follows: - // enc[0] = data type (typeString) - // end[1:3] = size - // enc[3:] = data - buf := make([]byte, len(s)+5) + buf := make([]byte, len(s)+7) _, err := EncodeString(buf, s) if err != nil { t.Errorf("EncodeString failed") @@ -75,10 +75,26 @@ func TestStrings(t *testing.T) { t.Errorf("DecodeString did not produce original string, got %v", ds) } } + // Long string encoding is as follows: + // enc[0] = data type (typeString) + // end[1:5] = size + // enc[5:] = data + s := string(make([]byte, 65536)) + buf := make([]byte, len(s)+7) + _, err := EncodeString(buf, s) + if err != nil { + t.Errorf("EncodeString failed") + } + if buf[0] != typeLongString { + t.Errorf("Expected typeLongString, got %v", buf[0]) + } + ds := DecodeLongString(buf[1:]) + if s != ds { + t.Errorf("DecodeLongString did not produce original string, got %v", ds) + } } -// TestNumbers tests 24-bit encoding and encoding. -// We don't test the others as they are just wrappers for standard functions in encoding/binary. +// TestNumbers tests number encoding and encoding. func TestNumbers(t *testing.T) { for _, n := range testNumbers { buf := make([]byte, 4) // NB: encoder requires an extra byte for some reason @@ -90,6 +106,14 @@ func TestNumbers(t *testing.T) { if n != dn { t.Errorf("DecodeInt24 did not produce original Number, got %v", dn) } + _, err = EncodeInt32(buf, n) + if err != nil { + t.Errorf("EncodeInt32 failed") + } + dn = int32(DecodeInt32(buf)) + if n != dn { + t.Errorf("DecodeInt32 did not produce original Number, got %v", dn) + } } } From b680e3e1640cfd0e49065b32af21e03df7e9402e Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 14 Jan 2019 10:14:25 +1030 Subject: [PATCH 47/52] EncodeInt24 and EncodeInt32 now take unsigned integers for consistency with decoder counterparts. --- rtmp/amf/amf.go | 11 ++++++----- rtmp/amf/amf_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 196dac10..6ea5e4b1 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -35,7 +35,8 @@ LICENSE */ // Package amf implements Action Message Format (AMF) encoding and decoding. -// In AMF, encoding of numbers is big endian by default, unless specified otherwise. +// In AMF, encoding of numbers is big endian by default, unless specified otherwise, +// and numbers are all unsigned. // See https://en.wikipedia.org/wiki/Action_Message_Format. package amf @@ -134,7 +135,7 @@ func DecodeBoolean(buf []byte) bool { } // EncodeInt24 encodes a 24-bit integer. -func EncodeInt24(buf []byte, val int32) ([]byte, error) { +func EncodeInt24(buf []byte, val uint32) ([]byte, error) { if len(buf) < 3 { return nil, ErrShortBuffer } @@ -145,11 +146,11 @@ func EncodeInt24(buf []byte, val int32) ([]byte, error) { } // EncodeInt32 encodes a 32-bit integer. -func EncodeInt32(buf []byte, val int32) ([]byte, error) { +func EncodeInt32(buf []byte, val uint32) ([]byte, error) { if len(buf) < 4 { return nil, ErrShortBuffer } - binary.BigEndian.PutUint32(buf, uint32(val)) + binary.BigEndian.PutUint32(buf, val) return buf[4:], nil } @@ -160,7 +161,7 @@ func EncodeString(buf []byte, val string) ([]byte, error) { return nil, ErrShortBuffer } - if len(val)+typeSize+binary.Size(int32(0)) > len(buf) { + if len(val)+typeSize+binary.Size(uint32(0)) > len(buf) { return nil, ErrShortBuffer } diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index 437e49c9..f8668ff6 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -41,7 +41,7 @@ var testStrings = [...]string{ "bazz", } -var testNumbers = [...]int32{ +var testNumbers = [...]uint32{ 0, 1, 0xababab, @@ -102,7 +102,7 @@ func TestNumbers(t *testing.T) { if err != nil { t.Errorf("EncodeInt24 failed") } - dn := int32(DecodeInt24(buf)) + dn := DecodeInt24(buf) if n != dn { t.Errorf("DecodeInt24 did not produce original Number, got %v", dn) } @@ -110,7 +110,7 @@ func TestNumbers(t *testing.T) { if err != nil { t.Errorf("EncodeInt32 failed") } - dn = int32(DecodeInt32(buf)) + dn = DecodeInt32(buf) if n != dn { t.Errorf("DecodeInt32 did not produce original Number, got %v", dn) } @@ -139,7 +139,7 @@ func TestProperties(t *testing.T) { if err != nil { t.Errorf("DecodeProperty of Number failed") } - if int32(prop.Number) != testNumbers[i] { + if prop.Number != float64(testNumbers[i]) { t.Errorf("EncodeProperty/DecodeProperty returned wrong Number; got %v, expected %v", int32(prop.Number), testNumbers[i]) } dec = dec[n:] From e778488aba1ea84bdd7ad183b625d73ab397516b Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 14 Jan 2019 10:17:47 +1030 Subject: [PATCH 48/52] Session.inChunkSize, outChunkSize, nBytesIn, nBytesInSent, serverBW, clientBW and clientBW2 now all uint32 to avoid needless conversions. --- rtmp/packet.go | 10 +++++----- rtmp/rtmp.go | 10 +++++----- rtmp/session.go | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/rtmp/packet.go b/rtmp/packet.go index 138dd917..b88b15bc 100644 --- a/rtmp/packet.go +++ b/rtmp/packet.go @@ -205,7 +205,7 @@ func (pkt *packet) readFrom(s *Session) error { pkt.resize(pkt.bodySize, (hbuf[0]&0xc0)>>6) } - toRead := int32(pkt.bodySize - pkt.bytesRead) + toRead := pkt.bodySize - pkt.bytesRead chunkSize := s.inChunkSize if toRead < chunkSize { @@ -376,12 +376,12 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { if ts > 0xffffff { tmp = 0xffffff } - amf.EncodeInt24(headBytes[headerIdx:], int32(tmp)) + amf.EncodeInt24(headBytes[headerIdx:], tmp) headerIdx += 3 // 24bits } if headerSizes[pkt.headerType] > 4 { - amf.EncodeInt24(headBytes[headerIdx:], int32(pkt.bodySize)) + amf.EncodeInt24(headBytes[headerIdx:], pkt.bodySize) headerIdx += 3 // 24bits headBytes[headerIdx] = pkt.packetType headerIdx++ @@ -393,7 +393,7 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { } if ts >= 0xffffff { - amf.EncodeInt32(headBytes[headerIdx:], int32(ts)) + amf.EncodeInt32(headBytes[headerIdx:], ts) headerIdx += 4 // 32bits } @@ -463,7 +463,7 @@ func (pkt *packet) writeTo(s *Session, queue bool) error { } if ts >= 0xffffff { extendedTimestamp := headBytes[origIdx+1+cSize:] - amf.EncodeInt32(extendedTimestamp[:4], int32(ts)) + amf.EncodeInt32(extendedTimestamp[:4], ts) } } } diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index d0a34e77..c79f8352 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -249,18 +249,18 @@ func handlePacket(s *Session, pkt *packet) error { switch pkt.packetType { case packetTypeChunkSize: - s.inChunkSize = int32(amf.DecodeInt32(pkt.body[:4])) + s.inChunkSize = amf.DecodeInt32(pkt.body[:4]) case packetTypeBytesReadReport: - s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) + s.serverBW = amf.DecodeInt32(pkt.body[:4]) case packetTypeServerBW: - s.serverBW = int32(amf.DecodeInt32(pkt.body[:4])) + s.serverBW = amf.DecodeInt32(pkt.body[:4]) case packetTypeClientBW: - s.clientBW = int32(amf.DecodeInt32(pkt.body[:4])) + s.clientBW = amf.DecodeInt32(pkt.body[:4]) if pkt.bodySize > 4 { - s.clientBW2 = pkt.body[4] + s.clientBW2 = uint32(pkt.body[4]) } else { s.clientBW2 = 0xff } diff --git a/rtmp/session.go b/rtmp/session.go index f555d00e..766522d5 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -44,14 +44,14 @@ import ( // Session holds the state for an RTMP session. type Session struct { url string - inChunkSize int32 - outChunkSize int32 - nBytesIn int32 - nBytesInSent int32 + inChunkSize uint32 + outChunkSize uint32 + nBytesIn uint32 + nBytesInSent uint32 streamID int32 - serverBW int32 - clientBW int32 - clientBW2 uint8 + serverBW uint32 + clientBW uint32 + clientBW2 uint32 isPlaying bool numInvokes int32 methodCalls []method @@ -207,7 +207,7 @@ func (s *Session) read(buf []byte) (int, error) { s.log(DebugLevel, pkg+"read failed", "error", err.Error()) return 0, err } - s.nBytesIn += int32(n) + s.nBytesIn += uint32(n) if s.nBytesIn > (s.nBytesInSent + s.clientBW/10) { err := sendBytesReceived(s) if err != nil { From 3ab2c1f69ac0a4d38d4e169663d8dc6b50ff1f26 Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 14 Jan 2019 10:31:42 +1030 Subject: [PATCH 49/52] Reverted Session.clientBW2 to uint8. --- rtmp/rtmp.go | 2 +- rtmp/session.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index c79f8352..56c9a653 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -260,7 +260,7 @@ func handlePacket(s *Session, pkt *packet) error { case packetTypeClientBW: s.clientBW = amf.DecodeInt32(pkt.body[:4]) if pkt.bodySize > 4 { - s.clientBW2 = uint32(pkt.body[4]) + s.clientBW2 = pkt.body[4] } else { s.clientBW2 = 0xff } diff --git a/rtmp/session.go b/rtmp/session.go index 766522d5..8da2626b 100644 --- a/rtmp/session.go +++ b/rtmp/session.go @@ -51,7 +51,7 @@ type Session struct { streamID int32 serverBW uint32 clientBW uint32 - clientBW2 uint32 + clientBW2 uint8 isPlaying bool numInvokes int32 methodCalls []method From 65d0952dd32b8e7e5c2c58300f3bb5f667fc5e6d Mon Sep 17 00:00:00 2001 From: scruzin Date: Tue, 15 Jan 2019 09:57:59 +1030 Subject: [PATCH 50/52] Improved Property doc comment. --- rtmp/amf/amf.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rtmp/amf/amf.go b/rtmp/amf/amf.go index 6ea5e4b1..837531bb 100644 --- a/rtmp/amf/amf.go +++ b/rtmp/amf/amf.go @@ -75,10 +75,15 @@ type Object struct { Properties []Property } -// Property represents an AMF property. +// Property represents an AMF property, which is effectively a +// union. The Type is the AMF data type (uint8 per the specification), +// and specifies which member holds a value. Numeric types use +// Number, string types use String and arrays and objects use +// Object. The Name is optional. type Property struct { - Name string Type uint8 + + Name string Number float64 String string Object Object From a1d328b37660b01e30813536ca86fe247cfd562d Mon Sep 17 00:00:00 2001 From: scruzin Date: Tue, 15 Jan 2019 10:04:36 +1030 Subject: [PATCH 51/52] Remove unnecessary underscores from for loops with ranges. --- rtmp/amf/amf_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rtmp/amf/amf_test.go b/rtmp/amf/amf_test.go index f8668ff6..59548c09 100644 --- a/rtmp/amf/amf_test.go +++ b/rtmp/amf/amf_test.go @@ -124,7 +124,7 @@ func TestProperties(t *testing.T) { // Encode/decode Number properties. enc := buf[:] var err error - for i, _ := range testNumbers { + for i := range testNumbers { enc, err = EncodeProperty(&Property{Type: typeNumber, Number: float64(testNumbers[i])}, enc) if err != nil { t.Errorf("EncodeProperty of Number failed") @@ -134,7 +134,7 @@ func TestProperties(t *testing.T) { prop := Property{} dec := buf[:] - for i, _ := range testNumbers { + for i := range testNumbers { n, err := DecodeProperty(&prop, dec, false) if err != nil { t.Errorf("DecodeProperty of Number failed") @@ -147,7 +147,7 @@ func TestProperties(t *testing.T) { // Encode/decode string properties. enc = buf[:] - for i, _ := range testStrings { + for i := range testStrings { enc, err = EncodeProperty(&Property{Type: typeString, String: testStrings[i]}, enc) if err != nil { t.Errorf("EncodeProperty of string failed") @@ -156,7 +156,7 @@ func TestProperties(t *testing.T) { } prop = Property{} dec = buf[:] - for i, _ := range testStrings { + for i := range testStrings { n, err := DecodeProperty(&prop, dec, false) if err != nil { t.Errorf("DecodeProperty of string failed") @@ -234,7 +234,7 @@ func TestObject(t *testing.T) { // Construct a more complicated object that includes a nested object. var obj2 Object - for i, _ := range testStrings { + for i := range testStrings { obj2.Properties = append(obj2.Properties, Property{Type: typeString, String: testStrings[i]}) obj2.Properties = append(obj2.Properties, Property{Type: typeNumber, Number: float64(testNumbers[i])}) } From 9e6d875089dabbd5cf64a2626f794c5055124fba Mon Sep 17 00:00:00 2001 From: scruzin Date: Tue, 15 Jan 2019 14:59:30 +1030 Subject: [PATCH 52/52] Remove ! after Dan. --- rtmp/rtmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 56c9a653..11a9d71b 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -7,7 +7,7 @@ DESCRIPTION AUTHORS Saxon Nelson-Milton - Dan Kortschak ! + Dan Kortschak Alan Noble LICENSE