%%%------------------------------------------------------------------- %%% 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. %%% %%% @doc This module facilitates encoding/decoding of thrift data %%% encoded with the binary protocol. %%% @end %%% ------------------------------------------------------------------- -module(otter_lib_thrift). -export([ encode/1, decode/1, encode_implicit_list/1, decode_implicit_list/1 ]). %% encode/decode basic thrift binary data %% e.g. The transport (e.g. HTTP) data is an "implicit" list starting %% with the element type, and number of elements .. %%-------------------------------------------------------------------- %% @doc Decodes a binary input as implicit list to a thrift decoded data %% structure, i.e. list of {Id, Type, Value} tuples. Implicit list is e.g. %% how OpenZipkin expects the spans to be send. It is called implicit as it %% does not contain a list type tag in the beginning. %% @end %%-------------------------------------------------------------------- -spec decode_implicit_list(BinaryData :: binary()) -> term(). decode_implicit_list(BinaryData) -> decode(list, BinaryData). %%-------------------------------------------------------------------- %% @doc Encodes a list of thrift data structures to binary e.g. to be sent %% on the wire. %% @end %%-------------------------------------------------------------------- -spec encode_implicit_list(term()) -> binary(). encode_implicit_list(Data) -> encode({list, Data}). %%-------------------------------------------------------------------- %% @doc Encodes a {Id, Type, Data} tuple to binary. %% @end %%-------------------------------------------------------------------- -spec encode({Id :: integer(), Type :: atom(), Data :: term()}) -> binary(); ({Type :: atom(), Data :: term()}) -> binary(). encode({Id, Type, Data}) -> TypeId = map_type(Type), EData = encode({Type, Data}), <>; %% .. and without Id (i.e. part of list/set/map) encode({bool, true}) -> <<1>>; encode({bool, false}) -> <<0>>; encode({byte, Val}) -> <>; encode({double, Val}) -> <>; encode({i16, Val}) -> <>; encode({i32, Val}) -> <>; encode({i64, Val}) -> <>; encode({string, Val}) when is_list(Val) -> Size = length(Val), % Might want to convert this to UTF-8 binary first, however for now % I'll leave it to the next encoding when binary can be provided in % UTF-8 format. In this part is kindly expected it to be ASCII % string Bytes = list_to_binary(Val), <>; encode({string, Val}) when is_binary(Val) -> Size = byte_size(Val), <>; encode({list, {ElementType, Data}}) -> ElementTypeId = map_type(ElementType), Size = length(Data), EData = list_to_binary([ encode({ElementType, Element}) || Element <- Data ]), <>; encode({set, Data}) -> encode({list, Data}); encode({struct, Data}) -> EData = list_to_binary([ encode(StructElement) || StructElement <- Data ]), <>; encode({map, {KeyType, ValType, Data}}) -> KeyTypeId = map_type(KeyType), ValTypeId = map_type(ValType), Size = length(Data), EData = list_to_binary([ [encode({KeyType, Key}), encode({ValType, Val})] || {Key, Val} <- Data ]), <>. %% Decoding functions %%-------------------------------------------------------------------- %% @doc Decodes a binary to {{Id, Type, Data}, Rest} tuple and rest of data. %% @end %%-------------------------------------------------------------------- -spec decode( BinaryData :: binary()) -> {{Id :: integer(), Type :: atom(), Data :: term()}, Rest :: binary()}. decode(<>) -> Type = map_type(TypeId), {Val, Rest} = decode(Type, Data), {{Id, Type, Val}, Rest}. decode(bool, <>) -> {Val == 1, Rest}; decode(byte, <>) -> {Val, Rest}; decode(double, <>) -> {Val, Rest}; decode(i16, <>) -> {Val, Rest}; decode(i32, <>) -> {Val, Rest}; decode(i64, <>) -> {Val, Rest}; decode(string, <>) -> <> = BytesAndRest, {Bytes, Rest}; decode(struct, Data) -> decode_struct(Data, []); decode(map, <>) -> decode_map( map_type(KeyTypeId), map_type(ValTypeId), Size, KVPsAndRest, [] ); %% Lists and Sets are encoded the same way decode(set, Data) -> decode(list, Data); decode(list, <>) -> decode_list( map_type(ElementTypeId), Size, ElementsAndRest, [] ). %% Helpers decode_struct(Data, Acc) -> case decode(Data) of {Val, <<0, Rest/bytes>>} -> {lists:reverse([Val | Acc]), Rest}; {Val, Rest} -> decode_struct(Rest, [Val | Acc]) end. decode_map(KeyType, ValType, 0, Rest, Acc) -> {{{KeyType, ValType}, lists:reverse(Acc)}, Rest}; decode_map(KeyType, ValType, Size, KVPsAndRest, Acc) -> {Key, ValAndRest} = decode(KeyType, KVPsAndRest), {Val, Rest} = decode(ValType, ValAndRest), decode_map(KeyType, ValType, Size-1, Rest, [{Key, Val} | Acc]). decode_list(ElementType, 0, Rest, Acc) -> {{ElementType, lists:reverse(Acc)}, Rest}; decode_list(ElementType, Size, Elements, Acc) -> {Data, Rest} = decode(ElementType, Elements), decode_list(ElementType, Size-1, Rest, [Data | Acc]). map_type(2) -> bool; map_type(3) -> byte; map_type(4) -> double; map_type(6) -> i16; map_type(8) -> i32; map_type(10) -> i64; map_type(11) -> string; map_type(12) -> struct; map_type(13) -> map; map_type(14) -> set; map_type(15) -> list; map_type(bool) -> 2; map_type(byte) -> 3; map_type(double)-> 4; map_type(i16) -> 6; map_type(i32) -> 8; map_type(i64) -> 10; map_type(string)-> 11; map_type(struct)-> 12; map_type(map) -> 13; map_type(set) -> 14; map_type(list) -> 15.