-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbencoding.cl
181 lines (163 loc) · 5.26 KB
/
bencoding.cl
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
(in-package :user)
(defmacro digit-byte-p (byte)
(let ((x (gensym)))
`(let ((,x ,byte))
(and (>= ,x #.(char-code #\0))
(<= ,x #.(char-code #\9))))))
(defun decode-number (buf start end &key integer)
(declare (ausb8 buf)
(fixnum start end))
(if (= start end)
(error "Invalid encoding (0-length number)"))
(let ((byte (aref buf start))
negative)
(declare (usb8 byte))
(if* (= byte #.(char-code #\-))
then (if (null integer)
(error "Invalid encoding: Negative not allowed"))
(setf negative t)
(incf start)
elseif (= byte #.(char-code #\0))
then (incf start)
(if (= start end)
(return-from decode-number 0))
(error "Invalid encoding (number prefixed with 0)"))
(let ((count 0))
(declare (fixnum count))
(while (< start end)
(setf byte (aref buf start))
(if (not (digit-byte-p byte))
(error "Invalid encoding: (non-digit '~c' in number)"
(code-char byte)))
(setf count (+ (* count 10) (- byte #.(char-code #\0))))
(incf start))
(if* negative
then (if (zerop count)
(error "Invalid encoding (negative zero)"))
(- count)
else count))))
;; external format is supposed to be utf8 but that might screw
;; up the binary sha1 hashes for the pieces.
(defun decode-byte-string (buf pos)
(declare (ausb8 buf)
(fixnum pos))
(let* ((end (position #.(char-code #\:) buf :start pos))
(count (decode-number buf pos end))
(start (1+ end))
(stop (+ start count)))
(declare (fixnum end count start stop))
#+ignore(values (subseq buf start stop) stop)
(values (octets-to-string buf :start start :end stop
:external-format :latin1)
stop)))
(defun decode-integer (buf pos)
(declare (ausb8 buf)
(fixnum pos))
(if (/= (aref buf pos) #.(char-code #\i))
(error "Invalid integer encoding (doesn't begin with 'i')"))
(let ((end (position #.(char-code #\e) buf :start pos)))
(if (null end)
(error "Invalid integer encoding (doesn't end with 'e')"))
(values
(decode-number buf (1+ pos) end :integer t)
(1+ end))))
(defun decode-list (buf pos)
(declare (ausb8 buf)
(fixnum pos))
(let ((byte (aref buf pos))
thing
res)
(declare (usb8 byte))
(if (/= byte #.(char-code #\l))
(error "Invalid list encoding (doesn't begin with 'l')"))
(incf pos)
(while (/= (setf byte (aref buf pos)) #.(char-code #\e))
(multiple-value-setq (thing pos)
(decode-bencoding buf pos))
(push thing res))
(values (nreverse res) (1+ pos))))
(defstruct dict
entries)
(defun dict-get (key dict)
(cdr (assoc key (dict-entries dict) :test #'equal)))
(defun decode-dictionary (buf pos)
(declare (ausb8 buf)
(fixnum pos))
(let ((byte (aref buf pos))
res)
(declare (usb8 byte))
(if (/= byte #.(char-code #\d))
(error "Invalid dictionary encoding (doesn't begin with 'd')"))
(incf pos)
(while (/= (setf byte (aref buf pos)) #.(char-code #\e))
(multiple-value-bind (key newpos)
(decode-byte-string buf pos)
(setf pos newpos)
(multiple-value-bind (value newpos)
(decode-bencoding buf pos)
(setf pos newpos)
(push (cons key value) res))))
(values (make-dict :entries (nreverse res)) (1+ pos))))
(defun decode-bencoding (buf &optional (pos 0))
(declare (ausb8 buf)
(fixnum pos))
(if (stringp buf)
(setf buf (string-to-octets buf
:null-terminate nil
:external-format :latin1)))
(let ((byte (aref buf pos)))
(declare (usb8 byte))
(if* (digit-byte-p byte)
then (decode-byte-string buf pos)
else (case byte
(#.(char-code #\i)
(decode-integer buf pos))
(#.(char-code #\l)
(decode-list buf pos))
(#.(char-code #\d)
(decode-dictionary buf pos))
(t
(error "Invalid encoding: '~c'" (code-char byte)))))))
(defun bdecode-file (filename)
(decode-bencoding (file-contents filename :element-type '(unsigned-byte 8))))
(defun encode-byte-string (string)
(let ((tmp (format nil "~a:~a" (length string) string)))
(string-to-octets tmp
:null-terminate nil
:external-format :latin1
:end (length tmp))))
(defun encode-integer (int)
(string-to-octets (format nil "i~ae" int)
:null-terminate nil
:external-format :latin1))
(defun encode-list (list)
(let (res)
(push (load-time-value (make-usb8 1 :initial-element #.(char-code #\l)))
res)
(dolist (entry list)
(push (bencode entry) res))
(push (load-time-value (make-usb8 1 :initial-element #.(char-code #\e)))
res)
(setf res (nreverse res))
(apply #'concatenate 'ausb8 res)))
(defun encode-dictionary (dict)
(let (res)
(push (load-time-value (make-usb8 1 :initial-element #.(char-code #\d)))
res)
(dolist (entry (dict-entries dict))
(push (bencode (car entry)) res)
(push (bencode (cdr entry)) res))
(push (load-time-value (make-usb8 1 :initial-element #.(char-code #\e)))
res)
(setf res (nreverse res))
(apply #'concatenate 'ausb8 res)))
(defun bencode (thing)
(if* (stringp thing)
then (encode-byte-string thing)
elseif (numberp thing)
then (encode-integer thing)
elseif (dict-p thing)
then (encode-dictionary thing)
elseif (listp thing)
then (encode-list thing)
else (error "unsupported data: ~s" thing)))