]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Initial commit.
authornsf <no.smile.face@gmail.com>
Wed, 20 Jun 2012 13:21:32 +0000 (19:21 +0600)
committernsf <no.smile.face@gmail.com>
Wed, 20 Jun 2012 13:21:32 +0000 (19:21 +0600)
README [new file with mode: 0644]
bencode/README [new file with mode: 0644]
bencode/_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent [new file with mode: 0644]
bencode/api.go [new file with mode: 0644]
bencode/both_test.go [new file with mode: 0644]
bencode/decode.go [new file with mode: 0644]
bencode/decode_test.go [new file with mode: 0644]
bencode/encode.go [new file with mode: 0644]
bencode/encode_test.go [new file with mode: 0644]
bencode/tags.go [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ce8fac9
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+BitTorrent Go library, work in progress..
\ No newline at end of file
diff --git a/bencode/README b/bencode/README
new file mode 100644 (file)
index 0000000..440bd5f
--- /dev/null
@@ -0,0 +1 @@
+Bencode encoding/decoding sub package. Uses similar API design to Go's json package.
diff --git a/bencode/_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent b/bencode/_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent
new file mode 100644 (file)
index 0000000..9ce7748
Binary files /dev/null and b/bencode/_testdata/archlinux-2011.08.19-netinstall-i686.iso.torrent differ
diff --git a/bencode/api.go b/bencode/api.go
new file mode 100644 (file)
index 0000000..ce94f24
--- /dev/null
@@ -0,0 +1,100 @@
+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
+*/
diff --git a/bencode/both_test.go b/bencode/both_test.go
new file mode 100644 (file)
index 0000000..837e611
--- /dev/null
@@ -0,0 +1,77 @@
+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")
+       }
+}
diff --git a/bencode/decode.go b/bencode/decode.go
new file mode 100644 (file)
index 0000000..0479cf8
--- /dev/null
@@ -0,0 +1,491 @@
+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
+}
diff --git a/bencode/decode_test.go b/bencode/decode_test.go
new file mode 100644 (file)
index 0000000..3cf5776
--- /dev/null
@@ -0,0 +1,34 @@
+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)
+               }
+       }
+}
diff --git a/bencode/encode.go b/bencode/encode.go
new file mode 100644 (file)
index 0000000..82f5b4c
--- /dev/null
@@ -0,0 +1,205 @@
+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
+}
diff --git a/bencode/encode_test.go b/bencode/encode_test.go
new file mode 100644 (file)
index 0000000..de89542
--- /dev/null
@@ -0,0 +1,53 @@
+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))
+               }
+       }
+}
diff --git a/bencode/tags.go b/bencode/tags.go
new file mode 100644 (file)
index 0000000..0943b41
--- /dev/null
@@ -0,0 +1,34 @@
+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
+}