2020-06-07 13:43:15 +08:00
|
|
|
package bencode
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestDecode(t *testing.T) {
|
|
|
|
type testCase struct {
|
|
|
|
in string
|
|
|
|
val interface{}
|
|
|
|
expect interface{}
|
|
|
|
err bool
|
|
|
|
unorderedFail bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type dT struct {
|
|
|
|
X string
|
|
|
|
Y int
|
|
|
|
Z string `bencode:"zff"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Embedded struct {
|
|
|
|
B string
|
|
|
|
}
|
|
|
|
|
|
|
|
type issue22 struct {
|
|
|
|
X string `bencode:"x"`
|
|
|
|
Time myTimeType `bencode:"t"`
|
|
|
|
Foo myBoolType `bencode:"f"`
|
|
|
|
Bar myStringType `bencode:"b"`
|
|
|
|
Slice mySliceType `bencode:"s"`
|
|
|
|
Y string `bencode:"y"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type issue26 struct {
|
|
|
|
X string `bencode:"x"`
|
|
|
|
Foo myBoolTextType `bencode:"f"`
|
|
|
|
Bar myTextStringType `bencode:"b"`
|
|
|
|
Slice myTextSliceType `bencode:"s"`
|
|
|
|
Y string `bencode:"y"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type issue22WithErrorChild struct {
|
|
|
|
Name string `bencode:"n"`
|
|
|
|
Error errorMarshalType `bencode:"e"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type issue26WithErrorChild struct {
|
|
|
|
Name string `bencode:"n"`
|
|
|
|
Error errorTextMarshalType `bencode:"e"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type discardNonFieldDef struct {
|
|
|
|
B string
|
|
|
|
D string
|
|
|
|
}
|
|
|
|
|
|
|
|
type twoDefsForSameKey struct {
|
|
|
|
A string
|
|
|
|
A2 string `bencode:"A"`
|
|
|
|
A3 string `bencode:"A"`
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
var decodeCases = []testCase{
|
|
|
|
// integers
|
|
|
|
{`i5e`, new(int), int(5), false, false},
|
|
|
|
{`i-10e`, new(int), int(-10), false, false},
|
|
|
|
{`i8e`, new(uint), uint(8), false, false},
|
|
|
|
{`i8e`, new(uint8), uint8(8), false, false},
|
|
|
|
{`i8e`, new(uint16), uint16(8), false, false},
|
|
|
|
{`i8e`, new(uint32), uint32(8), false, false},
|
|
|
|
{`i8e`, new(uint64), uint64(8), false, false},
|
|
|
|
{`i8e`, new(int), int(8), false, false},
|
|
|
|
{`i8e`, new(int8), int8(8), false, false},
|
|
|
|
{`i8e`, new(int16), int16(8), false, false},
|
|
|
|
{`i8e`, new(int32), int32(8), false, false},
|
|
|
|
{`i8e`, new(int64), int64(8), false, false},
|
|
|
|
{`i0e`, new(*int), new(int), false, false},
|
|
|
|
{`i-2e`, new(uint), nil, true, false},
|
|
|
|
|
|
|
|
// bools
|
|
|
|
{`i1e`, new(bool), true, false, false},
|
|
|
|
{`i0e`, new(bool), false, false, false},
|
|
|
|
{`i0e`, new(*bool), new(bool), false, false},
|
|
|
|
{`i8e`, new(bool), true, false, false},
|
|
|
|
|
|
|
|
// strings
|
|
|
|
{`3:foo`, new(string), "foo", false, false},
|
|
|
|
{`4:foob`, new(string), "foob", false, false},
|
|
|
|
{`0:`, new(*string), new(string), false, false},
|
|
|
|
{`6:short`, new(string), nil, true, false},
|
|
|
|
|
|
|
|
// lists
|
|
|
|
{`l3:foo3:bare`, new([]string), []string{"foo", "bar"}, false, false},
|
|
|
|
{`li15ei20ee`, new([]int), []int{15, 20}, false, false},
|
|
|
|
{`ld3:fooi0eed3:bari1eee`, new([]map[string]int), []map[string]int{
|
|
|
|
{"foo": 0},
|
|
|
|
{"bar": 1},
|
|
|
|
}, false, false},
|
|
|
|
|
|
|
|
// dicts
|
|
|
|
|
|
|
|
{`d3:foo3:bar4:foob3:fooe`, new(map[string]string), map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"foob": "foo",
|
|
|
|
}, false, false},
|
|
|
|
{`d1:X3:foo1:Yi10e3:zff3:bare`, new(dT), dT{"foo", 10, "bar"}, false, false},
|
|
|
|
|
|
|
|
// encoding/json takes, if set, the tag as name and doesn't falls back to the
|
|
|
|
// struct field's name.
|
|
|
|
{`d1:X3:foo1:Yi10e1:Z3:bare`, new(dT), dT{"foo", 10, ""}, false, false},
|
|
|
|
|
|
|
|
{`d1:X3:foo1:Yi10e1:h3:bare`, new(dT), dT{"foo", 10, ""}, false, false},
|
|
|
|
{`d3:fooli0ei1ee3:barli2ei3eee`, new(map[string][]int), map[string][]int{
|
2022-07-31 10:16:58 +08:00
|
|
|
"foo": {0, 1},
|
|
|
|
"bar": {2, 3},
|
2020-06-07 13:43:15 +08:00
|
|
|
}, false, false},
|
|
|
|
{`de`, new(map[string]string), map[string]string{}, false, false},
|
|
|
|
|
|
|
|
// into interfaces
|
|
|
|
{`i5e`, new(interface{}), int64(5), false, false},
|
|
|
|
{`li5ee`, new(interface{}), []interface{}{int64(5)}, false, false},
|
|
|
|
{`5:hello`, new(interface{}), "hello", false, false},
|
|
|
|
{`d5:helloi5ee`, new(interface{}), map[string]interface{}{"hello": int64(5)}, false, false},
|
|
|
|
|
|
|
|
// into values whose type support the Unmarshaler interface
|
|
|
|
{`1:y`, new(myTimeType), nil, true, false},
|
|
|
|
{fmt.Sprintf("i%de", now.Unix()), new(myTimeType), myTimeType{time.Unix(now.Unix(), 0)}, false, false},
|
|
|
|
{`1:y`, new(myBoolType), myBoolType(true), false, false},
|
|
|
|
{`i42e`, new(myBoolType), nil, true, false},
|
|
|
|
{`1:n`, new(myBoolType), myBoolType(false), false, false},
|
|
|
|
{`1:n`, new(errorMarshalType), nil, true, false},
|
|
|
|
{`li102ei111ei111ee`, new(myStringType), myStringType("foo"), false, false},
|
|
|
|
{`i42e`, new(myStringType), nil, true, false},
|
|
|
|
{`d1:ai1e3:foo3:bare`, new(mySliceType), mySliceType{"a", int64(1), "foo", "bar"}, false, false},
|
|
|
|
{`i42e`, new(mySliceType), nil, true, false},
|
|
|
|
|
|
|
|
// into values who have a child which type supports the Unmarshaler interface
|
|
|
|
{
|
|
|
|
fmt.Sprintf(`d1:b3:foo1:f1:y1:sd1:f3:foo1:ai42ee1:ti%de1:x1:x1:y1:ye`, now.Unix()),
|
|
|
|
new(issue22),
|
|
|
|
issue22{
|
|
|
|
X: "x",
|
|
|
|
Time: myTimeType{time.Unix(now.Unix(), 0)},
|
|
|
|
Foo: myBoolType(true),
|
|
|
|
Bar: myStringType("foo"),
|
|
|
|
Slice: mySliceType{"a", int64(42), "f", "foo"},
|
|
|
|
Y: "y",
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`d1:ei42e1:n3:fooe`,
|
|
|
|
new(issue22WithErrorChild),
|
|
|
|
nil,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
// into values whose type support the TextUnmarshaler interface
|
|
|
|
{`1:y`, new(myBoolTextType), myBoolTextType(true), false, false},
|
|
|
|
{`1:n`, new(myBoolTextType), myBoolTextType(false), false, false},
|
|
|
|
{`i42e`, new(myBoolTextType), nil, true, false},
|
|
|
|
{`1:n`, new(errorTextMarshalType), nil, true, false},
|
|
|
|
{`7:foo_bar`, new(myTextStringType), myTextStringType("bar"), false, false},
|
|
|
|
{`i42e`, new(myTextStringType), nil, true, false},
|
|
|
|
{`7:a,b,c,d`, new(myTextSliceType), myTextSliceType{"a", "b", "c", "d"}, false, false},
|
|
|
|
{`i42e`, new(myTextSliceType), nil, true, false},
|
|
|
|
|
|
|
|
// into values who have a child which type supports the TextUnmarshaler interface
|
|
|
|
{
|
|
|
|
`d1:b7:foo_bar1:f1:y1:s5:1,2,31:x1:x1:y1:ye`,
|
|
|
|
new(issue26),
|
|
|
|
issue26{
|
|
|
|
X: "x",
|
|
|
|
Foo: myBoolTextType(true),
|
|
|
|
Bar: myTextStringType("bar"),
|
|
|
|
Slice: myTextSliceType{"1", "2", "3"},
|
|
|
|
Y: "y",
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
`d1:ei42e1:n3:fooe`,
|
|
|
|
new(issue26WithErrorChild),
|
|
|
|
nil,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
// malformed
|
|
|
|
{`i53:foo`, new(interface{}), nil, true, false},
|
|
|
|
{`6:foo`, new(interface{}), nil, true, false},
|
|
|
|
{`di5ei2ee`, new(interface{}), nil, true, false},
|
|
|
|
{`d3:fooe`, new(interface{}), nil, true, false},
|
|
|
|
{`l3:foo3:bar`, new(interface{}), nil, true, false},
|
|
|
|
{`d-1:`, new(interface{}), nil, true, false},
|
|
|
|
|
|
|
|
// embedded structs
|
|
|
|
{`d1:A3:foo1:B3:bare`, new(struct {
|
|
|
|
A string
|
|
|
|
Embedded
|
|
|
|
}), struct {
|
|
|
|
A string
|
|
|
|
Embedded
|
|
|
|
}{"foo", Embedded{"bar"}}, false, false},
|
|
|
|
|
|
|
|
// Embedded structs with a valid tag are encoded as a definition
|
|
|
|
{`d1:B3:bar6:nestedd1:B3:fooee`, new(struct {
|
|
|
|
Embedded `bencode:"nested"`
|
|
|
|
}), struct {
|
|
|
|
Embedded `bencode:"nested"`
|
|
|
|
}{Embedded{"foo"}}, false, false},
|
|
|
|
|
|
|
|
// Don't fail when reading keys missing from the struct
|
|
|
|
{"d1:A7:discard1:B4:take1:C7:discard1:D4:takee",
|
|
|
|
new(discardNonFieldDef),
|
|
|
|
discardNonFieldDef{"take", "take"},
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Don't fail when reading the same key twice
|
|
|
|
{"d1:A1:a1:A1:b1:A1:c1:A1:de", new(twoDefsForSameKey),
|
|
|
|
twoDefsForSameKey{"", "", "d"}, false, false},
|
|
|
|
|
|
|
|
// Empty struct
|
|
|
|
{"de", new(struct{}), struct{}{}, false, false},
|
|
|
|
|
|
|
|
// Fail on unordered dictionaries
|
|
|
|
{"d1:Yi10e1:X1:a3:zff1:ce", new(dT), dT{}, true, true},
|
|
|
|
{"d3:zff1:c1:Yi10e1:X1:ae", new(dT), dT{}, true, true},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range decodeCases {
|
|
|
|
dec := NewDecoder(strings.NewReader(tt.in))
|
|
|
|
dec.SetFailOnUnorderedKeys(tt.unorderedFail)
|
|
|
|
err := dec.Decode(tt.val)
|
|
|
|
if !tt.err && err != nil {
|
|
|
|
t.Errorf("#%d (%v): Unexpected err: %v", i, tt.in, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tt.err && err == nil {
|
|
|
|
t.Errorf("#%d (%v): Expected err is nil", i, tt.in)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
v := reflect.ValueOf(tt.val).Elem().Interface()
|
|
|
|
if !reflect.DeepEqual(v, tt.expect) && !tt.err {
|
|
|
|
t.Errorf("#%d (%v): Val: %#v != %#v", i, tt.in, v, tt.expect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRawDecode(t *testing.T) {
|
|
|
|
type testCase struct {
|
|
|
|
in string
|
|
|
|
expect []byte
|
|
|
|
err bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var rawDecodeCases = []testCase{
|
|
|
|
{`i5e`, []byte(`i5e`), false},
|
|
|
|
{`5:hello`, []byte(`5:hello`), false},
|
|
|
|
{`li5ei10e5:helloe`, []byte(`li5ei10e5:helloe`), false},
|
|
|
|
{`llleee`, []byte(`llleee`), false},
|
|
|
|
{`li5eli5eli5eeee`, []byte(`li5eli5eli5eeee`), false},
|
|
|
|
{`d5:helloi5ee`, []byte(`d5:helloi5ee`), false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range rawDecodeCases {
|
|
|
|
var x RawMessage
|
|
|
|
err := DecodeString(tt.in, &x)
|
|
|
|
if !tt.err && err != nil {
|
|
|
|
t.Errorf("#%d: Unexpected err: %v", i, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tt.err && err == nil {
|
|
|
|
t.Errorf("#%d: Expected err is nil", i)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(x, RawMessage(tt.expect)) && !tt.err {
|
|
|
|
t.Errorf("#%d: Val: %#v != %#v", i, x, tt.expect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type myStringType string
|
|
|
|
|
|
|
|
// UnmarshalBencode implements Unmarshaler.UnmarshalBencode
|
|
|
|
func (mst *myStringType) UnmarshalBencode(b []byte) error {
|
|
|
|
var raw []byte
|
|
|
|
err := DecodeBytes(b, &raw)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*mst = myStringType(raw)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mySliceType []interface{}
|
|
|
|
|
|
|
|
// UnmarshalBencode implements Unmarshaler.UnmarshalBencode
|
|
|
|
func (mst *mySliceType) UnmarshalBencode(b []byte) error {
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
err := DecodeBytes(b, &m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
keys := make([]string, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
raw := make([]interface{}, 0, len(m)*2)
|
|
|
|
for _, key := range keys {
|
|
|
|
raw = append(raw, key, m[key])
|
|
|
|
}
|
|
|
|
|
|
|
|
*mst = mySliceType(raw)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type myTextStringType string
|
|
|
|
|
|
|
|
// UnmarshalText implements TextUnmarshaler.UnmarshalText
|
|
|
|
func (mst *myTextStringType) UnmarshalText(b []byte) error {
|
|
|
|
*mst = myTextStringType(bytes.TrimPrefix(b, []byte("foo_")))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type myTextSliceType []string
|
|
|
|
|
|
|
|
// UnmarshalText implements TextUnmarshaler.UnmarshalText
|
|
|
|
func (mst *myTextSliceType) UnmarshalText(b []byte) error {
|
|
|
|
raw := string(b)
|
|
|
|
*mst = strings.Split(raw, ",")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNestedRawDecode(t *testing.T) {
|
|
|
|
type testCase struct {
|
|
|
|
in string
|
|
|
|
val interface{}
|
|
|
|
expect interface{}
|
|
|
|
err bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type message struct {
|
|
|
|
Key string
|
|
|
|
Val int
|
|
|
|
Raw RawMessage
|
|
|
|
}
|
|
|
|
|
|
|
|
var cases = []testCase{
|
|
|
|
{`li5e5:hellod1:a1:beli5eee`, new([]RawMessage), []RawMessage{
|
|
|
|
RawMessage(`i5e`),
|
|
|
|
RawMessage(`5:hello`),
|
|
|
|
RawMessage(`d1:a1:be`),
|
|
|
|
RawMessage(`li5ee`),
|
|
|
|
}, false},
|
|
|
|
{`d1:a1:b1:c1:de`, new(map[string]RawMessage), map[string]RawMessage{
|
|
|
|
"a": RawMessage(`1:b`),
|
|
|
|
"c": RawMessage(`1:d`),
|
|
|
|
}, false},
|
|
|
|
{`d3:Key5:hello3:Rawldedei5e1:ae3:Vali10ee`, new(message), message{
|
|
|
|
Key: "hello",
|
|
|
|
Val: 10,
|
|
|
|
Raw: RawMessage(`ldedei5e1:ae`),
|
|
|
|
}, false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range cases {
|
|
|
|
err := DecodeString(tt.in, tt.val)
|
|
|
|
if !tt.err && err != nil {
|
|
|
|
t.Errorf("#%d: Unexpected err: %v", i, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tt.err && err == nil {
|
|
|
|
t.Errorf("#%d: Expected err is nil", i)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
v := reflect.ValueOf(tt.val).Elem().Interface()
|
|
|
|
if !reflect.DeepEqual(v, tt.expect) && !tt.err {
|
|
|
|
t.Errorf("#%d: Val:\n%#v !=\n%#v", i, v, tt.expect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|