Last active
August 29, 2015 14:01
-
-
Save bemasher/85051f0d65ccbaa8c5e1 to your computer and use it in GitHub Desktop.
Dirty Hack #37: Reflection is hard. Have a decoder you've written in Go and don't want to write all the logic for reflection to store data in native types? Does your decoder output map[string]interface{} and []interface{} flavored data? Problem solved: just marshal and unmarshal using the JSON package.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"log" | |
"strconv" | |
) | |
// Bencoding Specification | |
// Strings are length-prefixed base ten followed by a colon and the string. | |
// For example 4:spam corresponds to 'spam'. | |
// Integers are represented by an 'i' followed by the number in base 10 | |
// followed by an 'e'. For example i3e corresponds to 3 and i-3e corresponds | |
// to -3. Integers have no size limitation. i-0e is invalid. All encodings | |
// with a leading zero, such as i03e, are invalid, other than i0e, which of | |
// course corresponds to 0. | |
// Lists are encoded as an 'l' followed by their elements (also bencoded) | |
// followed by an 'e'. For example l4:spam4:eggse corresponds to ['spam', | |
// 'eggs']. | |
// Dictionaries are encoded as a 'd' followed by a list of alternating keys | |
// and their corresponding values followed by an 'e'. For example, | |
// d3:cow3:moo4:spam4:eggse corresponds to {'cow': 'moo', 'spam': 'eggs'} and | |
// d4:spaml1:a1:bee corresponds to {'spam': ['a', 'b']}. Keys must be strings | |
// and appear in sorted order (sorted as raw strings, not alphanumerics). | |
type Decoder struct { | |
*bufio.Reader | |
} | |
func NewDecoder(buf *bufio.Reader) Decoder { | |
return Decoder{buf} | |
} | |
func (d Decoder) Decode() interface{} { | |
return d.decode() | |
} | |
func (d Decoder) decode() interface{} { | |
data, err := d.Peek(1) | |
if err != nil { | |
log.Fatal("Error reading byte:", err) | |
} | |
switch data[0] { | |
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | |
return d.decodeString() | |
case 'i': | |
return d.decodeInt() | |
case 'd': | |
return d.decodeMap() | |
case 'l': | |
return d.decodeList() | |
case 'e': | |
return d.decodeList() | |
} | |
log.Println("This should never happen.") | |
return nil | |
} | |
func (d Decoder) decodeMap() (m map[string]interface{}) { | |
c, err := d.ReadByte() | |
if err != nil || c != 'd' { | |
log.Fatal("Invalid map prefix:", err) | |
} | |
m = make(map[string]interface{}) | |
for { | |
c, err := d.Peek(1) | |
if err != nil { | |
log.Fatal("Error decoding map:", err) | |
} | |
if c[0] == 'e' { | |
_, err := d.ReadByte() | |
if err != nil { | |
log.Fatal("Error reading termination:", err) | |
} | |
return | |
} | |
key := d.decodeString() | |
val := d.decode() | |
m[key] = val | |
} | |
return | |
} | |
func (d Decoder) decodeList() (l []interface{}) { | |
c, err := d.ReadByte() | |
if err != nil || c != 'l' { | |
log.Fatal("Invalid list prefix:", err) | |
} | |
for { | |
c, err := d.Peek(1) | |
if err != nil { | |
log.Fatal("Error decoding list:", err) | |
} | |
if c[0] == 'e' { | |
_, err := d.ReadByte() | |
if err != nil { | |
log.Fatal("Error reading termination:", err) | |
} | |
return | |
} | |
l = append(l, d.decode()) | |
} | |
return | |
} | |
func (d Decoder) decodeString() (s string) { | |
lenString, err := d.ReadString(':') | |
if err != nil { | |
log.Fatal("Error reading string length:", err) | |
} | |
lenString = lenString[:len(lenString)-1] | |
length, err := strconv.ParseInt(lenString, 10, 64) | |
if err != nil { | |
log.Fatal("Error parsing string length:", err) | |
} | |
stringBytes := make([]byte, length) | |
_, err = d.Read(stringBytes) | |
if err != nil { | |
log.Fatal("Error reading string:", err) | |
} | |
return string(stringBytes) | |
} | |
func (d Decoder) decodeInt() (i int) { | |
data, err := d.ReadString('e') | |
if err != nil { | |
log.Fatal("Error finding int sentinel:", err) | |
} | |
data = data[1 : len(data)-1] | |
val, err := strconv.ParseInt(data, 10, 64) | |
if err != nil { | |
log.Fatal("Error parsing int:", err) | |
} | |
return int(val) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d8:announce44:udp://tracker.openbittorrent.com:80/announce13:announce-listll44:udp://tracker.openbittorrent.com:80/announceel38:udp://tracker.publicbt.com:80/announceel32:udp://tracker.ccc.de:80/announceee10:created by16:BitTorrent/7.9.113:creation datei1401190995e8:encoding5:UTF-84:infod5:filesld6:lengthi4003e4:pathl12:interface.goeed6:lengthi4003e4:pathl10:reflect.goeed6:lengthi379e4:pathl12:file.torrenteee4:name7:bencode12:piece lengthi16384e6:pieces20:w&��'�~=W4���i̓ee |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"encoding/json" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
"github.com/kr/pretty" | |
) | |
type Torrent struct { | |
Announce string `json:"announce"` | |
AnnounceList [][]string `json:"announce-list,omitempty"` | |
CreatedBy string `json:"created by,omitempty"` | |
CreationDate int `json:"creation date,omitempty"` | |
Encoding string `json:"encoding,omitempty"` | |
Info struct { | |
Name string `json:"name"` | |
File | |
Files []File `json:"files,omitempty"` | |
PieceLength int `json:"piece length"` | |
Pieces string `json:"pieces"` | |
Private int `json:"private,omitempty"` | |
} `json:"info"` | |
} | |
type File struct { | |
Path []string `json:"path"` | |
Length int `json:"length"` | |
MD5Sum string `json:"md5sum,omitempty"` | |
} | |
func init() { | |
log.SetFlags(log.Lshortfile) | |
} | |
func main() { | |
filename := "directory.torrent" | |
torrentFile, err := os.Open(filename) | |
if err != nil { | |
log.Fatal("Error opening torrent:", err) | |
} | |
defer torrentFile.Close() | |
torrentBuf := bufio.NewReader(torrentFile) | |
decoder := NewDecoder(torrentBuf) | |
data := decoder.Decode() | |
r, w := io.Pipe() | |
jsonEncoder := json.NewEncoder(w) | |
jsonDecoder := json.NewDecoder(r) | |
var torrent Torrent | |
go jsonEncoder.Encode(data) | |
jsonDecoder.Decode(&torrent) | |
fmt.Println("Before:") | |
pretty.Println(data) | |
fmt.Println("After:") | |
pretty.Println(torrent) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Before: | |
map[string]interface {}{ | |
"announce": "udp://tracker.openbittorrent.com:80/announce", | |
"announce-list": []interface {}{ | |
[]interface {}{ | |
"udp://tracker.openbittorrent.com:80/announce", | |
}, | |
[]interface {}{ | |
"udp://tracker.publicbt.com:80/announce", | |
}, | |
[]interface {}{ | |
"udp://tracker.ccc.de:80/announce", | |
}, | |
}, | |
"created by": "BitTorrent/7.9.1", | |
"creation date": int(1401190995), | |
"encoding": "UTF-8", | |
"info": map[string]interface {}{ | |
"files": []interface {}{ | |
map[string]interface {}{ | |
"length": int(4003), | |
"path": []interface {}{ | |
"interface.go", | |
}, | |
}, | |
map[string]interface {}{ | |
"length": int(4003), | |
"path": []interface {}{ | |
"reflect.go", | |
}, | |
}, | |
map[string]interface {}{ | |
"length": int(379), | |
"path": []interface {}{ | |
"file.torrent", | |
}, | |
}, | |
}, | |
"name": "bencode", | |
"piece length": int(16384), | |
"pieces": "w&\xef\xf4\x03'\xc7~=W4\a\x86\x04\x19\xfd\xb4i̓", | |
}, | |
} | |
After: | |
main.Torrent{ | |
Announce: "udp://tracker.openbittorrent.com:80/announce", | |
AnnounceList: { | |
{"udp://tracker.openbittorrent.com:80/announce"}, | |
{"udp://tracker.publicbt.com:80/announce"}, | |
{"udp://tracker.ccc.de:80/announce"}, | |
}, | |
CreatedBy: "BitTorrent/7.9.1", | |
CreationDate: 1401190995, | |
Encoding: "UTF-8", | |
Info: { | |
Name: "bencode", | |
File: {}, | |
Files: { | |
{ | |
Path: {"interface.go"}, | |
Length: 4003, | |
MD5Sum: "", | |
}, | |
{ | |
Path: {"reflect.go"}, | |
Length: 4003, | |
MD5Sum: "", | |
}, | |
{ | |
Path: {"file.torrent"}, | |
Length: 379, | |
MD5Sum: "", | |
}, | |
}, | |
PieceLength: 16384, | |
Pieces: "w&��\x03'�~=W4\a�\x04\x19��i̓", | |
Private: 0, | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment