aboutsummaryrefslogtreecommitdiff
path: root/bencode
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2018-01-09 13:05:54 +0000
committerFelix Hanley <felix@userspace.com.au>2018-01-09 13:05:54 +0000
commitff55e6b4b031a592e51c0c628cc0d0742c16922b (patch)
tree3525f11139984741cc28b55ca2683e6c7eab24f9 /bencode
parentaa3ae997bbed64d6f06f549bfbf5d9ae4f5e84a3 (diff)
downloaddhtsearch-ff55e6b4b031a592e51c0c628cc0d0742c16922b.tar.gz
dhtsearch-ff55e6b4b031a592e51c0c628cc0d0742c16922b.tar.bz2
Begin splitting up libraries
Diffstat (limited to 'bencode')
-rw-r--r--bencode/bencode.go265
1 files changed, 265 insertions, 0 deletions
diff --git a/bencode/bencode.go b/bencode/bencode.go
new file mode 100644
index 0000000..6b2373a
--- /dev/null
+++ b/bencode/bencode.go
@@ -0,0 +1,265 @@
+package bencode
+
+// Lifted from github.com/shiyanhui/dht
+
+import (
+ "bytes"
+ "errors"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// find returns the index of first target in data starting from `start`.
+// It returns -1 if target not found.
+func find(data []byte, start int, target rune) (index int) {
+ index = bytes.IndexRune(data[start:], target)
+ if index != -1 {
+ return index + start
+ }
+ return index
+}
+
+// DecodeString decodes a string in the data. It returns a tuple
+// (decoded result, the end position, error).
+func DecodeString(data []byte, start int) (
+ result interface{}, index int, err error) {
+
+ if start >= len(data) || data[start] < '0' || data[start] > '9' {
+ err = errors.New("invalid string bencode")
+ return
+ }
+
+ i := find(data, start, ':')
+ if i == -1 {
+ err = errors.New("':' not found when decode string")
+ return
+ }
+
+ length, err := strconv.Atoi(string(data[start:i]))
+ if err != nil {
+ return
+ }
+
+ if length < 0 {
+ err = errors.New("invalid length of string")
+ return
+ }
+
+ index = i + 1 + length
+
+ if index > len(data) || index < i+1 {
+ err = errors.New("out of range")
+ return
+ }
+
+ result = string(data[i+1 : index])
+ return
+}
+
+// DecodeInt decodes int value in the data.
+func DecodeInt(data []byte, start int) (
+ result interface{}, index int, err error) {
+
+ if start >= len(data) || data[start] != 'i' {
+ err = errors.New("invalid int bencode")
+ return
+ }
+
+ index = find(data, start+1, 'e')
+
+ if index == -1 {
+ err = errors.New("':' not found when decode int")
+ return
+ }
+
+ result, err = strconv.Atoi(string(data[start+1 : index]))
+ if err != nil {
+ return
+ }
+ index++
+
+ return
+}
+
+// decodeItem decodes an item of dict or list.
+func decodeItem(data []byte, i int) (
+ result interface{}, index int, err error) {
+
+ var decodeFunc = []func([]byte, int) (interface{}, int, error){
+ DecodeString, DecodeInt, DecodeList, DecodeDict,
+ }
+
+ for _, f := range decodeFunc {
+ result, index, err = f(data, i)
+ if err == nil {
+ return
+ }
+ }
+
+ err = errors.New("invalid bencode when decode item")
+ return
+}
+
+// DecodeList decodes a list value.
+func DecodeList(data []byte, start int) (
+ result interface{}, index int, err error) {
+
+ if start >= len(data) || data[start] != 'l' {
+ err = errors.New("invalid list bencode")
+ return
+ }
+
+ var item interface{}
+ r := make([]interface{}, 0, 8)
+
+ index = start + 1
+ for index < len(data) {
+ char, _ := utf8.DecodeRune(data[index:])
+ if char == 'e' {
+ break
+ }
+
+ item, index, err = decodeItem(data, index)
+ if err != nil {
+ return
+ }
+ r = append(r, item)
+ }
+
+ if index == len(data) {
+ err = errors.New("'e' not found when decode list")
+ return
+ }
+ index++
+
+ result = r
+ return
+}
+
+// DecodeDict decodes a map value.
+func DecodeDict(data []byte, start int) (
+ result interface{}, index int, err error) {
+
+ if start >= len(data) || data[start] != 'd' {
+ err = errors.New("invalid dict bencode")
+ return
+ }
+
+ var item, key interface{}
+ r := make(map[string]interface{})
+
+ index = start + 1
+ for index < len(data) {
+ char, _ := utf8.DecodeRune(data[index:])
+ if char == 'e' {
+ break
+ }
+
+ if !unicode.IsDigit(char) {
+ err = errors.New("invalid dict bencode")
+ return
+ }
+
+ key, index, err = DecodeString(data, index)
+ if err != nil {
+ return
+ }
+
+ if index >= len(data) {
+ err = errors.New("out of range")
+ return
+ }
+
+ item, index, err = decodeItem(data, index)
+ if err != nil {
+ return
+ }
+
+ r[key.(string)] = item
+ }
+
+ if index == len(data) {
+ err = errors.New("'e' not found when decode dict")
+ return
+ }
+ index++
+
+ result = r
+ return
+}
+
+// Decode decodes a bencoded string to string, int, list or map.
+func Decode(data []byte) (result interface{}, err error) {
+ result, _, err = decodeItem(data, 0)
+ return
+}
+
+// EncodeString encodes a string value.
+func EncodeString(data string) string {
+ return strings.Join([]string{strconv.Itoa(len(data)), data}, ":")
+}
+
+// EncodeInt encodes a int value.
+func EncodeInt(data int) string {
+ return strings.Join([]string{"i", strconv.Itoa(data), "e"}, "")
+}
+
+// EncodeItem encodes an item of dict or list.
+func encodeItem(data interface{}) (item string) {
+ switch v := data.(type) {
+ case string:
+ item = EncodeString(string(v))
+ case int:
+ item = EncodeInt(v)
+ case []interface{}:
+ item = EncodeList(v)
+ case map[string]interface{}:
+ item = EncodeDict(v)
+ default:
+ panic("invalid type when encode item")
+ }
+ return
+}
+
+// EncodeList encodes a list value.
+func EncodeList(data []interface{}) string {
+ result := make([]string, len(data))
+
+ for i, item := range data {
+ result[i] = encodeItem(item)
+ }
+
+ return strings.Join([]string{"l", strings.Join(result, ""), "e"}, "")
+}
+
+// EncodeDict encodes a dict value.
+func EncodeDict(data map[string]interface{}) string {
+ result, i := make([]string, len(data)), 0
+
+ for key, val := range data {
+ result[i] = strings.Join(
+ []string{EncodeString(key), encodeItem(val)},
+ "")
+ i++
+ }
+
+ return strings.Join([]string{"d", strings.Join(result, ""), "e"}, "")
+}
+
+// Encode encodes a string, int, dict or list value to a bencoded string.
+func Encode(data interface{}) string {
+ switch v := data.(type) {
+ case string:
+ return EncodeString(v)
+ case int:
+ return EncodeInt(v)
+ case []interface{}:
+ return EncodeList(v)
+ case map[string]interface{}:
+ return EncodeDict(v)
+ default:
+ panic("invalid type when encode")
+ }
+}