--- /dev/null
+BitTorrent Go library, work in progress..
\ No newline at end of file
--- /dev/null
+Bencode encoding/decoding sub package. Uses similar API design to Go's json package.
--- /dev/null
+package bencode
+
+import "bytes"
+import "bufio"
+import "reflect"
+import "strconv"
+
+// In case if marshaler cannot encode a type in bencode, it will return this
+// error. Typical example of such type is float32/float64 which has no bencode
+// representation
+type MarshalTypeError struct {
+ Type reflect.Type
+}
+
+func (this *MarshalTypeError) Error() string {
+ return "bencode: unsupported type: " + this.Type.String()
+}
+
+// Unmarshal argument must be a non-nil value of some pointer type.
+type UnmarshalInvalidArgError struct {
+ Type reflect.Type
+}
+
+func (e *UnmarshalInvalidArgError) Error() string {
+ if e.Type == nil {
+ return "bencode: Unmarshal(nil)"
+ }
+
+ if e.Type.Kind() != reflect.Ptr {
+ return "bencode: Unmarshal(non-pointer " + e.Type.String() + ")"
+ }
+ return "bencode: Unmarshal(nil " + e.Type.String() + ")"
+}
+
+// Unmarshaler spotted a value that was not appropriate for a given specific Go
+// value
+type UnmarshalTypeError struct {
+ Value string
+ Type reflect.Type
+}
+
+func (e *UnmarshalTypeError) Error() string {
+ return "bencode: value (" + e.Value + ") is not appropriate for type: " +
+ e.Type.String()
+}
+
+// Unmarshaler tried to write to an unexported (therefore unwritable) field.
+type UnmarshalFieldError struct {
+ Key string
+ Type reflect.Type
+ Field reflect.StructField
+}
+
+func (e *UnmarshalFieldError) Error() string {
+ return "bencode: key \"" + e.Key + "\" led to an unexported field \"" +
+ e.Field.Name + "\" in type: " + e.Type.String()
+}
+
+type SyntaxError struct {
+ Offset int64 // location of the error
+ what string // error description
+}
+
+func (e *SyntaxError) Error() string {
+ return "bencode: syntax error (offset: " +
+ strconv.FormatInt(e.Offset, 10) +
+ "): " + e.what
+}
+
+func Marshal(v interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ e := encoder{Writer: bufio.NewWriter(&buf)}
+ err := e.encode(v)
+ if err != nil {
+ return nil, err
+ }
+ err = e.Flush()
+ return buf.Bytes(), err
+}
+
+type Marshaler interface {
+ MarshalBencode() ([]byte, error)
+}
+
+func Unmarshal(data []byte, v interface{}) error {
+ e := decoder{Reader: bufio.NewReader(bytes.NewBuffer(data))}
+ return e.decode(v)
+}
+
+/*
+func Unmarshal(data []byte, v interface{}) error
+
+type Decoder int
+func NewDecoder(r io.Reader) *Decoder
+func (dec *Decoder) Decode(v interface{}) error
+
+type Encoder int
+func NewEncoder(w io.Writer) *Encoder
+func (enc *Encoder) Encode(v interface{}) error
+*/
--- /dev/null
+package bencode
+
+import "testing"
+import "bytes"
+import "io/ioutil"
+import "time"
+
+func load_file(name string, t *testing.T) []byte {
+ data, err := ioutil.ReadFile(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return data
+}
+
+func TestBothInterface(t *testing.T) {
+ data1 := load_file("_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent", t)
+ var iface interface{}
+
+ err := Unmarshal(data1, &iface)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ data2, err := Marshal(iface)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(data1, data2) {
+ t.Fatalf("equality expected\n")
+ }
+}
+
+type torrent_file struct {
+ Info struct {
+ Name string `bencode:"name"`
+ Length int64 `bencode:"length"`
+ MD5Sum string `bencode:"md5sum,omitempty"`
+ PieceLength int64 `bencode:"piece length"`
+ Pieces string `bencode:"pieces"`
+ Private bool `bencode:"private,omitempty"`
+ } `bencode:"info"`
+
+ Announce string `bencode:"announce"`
+ AnnounceList [][]string `bencode:"announce-list,omitempty"`
+ CreationDate int64 `bencode:"creation date,omitempty"`
+ Comment string `bencode:"comment,omitempty"`
+ CreatedBy string `bencode:"created by,omitempty"`
+ URLList interface{} `bencode:"url-list,omitempty"`
+}
+
+func TestBoth(t *testing.T) {
+ data1 := load_file("_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent", t)
+ var f torrent_file
+
+ err := Unmarshal(data1, &f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("Name: %s\n", f.Info.Name)
+ t.Logf("Length: %v bytes\n", f.Info.Length)
+ t.Logf("Announce: %s\n", f.Announce)
+ t.Logf("CreationDate: %s\n", time.Unix(f.CreationDate, 0).String())
+ t.Logf("CreatedBy: %s\n", f.CreatedBy)
+ t.Logf("Comment: %s\n", f.Comment)
+
+ data2, err := Marshal(&f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(data1, data2) {
+ t.Fatalf("equality expected")
+ }
+}
--- /dev/null
+package bencode
+
+import "reflect"
+import "runtime"
+import "bufio"
+import "bytes"
+import "strconv"
+import "strings"
+import "io"
+
+type decoder struct {
+ *bufio.Reader
+ offset int64
+ buf bytes.Buffer
+ key string
+}
+
+func (d *decoder) decode(v interface{}) (err error) {
+ var _ runtime.Error
+ /*
+ defer func() {
+ if e := recover(); e != nil {
+ if _, ok := e.(runtime.Error); ok {
+ panic(e)
+ }
+ err = e.(error)
+ }
+ }()
+ */
+
+ pv := reflect.ValueOf(v)
+ if pv.Kind() != reflect.Ptr || pv.IsNil() {
+ return &UnmarshalInvalidArgError{reflect.TypeOf(v)}
+ }
+
+ d.parse_value(pv.Elem())
+ return nil
+}
+
+func check_for_unexpected_eof(err error, offset int64) {
+ if err == io.EOF {
+ panic(&SyntaxError{
+ Offset: offset,
+ what: "unexpected EOF",
+ })
+ }
+}
+
+func (d *decoder) read_byte() byte {
+ b, err := d.ReadByte()
+ if err != nil {
+ check_for_unexpected_eof(err, d.offset)
+ panic(err)
+ }
+
+ d.offset++
+ return b
+}
+
+// reads data writing it to 'd.buf' until 'sep' byte is encountered, 'sep' byte
+// is consumed, but not included into the 'd.buf'
+func (d *decoder) read_until(sep byte) {
+ for {
+ b := d.read_byte()
+ if b == sep {
+ return
+ }
+ d.buf.WriteByte(b)
+ }
+}
+
+func check_for_int_parse_error(err error, offset int64) {
+ if err != nil {
+ panic(&SyntaxError{
+ Offset: offset,
+ what: err.Error(),
+ })
+ }
+}
+
+// called when 'i' was consumed
+func (d *decoder) parse_int(v reflect.Value) {
+ start := d.offset - 1
+ d.read_until('e')
+ if d.buf.Len() == 0 {
+ panic(&SyntaxError{
+ Offset: start,
+ what: "empty integer value",
+ })
+ }
+
+ switch v.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ n, err := strconv.ParseInt(d.buf.String(), 10, 64)
+ check_for_int_parse_error(err, start)
+
+ if v.OverflowInt(n) {
+ panic(&UnmarshalTypeError{
+ Value: "integer " + d.buf.String(),
+ Type: v.Type(),
+ })
+ }
+ v.SetInt(n)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ n, err := strconv.ParseUint(d.buf.String(), 10, 64)
+ check_for_int_parse_error(err, start)
+
+ if v.OverflowUint(n) {
+ panic(&UnmarshalTypeError{
+ Value: "integer " + d.buf.String(),
+ Type: v.Type(),
+ })
+ }
+ v.SetUint(n)
+ case reflect.Bool:
+ if d.buf.Len() == 1 && d.buf.Bytes()[0] == '0' {
+ v.SetBool(false)
+ }
+ v.SetBool(true)
+ default:
+ panic(&UnmarshalTypeError{
+ Value: "integer " + d.buf.String(),
+ Type: v.Type(),
+ })
+ }
+ d.buf.Reset()
+}
+
+func (d *decoder) parse_string(v reflect.Value) {
+ start := d.offset - 1
+
+ // read the string length first
+ d.read_until(':')
+ length, err := strconv.ParseInt(d.buf.String(), 10, 64)
+ check_for_int_parse_error(err, start)
+
+ d.buf.Reset()
+ n, err := io.CopyN(&d.buf, d, length)
+ d.offset += n
+ if err != nil {
+ check_for_unexpected_eof(err, d.offset)
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "unexpected I/O error: " + err.Error(),
+ })
+ }
+
+ switch v.Kind() {
+ case reflect.String:
+ v.SetString(d.buf.String())
+ case reflect.Slice:
+ if v.Type().Elem().Kind() != reflect.Uint8 {
+ panic(&UnmarshalTypeError{
+ Value: "string",
+ Type: v.Type(),
+ })
+ }
+ v.Set(reflect.ValueOf(d.buf.Bytes()))
+ default:
+ panic(&UnmarshalTypeError{
+ Value: "string",
+ Type: v.Type(),
+ })
+ }
+
+ d.buf.Reset()
+}
+
+func (d *decoder) parse_dict(v reflect.Value) {
+ switch v.Kind() {
+ case reflect.Map:
+ t := v.Type()
+ if t.Key().Kind() != reflect.String {
+ panic(&UnmarshalTypeError{
+ Value: "object",
+ Type: t,
+ })
+ }
+ if v.IsNil() {
+ v.Set(reflect.MakeMap(t))
+ }
+ case reflect.Struct:
+ default:
+ panic(&UnmarshalTypeError{
+ Value: "object",
+ Type: v.Type(),
+ })
+ }
+
+ var map_elem reflect.Value
+
+ // so, at this point 'd' byte was consumed, let's just read key/value
+ // pairs one by one
+ for {
+ var valuev reflect.Value
+ keyv := reflect.ValueOf(&d.key).Elem()
+ if !d.parse_value(keyv) {
+ return
+ }
+
+ // get valuev as a map value or as a struct field
+ switch v.Kind() {
+ case reflect.Map:
+ elem_type := v.Type().Elem()
+ if !map_elem.IsValid() {
+ map_elem = reflect.New(elem_type).Elem()
+ } else {
+ map_elem.Set(reflect.Zero(elem_type))
+ }
+ valuev = map_elem
+ case reflect.Struct:
+ var f reflect.StructField
+ var ok bool
+
+ t := v.Type()
+ for i, n := 0, t.NumField(); i < n; i++ {
+ f = t.Field(i)
+ tag := f.Tag.Get("bencode")
+ if tag == "-" {
+ continue
+ }
+ if f.Anonymous {
+ continue
+ }
+
+ tag_name, _ := parse_tag(tag)
+ if tag_name == d.key {
+ ok = true
+ break
+ }
+
+ if f.Name == d.key {
+ ok = true
+ break
+ }
+
+ if strings.EqualFold(f.Name, d.key) {
+ ok = true
+ break
+ }
+ }
+
+ if ok {
+ if f.PkgPath != "" {
+ panic(&UnmarshalFieldError{
+ Key: d.key,
+ Type: v.Type(),
+ Field: f,
+ })
+ } else {
+ valuev = v.FieldByIndex(f.Index)
+ }
+ } else {
+ _, ok := d.parse_value_interface()
+ if !ok {
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "unexpected end of dict, no matching value for a given key",
+ })
+ }
+ continue
+ }
+ }
+
+ // now we need to actually parse it
+ if !d.parse_value(valuev) {
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "unexpected end of dict, no matching value for a given key",
+ })
+ }
+
+ if v.Kind() == reflect.Map {
+ v.SetMapIndex(keyv, valuev)
+ }
+ }
+}
+
+func (d *decoder) parse_list(v reflect.Value) {
+ switch v.Kind() {
+ case reflect.Array, reflect.Slice:
+ default:
+ panic(&UnmarshalTypeError{
+ Value: "array",
+ Type: v.Type(),
+ })
+ }
+
+ i := 0
+ for {
+ if v.Kind() == reflect.Slice && i >= v.Len() {
+ v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
+ }
+
+ ok := false
+ if i < v.Len() {
+ ok = d.parse_value(v.Index(i))
+ } else {
+ _, ok = d.parse_value_interface()
+ }
+
+ if !ok {
+ break
+ }
+
+ i++
+ }
+
+ if i < v.Len() {
+ if v.Kind() == reflect.Array {
+ z := reflect.Zero(v.Type().Elem())
+ for n := v.Len(); i < n; i++ {
+ v.Index(i).Set(z)
+ }
+ } else {
+ v.SetLen(i)
+ }
+ }
+
+ if i == 0 && v.Kind() == reflect.Slice {
+ v.Set(reflect.MakeSlice(v.Type(), 0, 0))
+ }
+}
+
+// returns true if there was a value and it's now stored in 'v', otherwise there
+// was an end symbol ("e") and no value was stored
+func (d *decoder) parse_value(v reflect.Value) bool {
+ if pv := v; pv.Kind() == reflect.Ptr {
+ // if the pointer is nil, allocate a new element of the type it
+ // points to
+ if pv.IsNil() {
+ pv.Set(reflect.New(pv.Type().Elem()))
+ }
+ v = pv.Elem()
+ }
+
+ // common case
+ if v.Kind() == reflect.Interface {
+ iface, _ := d.parse_value_interface()
+ v.Set(reflect.ValueOf(iface))
+ return true
+ }
+
+ b, err := d.ReadByte()
+ if err != nil {
+ panic(err)
+ }
+ d.offset++
+
+ switch b {
+ case 'e':
+ return false
+ case 'd':
+ d.parse_dict(v)
+ case 'l':
+ d.parse_list(v)
+ case 'i':
+ d.parse_int(v)
+ default:
+ if b >= '0' && b <= '9' {
+ // string
+ // append first digit of the length to the buffer
+ d.buf.WriteByte(b)
+ d.parse_string(v)
+ break
+ }
+
+ // unknown value
+ panic(&SyntaxError{
+ Offset: d.offset - 1,
+ what: "unknown value type (invalid bencode?)",
+ })
+ }
+
+ return true
+}
+
+func (d *decoder) parse_value_interface() (interface{}, bool) {
+ b, err := d.ReadByte()
+ if err != nil {
+ panic(err)
+ }
+ d.offset++
+
+ switch b {
+ case 'e':
+ return nil, false
+ case 'd':
+ return d.parse_dict_interface(), true
+ case 'l':
+ return d.parse_list_interface(), true
+ case 'i':
+ return d.parse_int_interface(), true
+ default:
+ if b >= '0' && b <= '9' {
+ // string
+ // append first digit of the length to the buffer
+ d.buf.WriteByte(b)
+ return d.parse_string_interface(), true
+ }
+
+ // unknown value
+ panic(&SyntaxError{
+ Offset: d.offset - 1,
+ what: "unknown value type (invalid bencode?)",
+ })
+ }
+ panic("unreachable")
+}
+
+func (d *decoder) parse_int_interface() interface{} {
+ start := d.offset - 1
+ d.read_until('e')
+ if d.buf.Len() == 0 {
+ panic(&SyntaxError{
+ Offset: start,
+ what: "empty integer value",
+ })
+ }
+
+ n, err := strconv.ParseInt(d.buf.String(), 10, 64)
+ check_for_int_parse_error(err, start)
+ d.buf.Reset()
+ return n
+}
+
+func (d *decoder) parse_string_interface() interface{} {
+ start := d.offset - 1
+
+ // read the string length first
+ d.read_until(':')
+ length, err := strconv.ParseInt(d.buf.String(), 10, 64)
+ check_for_int_parse_error(err, start)
+
+ d.buf.Reset()
+ n, err := io.CopyN(&d.buf, d, length)
+ d.offset += n
+ if err != nil {
+ check_for_unexpected_eof(err, d.offset)
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "unexpected I/O error: " + err.Error(),
+ })
+ }
+
+ s := d.buf.String()
+ d.buf.Reset()
+ return s
+}
+
+func (d *decoder) parse_dict_interface() interface{} {
+ dict := make(map[string]interface{})
+ for {
+ keyi, ok := d.parse_value_interface()
+ if !ok {
+ break
+ }
+
+ key, ok := keyi.(string)
+ if !ok {
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "non-string key in a dict",
+ })
+ }
+
+ valuei, ok := d.parse_value_interface()
+ if !ok {
+ panic(&SyntaxError{
+ Offset: d.offset,
+ what: "unexpected end of dict, no matching value for a given key",
+ })
+ }
+
+ dict[key] = valuei
+ }
+ return dict
+}
+
+func (d *decoder) parse_list_interface() interface{} {
+ var list []interface{}
+ for {
+ valuei, ok := d.parse_value_interface()
+ if !ok {
+ break
+ }
+
+ list = append(list, valuei)
+ }
+ return list
+}
--- /dev/null
+package bencode
+
+import "testing"
+import "reflect"
+
+type random_decode_test struct {
+ data string
+ expected interface{}
+}
+
+var random_decode_tests = []random_decode_test{
+ {"i57e", int64(57)},
+ {"i-9223372036854775808e", int64(-9223372036854775808)},
+ {"5:hello", "hello"},
+ {"29:unicode test проверка", "unicode test проверка"},
+ {"d1:ai5e1:b5:helloe", map[string]interface{}{"a": int64(5), "b": "hello"}},
+ {"li5ei10ei15ei20e7:bencodee",
+ []interface{}{int64(5), int64(10), int64(15), int64(20), "bencode"}},
+}
+
+func TestRandomDecode(t *testing.T) {
+ for _, test := range random_decode_tests {
+ var value interface{}
+ err := Unmarshal([]byte(test.data), &value)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ if !reflect.DeepEqual(test.expected, value) {
+ t.Errorf("got: %v (%T), expected: %v (%T)\n",
+ value, value, test.expected, test.expected)
+ }
+ }
+}
--- /dev/null
+package bencode
+
+import "bufio"
+import "reflect"
+import "runtime"
+import "strconv"
+import "sync"
+import "sort"
+
+func is_empty_value(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+ return false
+}
+
+type encoder struct {
+ *bufio.Writer
+ scratch [64]byte
+}
+
+func (e *encoder) encode(v interface{}) (err error) {
+ defer func() {
+ if e := recover(); e != nil {
+ if _, ok := e.(runtime.Error); ok {
+ panic(e)
+ }
+ err = e.(error)
+ }
+ }()
+ e.reflect_value(reflect.ValueOf(v))
+ return nil
+}
+
+type string_values []reflect.Value
+
+func (sv string_values) Len() int { return len(sv) }
+func (sv string_values) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
+func (sv string_values) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
+func (sv string_values) get(i int) string { return sv[i].String() }
+
+func (e *encoder) reflect_string(s string) {
+ b := strconv.AppendInt(e.scratch[:0], int64(len(s)), 10)
+ e.Write(b)
+ e.WriteString(":")
+ e.WriteString(s)
+}
+
+func (e *encoder) reflect_byte_slice(s []byte) {
+ b := strconv.AppendInt(e.scratch[:0], int64(len(s)), 10)
+ e.Write(b)
+ e.WriteString(":")
+ e.Write(s)
+}
+
+func (e *encoder) reflect_value(v reflect.Value) {
+ if !v.IsValid() {
+ return
+ }
+
+ switch v.Kind() {
+ case reflect.Bool:
+ if v.Bool() {
+ e.WriteString("i1e")
+ } else {
+ e.WriteString("i0e")
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ b := strconv.AppendInt(e.scratch[:0], v.Int(), 10)
+ e.WriteString("i")
+ e.Write(b)
+ e.WriteString("e")
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10)
+ e.WriteString("i")
+ e.Write(b)
+ e.WriteString("e")
+ case reflect.String:
+ e.reflect_string(v.String())
+ case reflect.Struct:
+ e.WriteString("d")
+ for _, ef := range encode_fields(v.Type()) {
+ field_value := v.Field(ef.i)
+ if ef.omit_empty && is_empty_value(field_value) {
+ continue
+ }
+
+ e.reflect_string(ef.tag)
+ e.reflect_value(field_value)
+ }
+ e.WriteString("e")
+ case reflect.Map:
+ if v.Type().Key().Kind() != reflect.String {
+ panic(&MarshalTypeError{v.Type()})
+ }
+ if v.IsNil() {
+ e.WriteString("de")
+ break
+ }
+ e.WriteString("d")
+ sv := string_values(v.MapKeys())
+ sort.Sort(sv)
+ for _, key := range sv {
+ e.reflect_string(key.String())
+ e.reflect_value(v.MapIndex(key))
+ }
+ e.WriteString("e")
+ case reflect.Slice:
+ if v.IsNil() {
+ e.WriteString("le")
+ break
+ }
+ if v.Type().Elem().Kind() == reflect.Uint8 {
+ s := v.Bytes()
+ e.reflect_byte_slice(s)
+ break
+ }
+ fallthrough
+ case reflect.Array:
+ e.WriteString("l")
+ for i, n := 0, v.Len(); i < n; i++ {
+ e.reflect_value(v.Index(i))
+ }
+ e.WriteString("e")
+ case reflect.Interface, reflect.Ptr:
+ if v.IsNil() {
+ break
+ }
+ e.reflect_value(v.Elem())
+ default:
+ panic(&MarshalTypeError{v.Type()})
+ }
+}
+
+type encode_field struct {
+ i int
+ tag string
+ omit_empty bool
+}
+
+type encode_fields_sort_type []encode_field
+
+func (ef encode_fields_sort_type) Len() int { return len(ef) }
+func (ef encode_fields_sort_type) Swap(i, j int) { ef[i], ef[j] = ef[j], ef[i] }
+func (ef encode_fields_sort_type) Less(i, j int) bool { return ef[i].tag < ef[j].tag }
+
+var (
+ type_cache_lock sync.RWMutex
+ encode_fields_cache = make(map[reflect.Type][]encode_field)
+)
+
+func encode_fields(t reflect.Type) []encode_field {
+ type_cache_lock.RLock()
+ fs, ok := encode_fields_cache[t]
+ type_cache_lock.RUnlock()
+ if ok {
+ return fs
+ }
+
+ type_cache_lock.Lock()
+ defer type_cache_lock.Unlock()
+ fs, ok = encode_fields_cache[t]
+ if ok {
+ return fs
+ }
+
+ for i, n := 0, t.NumField(); i < n; i++ {
+ f := t.Field(i)
+ if f.PkgPath != "" {
+ continue
+ }
+ if f.Anonymous {
+ continue
+ }
+ var ef encode_field
+ ef.i = i
+ ef.tag = f.Name
+
+ tv := f.Tag.Get("bencode")
+ if tv != "" {
+ if tv == "-" {
+ continue
+ }
+ name, opts := parse_tag(tv)
+ ef.tag = name
+ ef.omit_empty = opts.contains("omitempty")
+ }
+ fs = append(fs, ef)
+ }
+ fss := encode_fields_sort_type(fs)
+ sort.Sort(fss)
+ encode_fields_cache[t] = fs
+ return fs
+}
--- /dev/null
+package bencode
+
+import "testing"
+import "bytes"
+
+type random_encode_test struct {
+ value interface{}
+ expected string
+}
+
+type random_struct struct {
+ ABC int `bencode:"abc"`
+ SkipThisOne string `bencode:"-"`
+ CDE string
+}
+
+var random_encode_tests = []random_encode_test{
+ {int(10), "i10e"},
+ {uint(10), "i10e"},
+ {"hello, world", "12:hello, world"},
+ {true, "i1e"},
+ {false, "i0e"},
+ {int8(-8), "i-8e"},
+ {int16(-16), "i-16e"},
+ {int32(32), "i32e"},
+ {int64(-64), "i-64e"},
+ {uint8(8), "i8e"},
+ {uint16(16), "i16e"},
+ {uint32(32), "i32e"},
+ {uint64(64), "i64e"},
+ {random_struct{123, "nono", "hello"}, "d3:CDE5:hello3:abci123ee"},
+ {map[string]string{"a": "b", "c": "d"}, "d1:a1:b1:c1:de"},
+ {[]byte{1, 2, 3, 4}, "4:\x01\x02\x03\x04"},
+ {[4]byte{1, 2, 3, 4}, "li1ei2ei3ei4ee"},
+ {nil, ""},
+ {[]byte{}, "0:"},
+ {"", "0:"},
+ {[]int{}, "le"},
+ {map[string]int{}, "de"},
+}
+
+func TestRandomEncode(t *testing.T) {
+ for _, test := range random_encode_tests {
+ data, err := Marshal(test.value)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(data, []byte(test.expected)) {
+ t.Errorf("got: %s, expected: %s\n",
+ string(data), string(test.expected))
+ }
+ }
+}
--- /dev/null
+package bencode
+
+import (
+ "strings"
+)
+
+type tag_options string
+
+func parse_tag(tag string) (string, tag_options) {
+ if idx := strings.Index(tag, ","); idx != -1 {
+ return tag[:idx], tag_options(tag[idx+1:])
+ }
+ return tag, tag_options("")
+}
+
+func (this tag_options) contains(option_name string) bool {
+ if len(this) == 0 {
+ return false
+ }
+
+ s := string(this)
+ for s != "" {
+ var next string
+ i := strings.Index(s, ",")
+ if i != -1 {
+ s, next = s[:i], s[i+1:]
+ }
+ if s == option_name {
+ return true
+ }
+ s = next
+ }
+ return false
+}