]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | # |
2 | # Licensed to the Apache Software Foundation (ASF) under one | |
3 | # or more contributor license agreements. See the NOTICE file | |
4 | # distributed with this work for additional information | |
5 | # regarding copyright ownership. The ASF licenses this file | |
6 | # to you under the Apache License, Version 2.0 (the | |
7 | # "License"); you may not use this file except in compliance | |
8 | # with the License. You may obtain a copy of the License at | |
9 | # | |
10 | # http://www.apache.org/licenses/LICENSE-2.0 | |
11 | # | |
12 | # Unless required by applicable law or agreed to in writing, | |
13 | # software distributed under the License is distributed on an | |
14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | # KIND, either express or implied. See the License for the | |
16 | # specific language governing permissions and limitations | |
17 | # under the License. | |
18 | # | |
19 | ||
20 | module Thrift | |
21 | class CompactProtocol < BaseProtocol | |
22 | ||
23 | PROTOCOL_ID = [0x82].pack('c').unpack('c').first | |
24 | VERSION = 1 | |
25 | VERSION_MASK = 0x1f | |
26 | TYPE_MASK = 0xE0 | |
27 | TYPE_BITS = 0x07 | |
28 | TYPE_SHIFT_AMOUNT = 5 | |
29 | ||
30 | TSTOP = ["", Types::STOP, 0] | |
31 | ||
32 | # | |
33 | # All of the on-wire type codes. | |
34 | # | |
35 | class CompactTypes | |
36 | BOOLEAN_TRUE = 0x01 | |
37 | BOOLEAN_FALSE = 0x02 | |
38 | BYTE = 0x03 | |
39 | I16 = 0x04 | |
40 | I32 = 0x05 | |
41 | I64 = 0x06 | |
42 | DOUBLE = 0x07 | |
43 | BINARY = 0x08 | |
44 | LIST = 0x09 | |
45 | SET = 0x0A | |
46 | MAP = 0x0B | |
47 | STRUCT = 0x0C | |
48 | ||
49 | def self.is_bool_type?(b) | |
50 | (b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE | |
51 | end | |
52 | ||
53 | COMPACT_TO_TTYPE = { | |
54 | Types::STOP => Types::STOP, | |
55 | BOOLEAN_FALSE => Types::BOOL, | |
56 | BOOLEAN_TRUE => Types::BOOL, | |
57 | BYTE => Types::BYTE, | |
58 | I16 => Types::I16, | |
59 | I32 => Types::I32, | |
60 | I64 => Types::I64, | |
61 | DOUBLE => Types::DOUBLE, | |
62 | BINARY => Types::STRING, | |
63 | LIST => Types::LIST, | |
64 | SET => Types::SET, | |
65 | MAP => Types::MAP, | |
66 | STRUCT => Types::STRUCT | |
67 | } | |
68 | ||
69 | TTYPE_TO_COMPACT = { | |
70 | Types::STOP => Types::STOP, | |
71 | Types::BOOL => BOOLEAN_TRUE, | |
72 | Types::BYTE => BYTE, | |
73 | Types::I16 => I16, | |
74 | Types::I32 => I32, | |
75 | Types::I64 => I64, | |
76 | Types::DOUBLE => DOUBLE, | |
77 | Types::STRING => BINARY, | |
78 | Types::LIST => LIST, | |
79 | Types::SET => SET, | |
80 | Types::MAP => MAP, | |
81 | Types::STRUCT => STRUCT | |
82 | } | |
83 | ||
84 | def self.get_ttype(compact_type) | |
85 | val = COMPACT_TO_TTYPE[compact_type & 0x0f] | |
86 | raise "don't know what type: #{compact_type & 0x0f}" unless val | |
87 | val | |
88 | end | |
89 | ||
90 | def self.get_compact_type(ttype) | |
91 | val = TTYPE_TO_COMPACT[ttype] | |
92 | raise "don't know what type: #{ttype & 0x0f}" unless val | |
93 | val | |
94 | end | |
95 | end | |
96 | ||
97 | def initialize(transport) | |
98 | super(transport) | |
99 | ||
100 | @last_field = [0] | |
101 | @boolean_value = nil | |
102 | ||
103 | # Pre-allocated read buffer for read_double(). | |
104 | @rbuf = Bytes.empty_byte_buffer(8) | |
105 | end | |
106 | ||
107 | def write_message_begin(name, type, seqid) | |
108 | write_byte(PROTOCOL_ID) | |
109 | write_byte((VERSION & VERSION_MASK) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK)) | |
110 | write_varint32(seqid) | |
111 | write_string(name) | |
112 | nil | |
113 | end | |
114 | ||
115 | def write_struct_begin(name) | |
116 | @last_field.push(0) | |
117 | nil | |
118 | end | |
119 | ||
120 | def write_struct_end | |
121 | @last_field.pop | |
122 | nil | |
123 | end | |
124 | ||
125 | def write_field_begin(name, type, id) | |
126 | if type == Types::BOOL | |
127 | # we want to possibly include the value, so we'll wait. | |
128 | @boolean_field = [type, id] | |
129 | else | |
130 | write_field_begin_internal(type, id) | |
131 | end | |
132 | nil | |
133 | end | |
134 | ||
135 | # | |
136 | # The workhorse of writeFieldBegin. It has the option of doing a | |
137 | # 'type override' of the type header. This is used specifically in the | |
138 | # boolean field case. | |
139 | # | |
140 | def write_field_begin_internal(type, id, type_override=nil) | |
141 | last_id = @last_field.pop | |
142 | ||
143 | # if there's a type override, use that. | |
144 | typeToWrite = type_override || CompactTypes.get_compact_type(type) | |
145 | ||
146 | # check if we can use delta encoding for the field id | |
147 | if id > last_id && id - last_id <= 15 | |
148 | # write them together | |
149 | write_byte((id - last_id) << 4 | typeToWrite) | |
150 | else | |
151 | # write them separate | |
152 | write_byte(typeToWrite) | |
153 | write_i16(id) | |
154 | end | |
155 | ||
156 | @last_field.push(id) | |
157 | nil | |
158 | end | |
159 | ||
160 | def write_field_stop | |
161 | write_byte(Types::STOP) | |
162 | end | |
163 | ||
164 | def write_map_begin(ktype, vtype, size) | |
165 | if (size == 0) | |
166 | write_byte(0) | |
167 | else | |
168 | write_varint32(size) | |
169 | write_byte(CompactTypes.get_compact_type(ktype) << 4 | CompactTypes.get_compact_type(vtype)) | |
170 | end | |
171 | end | |
172 | ||
173 | def write_list_begin(etype, size) | |
174 | write_collection_begin(etype, size) | |
175 | end | |
176 | ||
177 | def write_set_begin(etype, size) | |
178 | write_collection_begin(etype, size); | |
179 | end | |
180 | ||
181 | def write_bool(bool) | |
182 | type = bool ? CompactTypes::BOOLEAN_TRUE : CompactTypes::BOOLEAN_FALSE | |
183 | unless @boolean_field.nil? | |
184 | # we haven't written the field header yet | |
185 | write_field_begin_internal(@boolean_field.first, @boolean_field.last, type) | |
186 | @boolean_field = nil | |
187 | else | |
188 | # we're not part of a field, so just write the value. | |
189 | write_byte(type) | |
190 | end | |
191 | end | |
192 | ||
193 | def write_byte(byte) | |
194 | @trans.write([byte].pack('c')) | |
195 | end | |
196 | ||
197 | def write_i16(i16) | |
198 | write_varint32(int_to_zig_zag(i16)) | |
199 | end | |
200 | ||
201 | def write_i32(i32) | |
202 | write_varint32(int_to_zig_zag(i32)) | |
203 | end | |
204 | ||
205 | def write_i64(i64) | |
206 | write_varint64(long_to_zig_zag(i64)) | |
207 | end | |
208 | ||
209 | def write_double(dub) | |
210 | @trans.write([dub].pack("G").reverse) | |
211 | end | |
212 | ||
213 | def write_string(str) | |
214 | buf = Bytes.convert_to_utf8_byte_buffer(str) | |
215 | write_binary(buf) | |
216 | end | |
217 | ||
218 | def write_binary(buf) | |
219 | write_varint32(buf.bytesize) | |
220 | @trans.write(buf) | |
221 | end | |
222 | ||
223 | def read_message_begin | |
224 | protocol_id = read_byte() | |
225 | if protocol_id != PROTOCOL_ID | |
226 | raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}") | |
227 | end | |
228 | ||
229 | version_and_type = read_byte() | |
230 | version = version_and_type & VERSION_MASK | |
231 | if (version != VERSION) | |
232 | raise ProtocolException.new("Expected version #{VERSION} but got #{version}"); | |
233 | end | |
234 | ||
235 | type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS | |
236 | seqid = read_varint32() | |
237 | messageName = read_string() | |
238 | [messageName, type, seqid] | |
239 | end | |
240 | ||
241 | def read_struct_begin | |
242 | @last_field.push(0) | |
243 | "" | |
244 | end | |
245 | ||
246 | def read_struct_end | |
247 | @last_field.pop() | |
248 | nil | |
249 | end | |
250 | ||
251 | def read_field_begin | |
252 | type = read_byte() | |
253 | ||
254 | # if it's a stop, then we can return immediately, as the struct is over. | |
255 | if (type & 0x0f) == Types::STOP | |
256 | TSTOP | |
257 | else | |
258 | field_id = nil | |
259 | ||
260 | # mask off the 4 MSB of the type header. it could contain a field id delta. | |
261 | modifier = (type & 0xf0) >> 4 | |
262 | if modifier == 0 | |
263 | # not a delta. look ahead for the zigzag varint field id. | |
264 | @last_field.pop | |
265 | field_id = read_i16() | |
266 | else | |
267 | # has a delta. add the delta to the last read field id. | |
268 | field_id = @last_field.pop + modifier | |
269 | end | |
270 | ||
271 | # if this happens to be a boolean field, the value is encoded in the type | |
272 | if CompactTypes.is_bool_type?(type) | |
273 | # save the boolean value in a special instance variable. | |
274 | @bool_value = (type & 0x0f) == CompactTypes::BOOLEAN_TRUE | |
275 | end | |
276 | ||
277 | # push the new field onto the field stack so we can keep the deltas going. | |
278 | @last_field.push(field_id) | |
279 | ["", CompactTypes.get_ttype(type & 0x0f), field_id] | |
280 | end | |
281 | end | |
282 | ||
283 | def read_map_begin | |
284 | size = read_varint32() | |
285 | key_and_value_type = size == 0 ? 0 : read_byte() | |
286 | [CompactTypes.get_ttype(key_and_value_type >> 4), CompactTypes.get_ttype(key_and_value_type & 0xf), size] | |
287 | end | |
288 | ||
289 | def read_list_begin | |
290 | size_and_type = read_byte() | |
291 | size = (size_and_type >> 4) & 0x0f | |
292 | if size == 15 | |
293 | size = read_varint32() | |
294 | end | |
295 | type = CompactTypes.get_ttype(size_and_type) | |
296 | [type, size] | |
297 | end | |
298 | ||
299 | def read_set_begin | |
300 | read_list_begin | |
301 | end | |
302 | ||
303 | def read_bool | |
304 | unless @bool_value.nil? | |
305 | bv = @bool_value | |
306 | @bool_value = nil | |
307 | bv | |
308 | else | |
309 | read_byte() == CompactTypes::BOOLEAN_TRUE | |
310 | end | |
311 | end | |
312 | ||
313 | def read_byte | |
314 | val = trans.read_byte | |
315 | if (val > 0x7f) | |
316 | val = 0 - ((val - 1) ^ 0xff) | |
317 | end | |
318 | val | |
319 | end | |
320 | ||
321 | def read_i16 | |
322 | zig_zag_to_int(read_varint32()) | |
323 | end | |
324 | ||
325 | def read_i32 | |
326 | zig_zag_to_int(read_varint32()) | |
327 | end | |
328 | ||
329 | def read_i64 | |
330 | zig_zag_to_long(read_varint64()) | |
331 | end | |
332 | ||
333 | def read_double | |
334 | trans.read_into_buffer(@rbuf, 8) | |
335 | val = @rbuf.reverse.unpack('G').first | |
336 | val | |
337 | end | |
338 | ||
339 | def read_string | |
340 | buffer = read_binary | |
341 | Bytes.convert_to_string(buffer) | |
342 | end | |
343 | ||
344 | def read_binary | |
345 | size = read_varint32() | |
346 | trans.read_all(size) | |
347 | end | |
348 | ||
349 | def to_s | |
350 | "compact(#{super.to_s})" | |
351 | end | |
352 | ||
353 | private | |
354 | ||
355 | # | |
356 | # Abstract method for writing the start of lists and sets. List and sets on | |
357 | # the wire differ only by the type indicator. | |
358 | # | |
359 | def write_collection_begin(elem_type, size) | |
360 | if size <= 14 | |
361 | write_byte(size << 4 | CompactTypes.get_compact_type(elem_type)) | |
362 | else | |
363 | write_byte(0xf0 | CompactTypes.get_compact_type(elem_type)) | |
364 | write_varint32(size) | |
365 | end | |
366 | end | |
367 | ||
368 | def write_varint32(n) | |
369 | # int idx = 0; | |
370 | while true | |
371 | if (n & ~0x7F) == 0 | |
372 | # i32buf[idx++] = (byte)n; | |
373 | write_byte(n) | |
374 | break | |
375 | # return; | |
376 | else | |
377 | # i32buf[idx++] = (byte)((n & 0x7F) | 0x80); | |
378 | write_byte((n & 0x7F) | 0x80) | |
379 | n = n >> 7 | |
380 | end | |
381 | end | |
382 | # trans_.write(i32buf, 0, idx); | |
383 | end | |
384 | ||
385 | SEVEN_BIT_MASK = 0x7F | |
386 | EVERYTHING_ELSE_MASK = ~SEVEN_BIT_MASK | |
387 | ||
388 | def write_varint64(n) | |
389 | while true | |
390 | if (n & EVERYTHING_ELSE_MASK) == 0 #TODO need to find a way to make this into a long... | |
391 | write_byte(n) | |
392 | break | |
393 | else | |
394 | write_byte((n & SEVEN_BIT_MASK) | 0x80) | |
395 | n >>= 7 | |
396 | end | |
397 | end | |
398 | end | |
399 | ||
400 | def read_varint32() | |
401 | read_varint64() | |
402 | end | |
403 | ||
404 | def read_varint64() | |
405 | shift = 0 | |
406 | result = 0 | |
407 | while true | |
408 | b = read_byte() | |
409 | result |= (b & 0x7f) << shift | |
410 | break if (b & 0x80) != 0x80 | |
411 | shift += 7 | |
412 | end | |
413 | result | |
414 | end | |
415 | ||
416 | def int_to_zig_zag(n) | |
417 | (n << 1) ^ (n >> 31) | |
418 | end | |
419 | ||
420 | def long_to_zig_zag(l) | |
421 | # puts "zz encoded #{l} to #{(l << 1) ^ (l >> 63)}" | |
422 | (l << 1) ^ (l >> 63) | |
423 | end | |
424 | ||
425 | def zig_zag_to_int(n) | |
426 | (n >> 1) ^ -(n & 1) | |
427 | end | |
428 | ||
429 | def zig_zag_to_long(n) | |
430 | (n >> 1) ^ -(n & 1) | |
431 | end | |
432 | end | |
433 | ||
434 | class CompactProtocolFactory < BaseProtocolFactory | |
435 | def get_protocol(trans) | |
436 | CompactProtocol.new(trans) | |
437 | end | |
438 | ||
439 | def to_s | |
440 | "compact" | |
441 | end | |
442 | end | |
443 | end |