-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbencode.nim
231 lines (180 loc) · 5.29 KB
/
bencode.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# Copyright 2017 Federico Ceratto <federico.ceratto@gmail.com>
# Released under LGPLv3 License, see LICENSE file
#
## bencode library
from algorithm import sorted, sort
from json import JsonNodeKind, JsonNode
from lexbase import BaseLexer
import hashes
import json
import os
import strutils
import tables
when defined(coverage):
import coverage
# FIXME
{.push cov.}
type
BENodeKind* = enum
tkEof,
tkInt,
tkBytes,
tkList,
tkDict
BENode* = object
case kind: BENodeKind
of tkInt: intVal*: int
of tkBytes: strVal*: string
of tkList: listVal*: seq[BENode]
of tkDict: dictVal*: Table[string, BENode]
else:
discard
BEncodeParser* = object of BaseLexer
BEDecoded = tuple[obj: BENode, remaining: string]
proc pprint*(self: BENode, indent=0, hex=false): void =
## Pretty-print BENode tree
case self.kind:
of tkInt:
echo repeat(' ', indent) & $self.intVal
of tkBytes:
var line = repeat(' ', indent)
if hex and self.strVal.len > 12: # FIXME
for c in self.strVal:
line.add c.int.toHex(2)
else:
line.add self.strVal
echo line
of tkList:
echo repeat(' ', indent) & "["
for item in self.listVal:
pprint(item, indent + 1, hex=hex)
echo repeat(' ', indent) & "]"
of tkDict:
echo repeat(' ', indent) & "{"
for k, v in self.dictVal.pairs(): #TODO sort
echo repeat(' ', indent + 1) & k
pprint(v, indent + 2, hex=hex)
echo repeat(' ', indent) & "}"
of tkEof:
echo repeat(' ', indent) & "END"
proc bdec(s: string): BEDecoded =
if len(s) == 0:
return (BENode(kind: tkEof), "")
if s[0] == 'i': # integer
let e_pos = s.find('e')
let remaining = s[(e_pos+1)..<len(s)]
return (BENode(kind: tkInt, intVal: s[1..<e_pos].parseInt), remaining)
if s[0] == 'd': # dict
var d = BENode(kind: tkDict, dictVal: initTable[string, BENode]())
var remaining = s[1..<len(s)]
while len(remaining) != 0 and remaining[0] != 'e':
var decoded = bdec(remaining)
if decoded.obj.kind != tkBytes:
echo "error"
return (BENode(kind: tkEof), s)
remaining = decoded.remaining
let dict_key = decoded.obj.strVal
decoded = bdec(remaining)
remaining = decoded.remaining
let dict_val = decoded.obj
d.dictVal[dict_key] = dict_val
remaining = remaining[1..<len(remaining)] # trim the "e"
return (d, remaining)
if s[0] == 'l': # list
var list = BENode(kind: tkList, listVal: @[])
var remaining = s[1..<len(s)]
while len(remaining) != 0 and remaining[0] != 'e':
var decoded = bdec(remaining)
list.listVal.add decoded.obj
remaining = decoded.remaining
return (list, remaining)
# assume it is a byte string
let colon_pos = s.find(':')
if colon_pos == -1:
return (BENode(kind: tkEof), s)
var bytes_len: int
try:
bytes_len = s[0..<colon_pos].parseInt
except:
return (BENode(kind: tkEof), s)
let bytes_string = s[(colon_pos+1)..(colon_pos+bytes_len)]
let remaining = s[(colon_pos + bytes_len + 1)..<len(s)]
return (BENode(kind: tkBytes, strVal: bytes_string), remaining)
proc bdecode*(s: string): BENode =
## Decode string into BENode tree
return bdec(s).obj
proc bencode*(self: BENode): string
proc bencode*(i: int): string =
## Encode int
"i$#e" % $i
proc bencode*(s: string): string =
## Encode string
"$#:$#" % [$len(s), s]
proc bencode*(sequence: seq[int]): string =
## Encode sequence of ints
result = "l"
for item in sequence:
result.add bencode(item)
result.add("e")
proc bencode*(sequence: seq[string]): string =
## Encode sequence of strings
result = "l"
for item in sequence:
result.add bencode(item)
result.add("e")
proc bencode*(t: Table): string =
## Encode table
result = "d"
var keys: seq[string] = @[]
for k in t.keys():
keys.add k
keys.sort(proc (x,y: string): int = cmp(x, y))
for k in keys:
result.add bencode(k)
result.add bencode(t[k])
result.add("e")
proc bencode*(sequence: seq[BENode]): string =
## Encode sequence of BENode
result = "l"
for item in sequence:
result.add bencode(item)
result.add("e")
proc bencode*(self: BENode): string =
## Encode BENode tree
case self.kind:
of tkInt:
return self.intVal.bencode
of tkBytes:
return self.strVal.bencode
of tkList:
return self.listVal.bencode
of tkDict:
return self.dictVal.bencode
of tkEof:
raise newException(Exception, "unexpected tkEof item")
proc bencode*[A, B](pairs: openArray[(A, B)]): string =
pairs.toTable.bencode
proc BEString*(s: string): BENode =
BENode(kind: tkBytes, strVal:s)
proc BEDict*(t: Table[string, BENode]): BENode =
BENode(kind: tkDict, dictVal: t)
proc toBENode*(j: JsonNode): BENode =
## Convert Json object to BENode
case j.kind:
of JString:
return BEString(j.str)
of JInt:
return BENode(kind: tkInt, intVal: j.num.int)
of JObject:
result = BENode(kind: tkDict, dictVal: initTable[string, BENode]())
for pair in j.pairs:
let val = pair.val.toBENode()
result.dictVal[pair.key] = val
of JArray:
result = BENode(kind: tkList, listVal: @[])
for e in j.elems:
result.listVal.add e.toBENode()
else:
raise newException(Exception, "unable to bencode unsupported type")
proc bencode*(j: JsonNode): string =
j.toBENode.bencode