-
Notifications
You must be signed in to change notification settings - Fork 7
/
binary-protocol.lisp
286 lines (229 loc) · 13.2 KB
/
binary-protocol.lisp
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.apache.thrift.implementation; -*-
(in-package :org.apache.thrift.implementation)
;;; This file defines the concrete `binary-protocol` layer for the `org.apache.thrift` library.
;;;
;;; copyright 2010 [james anderson](james.anderson@setf.de)
;;;
;;; Licensed to the Apache Software Foundation (ASF) under one
;;; or more contributor license agreements. See the NOTICE file
;;; distributed with this work for additional information
;;; regarding copyright ownership. The ASF licenses this file
;;; to you under the Apache License, Version 2.0 (the
;;; "License"); you may not use this file except in compliance
;;; with the License. You may obtain a copy of the License at
;;;
;;; http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing,
;;; software distributed under the License is distributed on an
;;; "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
;;; KIND, either express or implied. See the License for the
;;; specific language governing permissions and limitations
;;; under the License.
;;;
;;; classes
(defclass binary-protocol (encoded-protocol)
((field-id-mode :initform :identifier-number :allocation :class)
(struct-id-mode :initform :none :allocation :class))
(:default-initargs
:version-id #x80
:version-number #x01))
;;;
;;; type code <-> name operators are specific to each protocol
(defmethod type-code-name ((protocol binary-protocol) (type-code fixnum))
(or (car (rassoc type-code *binary-transport-types* :test #'eql))
(error "Invalid type code: ~s." type-code)))
(defmethod type-name-code ((protocol binary-protocol) (type-name symbol))
(or (cdr (assoc type-name *binary-transport-types*))
(error "Invalid type name: ~s." type-name)))
(defmethod type-name-code ((transport binary-protocol) (type-name cons))
(type-name-code transport (first type-name)))
(defmethod message-type-code ((protocol binary-protocol) (message-name symbol))
(or (cdr (assoc message-name *binary-message-types*))
(error "Invalid message type name: ~s." message-name)))
(defmethod message-type-name ((protocol binary-protocol) (type-code fixnum))
(or (car (rassoc type-code *binary-message-types* :test 'eql))
(error "Invalid message type code: ~s." type-code)))
;;; input
(defmethod stream-read-type ((protocol binary-protocol))
(type-code-name protocol (stream-read-byte (protocol-input-transport protocol))))
(defmethod stream-read-message-type ((protocol binary-protocol))
(message-type-name protocol (stream-read-i16 protocol)))
(defmethod stream-read-bool ((protocol binary-protocol))
(= (stream-read-byte (protocol-input-transport protocol)) 1))
(defmethod stream-read-i08 ((protocol binary-protocol))
(stream-read-byte (protocol-input-transport protocol)))
(macrolet ((read-and-decode-integer (protocol byte-count &aux (bit-count (* byte-count 8)))
`(let ((value 0)
(buffer (make-array ,byte-count :element-type '(unsigned-byte 8))))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (,byte-count)) buffer)
(type (unsigned-byte ,(* byte-count 8)) value))
(stream-read-sequence (protocol-input-transport ,protocol) buffer)
,@(loop for i from 0 below byte-count
collect `(setf value ,(if (= i 0)
`(aref buffer ,i)
`(+ (ash value 8) (aref buffer ,i)))))
;; (format *trace-output* "(in 0x~16,'0x)" value)
(,(cons-symbol :org.apache.thrift.implementation
:signed-byte- (prin1-to-string bit-count)) value))))
(defmethod stream-read-i16 ((protocol binary-protocol))
(read-and-decode-integer protocol 2))
(defmethod stream-read-i32 ((protocol binary-protocol))
(read-and-decode-integer protocol 4))
(defmethod stream-read-i64 ((protocol binary-protocol))
(read-and-decode-integer protocol 8)))
(defmethod stream-read-double ((protocol binary-protocol))
#+allegro (let* ((buffer (make-array 8 :element-type *binary-transport-element-type*))
(b (stream-read-sequence protocol buffer)))
(declare (dynamic-extent buffer))
(apply #'excl:shorts-to-double-float
(mapcar #'bytes-int (list (subseq b 0 2) (subseq b 2 4)
(subseq b 4 6) (subseq b 6 8)))))
#-allegro (let ((value 0)
(buffer (make-array 8 :element-type '(unsigned-byte 8))))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (8)) buffer)
(type (unsigned-byte 64) value))
(stream-read-sequence (protocol-input-transport protocol) buffer)
;; it it matters, could unwrap it with fewer intermediates saves
(macrolet ((unpack-buffer ()
`(progn
,@(loop for i from 0 below 8
collect `(setf value ,(if (= i 0)
`(aref buffer ,i)
`(+ (ash value 8) (aref buffer ,i))))))))
(unpack-buffer)
(ieee-754-64-integer-to-float value))))
(defmethod stream-read-float ((protocol binary-protocol))
"As a special for for use with rdf - not part of the thrift. used just for specifically
coded struct declarations."
;; this is not part of the thrift spec, but is useful elsewhere
(let ((value 0)
(buffer (make-array 4 :element-type '(unsigned-byte 8))))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (4)) buffer)
(type (unsigned-byte 32) value))
(stream-read-sequence (protocol-input-transport protocol) buffer)
;; it it matters, could unwrap it with fewer intermediates saves
(macrolet ((unpack-buffer ()
`(progn
,@(loop for i from 0 below 4
collect `(setf value ,(if (= i 0)
`(aref buffer ,i)
`(+ (ash value 8) (aref buffer ,i))))))))
(unpack-buffer)
(ieee-754-32-integer-to-float value))))
(defmethod stream-read-string ((protocol binary-protocol))
(let* ((l (stream-read-i32 protocol))
(a (make-array l :element-type *binary-transport-element-type*)))
(declare (dynamic-extent a))
(stream-read-sequence (protocol-input-transport protocol) a)
(funcall (transport-string-decoder protocol) a)))
(defmethod stream-read-binary ((protocol binary-protocol))
"Read an 'unencoded' binary array.
Although the spec describes a 'byte' array, and elsewhere specifies bytes to be signed, that makes no
sense. It contradicts the encoding for UTF and would be generally useless for binary data. The various
extant language bindings read as if they either the issue or cast."
(let* ((l (stream-read-i32 protocol))
(result (make-array l :element-type *binary-transport-element-type*)))
;; would need to check the length before trying stack allocation
(stream-read-sequence (protocol-input-transport protocol) result)
result))
;;; output
(defmethod stream-write-type ((protocol binary-protocol) type-name)
(stream-write-byte (protocol-output-transport protocol) (type-name-code protocol type-name))
1)
(defmethod stream-write-message-type ((protocol binary-protocol) message-type-name)
(stream-write-i16 protocol (message-type-code protocol message-type-name)))
(defmethod stream-write-bool ((protocol binary-protocol) val)
(stream-write-byte (protocol-output-transport protocol) (if val 1 0))
1)
(defmethod stream-write-i08 ((protocol binary-protocol) val)
(stream-write-byte (protocol-output-transport protocol) val)
1)
(macrolet ((encode-and-write-integer (protocol value byte-count)
`(let ((buffer (make-array ,byte-count :element-type '(unsigned-byte 8))))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (,byte-count)) buffer))
(assert (typep ,value '(signed-byte ,(* byte-count 8))) ()
'type-error :datum ,value :expected-type '(signed-byte ,(* byte-count 8)))
(locally (declare (type (signed-byte ,(* byte-count 8)) ,value))
;; (format *trace-output* "~%(out 0x~16,'0x)" ,value)
,@(loop for i from (1- byte-count) downto 0
append `((setf (aref buffer ,i) (logand #xff ,value))
(setf ,value (ash ,value -8))))
(stream-write-sequence (protocol-output-transport ,protocol) buffer)
,byte-count))))
;; no sign conversion as shift&mask encodes the sign bit
(defmethod stream-write-i16 ((protocol binary-protocol) val)
(encode-and-write-integer protocol val 2))
(defmethod stream-write-i32 ((protocol binary-protocol) val)
(encode-and-write-integer protocol val 4))
(defmethod stream-write-i64 ((protocol binary-protocol) val)
(encode-and-write-integer protocol val 8)))
(defmethod stream-write-double ((protocol binary-protocol) val)
#+allegro (dolist (b (mapcar #'(lambda (x) (int-bytes x 2))
(multiple-value-list (excl:double-float-to-shorts
(coerce val 'double-float)))))
(stream-write-byte protocol b))
;; distinct from i64, as it's unsigned
#-allegro (let ((buffer (make-array 8 :element-type '(unsigned-byte 8)))
(int-value (ieee-754-64-float-to-integer val)))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (8)) buffer)
(type (unsigned-byte 64) int-value))
;; if the conversion is correct, this is redundant, sbcl eliminate it
(assert (typep int-value '(unsigned-byte 64)) ()
'type-error :datum int-value :expected-type '(unsigned-byte 64))
;; (format *trace-output* "~%(out 0x~16,'0x)" int-value)
(macrolet ((pack-buffer ()
`(progn ,@(loop for i from 7 downto 0
append `((setf (aref buffer ,i) (logand #xff int-value))
(setf int-value (ash int-value -8)))))))
(pack-buffer))
(stream-write-sequence (protocol-output-transport protocol) buffer)
8))
(defmethod stream-write-float ((protocol binary-protocol) val)
" Not part of the spec, but is useful elsewhere"
;; distinct from i34, as it's unsigned
(let ((buffer (make-array 4 :element-type '(unsigned-byte 8)))
(int-value (ieee-754-32-float-to-integer val)))
(declare (dynamic-extent buffer)
(type (simple-array (unsigned-byte 8) (4)) buffer)
(type (unsigned-byte 32) int-value))
;; if the conversion is correct, this is redundant, sbcl eliminate it
(assert (typep int-value '(unsigned-byte 32)) ()
'type-error :datum int-value :expected-type '(unsigned-byte 64))
;; (format *trace-output* "~%(out 0x~16,'0x)" int-value)
(macrolet ((pack-buffer ()
`(progn ,@(loop for i from 3 downto 0
append `((setf (aref buffer ,i) (logand #xff int-value))
(setf int-value (ash int-value -8)))))))
(pack-buffer))
(stream-write-sequence (protocol-output-transport protocol) buffer)
4))
(defmethod stream-write-string ((protocol binary-protocol) (string string) &optional (start 0) end)
(assert (and (zerop start) (or (null end) (= end (length string)))) ()
"Substring writes are not supported.")
(let ((bytes (funcall (transport-string-encoder protocol) string)))
(stream-write-i32 protocol (length bytes))
(stream-write-sequence (protocol-output-transport protocol) bytes)
(+ 4 (length bytes))))
(defmethod stream-write-string ((protocol binary-protocol) (bytes vector) &optional (start 0) end)
(assert (and (zerop start) (or (null end) (= end (length bytes)))) ()
"Substring writes are not supported.")
(stream-write-i32 protocol (length bytes))
(stream-write-sequence (protocol-output-transport protocol) bytes)
(+ 4 (length bytes)))
(defmethod stream-write-binary ((protocol binary-protocol) (bytes vector))
(let ((unsigned-bytes (make-array (length bytes) :element-type '(unsigned-byte 8))))
(stream-write-i32 protocol (length bytes))
(map-into unsigned-bytes #'unsigned-byte-8 bytes)
(stream-write-sequence (protocol-output-transport protocol) unsigned-bytes)
(+ 4 (length bytes))))
(defmethod stream-write-binary ((protocol binary-protocol) (string string))
(let ((bytes (funcall (transport-string-encoder protocol) string)))
(stream-write-i32 protocol (length bytes))
(stream-write-sequence (protocol-output-transport protocol) bytes)
(+ 4 (length bytes))))