// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tls import ( "bytes" "encoding/hex" "reflect" "strings" "testing" ) type testStruct struct { Data []byte `tls:"minlen:2,maxlen:4"` IntVal uint16 Other [4]byte Enum Enum `tls:"size:2"` } type testVariant struct { Which Enum `tls:"size:1"` Val16 *uint16 `tls:"selector:Which,val:0"` Val32 *uint32 `tls:"selector:Which,val:1"` } type testTwoVariants struct { Which Enum `tls:"size:1"` Val16 *uint16 `tls:"selector:Which,val:0"` Val32 *uint32 `tls:"selector:Which,val:1"` Second Enum `tls:"size:1"` Second16 *uint16 `tls:"selector:Second,val:0"` Second32 *uint32 `tls:"selector:Second,val:1"` } // Check that library users can define their own Enum types. type aliasEnum Enum type testAliasEnum struct { Val aliasEnum `tls:"size:1"` Val16 *uint16 `tls:"selector:Val,val:1"` Val32 *uint32 `tls:"selector:Val,val:2"` } type testNonByteSlice struct { Vals []uint16 `tls:"minlen:2,maxlen:6"` } type testSliceOfStructs struct { Vals []testVariant `tls:"minlen:0,maxlen:100"` } type testInnerType struct { Val []byte `tls:"minlen:0,maxlen:65535"` } type testSliceOfSlices struct { Inners []testInnerType `tls:"minlen:0,maxlen:65535"` } func TestMarshalUnmarshalRoundTrip(t *testing.T) { thing := testStruct{Data: []byte{0x01, 0x02, 0x03}, IntVal: 42, Other: [4]byte{1, 2, 3, 4}, Enum: 17} data, err := Marshal(thing) if err != nil { t.Fatalf("Failed to Marshal(%+v): %s", thing, err.Error()) } var other testStruct rest, err := Unmarshal(data, &other) if err != nil { t.Fatalf("Failed to Unmarshal(%s)", hex.EncodeToString(data)) } if len(rest) > 0 { t.Errorf("Data left over after Unmarshal(%s): %s", hex.EncodeToString(data), hex.EncodeToString(rest)) } } func TestFieldTagToFieldInfo(t *testing.T) { var tests = []struct { tag string want *fieldInfo errstr string }{ {"", nil, ""}, {"bogus", nil, ""}, {"also,bogus", nil, ""}, {"also,bogus:99", nil, ""}, {"maxval:1xyz", nil, ""}, {"maxval:1", &fieldInfo{count: 1, countSet: true}, ""}, {"maxval:255", &fieldInfo{count: 1, countSet: true}, ""}, {"maxval:256", &fieldInfo{count: 2, countSet: true}, ""}, {"maxval:65535", &fieldInfo{count: 2, countSet: true}, ""}, {"maxval:65536", &fieldInfo{count: 3, countSet: true}, ""}, {"maxval:16777215", &fieldInfo{count: 3, countSet: true}, ""}, {"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:4294967295", &fieldInfo{count: 4, countSet: true}, ""}, {"maxval:4294967296", &fieldInfo{count: 5, countSet: true}, ""}, {"maxval:1099511627775", &fieldInfo{count: 5, countSet: true}, ""}, {"maxval:1099511627776", &fieldInfo{count: 6, countSet: true}, ""}, {"maxval:281474976710655", &fieldInfo{count: 6, countSet: true}, ""}, {"maxval:281474976710656", &fieldInfo{count: 7, countSet: true}, ""}, {"maxval:72057594037927935", &fieldInfo{count: 7, countSet: true}, ""}, {"maxval:72057594037927936", &fieldInfo{count: 8, countSet: true}, ""}, {"minlen:1x", nil, ""}, {"maxlen:1x", nil, ""}, {"maxlen:1", &fieldInfo{count: 1, countSet: true, maxlen: 1}, ""}, {"maxlen:255", &fieldInfo{count: 1, countSet: true, maxlen: 255}, ""}, {"maxlen:65535", &fieldInfo{count: 2, countSet: true, maxlen: 65535}, ""}, {"minlen:65530,maxlen:65535", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""}, {"maxlen:65535,minlen:65530", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""}, {"minlen:65536,maxlen:65535", nil, "inverted"}, {"maxlen:16777215", &fieldInfo{count: 3, countSet: true, maxlen: 16777215}, ""}, {"maxlen:281474976710655", &fieldInfo{count: 6, countSet: true, maxlen: 281474976710655}, ""}, {"maxlen:72057594037927936", &fieldInfo{count: 8, countSet: true, maxlen: 72057594037927936}, ""}, {"size:0", nil, "unknown size"}, {"size:1", &fieldInfo{count: 1, countSet: true}, ""}, {"size:2", &fieldInfo{count: 2, countSet: true}, ""}, {"size:3", &fieldInfo{count: 3, countSet: true}, ""}, {"size:4", &fieldInfo{count: 4, countSet: true}, ""}, {"size:5", &fieldInfo{count: 5, countSet: true}, ""}, {"size:6", &fieldInfo{count: 6, countSet: true}, ""}, {"size:7", &fieldInfo{count: 7, countSet: true}, ""}, {"size:8", &fieldInfo{count: 8, countSet: true}, ""}, {"size:9", nil, "too large"}, {"size:1x", nil, ""}, {"size:1,val:9", nil, "selector value"}, {"selector:Bob,val:x9", &fieldInfo{selector: "Bob"}, ""}, {"selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""}, {"val:9,selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""}, } for _, test := range tests { got, err := fieldTagToFieldInfo(test.tag, "") if test.errstr != "" { if err == nil { t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want error %q", test.tag, got, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want error %q", test.tag, err.Error(), test.errstr) } continue } if err != nil { t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want %+v", test.tag, err.Error(), test.want) } else if !reflect.DeepEqual(got, test.want) { t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want %+v", test.tag, got, test.want) } } } // Can't take the address of a numeric constant so use helper functions func newByte(n byte) *byte { return &n } func newUint8(n uint8) *uint8 { return &n } func newUint16(n uint16) *uint16 { return &n } func newUint24(n Uint24) *Uint24 { return &n } func newUint32(n uint32) *uint32 { return &n } func newUint64(n uint64) *uint64 { return &n } func newInt16(n int16) *int16 { return &n } func newEnum(n Enum) *Enum { return &n } func TestUnmarshalMarshalWithParamsRoundTrip(t *testing.T) { var tests = []struct { data string // hex encoded params string item interface{} }{ {"00", "", newUint8(0)}, {"03", "", newByte(3)}, {"0101", "", newUint16(0x0101)}, {"010203", "", newUint24(0x010203)}, {"000000", "", newUint24(0x00)}, {"00000009", "", newUint32(0x09)}, {"0000000901020304", "", newUint64(0x0901020304)}, {"030405", "", &[3]byte{3, 4, 5}}, {"03", "", &[1]byte{3}}, {"0001", "size:2", newEnum(1)}, {"0100000001", "size:5", newEnum(0x100000001)}, {"12", "maxval:18", newEnum(18)}, // Note that maxval is just used to give enum size; it's not policed {"20", "maxval:18", newEnum(32)}, {"020a0b", "minlen:1,maxlen:5", &[]byte{0xa, 0xb}}, {"020a0b0101010203040011", "", &testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}}, {"000102", "", &testVariant{Which: 0, Val16: newUint16(0x0102)}}, {"0101020304", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}}, {"0001020104030201", "", &testTwoVariants{Which: 0, Val16: newUint16(0x0102), Second: 1, Second32: newUint32(0x04030201)}}, {"06010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}}, {"00", "", &testSliceOfStructs{Vals: []testVariant{}}}, {"080001020101020304", "", &testSliceOfStructs{ Vals: []testVariant{ {Which: 0, Val16: newUint16(0x0102)}, {Which: 1, Val32: newUint32(0x01020304)}, }, }, }, {"000a00030102030003040506", "", &testSliceOfSlices{ Inners: []testInnerType{ {Val: []byte{1, 2, 3}}, {Val: []byte{4, 5, 6}}, }, }, }, {"011011", "", &testAliasEnum{Val: 1, Val16: newUint16(0x1011)}}, {"0403", "", &SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}}, {"04030003010203", "", &DigitallySigned{ Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}, Signature: []byte{1, 2, 3}, }, }, } for _, test := range tests { inVal := reflect.ValueOf(test.item).Elem() pv := reflect.New(reflect.TypeOf(test.item).Elem()) val := pv.Interface() inData, _ := hex.DecodeString(test.data) if _, err := UnmarshalWithParams(inData, val, test.params); err != nil { t.Errorf("Unmarshal(%s)=nil,%q; want %+v", test.data, err.Error(), inVal) } else if !reflect.DeepEqual(val, test.item) { t.Errorf("Unmarshal(%s)=%+v,nil; want %+v", test.data, reflect.ValueOf(val).Elem(), inVal) } if data, err := MarshalWithParams(inVal.Interface(), test.params); err != nil { t.Errorf("Marshal(%+v)=nil,%q; want %s", inVal, err.Error(), test.data) } else if !bytes.Equal(data, inData) { t.Errorf("Marshal(%+v)=%s,nil; want %s", inVal, hex.EncodeToString(data), test.data) } } } type testInvalidFieldTag struct { Data []byte `tls:"minlen:3,maxlen:2"` } type testDuplicateSelectorVal struct { Which Enum `tls:"size:1"` Val *uint16 `tls:"selector:Which,val:0"` DupVal *uint32 `tls:"selector:Which"` // implicit val:0 } type testMissingSelector struct { Val *uint16 `tls:"selector:Missing,val:0"` } type testChoiceNotPointer struct { Which Enum `tls:"size:1"` Val uint16 `tls:"selector:Which,val:0"` } type nonEnumAlias uint16 func newNonEnumAlias(n nonEnumAlias) *nonEnumAlias { return &n } func TestUnmarshalWithParamsFailures(t *testing.T) { var tests = []struct { data string // hex encoded params string item interface{} errstr string }{ {"", "", newUint8(0), "truncated"}, {"0x01", "", newUint16(0x0101), "truncated"}, {"0103", "", newUint24(0x010203), "truncated"}, {"00", "", newUint24(0x00), "truncated"}, {"000009", "", newUint32(0x09), "truncated"}, {"00000901020304", "", newUint64(0x0901020304), "truncated"}, {"0102", "", newInt16(0x0102), "unsupported type"}, // TLS encoding only supports unsigned integers {"0607", "", &[3]byte{6, 7, 8}, "truncated array"}, {"01010202", "", &[3]uint16{0x101, 0x202}, "unsupported array"}, {"01", "", newEnum(1), "no field size"}, {"00", "size:2", newEnum(0), "truncated"}, {"00", "size:9", newEnum(0), "too large"}, {"020a0b", "minlen:4,maxlen:8", &[]byte{0x0a, 0x0b}, "too small"}, {"040a0b0c0d", "minlen:1,maxlen:3", &[]byte{0x0a, 0x0b, 0x0c, 0x0d}, "too large"}, {"020a0b", "minlen:8,maxlen:6", &[]byte{0x0a, 0x0b}, "inverted"}, {"020a", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"}, {"02", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"}, {"0001", "minlen:0,maxlen:256", &[]byte{0x0a, 0x0b}, "truncated"}, {"020a", "minlen:0", &[]byte{0x0a, 0x0b}, "unknown size"}, {"020a", "", &[]byte{0x0a, 0x0b}, "no field size information"}, {"020a0b", "", &testInvalidFieldTag{}, "range inverted"}, {"020a0b01010102030400", "", &testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "truncated"}, {"010102", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}, "truncated"}, {"092122", "", &testVariant{Which: 0, Val16: newUint16(0x2122)}, "unhandled value for selector"}, {"0001020304", "", &testDuplicateSelectorVal{Which: 0, Val: newUint16(0x0102)}, "duplicate selector value"}, {"0102", "", &testMissingSelector{Val: newUint16(1)}, "selector not seen"}, {"000007", "", &testChoiceNotPointer{Which: 0, Val: 7}, "choice field not a pointer type"}, {"05010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}, "truncated"}, {"0101", "size:2", newNonEnumAlias(0x0102), "unsupported type"}, {"0403010203", "", &DigitallySigned{ Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}, Signature: []byte{1, 2, 3}}, "truncated"}, } for _, test := range tests { pv := reflect.New(reflect.TypeOf(test.item).Elem()) val := pv.Interface() in, _ := hex.DecodeString(test.data) if _, err := UnmarshalWithParams(in, val, test.params); err == nil { t.Errorf("Unmarshal(%s)=%+v,nil; want error %q", test.data, reflect.ValueOf(val).Elem(), test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("Unmarshal(%s)=nil,%q; want error %q", test.data, err.Error(), test.errstr) } } } func TestMarshalWithParamsFailures(t *testing.T) { var tests = []struct { item interface{} params string errstr string }{ {Uint24(0x1000000), "", "overflow"}, {int16(0x0102), "", "unsupported type"}, // All TLS ints are unsigned {Enum(1), "", "field tag missing"}, {Enum(256), "size:1", "too large"}, {Enum(256), "maxval:255", "too large"}, {Enum(2), "", "field tag missing"}, {Enum(256), "size:9", "too large"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:1,maxlen:3", "too large"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:13", "too small"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:3", "inverted"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6", "unknown size"}, {[]byte{0xa, 0xb, 0xc, 0xd}, "", "field tag missing"}, {[3]uint16{0x101, 0x202}, "", "unsupported array"}, {testInvalidFieldTag{}, "", "inverted"}, {testStruct{Data: []byte{0xa}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "", "too small"}, {testVariant{Which: 0, Val32: newUint32(0x01020304)}, "", "chosen field is nil"}, {testVariant{Which: 0, Val16: newUint16(11), Val32: newUint32(0x01020304)}, "", "unchosen field is non-nil"}, {testVariant{Which: 3}, "", "unhandled value for selector"}, {testMissingSelector{Val: newUint16(1)}, "", "selector not seen"}, {testChoiceNotPointer{Which: 0, Val: 7}, "", "choice field not a pointer"}, {testDuplicateSelectorVal{Which: 0, Val: newUint16(1)}, "", "duplicate selector value"}, {testNonByteSlice{Vals: []uint16{1, 2, 3, 4}}, "", "too large"}, {testSliceOfStructs{[]testVariant{{Which: 3}}}, "", "unhandled value for selector"}, {nonEnumAlias(0x0102), "", "unsupported type"}, } for _, test := range tests { if data, err := MarshalWithParams(test.item, test.params); err == nil { t.Errorf("Marshal(%+v)=%x,nil; want error %q", test.item, data, test.errstr) } else if !strings.Contains(err.Error(), test.errstr) { t.Errorf("Marshal(%+v)=nil,%q; want error %q", test.item, err.Error(), test.errstr) } } }