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 {