Skip to content

Commit 8d9f67d

Browse files
committed
Initial commit
0 parents  commit 8d9f67d

12 files changed

+543
-0
lines changed

.travis.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
language: go
2+
sudo: false
3+
go:
4+
- 1.7
5+
- 1.8
6+
before_install:
7+
- go get github.com/mattn/goveralls
8+
go_import_path: github.com/IncSW/go-bencode
9+
script:
10+
- make test
11+
- make bench
12+
after_success:
13+
- travis_retry make coveralls

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Aleksey Lin <[email protected]> (https://incsw.in)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
coveralls:
2+
goveralls -service=travis-ci
3+
4+
test:
5+
go test -v -race
6+
7+
bench:
8+
go test -v -bench=. -run "^Benchmark"
9+
10+
all: test

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# go-bencode [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
2+
3+
## Installation
4+
5+
`go get github.com/IncSW/go-bencode`
6+
7+
```go
8+
import bencode "github.com/IncSW/go-bencode"
9+
```
10+
11+
## Quick Start
12+
13+
```go
14+
data, err := Marshal(value)
15+
```
16+
17+
```go
18+
data, err := Unmarshal(value)
19+
```
20+
21+
## Performance
22+
23+
### Marshal
24+
25+
| Library | Time | Bytes Allocated | Objects Allocated |
26+
| :--- | :---: | :---: | :---: |
27+
| IncSW/go-bencode | 1493 ns/op | 554 B/op | 15 allocs/op |
28+
| chihaya/bencode | 3614 ns/op | 1038 B/op | 64 allocs/op |
29+
| jackpal/bencode-go | 8497 ns/op | 2289 B/op | 66 allocs/op |
30+
31+
### Unmarshal
32+
33+
| Library | Time | Bytes Allocated | Objects Allocated |
34+
| :--- | :---: | :---: | :---: |
35+
| IncSW/go-bencode | 3151 ns/op | 1360 B/op | 46 allocs/op |
36+
| chihaya/bencode | 5281 ns/op | 5984 B/op | 66 allocs/op |
37+
| jackpal/bencode-go | 6850 ns/op | 3073 B/op | 102 allocs/op |
38+
39+
## License
40+
41+
[MIT License](LICENSE).

benchmarks/chihaya_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package benchmarks
2+
3+
import (
4+
"testing"
5+
6+
bencode "github.com/chihaya/chihaya/frontend/http/bencode"
7+
)
8+
9+
func BenchmarkChihayaBencodeMarshal(b *testing.B) {
10+
b.ReportAllocs()
11+
for n := 0; n < b.N; n++ {
12+
bencode.Marshal(marshalTestData)
13+
}
14+
}
15+
16+
func BenchmarkChihayaBencodeUnmarshal(b *testing.B) {
17+
b.ReportAllocs()
18+
for n := 0; n < b.N; n++ {
19+
bencode.Unmarshal(unmarshalTestData)
20+
}
21+
}

benchmarks/jackpal_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package benchmarks
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
bencode "github.com/jackpal/bencode-go"
8+
)
9+
10+
func BenchmarkJackpalBencodeMarshal(b *testing.B) {
11+
b.ReportAllocs()
12+
for n := 0; n < b.N; n++ {
13+
bencode.Marshal(bytes.NewBuffer(nil), marshalTestData)
14+
}
15+
}
16+
17+
func BenchmarkJackpalBencodeUnmarshal(b *testing.B) {
18+
b.ReportAllocs()
19+
for n := 0; n < b.N; n++ {
20+
bencode.Decode(bytes.NewReader(unmarshalTestData))
21+
}
22+
}

