%% 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-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 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 | boolean() | {boolean, boolean()} | {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()}]} | {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_prim/0, amqp10_described/0 ]). -define(AMQP_FRAME_TYPE, 0). -define(DOFF, 2). -define(VAR_1_LIMIT, 16#FF). -spec build_frame(non_neg_integer(), iolist()) -> iolist(). build_frame(Channel, Body) -> build_frame(Channel, ?AMQP_FRAME_TYPE, Body). -spec build_frame(non_neg_integer(), non_neg_integer(), iolist()) -> iolist(). build_frame(Channel, FrameType, Body) -> Size = iolist_size(Body) + 8, % frame header and no extension [<>, Body]. build_heartbeat_frame() -> %% length is inclusive <<8:32, ?DOFF:8, ?AMQP_FRAME_TYPE:8, 0:16>>. -spec generate(amqp10_type()) -> iodata(). generate(Type) -> case generate1(Type) of Byte when is_integer(Byte) -> [Byte]; IoData -> IoData end. generate1({described, Descriptor, Value}) -> DescBin = generate1(Descriptor), ValueBin = generate1(Value), [?DESCRIBED, DescBin, ValueBin]; generate1(null) -> 16#40; generate1(true) -> 16#41; generate1(false) -> 16#42; generate1({boolean, true}) -> [16#56, 16#01]; generate1({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. generate1({ubyte, V}) -> [16#50, V]; generate1({ushort, V}) -> <<16#60,V:16/unsigned>>; generate1({uint, V}) when V =:= 0 -> 16#43; generate1({uint, V}) when V < 256 -> [16#52, V]; generate1({uint, V}) -> <<16#70,V:32/unsigned>>; generate1({ulong, V}) when V =:= 0 -> 16#44; generate1({ulong, V}) when V < 256 -> [16#53, V]; generate1({ulong, V}) -> <<16#80,V:64/unsigned>>; generate1({byte, V}) -> <<16#51,V:8/signed>>; generate1({short, V}) -> <<16#61,V:16/signed>>; generate1({int, V}) when V<128 andalso V>-129 -> <<16#54,V:8/signed>>; generate1({int, V}) -> <<16#71,V:32/signed>>; generate1({long, V}) when V<128 andalso V>-129 -> <<16#55,V:8/signed>>; generate1({long, V}) -> <<16#81,V:64/signed>>; generate1({float, V}) -> <<16#72,V:32/float>>; generate1({double, V}) -> <<16#82,V:64/float>>; generate1({char,V}) when V>=0 andalso V=<16#10ffff -> <<16#73,V:32>>; %% AMQP timestamp is "64-bit two's-complement integer representing milliseconds since the unix epoch". %% For small integers (i.e. values that can be stored in a single word), %% Erlang uses two’s complement to represent the signed integers. generate1({timestamp,V}) -> <<16#83,V:64/signed>>; generate1({uuid, V}) -> <<16#98,V:16/binary>>; generate1({utf8, V}) when size(V) =< ?VAR_1_LIMIT -> [16#a1, size(V), V]; generate1({utf8, V}) -> [<<16#b1, (size(V)):32>>, V]; generate1({symbol, V}) when size(V) =< ?VAR_1_LIMIT -> [16#a3, size(V), V]; generate1({symbol, V}) -> [<<16#b3, (size(V)):32>>, V]; generate1({binary, V}) -> Size = iolist_size(V), case Size =< ?VAR_1_LIMIT of true -> [16#a0, Size, V]; false -> [<<16#b0, Size:32>>, V] end; generate1({list, []}) -> 16#45; generate1({list, List}) -> Count = length(List), Compound = lists:map(fun generate1/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, Count:32>>, Compound]; true -> [16#c0, S + 1, Count, Compound] end; generate1({map, KvList}) -> Count = length(KvList) * 2, Compound = lists:map(fun ({Key, Val}) -> [(generate1(Key)), (generate1(Val))] end, KvList), S = iolist_size(Compound), %% See generate1({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, Count, Compound] end; generate1({array, Type, List}) -> Count = length(List), Array = [constructor(Type), [generate2(Type, I) || I <- List]], S = iolist_size(Array), %% See generate1({list, ...}) for an explanation of this test. if Count >= (256 - 1) orelse (S + 1) >= 256 -> [<<16#f0, (S + 4):32, Count:32>>, Array]; true -> [16#e0, S + 1, Count, Array] end; generate1({as_is, TypeCode, Bin}) -> <>. 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(binary) -> 16#b0; constructor(utf8) -> 16#b1; constructor(list) -> 16#d0; % use large list type for all array elements constructor(map) -> 16#d1; % use large map type for all array elements constructor(array) -> 16#f0; % use large array type for all nested arrays constructor({described, Descriptor, Primitive}) -> [16#00, generate1(Descriptor), constructor(Primitive)]. generate2(symbol, {symbol, V}) -> [<<(size(V)):32>>, V]; generate2(utf8, {utf8, V}) -> [<<(size(V)):32>>, V]; generate2(binary, {binary, V}) -> [<<(size(V)):32>>, V]; generate2(boolean, true) -> 16#01; generate2(boolean, false) -> 16#00; generate2(boolean, {boolean, true}) -> 16#01; generate2(boolean, {boolean, false}) -> 16#00; generate2(null, null) -> 16#40; generate2(char, {char,V}) when V>=0 andalso V=<16#10ffff -> <>; generate2(ubyte, {ubyte, V}) -> V; generate2(byte, {byte, V}) -> <>; generate2(ushort, {ushort, V}) -> <>; generate2(short, {short, V}) -> <>; generate2(uint, {uint, V}) -> <>; generate2(int, {int, V}) -> <>; generate2(ulong, {ulong, V}) -> <>; generate2(long, {long, V}) -> <>; generate2(float, {float, V}) -> <>; generate2(double, {double, V}) -> <>; generate2(timestamp, {timestamp,V}) -> <>; generate2(uuid, {uuid, V}) -> <>; generate2({described, D, P}, {described, D, V}) -> generate2(P, V); generate2(list, {list, List}) -> Count = length(List), Compound = lists:map(fun generate1/1, List), S = iolist_size(Compound), [<<(S + 4):32, Count:32>>, Compound]; generate2(map, {map, KvList}) -> Count = length(KvList) * 2, Compound = lists:map(fun ({Key, Val}) -> [(generate1(Key)), (generate1(Val))] end, KvList), S = iolist_size(Compound), [<<(S + 4):32, Count:32>>, Compound]; generate2(array, {array, Type, List}) -> Count = length(List), Array = [constructor(Type), [generate2(Type, I) || I <- List]], S = iolist_size(Array), [<<(S + 4):32, Count:32>>, Array].