%% This Source Code Form is subject to the terms of the Mozilla Public %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% %% Copyright (c) 2007-2022 VMware, Inc. or its affiliates. All rights reserved. %% -module(amqp10_binary_generator). -export([generate/1, build_frame/2, build_frame/3, build_heartbeat_frame/0]). -include("amqp10_framing.hrl"). -type signed_byte() :: -128 .. 127. -type amqp10_ctor() :: ubyte | ushort | byte | short | int | uing | float | double | char | timestamp | uuid | utf8 | symbol | binary | list | map | array | {described, amqp10_type(), amqp10_ctor()}. -type amqp10_prim() :: null | true | false | {boolean, true | false} | {ubyte, byte()} | {ushort, non_neg_integer()} | {uint, non_neg_integer()} | {ulong, non_neg_integer()} | {byte, signed_byte()} | {short, integer()} | {int, integer()} | {float, float()} | {double, float()} | {char, binary()} | {timestamp, integer()} | {uuid, binary()} | {utf8, binary()} | {symbol, binary()} | {binary, binary()} | {list, [amqp10_type()]} | {map, [{amqp10_prim(), amqp10_prim()}]} | %% TODO: make map a map {array, amqp10_ctor(), [amqp10_type()]}. -type amqp10_described() :: {described, amqp10_type(), amqp10_prim()}. -type amqp10_type() :: amqp10_prim() | amqp10_described(). -export_type([ amqp10_ctor/0, amqp10_type/0, amqp10_described/0 ]). -define(AMQP_FRAME_TYPE, 0). -define(DOFF, 2). -define(VAR_1_LIMIT, 16#FF). -spec build_frame(integer(), iolist()) -> iolist(). build_frame(Channel, Payload) -> build_frame(Channel, ?AMQP_FRAME_TYPE, Payload). build_frame(Channel, FrameType, Payload) -> Size = iolist_size(Payload) + 8, % frame header and no extension [ <>, Payload ]. build_heartbeat_frame() -> %% length is inclusive <<8:32, ?DOFF:8, ?AMQP_FRAME_TYPE:8, 0:16>>. -spec generate(amqp10_type()) -> iolist(). generate({described, Descriptor, Value}) -> DescBin = generate(Descriptor), ValueBin = generate(Value), [ ?DESCRIBED_BIN, DescBin, ValueBin ]; generate(null) -> <<16#40>>; generate(true) -> <<16#41>>; generate(false) -> <<16#42>>; generate({boolean, true}) -> <<16#56, 16#01>>; generate({boolean, false}) -> <<16#56, 16#00>>; %% some integral types have a compact encoding as a byte; this is in %% particular for the descriptors of AMQP types, which have the domain %% bits set to zero and values < 256. generate({ubyte, V}) -> <<16#50,V:8/unsigned>>; generate({ushort, V}) -> <<16#60,V:16/unsigned>>; generate({uint, V}) when V =:= 0 -> <<16#43>>; generate({uint, V}) when V < 256 -> <<16#52,V:8/unsigned>>; generate({uint, V}) -> <<16#70,V:32/unsigned>>; generate({ulong, V}) when V =:= 0 -> <<16#44>>; generate({ulong, V}) when V < 256 -> <<16#53,V:8/unsigned>>; generate({ulong, V}) -> <<16#80,V:64/unsigned>>; generate({byte, V}) -> <<16#51,V:8/signed>>; generate({short, V}) -> <<16#61,V:16/signed>>; generate({int, V}) when V<128 andalso V>-129 -> <<16#54,V:8/signed>>; generate({int, V}) -> <<16#71,V:32/signed>>; generate({long, V}) when V<128 andalso V>-129 -> <<16#55,V:8/signed>>; generate({long, V}) -> <<16#81,V:64/signed>>; generate({float, V}) -> <<16#72,V:32/float>>; generate({double, V}) -> <<16#82,V:64/float>>; generate({char, V}) -> <<16#73,V:4/binary>>; generate({timestamp,V}) -> <<16#83,V:64/signed>>; generate({uuid, V}) -> <<16#98,V:16/binary>>; generate({utf8, V}) when size(V) < ?VAR_1_LIMIT -> [<<16#a1,(size(V)):8>>, V]; generate({utf8, V}) -> [<<16#b1,(size(V)):32>>, V]; generate({symbol, V}) -> [<<16#a3,(size(V)):8>>, V]; generate({binary, V}) -> Size = iolist_size(V), if Size < ?VAR_1_LIMIT -> [<<16#a0,Size:8>>, V]; true -> [<<16#b0,Size:32>>, V] end; generate({list, []}) -> <<16#45>>; generate({list, List}) -> Count = length(List), Compound = lists:map(fun generate/1, List), S = iolist_size(Compound), %% If the list contains less than (256 - 1) elements and if the %% encoded size (including the encoding of "Count", thus S + 1 %% in the test) is less than 256 bytes, we use the short form. %% Otherwise, we use the large form. if Count >= (256 - 1) orelse (S + 1) >= 256 -> [<<16#d0, (S + 4):32/unsigned, Count:32/unsigned>>, Compound]; true -> [<<16#c0, (S + 1):8/unsigned, Count:8/unsigned>>, Compound] end; generate({map, ListOfPairs}) -> Count = length(ListOfPairs) * 2, Compound = lists:map(fun ({Key, Val}) -> [(generate(Key)), (generate(Val))] end, ListOfPairs), S = iolist_size(Compound), %% See generate({list, ...}) for an explanation of this test. if Count >= (256 - 1) orelse (S + 1) >= 256 -> [<<16#d1, (S + 4):32, Count:32>>, Compound]; true -> [<<16#c1, (S + 1):8, Count:8>>, Compound] end; generate({array, Type, List}) -> Count = length(List), Body = iolist_to_binary([constructor(Type), [generate(Type, I) || I <- List]]), S = size(Body), %% See generate({list, ...}) for an explanation of this test. if Count >= (256 - 1) orelse (S + 1) >= 256 -> [<<16#f0, (S + 4):32/unsigned, Count:32/unsigned>>, Body]; true -> [<<16#e0, (S + 1):8/unsigned, Count:8/unsigned>>, Body] end; generate({as_is, TypeCode, Bin}) -> <>. %% TODO again these are a stub to get SASL working. New codec? Will %% that ever happen? If not we really just need to split generate/1 %% up into things like these... %% for these constructors map straight-forwardly constructor(symbol) -> <<16#b3>>; constructor(ubyte) -> <<16#50>>; constructor(ushort) -> <<16#60>>; constructor(short) -> <<16#61>>; constructor(uint) -> <<16#70>>; constructor(ulong) -> <<16#80>>; constructor(byte) -> <<16#51>>; constructor(int) -> <<16#71>>; constructor(long) -> <<16#81>>; constructor(float) -> <<16#72>>; constructor(double) -> <<16#82>>; constructor(char) -> <<16#73>>; constructor(timestamp) -> <<16#83>>; constructor(uuid) -> <<16#98>>; constructor(null) -> <<16#40>>; constructor(boolean) -> <<16#56>>; constructor(array) -> <<16#f0>>; % use large array type for all nested arrays constructor(utf8) -> <<16#b1>>; constructor({described, Descriptor, Primitive}) -> [<<16#00>>, generate(Descriptor), constructor(Primitive)]. % returns io_list generate(symbol, {symbol, V}) -> [<<(size(V)):32>>, V]; generate(utf8, {utf8, V}) -> [<<(size(V)):32>>, V]; generate(boolean, true) -> <<16#01>>; generate(boolean, false) -> <<16#00>>; generate(boolean, {boolean, true}) -> <<16#01>>; generate(boolean, {boolean, false}) -> <<16#00>>; generate(ubyte, {ubyte, V}) -> <>; generate(byte, {byte, V}) -> <>; generate(ushort, {ushort, V}) -> <>; generate(short, {short, V}) -> <>; generate(uint, {uint, V}) -> <>; generate(int, {int, V}) -> <>; generate(ulong, {ulong, V}) -> <>; generate(long, {long, V}) -> <>; generate({described, D, P}, {described, D, V}) -> generate(P, V); generate(array, {array, Type, List}) -> Count = length(List), Body = iolist_to_binary([constructor(Type), [generate(Type, I) || I <- List]]), S = size(Body), %% See generate({list, ...}) for an explanation of this test. [<<(S + 4):32/unsigned, Count:32/unsigned>>, Body].