benchmarks/main_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package benchmarks
2+
3+
var marshalTestData = map[string]interface{}{
4+
"announce": []byte("udp://tracker.publicbt.com:80/announce"),
5+
"announce-list": []interface{}{
6+
[]interface{}{[]byte("udp://tracker.publicbt.com:80/announce")},
7+
[]interface{}{[]byte("udp://tracker.openbittorrent.com:80/announce")},
8+
},
9+
"comment": []byte("Debian CD from cdimage.debian.org"),
10+
"info": map[string]interface{}{
11+
"name": []byte("debian-8.8.0-arm64-netinst.iso"),
12+
"length": 170917888,
13+
"piece length": 262144,
14+
},
15+
}
16+
17+
var unmarshalTestData = []byte("d4:infod6:lengthi170917888e12:piece lengthi262144e4:name30:debian-8.8.0-arm64-netinst.isoe8:announce38:udp://tracker.publicbt.com:80/announce13:announce-listll38:udp://tracker.publicbt.com:80/announceel44:udp://tracker.openbittorrent.com:80/announceee7:comment33:Debian CD from cdimage.debian.orge")

benchmarks/own_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package benchmarks
2+
3+
import (
4+
"testing"
5+
6+
bencode "github.com/IncSW/go-bencode"
7+
)
8+
9+
func BenchmarkMarshal(b *testing.B) {
10+
b.ReportAllocs()
11+
for n := 0; n < b.N; n++ {
12+
bencode.Marshal(marshalTestData)
13+
}
14+
}
15+
16+
func BenchmarkUnmarshal(b *testing.B) {
17+
b.ReportAllocs()
18+
for n := 0; n < b.N; n++ {
19+
bencode.Unmarshal(unmarshalTestData)
20+
}
21+
}

marshaler.go

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package bencode
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
)
7+
8+
func Marshal(data interface{}) ([]byte, error) {
9+
result := make([]byte, 512) // WTF
10+
length, _, err := marshal(data, &result, 0, 512)
11+
if err != nil {
12+
return nil, err
13+
}
14+
15+
return result[:length], nil
16+
}
17+
18+
func marshal(data interface{}, result *[]byte, offset int, length int) (int, int, error) {
19+
switch value := data.(type) {
20+
case int64:
21+
offset, length = marshalInt(value, result, offset, length)
22+
23+
return offset, length, nil
24+
25+
case int:
26+
offset, length = marshalInt(int64(value), result, offset, length)
27+
28+
return offset, length, nil
29+
30+
case []byte:
31+
offset, length = marshalBytes(value, result, offset, length)
32+
33+
return offset, length, nil
34+
35+
case string:
36+
offset, length = marshalBytes([]byte(value), result, offset, length)
37+
38+
return offset, length, nil
39+
40+
case []interface{}:
41+
return marshalList(value, result, offset, length)
42+
43+
case map[string]interface{}:
44+
return marshalDictionary(value, result, offset, length)
45+
46+
default:
47+
return 0, 0, fmt.Errorf("bencode: unsupported type: %T", value)
48+
}
49+
}
50+
51+
func prepareBuffer(result *[]byte, offset int, length int, neededLength int) int {
52+
availableLength := length - offset
53+
if availableLength >= neededLength {
54+
return length
55+
}
56+
57+
rate := 1
58+
for availableLength < neededLength {
59+
rate++
60+
availableLength = length*rate - offset
61+
}
62+
63+
if rate > 1 {
64+
newResult := make([]byte, length*rate)
65+
copy(newResult, (*result)[:length])
66+
length *= rate
67+
*result = newResult
68+
}
69+
70+
return length
71+
}
72+
73+
func marshalInt(data int64, result *[]byte, offset int, length int) (int, int) {
74+
intBuffer := []byte(strconv.FormatInt(data, 10))
75+
intBufferLength := len(intBuffer)
76+
length = prepareBuffer(result, offset, length, intBufferLength+2)
77+
78+
(*result)[offset] = 'i'
79+
offset++
80+
copy((*result)[offset:], intBuffer)
81+
offset += intBufferLength
82+
(*result)[offset] = 'e'
83+
offset++
84+
85+
return offset, length
86+
}
87+
88+
func marshalBytes(data []byte, result *[]byte, offset int, length int) (int, int) {
89+
dataLength := len(data)
90+
lengthBuffer := []byte(strconv.Itoa(dataLength))
91+
lengthBufferLength := len(lengthBuffer)
92+
length = prepareBuffer(result, offset, length, lengthBufferLength+1+dataLength)
93+
94+
copy((*result)[offset:], lengthBuffer)
95+
offset += lengthBufferLength
96+
(*result)[offset] = ':'
97+
offset++
98+
copy((*result)[offset:], data)
99+
offset += dataLength
100+
101+
return offset, length
102+
}
103+
104+
func marshalList(data []interface{}, result *[]byte, offset int, length int) (int, int, error) {
105+
length = prepareBuffer(result, offset, length, 1)
106+
107+
(*result)[offset] = 'l'
108+
offset++
109+
110+
for _, data := range data {
111+
var err error
112+
offset, length, err = marshal(data, result, offset, length)
113+
if err != nil {
114+
return 0, 0, err
115+
}
116+
}
117+
118+
length = prepareBuffer(result, offset, length, 1)
119+
120+
(*result)[offset] = 'e'
121+
offset++
122+
123+
return offset, length, nil
124+
}
125+
126+
func marshalDictionary(data map[string]interface{}, result *[]byte, offset int, length int) (int, int, error) {
127+
length = prepareBuffer(result, offset, length, 1)
128+
129+
(*result)[offset] = 'd'
130+
offset++
131+
132+
for key, data := range data {
133+
offset, length = marshalBytes([]byte(key), result, offset, length)
134+
var err error
135+
offset, length, err = marshal(data, result, offset, length)
136+
if err != nil {
137+
return 0, 0, err
138+
}
139+
}
140+
141+
length = prepareBuffer(result, offset, length, 1)
142+
143+
(*result)[offset] = 'e'
144+
offset++
145+
146+
return offset, length, nil
147+
}

