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_compact_protocol).
21
22-behaviour(thrift_protocol).
23
24-include("thrift_constants.hrl").
25-include("thrift_protocol.hrl").
26
27-export([new/1, new/2,
28         read/2,
29         write/2,
30         flush_transport/1,
31         close_transport/1,
32         new_protocol_factory/2
33        ]).
34
35-define(ID_NONE, 16#10000).
36-define(CBOOL_NONE, 0).
37-define(CBOOL_TRUE, 1).
38-define(CBOOL_FALSE, 2).
39
40-record(t_compact, {transport,
41                           % state for pending boolean fields
42                           read_stack=[],
43                           read_value=?CBOOL_NONE,
44                           write_stack=[],
45                           write_id=?ID_NONE
46                          }).
47-type state() :: #t_compact{}.
48-include("thrift_protocol_behaviour.hrl").
49
50-define(PROTOCOL_ID, 16#82).
51-define(VERSION_MASK, 16#1f).
52-define(VERSION_1, 16#01).
53-define(TYPE_MASK, 16#E0).
54-define(TYPE_BITS, 16#07).
55-define(TYPE_SHIFT_AMOUNT, 5).
56
57typeid_to_compact(?tType_STOP) -> 16#0;
58typeid_to_compact(?tType_BOOL) -> 16#2;
59typeid_to_compact(?tType_I8) -> 16#3;
60typeid_to_compact(?tType_I16) -> 16#4;
61typeid_to_compact(?tType_I32) -> 16#5;
62typeid_to_compact(?tType_I64) -> 16#6;
63typeid_to_compact(?tType_DOUBLE) -> 16#7;
64typeid_to_compact(?tType_STRING) -> 16#8;
65typeid_to_compact(?tType_STRUCT) -> 16#C;
66typeid_to_compact(?tType_MAP) -> 16#B;
67typeid_to_compact(?tType_SET) -> 16#A;
68typeid_to_compact(?tType_LIST) -> 16#9.
69
70compact_to_typeid(16#0) ->  ?tType_STOP;
71compact_to_typeid(?CBOOL_FALSE) ->  ?tType_BOOL;
72compact_to_typeid(?CBOOL_TRUE) ->  ?tType_BOOL;
73compact_to_typeid(16#7) ->  ?tType_DOUBLE;
74compact_to_typeid(16#3) ->  ?tType_I8;
75compact_to_typeid(16#4) ->  ?tType_I16;
76compact_to_typeid(16#5) ->  ?tType_I32;
77compact_to_typeid(16#6) ->  ?tType_I64;
78compact_to_typeid(16#8) ->  ?tType_STRING;
79compact_to_typeid(16#C) ->  ?tType_STRUCT;
80compact_to_typeid(16#B) ->  ?tType_MAP;
81compact_to_typeid(16#A) ->  ?tType_SET;
82compact_to_typeid(16#9) ->  ?tType_LIST.
83
84bool_to_cbool(Value) when Value -> ?CBOOL_TRUE;
85bool_to_cbool(_) -> ?CBOOL_FALSE.
86cbool_to_bool(Value) -> Value =:= ?CBOOL_TRUE.
87
88new(Transport) -> new(Transport, _Options = []).
89
90new(Transport, _Options) ->
91  State  = #t_compact{transport = Transport},
92  thrift_protocol:new(?MODULE, State).
93
94flush_transport(This = #t_compact{transport = Transport}) ->
95  {NewTransport, Result} = thrift_transport:flush(Transport),
96  {This#t_compact{transport = NewTransport}, Result}.
97
98close_transport(This = #t_compact{transport = Transport}) ->
99  {NewTransport, Result} = thrift_transport:close(Transport),
100  {This#t_compact{transport = NewTransport}, Result}.
101
102%%%
103%%% instance methods
104%%%
105
106write_field_begin(This0 = #t_compact{write_stack=[LastId|T]}, CompactType, Id) ->
107  IdDiff = Id - LastId,
108  This1 = This0#t_compact{write_stack=[Id|T]},
109  case (IdDiff > 0) and (IdDiff < 16) of
110    true -> write(This1, {byte, (IdDiff bsl 4) bor CompactType});
111    false ->
112      {This2, ok} = write(This1, {byte, CompactType}),
113      write(This2, {i16, Id})
114  end.
115
116-spec to_zigzag(integer()) -> non_neg_integer().
117to_zigzag(Value) -> 16#FFFFFFFFFFFFFFFF band ((Value bsl 1) bxor (Value bsr 63)).
118
119-spec from_zigzag(non_neg_integer()) -> integer().
120from_zigzag(Value) -> (Value bsr 1) bxor -(Value band 1).
121
122-spec to_varint(non_neg_integer(), iolist()) -> iolist().
123to_varint(Value, Acc) when (Value < 16#80) -> [Acc, Value];
124to_varint(Value, Acc) ->
125  to_varint(Value bsr 7, [Acc, ((Value band 16#7F) bor 16#80)]).
126
127-spec read_varint(#t_compact{}, non_neg_integer(), non_neg_integer()) -> non_neg_integer().
128read_varint(This0, Acc, Count) ->
129  {This1, {ok, Byte}} = read(This0, byte),
130  case (Byte band 16#80) of
131    0 -> {This1, {ok, (Byte bsl (7 * Count)) + Acc}};
132    _ -> read_varint(This1, ((Byte band 16#7f) bsl (7 * Count)) + Acc, Count + 1)
133  end.
134
135write(This0, #protocol_message_begin{
136        name = Name,
137        type = Type,
138        seqid = Seqid}) ->
139  {This1, ok} = write(This0, {byte, ?PROTOCOL_ID}),
140  {This2, ok} = write(This1, {byte, (?VERSION_1 band ?VERSION_MASK) bor (Type bsl ?TYPE_SHIFT_AMOUNT)}),
141  {This3, ok} = write(This2, {ui32, Seqid}),
142  {This4, ok} = write(This3, {string, Name}),
143  {This4, ok};
144
145write(This, message_end) -> {This, ok};
146
147write(This0, #protocol_field_begin{
148       name = _Name,
149       type = Type,
150       id = Id})
151when (Type =:= ?tType_BOOL) -> {This0#t_compact{write_id = Id}, ok};
152
153write(This0, #protocol_field_begin{
154       name = _Name,
155       type = Type,
156       id = Id}) ->
157  write_field_begin(This0, typeid_to_compact(Type), Id);
158
159write(This, field_stop) -> write(This, {byte, ?tType_STOP});
160
161write(This, field_end) -> {This, ok};
162
163write(This0, #protocol_map_begin{
164      ktype = _Ktype,
165      vtype = _Vtype,
166      size = Size})
167when Size =:= 0 ->
168  write(This0, {byte, 0});
169
170write(This0, #protocol_map_begin{
171       ktype = Ktype,
172       vtype = Vtype,
173       size = Size}) ->
174  {This1, ok} = write(This0, {ui32, Size}),
175  write(This1, {byte, (typeid_to_compact(Ktype) bsl 4) bor typeid_to_compact(Vtype)});
176
177write(This, map_end) -> {This, ok};
178
179write(This0, #protocol_list_begin{
180        etype = Etype,
181        size = Size})
182when Size < 16#f ->
183  write(This0, {byte, (Size bsl 4) bor typeid_to_compact(Etype)});
184
185write(This0, #protocol_list_begin{
186        etype = Etype,
187        size = Size}) ->
188  {This1, ok} = write(This0, {byte, 16#f0 bor typeid_to_compact(Etype)}),
189  write(This1, {ui32, Size});
190
191write(This, list_end) -> {This, ok};
192
193write(This0, #protocol_set_begin{
194        etype = Etype,
195        size = Size}) ->
196  write(This0, #protocol_list_begin{etype = Etype, size =  Size});
197
198write(This, set_end) -> {This, ok};
199
200write(This = #t_compact{write_stack = Stack}, #protocol_struct_begin{}) ->
201  {This#t_compact{write_stack = [0|Stack]}, ok};
202write(This = #t_compact{write_stack = [_|T]}, struct_end) ->
203  {This#t_compact{write_stack = T}, ok};
204
205write(This = #t_compact{write_id = ?ID_NONE}, {bool, Value}) ->
206  write(This, {byte, bool_to_cbool(Value)});
207
208write(This0 = #t_compact{write_id = Id}, {bool, Value}) ->
209  {This1, ok} = write_field_begin(This0, bool_to_cbool(Value), Id),
210  {This1#t_compact{write_id = ?ID_NONE}, ok};
211
212write(This, {byte, Value}) when is_integer(Value) ->
213  write(This, <<Value:8/big-signed>>);
214
215write(This, {i16, Value}) when is_integer(Value) -> write(This, to_varint(to_zigzag(Value), []));
216write(This, {ui32, Value}) when is_integer(Value) -> write(This, to_varint(Value, []));
217write(This, {i32, Value}) when is_integer(Value) ->
218  write(This, to_varint(to_zigzag(Value), []));
219write(This, {i64, Value}) when is_integer(Value) -> write(This, to_varint(to_zigzag(Value), []));
220
221write(This, {double, Double}) ->
222  write(This, <<Double:64/float-signed-little>>);
223
224write(This0, {string, Str}) when is_list(Str) ->
225  % TODO: limit length
226  {This1, ok} = write(This0, {ui32, length(Str)}),
227  {This2, ok} = write(This1, list_to_binary(Str)),
228  {This2, ok};
229
230write(This0, {string, Bin}) when is_binary(Bin) ->
231  % TODO: limit length
232  {This1, ok} = write(This0, {ui32, size(Bin)}),
233  {This2, ok} = write(This1, Bin),
234  {This2, ok};
235
236%% Data :: iolist()
237write(This = #t_compact{transport = Trans}, Data) ->
238  {NewTransport, Result} = thrift_transport:write(Trans, Data),
239  {This#t_compact{transport = NewTransport}, Result}.
240
241%%
242%%
243
244read(This0, message_begin) ->
245  {This1, {ok, ?PROTOCOL_ID}} = read(This0, ubyte),
246  {This2, {ok, VerAndType}} = read(This1, ubyte),
247  ?VERSION_1 = VerAndType band ?VERSION_MASK,
248  {This3, {ok, SeqId}} = read(This2, ui32),
249  {This4, {ok, Name}} = read(This3, string),
250  {This4, #protocol_message_begin{
251             name  = binary_to_list(Name),
252             type  = (VerAndType bsr ?TYPE_SHIFT_AMOUNT) band ?TYPE_BITS,
253             seqid = SeqId}};
254
255read(This, message_end) -> {This, ok};
256
257read(This = #t_compact{read_stack = Stack}, struct_begin) ->
258  {This#t_compact{read_stack = [0|Stack]}, ok};
259read(This = #t_compact{read_stack = [_H|T]}, struct_end) ->
260  {This#t_compact{read_stack = T}, ok};
261
262read(This0 = #t_compact{read_stack = [LastId|T]}, field_begin) ->
263  {This1, {ok, Byte}} = read(This0, ubyte),
264  case Byte band 16#f of
265    CompactType = ?tType_STOP ->
266      {This1, #protocol_field_begin{type = CompactType}};
267    CompactType ->
268      {This2, {ok, Id}} = case Byte bsr 4 of
269                            0 -> read(This1, i16);
270                            IdDiff ->
271                              {This1, {ok, LastId + IdDiff}}
272                          end,
273      case compact_to_typeid(CompactType) of
274        ?tType_BOOL ->
275          {This2#t_compact{read_stack = [Id|T], read_value = cbool_to_bool(CompactType)},
276           #protocol_field_begin{type = ?tType_BOOL, id = Id}};
277        Type ->
278          {This2#t_compact{read_stack = [Id|T]},
279           #protocol_field_begin{type = Type, id = Id}}
280      end
281  end;
282
283read(This, field_end) -> {This, ok};
284
285read(This0, map_begin) ->
286  {This1, {ok, Size}}  = read(This0, ui32),
287  {This2, {ok, KV}} = case Size of
288                        0 -> {This1, {ok, 0}};
289                        _ -> read(This1, ubyte)
290                      end,
291  {This2, #protocol_map_begin{ktype = compact_to_typeid(KV bsr 4),
292                              vtype = compact_to_typeid(KV band 16#f),
293                              size = Size}};
294read(This, map_end) -> {This, ok};
295
296read(This0, list_begin) ->
297  {This1, {ok, SizeAndType}} = read(This0, ubyte),
298  {This2, {ok, Size}} = case (SizeAndType bsr 4) band 16#f of
299                          16#f -> read(This1, ui32);
300                          Else -> {This1, {ok, Else}}
301                        end,
302  {This2, #protocol_list_begin{etype = compact_to_typeid(SizeAndType band 16#f),
303                               size = Size}};
304
305read(This, list_end) -> {This, ok};
306
307read(This0, set_begin) ->
308  {This1, {ok, SizeAndType}} = read(This0, ubyte),
309  {This2, {ok, Size}} = case (SizeAndType bsr 4) band 16#f of
310                          16#f -> read(This1, ui32);
311                          Else -> {This1, {ok, Else}}
312                        end,
313  {This2, #protocol_set_begin{etype = compact_to_typeid(SizeAndType band 16#f),
314                               size = Size}};
315
316read(This, set_end) -> {This, ok};
317
318read(This0, field_stop) ->
319  {This1, {ok, ?tType_STOP}} = read(This0, ubyte),
320  {This1, ok};
321
322%%
323
324read(This0 = #t_compact{read_value = ?CBOOL_NONE}, bool) ->
325  {This1, {ok, Byte}} = read(This0, ubyte),
326  {This1, {ok, cbool_to_bool(Byte)}};
327
328read(This0 = #t_compact{read_value = Bool}, bool) ->
329  {This0#t_compact{read_value = ?CBOOL_NONE}, {ok, Bool}};
330
331read(This0, ubyte) ->
332  {This1, {ok, <<Val:8/integer-unsigned-big, _/binary>>}} = read_data(This0, 1),
333  {This1, {ok, Val}};
334
335read(This0, byte) ->
336  {This1, Bytes} = read_data(This0, 1),
337  case Bytes of
338    {ok, <<Val:8/integer-signed-big, _/binary>>} -> {This1, {ok, Val}};
339    Else -> {This1, Else}
340  end;
341
342read(This0, i16) ->
343  {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
344  {This1, {ok, from_zigzag(Zigzag)}};
345
346read(This0, ui32) -> read_varint(This0, 0, 0);
347
348read(This0, i32) ->
349  {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
350  {This1, {ok, from_zigzag(Zigzag)}};
351
352read(This0, i64) ->
353  {This1, {ok, Zigzag}} = read_varint(This0, 0, 0),
354  {This1, {ok, from_zigzag(Zigzag)}};
355
356read(This0, double) ->
357  {This1, Bytes} = read_data(This0, 8),
358  case Bytes of
359    {ok, <<Val:64/float-signed-little, _/binary>>} -> {This1, {ok, Val}};
360    Else -> {This1, Else}
361  end;
362
363% returns a binary directly, call binary_to_list if necessary
364read(This0, string) ->
365  {This1, {ok, Sz}}  = read(This0, ui32),
366  read_data(This1, Sz).
367
368-spec read_data(#t_compact{}, non_neg_integer()) ->
369    {#t_compact{}, {ok, binary()} | {error, _Reason}}.
370read_data(This, 0) -> {This, {ok, <<>>}};
371read_data(This = #t_compact{transport = Trans}, Len) when is_integer(Len) andalso Len > 0 ->
372    {NewTransport, Result} = thrift_transport:read(Trans, Len),
373    {This#t_compact{transport = NewTransport}, Result}.
374
375
376%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
377
378%% returns a (fun() -> thrift_protocol())
379new_protocol_factory(TransportFactory, _Options) ->
380  F = fun() ->
381          case TransportFactory() of
382            {ok, Transport} ->
383              thrift_compact_protocol:new(
384                Transport,
385                []);
386            {error, Error} ->
387              {error, Error}
388          end
389      end,
390  {ok, F}.
391