blob: c8f436559b9ca8b489cf8481558b098f36bd3e93 [file] [log] [blame]
#
# 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.
#
module Thrift
class CompactProtocol < BaseProtocol
PROTOCOL_ID = [0x82].pack('c').unpack('c').first
VERSION = 1
VERSION_MASK = 0x1f
TYPE_MASK = 0xE0
TYPE_SHIFT_AMOUNT = 5
TSTOP = ["", Types::STOP, 0]
#
# All of the on-wire type codes.
#
class CompactTypes
BOOLEAN_TRUE = 0x01
BOOLEAN_FALSE = 0x02
BYTE = 0x03
I16 = 0x04
I32 = 0x05
I64 = 0x06
DOUBLE = 0x07
BINARY = 0x08
LIST = 0x09
SET = 0x0A
MAP = 0x0B
STRUCT = 0x0C
def self.is_bool_type?(b)
(b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE
end
COMPACT_TO_TTYPE = {
Types::STOP => Types::STOP,
BOOLEAN_FALSE => Types::BOOL,
BOOLEAN_TRUE => Types::BOOL,
BYTE => Types::BYTE,
I16 => Types::I16,
I32 => Types::I32,
I64 => Types::I64,
DOUBLE => Types::DOUBLE,
BINARY => Types::STRING,
LIST => Types::LIST,
SET => Types::SET,
MAP => Types::MAP,
STRUCT => Types::STRUCT
}
TTYPE_TO_COMPACT = {
Types::STOP => Types::STOP,
Types::BOOL => BOOLEAN_TRUE,
Types::BYTE => BYTE,
Types::I16 => I16,
Types::I32 => I32,
Types::I64 => I64,
Types::DOUBLE => DOUBLE,
Types::STRING => BINARY,
Types::LIST => LIST,
Types::SET => SET,
Types::MAP => MAP,
Types::STRUCT => STRUCT
}
def self.get_ttype(compact_type)
val = COMPACT_TO_TTYPE[compact_type & 0x0f]
raise "don't know what type: #{compact_type & 0x0f}" unless val
val
end
def self.get_compact_type(ttype)
val = TTYPE_TO_COMPACT[ttype]
raise "don't know what type: #{ttype & 0x0f}" unless val
val
end
end
def initialize(transport)
super(transport)
@last_field = [0]
@boolean_value = nil
end
def write_message_begin(name, type, seqid)
write_byte(PROTOCOL_ID)
write_byte((VERSION & VERSION_MASK) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK))
write_varint32(seqid)
write_string(name)
nil
end
def write_struct_begin(name)
@last_field.push(0)
nil
end
def write_struct_end
@last_field.pop
nil
end
def write_field_begin(name, type, id)
if type == Types::BOOL
# we want to possibly include the value, so we'll wait.
@boolean_field = [type, id]
else
write_field_begin_internal(type, id)
end
nil
end
#
# The workhorse of writeFieldBegin. It has the option of doing a
# 'type override' of the type header. This is used specifically in the
# boolean field case.
#
def write_field_begin_internal(type, id, type_override=nil)
last_id = @last_field.pop
# if there's a type override, use that.
typeToWrite = type_override || CompactTypes.get_compact_type(type)
# check if we can use delta encoding for the field id
if id > last_id && id - last_id <= 15
# write them together
write_byte((id - last_id) << 4 | typeToWrite)
else
# write them separate
write_byte(typeToWrite)
write_i16(id)
end
@last_field.push(id)
nil
end
def write_field_stop
write_byte(Types::STOP)
end
def write_map_begin(ktype, vtype, size)
if (size == 0)
write_byte(0)
else
write_varint32(size)
write_byte(CompactTypes.get_compact_type(ktype) << 4 | CompactTypes.get_compact_type(vtype))
end
end
def write_list_begin(etype, size)
write_collection_begin(etype, size)
end
def write_set_begin(etype, size)
write_collection_begin(etype, size);
end
def write_bool(bool)
type = bool ? CompactTypes::BOOLEAN_TRUE : CompactTypes::BOOLEAN_FALSE
unless @boolean_field.nil?
# we haven't written the field header yet
write_field_begin_internal(@boolean_field.first, @boolean_field.last, type)
@boolean_field = nil
else
# we're not part of a field, so just write the value.
write_byte(type)
end
end
def write_byte(byte)
@trans.write([byte].pack('c'))
end
def write_i16(i16)
write_varint32(int_to_zig_zag(i16))
end
def write_i32(i32)
write_varint32(int_to_zig_zag(i32))
end
def write_i64(i64)
write_varint64(long_to_zig_zag(i64))
end
def write_double(dub)
@trans.write([dub].pack("G").reverse)
end
def write_string(str)
write_varint32(str.length)
@trans.write(str)
end
def read_message_begin
protocol_id = read_byte()
if protocol_id != PROTOCOL_ID
raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}")
end
version_and_type = read_byte()
version = version_and_type & VERSION_MASK
if (version != VERSION)
raise ProtocolException.new("Expected version #{VERSION} but got #{version}");
end
type = (version_and_type >> TYPE_SHIFT_AMOUNT) & 0x03
seqid = read_varint32()
messageName = read_string()
[messageName, type, seqid]
end
def read_struct_begin
@last_field.push(0)
""
end
def read_struct_end
@last_field.pop()
nil
end
def read_field_begin
type = read_byte()
# if it's a stop, then we can return immediately, as the struct is over.
if (type & 0x0f) == Types::STOP
TSTOP
else
field_id = nil
# mask off the 4 MSB of the type header. it could contain a field id delta.
modifier = (type & 0xf0) >> 4
if modifier == 0
# not a delta. look ahead for the zigzag varint field id.
field_id = read_i16()
else
# has a delta. add the delta to the last read field id.
field_id = @last_field.pop + modifier
end
# if this happens to be a boolean field, the value is encoded in the type
if CompactTypes.is_bool_type?(type)
# save the boolean value in a special instance variable.
@bool_value = (type & 0x0f) == CompactTypes::BOOLEAN_TRUE
end
# push the new field onto the field stack so we can keep the deltas going.
@last_field.push(field_id)
["", CompactTypes.get_ttype(type & 0x0f), field_id]
end
end
def read_map_begin
size = read_varint32()
key_and_value_type = size == 0 ? 0 : read_byte()
[CompactTypes.get_ttype(key_and_value_type >> 4), CompactTypes.get_ttype(key_and_value_type & 0xf), size]
end
def read_list_begin
size_and_type = read_byte()
size = (size_and_type >> 4) & 0x0f
if size == 15
size = read_varint32()
end
type = CompactTypes.get_ttype(size_and_type)
[type, size]
end
def read_set_begin
read_list_begin
end
def read_bool
unless @bool_value.nil?
bv = @bool_value
@bool_value = nil
bv
else
read_byte() == CompactTypes::BOOLEAN_TRUE
end
end
def read_byte
dat = trans.read_all(1)
val = dat[0]
if (val > 0x7f)
val = 0 - ((val - 1) ^ 0xff)
end
val
end
def read_i16
zig_zag_to_int(read_varint32())
end
def read_i32
zig_zag_to_int(read_varint32())
end
def read_i64
zig_zag_to_long(read_varint64())
end
def read_double
dat = trans.read_all(8)
val = dat.reverse.unpack('G').first
val
end
def read_string
size = read_varint32()
trans.read_all(size)
end
private
#
# Abstract method for writing the start of lists and sets. List and sets on
# the wire differ only by the type indicator.
#
def write_collection_begin(elem_type, size)
if size <= 14
write_byte(size << 4 | CompactTypes.get_compact_type(elem_type))
else
write_byte(0xf0 | CompactTypes.get_compact_type(elem_type))
write_varint32(size)
end
end
def write_varint32(n)
# int idx = 0;
while true
if (n & ~0x7F) == 0
# i32buf[idx++] = (byte)n;
write_byte(n)
break
# return;
else
# i32buf[idx++] = (byte)((n & 0x7F) | 0x80);
write_byte((n & 0x7F) | 0x80)
n = n >> 7
end
end
# trans_.write(i32buf, 0, idx);
end
SEVEN_BIT_MASK = 0x7F
EVERYTHING_ELSE_MASK = ~SEVEN_BIT_MASK
def write_varint64(n)
while true
if (n & EVERYTHING_ELSE_MASK) == 0 #TODO need to find a way to make this into a long...
write_byte(n)
break
else
write_byte((n & SEVEN_BIT_MASK) | 0x80)
n >>= 7
end
end
end
def read_varint32()
read_varint64()
end
def read_varint64()
shift = 0
result = 0
while true
b = read_byte()
result |= (b & 0x7f) << shift
break if (b & 0x80) != 0x80
shift += 7
end
result
end
def int_to_zig_zag(n)
(n << 1) ^ (n >> 31)
end
def long_to_zig_zag(l)
# puts "zz encoded #{l} to #{(l << 1) ^ (l >> 63)}"
(l << 1) ^ (l >> 63)
end
def zig_zag_to_int(n)
(n >> 1) ^ -(n & 1)
end
def zig_zag_to_long(n)
(n >> 1) ^ -(n & 1)
end
end
class CompactProtocolFactory < BaseProtocolFactory
def get_protocol(trans)
CompactProtocol.new(trans)
end
end
end