marshaler_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package bencode
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
var marshalTestData = map[string]interface{}{
10+
"announce": []byte("udp://tracker.publicbt.com:80/announce"),
11+
"announce-list": []interface{}{
12+
[]interface{}{[]byte("udp://tracker.publicbt.com:80/announce")},
13+
[]interface{}{[]byte("udp://tracker.openbittorrent.com:80/announce")},
14+
},
15+
"comment": []byte("Debian CD from cdimage.debian.org"),
16+
"info": map[string]interface{}{
17+
"name": []byte("debian-8.8.0-arm64-netinst.iso"),
18+
"length": 170917888,
19+
"piece length": 262144,
20+
},
21+
}
22+
23+
func TestMarshal(t *testing.T) {
24+
assert := assert.New(t)
25+
26+
result, err := Marshal(int64(38))
27+
if !assert.NoError(err) || !assert.Equal([]byte("i38e"), result) {
28+
return
29+
}
30+
31+
result, err = Marshal([]byte("udp://tracker.publicbt.com:80/announce"))
32+
if !assert.NoError(err) || !assert.Equal([]byte("38:udp://tracker.publicbt.com:80/announce"), result) {
33+
return
34+
}
35+
36+
result, err = Marshal([]interface{}{
37+
[]interface{}{[]byte("udp://tracker.publicbt.com:80/announce")},
38+
[]interface{}{[]byte("udp://tracker.openbittorrent.com:80/announce")},
39+
})
40+
if !assert.NoError(err) || !assert.Equal([]byte("ll38:udp://tracker.publicbt.com:80/announceel44:udp://tracker.openbittorrent.com:80/announceee"), result) {
41+
return
42+
}
43+
44+
result, err = Marshal(map[string]interface{}{
45+
"announce": []byte("udp://tracker.publicbt.com:80/announce"),
46+
"announce-list": []interface{}{
47+
[]interface{}{[]byte("udp://tracker.publicbt.com:80/announce")},
48+
[]interface{}{[]byte("udp://tracker.openbittorrent.com:80/announce")},
49+
},
50+
})
51+
if !assert.NoError(err) || !assert.Equal([]byte("d8:announce38:udp://tracker.publicbt.com:80/announce13:announce-listll38:udp://tracker.publicbt.com:80/announceel44:udp://tracker.openbittorrent.com:80/announceeee"), result) {
52+
return
53+
}
54+
}
55+
56+
func BenchmarkMarshal(b *testing.B) {
57+
b.ReportAllocs()
58+
for n := 0; n < b.N; n++ {
59+
Marshal(marshalTestData)
60+
}
61+
}

0 commit comments

Comments
 (0)