%@doc Standard binary encoding of Bson documents, version 1.0. See bsonspec.org. -module(bson_binary). -export([put_document/1, get_document/1, get_map/1, put_cstring/1, get_cstring/1]). -include("bson_binary.hrl"). -define(fits_int32(N), -16#80000000 =< N andalso N =< 16#7fffffff). -define(fits_int64(N), -16#8000000000000000 =< N andalso N =< 16#7fffffffffffffff). -define(put_tagname(Tag, N), (Tag):8, (put_cstring(N)) / binary). % Name is expected to be in scope at call site %% @doc utf8 binary cannot contain a 0 byte. -spec put_cstring(bson:utf8()) -> binary(). put_cstring(UBin) -> <>. -spec get_cstring(binary()) -> {bson:utf8(), binary()}. get_cstring(Bin) -> % list_to_tuple (binary:split (Bin, <<0>>)). {Pos, _Len} = binary:match(Bin, <<0>>), % _Len = 1 but don't match 1 to avoid check <> = Bin, {UBin, Rest}. -spec put_document(bson:document()) -> binary(). put_document(Document) -> Bin = bson:doc_foldl(fun put_field_accum/3, <<>>, Document), <>. -spec get_document(binary()) -> {bson:document(), binary()}. get_document(<>) -> Size = N - 5, <> = Bin, Doc = list_to_tuple(get_fields(DocBin, [])), {Doc, Bin1}. -spec get_map(binary()) -> {map(), binary()}. get_map(<>) -> Size = N - 5, <> = Bin, Doc = get_fields(DocBin, #{}), {Doc, Bin1}. %% @private put_field_accum(Label, Value, Bin) when is_atom(Label) -> <>; put_field_accum(Label, Value, Bin) when is_binary(Label) -> <>. %% @private get_fields(<<>>, Acc) when is_map(Acc) -> Acc; get_fields(<<>>, Acc) -> lists:reverse(Acc); get_fields(Bin, Acc) when is_map(Acc) -> {Name, Value, Bin1} = get_field(Bin, map), get_fields(Bin1, Acc#{Name => Value}); get_fields(Bin, Acc) -> {Name, Value, Bin1} = get_field(Bin, normal), get_fields(Bin1, [Value, Name | Acc]). %% @private -spec put_field(bson:utf8(), bson:value()) -> binary(). put_field(N, false) -> <>; put_field(N, true) -> <>; put_field(N, NULL) when NULL =:= null; NULL =:= undefined -> <>; put_field(N, 'MIN_KEY') -> <>; put_field(N, 'MAX_KEY') -> <>; put_field(N, {Oid}) -> <>; put_field(N, {bin, BinType, Bin}) -> <>; put_field(N, {regex, Pat, Opt}) -> <>; put_field(N, {javascript, {}, Code}) -> <>; put_field(N, {javascript, Env, Code}) -> <>; put_field(N, {mongostamp, Inc, Time}) -> <>; put_field(N, UnixTime = {_, _, _}) -> <>; put_field(N, V) when is_float(V) -> <>; put_field(N, V) when is_binary(V) -> <>; put_field(N, V) when is_tuple(V) -> <>; put_field(N, V) when is_list(V) -> <>; put_field(N, V) when is_map(V) -> <>; put_field(N, V) when is_atom(V) -> <>; put_field(N, V) when is_integer(V) andalso ?fits_int32(V) -> <>; put_field(N, V) when is_integer(V) andalso ?fits_int64(V) -> <>; put_field(N, V) when is_integer(V) -> erlang:error(bson_int_too_large, [N, V]); put_field(N, V) -> erlang:error(bad_bson, [N, V]). %% @private get_field(<<1:8, _/binary>>, _, Bin1, _) -> <> = Bin1, {N, Bin2}; get_field(<<2:8, _/binary>>, _, Bin1, _) -> get_string(Bin1); get_field(<<3:8, _/binary>>, _, Bin1, map) -> get_map(Bin1); get_field(<<3:8, _/binary>>, _, Bin1, _) -> get_document(Bin1); get_field(<<4:8, _/binary>>, _, Bin1, Type) -> get_array(Bin1, Type); get_field(<<5:8, _/binary>>, _, Bin1, _) -> {BinType, Bin, Bin2} = get_binary(Bin1), {{bin, BinType, Bin}, Bin2}; get_field(<<6:8, _/binary>>, _, Bin1, _) -> % Treat the deprecated "undefined" value as null, which we call 'undefined'! {undefined, Bin1}; get_field(<<7:8, _/binary>>, _, Bin1, _) -> {Oid, Bin2} = get_oid(Bin1), {{Oid}, Bin2}; get_field(<<8:8, _/binary>>, _, <>, _) -> {Bit == 1, Bin2}; get_field(<<9:8, _/binary>>, _, Bin1, _) -> get_unixtime(Bin1); get_field(<<10:8, _/binary>>, _, Bin1, _) -> {undefined, Bin1}; get_field(<<11:8, _/binary>>, _, Bin1, _) -> {Pat, Bin2} = get_cstring(Bin1), {Opt, Bin3} = get_cstring(Bin2), {{regex, Pat, Opt}, Bin3}; get_field(<<13:8, _/binary>>, _, Bin1, _) -> {Code, Bin2} = get_string(Bin1), {{javascript, {}, Code}, Bin2}; get_field(<<14:8, _/binary>>, _, Bin1, _) -> {UBin, Bin2} = get_string(Bin1), {binary_to_atom(UBin, utf8), Bin2}; get_field(<<15:8, _/binary>>, _, Bin1, _) -> {Code, Env, Bin2} = get_closure(Bin1), {{javascript, Env, Code}, Bin2}; get_field(<<16:8, _/binary>>, _, Bin1, _) -> <> = Bin1, {N, Bin2}; get_field(<<17:8, _/binary>>, _, Bin1, _) -> <> = Bin1, {{mongostamp, Inc, Tim}, Bin2}; get_field(<<18:8, _/binary>>, _, Bin1, _) -> <> = Bin1, {N, Bin2}; get_field(<<127:8, _/binary>>, _, Bin1, _) -> {'MAX_KEY', Bin1}; get_field(<<255:8, _/binary>>, _, Bin1, _) -> {'MIN_KEY', Bin1}; get_field(<>, _, _, _) -> erlang:error(unknown_bson_tag, [<>]). %% @private -spec get_field(binary(), atom()) -> {bson:utf8(), bson:value(), binary()}. get_field(R = <<_:8, Bin0/binary>>, Type) -> {Name, Bin1} = get_cstring(Bin0), {Value, BinRest} = get_field(R, Name, Bin1, Type), {Name, Value, BinRest}. %% @private -spec put_string(bson:utf8()) -> binary(). put_string(UBin) -> <>. %% @private -spec get_string(binary()) -> {bson:utf8(), binary()}. get_string(<>) -> Size = N - 1, <> = Bin, {UBin, Rest}. %% @private -spec put_array(bson:arr()) -> binary(). % encoded same as document with labels '0', '1', etc. put_array(Values) -> {_N, Bin} = lists:foldl(fun put_value_accum/2, {0, <<>>}, Values), <>. %% @private put_value_accum(Value, {N, Bin}) -> {N + 1, <>}. %% @private -spec get_array(binary(), normal | map) -> {bson:arr(), binary()}. % encoded same as document with labels '0', '1', etc. which we ignore get_array(<>, Type) -> Size = N - 5, <> = Bin, Array = get_values(DBin, [], Type), {Array, Bin1}. %% @private get_values(<<>>, Acc, _) -> lists:reverse(Acc); get_values(Bin, Acc, Type) -> {_, Value, Bin1} = get_field(Bin, Type), get_values(Bin1, [Value | Acc], Type). -type bintype() :: bin | function | uuid | md5 | userdefined. %% @private -spec put_binary(bintype(), binary()) -> binary(). put_binary(BinType, Bin) -> Tag = case BinType of bin -> 0; function -> 1; uuid -> 3; md5 -> 5; userdefined -> 128 end, <>. %% @private -spec get_binary(binary()) -> {bintype(), binary(), binary()}. get_binary(<>) -> BinType = case Tag of 0 -> bin; 1 -> function; 3 -> uuid; 5 -> md5; 128 -> userdefined end, <> = Bin, {BinType, VBin, Bin1}. %% @private -spec put_closure(bson:utf8(), bson:document()) -> binary(). put_closure(Code, Env) -> Bin = <<(put_string(Code))/binary, (put_document(Env))/binary>>, <>. %% @private -spec get_closure(binary()) -> {bson:utf8(), bson:document(), binary()}. get_closure(<>) -> {Code, Bin1} = get_string(Bin), {Env, Bin2} = get_document(Bin1), {Code, Env, Bin2}. %% @private -spec put_unixtime(bson:unixtime()) -> binary(). put_unixtime({MegaSecs, Secs, MicroSecs}) -> <>. %% @private -spec get_unixtime(binary()) -> {bson:unixtime(), binary()}. get_unixtime(<>) -> {{MilliSecs div 1000000000, (MilliSecs div 1000) rem 1000000, (MilliSecs * 1000) rem 1000000}, Bin}. %% @private -spec put_oid(<<_:96>>) -> <<_:96>>. put_oid(<>) -> Oid. %% @private -spec get_oid(binary()) -> {<<_:96>>, binary()}. get_oid(<>) -> {Oid, Bin}.