Packages
amqp10_client
4.2.0
4.2.1
4.2.0
retired
4.1.6
4.1.5
retired
4.1.5-rc.2
retired
4.1.5-rc.1
retired
4.1.5-1
4.0.3
4.0.3-rc.1
3.13.0-rc.2
3.13.0-rc.1
3.12.14
3.12.13
3.12.12
3.12.11
3.12.10
3.12.9
3.12.8
3.12.7
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.12.0-rc.4
3.12.0-rc.3
3.12.0-rc.2
3.12.0-rc.1
3.11.28
3.11.27
3.11.26
3.11.25
3.11.24
3.11.23
3.11.22
3.11.21
3.11.20
3.11.19
3.11.18
3.11.17
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.11.0-rc.2
3.11.0-rc.1
3.11.0-1
3.10.25
3.10.24
3.10.23
3.10.22
3.10.20
3.10.19
3.10.18
3.10.17
3.10.16
3.10.15
3.10.14
3.10.13
3.10.12
3.10.11
3.10.10
3.10.9
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
retired
3.10.2-1
3.10.1
retired
3.10.0
retired
3.10.0-rc.6
3.10.0-rc.5
3.10.0-rc.1
3.9.29
3.9.28
3.9.27
3.9.26
3.9.25
3.9.24
3.9.23
3.9.22
3.9.21
3.9.20
3.9.19
3.9.18
retired
3.9.17
3.9.16
3.9.15
3.8.35
3.8.34
3.8.33
3.8.32
3.8.31
3.8.30
0.0.0-rc.1
AMQP 1.0 client
Retired package: Release invalid - invalid
Current section
Files
Jump to
Current section
Files
src/amqp10_client_connection.erl
%% 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-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
%%
-module(amqp10_client_connection).
-behaviour(gen_statem).
-include("amqp10_client_internal.hrl").
-include_lib("amqp10_common/include/amqp10_framing.hrl").
-include_lib("amqp10_common/include/amqp10_types.hrl").
-include_lib("kernel/include/logger.hrl").
%% public API
-export([open/1,
close/2]).
%% private API
-export([start_link/2,
socket_ready/2,
protocol_header_received/5,
begin_session/1,
heartbeat/1]).
%% gen_statem callbacks
-export([init/1,
callback_mode/0,
terminate/3,
code_change/4]).
%% gen_statem state callbacks
%% see figure 2.23
-export([expecting_socket/3,
sasl_hdr_sent/3,
sasl_hdr_rcvds/3,
sasl_init_sent/3,
hdr_sent/3,
open_sent/3,
opened/3,
close_sent/3]).
-export([format_status/1]).
-type milliseconds() :: non_neg_integer().
-type address() :: inet:socket_address() | inet:hostname().
-type encrypted_sasl() :: {plaintext, binary()} | {encrypted, binary()}.
-type decrypted_sasl() :: none | anon | external | {plain, User :: binary(), Pwd :: binary()}.
-type sasl() :: encrypted_sasl() | decrypted_sasl().
-type connection_config() ::
#{container_id => binary(), % AMQP container id
hostname => binary(), % the dns name of the target host
addresses => [address()],
address => address(),
port => inet:port_number(),
tls_opts => {secure_port, [ssl:tls_option()]},
ws_path => string(),
ws_opts => gun:opts(),
notify => pid() | none, % the pid to send connection events to
notify_when_opened => pid() | none,
notify_when_closed => pid() | none,
notify_with_performative => boolean(),
%% incoming maximum frame size set by our client application
max_frame_size => pos_integer(), % TODO: constrain to large than 512
%% outgoing maximum frame size set by AMQP peer in OPEN performative
outgoing_max_frame_size => pos_integer() | undefined,
idle_time_out => milliseconds(),
% set to a negative value to allow a sender to "overshoot" the flow
% control by this margin
transfer_limit_margin => 0 | neg_integer(),
sasl => sasl(),
properties => amqp10_client_types:properties()
}.
-record(state,
{next_channel = 0 :: non_neg_integer(),
connection_sup :: pid(),
reader_m_ref :: reference() | undefined,
sessions_sup :: pid() | undefined,
pending_session_reqs = [] :: [term()],
reader :: pid() | undefined,
socket :: amqp10_client_socket:socket() | undefined,
idle_time_out :: non_neg_integer() | undefined,
heartbeat_timer :: timer:tref() | undefined,
config :: connection_config()
}).
-export_type([connection_config/0]).
%% -------------------------------------------------------------------
%% Public API.
%% -------------------------------------------------------------------
-spec open(connection_config()) -> supervisor:startchild_ret().
open(Config0) ->
Config = maps:update_with(sasl, fun maybe_encrypt_sasl/1, Config0),
%% Start the supervision tree dedicated to that connection. It
%% starts at least a connection process (the PID we want to return)
%% and a reader process (responsible for opening and reading the
%% socket).
case supervisor:start_child(amqp10_client_sup, [Config]) of
{ok, ConnSup} ->
%% We query the PIDs of the connection and reader processes. The
%% reader process needs to know the connection PID to send it the
%% socket.
Children = supervisor:which_children(ConnSup),
{_, Reader, _, _} = lists:keyfind(reader, 1, Children),
{_, Connection, _, _} = lists:keyfind(connection, 1, Children),
{_, SessionsSup, _, _} = lists:keyfind(sessions, 1, Children),
set_other_procs(Connection, #{sessions_sup => SessionsSup,
reader => Reader}),
{ok, Connection};
Error ->
Error
end.
-spec close(pid(), {amqp10_client_types:amqp_error()
| amqp10_client_types:connection_error(), binary()} | none) -> ok.
close(Pid, Reason) ->
gen_statem:cast(Pid, {close, Reason}).
-spec maybe_encrypt_sasl(decrypted_sasl()) -> sasl().
maybe_encrypt_sasl(Sasl)
when Sasl =:= none orelse
Sasl =:= anon orelse
Sasl =:= external ->
Sasl;
maybe_encrypt_sasl(Plain = {plain, _User, _Passwd}) ->
credentials_obfuscation:encrypt(term_to_binary(Plain)).
-spec maybe_decrypt_sasl(sasl()) -> decrypted_sasl().
maybe_decrypt_sasl(Sasl)
when Sasl =:= none orelse
Sasl =:= anon orelse
Sasl =:= external ->
Sasl;
maybe_decrypt_sasl(Encrypted) ->
binary_to_term(credentials_obfuscation:decrypt(Encrypted)).
%% -------------------------------------------------------------------
%% Private API.
%% -------------------------------------------------------------------
start_link(Sup, Config) ->
gen_statem:start_link(?MODULE, [Sup, Config], []).
set_other_procs(Pid, OtherProcs) ->
gen_statem:cast(Pid, {set_other_procs, OtherProcs}).
-spec socket_ready(pid(), amqp10_client_socket:socket()) -> ok.
socket_ready(Pid, Socket) ->
gen_statem:cast(Pid, {socket_ready, Socket}).
-spec protocol_header_received(pid(), 0 | 3, non_neg_integer(),
non_neg_integer(), non_neg_integer()) -> ok.
protocol_header_received(Pid, Protocol, Maj, Min, Rev) ->
gen_statem:cast(Pid, {protocol_header_received, Protocol, Maj, Min, Rev}).
-spec begin_session(pid()) -> supervisor:startchild_ret().
begin_session(Pid) ->
gen_statem:call(Pid, begin_session, ?TIMEOUT).
heartbeat(Pid) ->
gen_statem:cast(Pid, heartbeat).
%% -------------------------------------------------------------------
%% gen_statem callbacks.
%% -------------------------------------------------------------------
callback_mode() -> [state_functions].
init([Sup, Config0]) ->
process_flag(trap_exit, true),
Config = maps:merge(config_defaults(), Config0),
{ok, expecting_socket, #state{connection_sup = Sup,
config = Config}}.
expecting_socket(_EvtType, {socket_ready, Socket},
State = #state{config = Cfg}) ->
State1 = State#state{socket = Socket},
Sasl = credentials_obfuscation:decrypt(maps:get(sasl, Cfg)),
case Sasl of
none ->
ok = amqp10_client_socket:send(Socket, ?AMQP_PROTOCOL_HEADER),
{next_state, hdr_sent, State1};
_ ->
ok = amqp10_client_socket:send(Socket, ?SASL_PROTOCOL_HEADER),
{next_state, sasl_hdr_sent, State1}
end;
expecting_socket(_EvtType, {set_other_procs, OtherProcs}, State) ->
{keep_state, set_other_procs0(OtherProcs, State)};
expecting_socket({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
%% The caller already asked for a new session but the connection
%% isn't fully opened. Let's queue this request until the connection
%% is ready.
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1}.
sasl_hdr_sent(_EvtType, {protocol_header_received, 3, 1, 0, 0}, State) ->
{next_state, sasl_hdr_rcvds, State};
sasl_hdr_sent({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1};
sasl_hdr_sent(info, {'DOWN', MRef, process, _Pid, _},
#state{reader_m_ref = MRef}) ->
{stop, {shutdown, reader_down}}.
sasl_hdr_rcvds(_EvtType, #'v1_0.sasl_mechanisms'{
sasl_server_mechanisms = {array, symbol, AvailableMechs}},
State = #state{config = #{sasl := Sasl}}) ->
DecryptedSasl = maybe_decrypt_sasl(Sasl),
OurMech = {symbol, decrypted_sasl_to_mechanism(DecryptedSasl)},
case lists:member(OurMech, AvailableMechs) of
true ->
ok = send_sasl_init(State, DecryptedSasl),
{next_state, sasl_init_sent, State};
false ->
{stop, {sasl_not_supported, DecryptedSasl}, State}
end;
sasl_hdr_rcvds({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1}.
sasl_init_sent(_EvtType, #'v1_0.sasl_outcome'{code = {ubyte, 0}},
#state{socket = Socket} = State) ->
ok = amqp10_client_socket:send(Socket, ?AMQP_PROTOCOL_HEADER),
{next_state, hdr_sent, State};
sasl_init_sent(_EvtType, #'v1_0.sasl_outcome'{code = {ubyte, C}},
#state{} = State) when C==1;C==2;C==3;C==4 ->
{stop, sasl_auth_failure, State};
sasl_init_sent({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1}.
hdr_sent(_EvtType, {protocol_header_received, 0, 1, 0, 0}, State) ->
case send_open(State) of
ok -> {next_state, open_sent, State};
Error -> {stop, Error, State}
end;
hdr_sent(_EvtType, {protocol_header_received, Protocol, Maj, Min,
Rev}, State) ->
?LOG_WARNING("Unsupported protocol version: ~b ~b.~b.~b",
[Protocol, Maj, Min, Rev]),
{stop, normal, State};
hdr_sent({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1}.
open_sent(_EvtType, #'v1_0.open'{max_frame_size = MaybeMaxFrameSize,
idle_time_out = Timeout} = Open,
#state{pending_session_reqs = PendingSessionReqs,
config = Config} = State0) ->
State = case Timeout of
undefined -> State0;
{uint, T} when T > 0 ->
{ok, Tmr} = start_heartbeat_timer(T div 2),
State0#state{idle_time_out = T div 2,
heartbeat_timer = Tmr};
_ -> State0
end,
MaxFrameSize = case unpack(MaybeMaxFrameSize) of
undefined ->
%% default as per 2.7.1
?UINT_MAX;
Bytes when is_integer(Bytes) ->
Bytes
end,
State1 = State#state{config = Config#{outgoing_max_frame_size => MaxFrameSize}},
State2 = lists:foldr(
fun(From, S0) ->
{Ret, S2} = handle_begin_session(From, S0),
_ = gen_statem:reply(From, Ret),
S2
end, State1, PendingSessionReqs),
ok = notify_opened(Config, Open),
{next_state, opened, State2#state{pending_session_reqs = []}};
open_sent({call, From}, begin_session,
#state{pending_session_reqs = PendingSessionReqs} = State) ->
State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]},
{keep_state, State1};
open_sent(_EvtType, {close, Reason}, State) ->
case send_close(State, Reason) of
ok ->
%% "After writing this frame the peer SHOULD continue to read from the connection
%% until it receives the partner's close frame (in order to guard against
%% erroneously or maliciously implemented partners, a peer SHOULD implement a
%% timeout to give its partner a reasonable time to receive and process the close
%% before giving up and simply closing the underlying transport mechanism)." [§2.4.3]
{next_state, close_sent, State, {state_timeout, ?TIMEOUT, received_no_close_frame}};
{error, closed} ->
{stop, normal, State};
Error ->
{stop, Error, State}
end;
open_sent(info, {'DOWN', MRef, process, _, _},
#state{reader_m_ref = MRef}) ->
{stop, {shutdown, reader_down}}.
opened(_EvtType, heartbeat, State = #state{idle_time_out = T}) ->
ok = send_heartbeat(State),
{ok, Tmr} = start_heartbeat_timer(T),
{keep_state, State#state{heartbeat_timer = Tmr}};
opened(_EvtType, {close, Reason}, State) ->
%% TODO: stop all sessions writing
%% We could still accept incoming frames (See: 2.4.6)
case send_close(State, Reason) of
ok ->
%% "After writing this frame the peer SHOULD continue to read from the connection
%% until it receives the partner's close frame (in order to guard against
%% erroneously or maliciously implemented partners, a peer SHOULD implement a
%% timeout to give its partner a reasonable time to receive and process the close
%% before giving up and simply closing the underlying transport mechanism)." [§2.4.3]
{next_state, close_sent, State, {state_timeout, ?TIMEOUT, received_no_close_frame}};
{error, closed} ->
{stop, normal, State};
Error ->
{stop, Error, State}
end;
opened(_EvtType, #'v1_0.close'{} = Close, State = #state{config = Config}) ->
%% We receive the first close frame, reply and terminate.
ok = notify_closed(Config, Close),
case send_close(State, none) of
ok -> {stop, normal, State};
{error, closed} -> {stop, normal, State};
Error -> {stop, Error, State}
end;
opened({call, From}, begin_session, State) ->
{Ret, State1} = handle_begin_session(From, State),
{keep_state, State1, [{reply, From, Ret}]};
opened(info, {'DOWN', MRef, process, _, _Info},
#state{reader_m_ref = MRef, config = Config}) ->
%% reader has gone down and we are not already shutting down
ok = notify_closed(Config, shutdown),
{stop, normal};
opened(_EvtType, Frame, State) ->
?LOG_WARNING("Unexpected connection frame ~tp when in state ~tp ",
[Frame, State]),
keep_state_and_data.
close_sent(_EvtType, heartbeat, _Data) ->
keep_state_and_data;
close_sent(_EvtType, {'EXIT', _Pid, shutdown}, _Data) ->
%% monitored processes may exit during closure
keep_state_and_data;
close_sent(_EvtType, {'EXIT', _Pid, {shutdown, _}}, _Data) ->
%% monitored processes may exit during closure
keep_state_and_data;
close_sent(_EvtType, {'DOWN', _Ref, process, ReaderPid, _Reason},
#state{reader = ReaderPid}) ->
%% if the reader exits we probably won't receive a close frame
{stop, normal};
close_sent(_EvtType, #'v1_0.close'{} = Close, #state{config = Config}) ->
ok = notify_closed(Config, Close),
{stop, normal};
close_sent(state_timeout, received_no_close_frame, _Data) ->
{stop, normal};
close_sent(_EvtType, #'v1_0.open'{}, _Data) ->
%% Transition from CLOSE_PIPE to CLOSE_SENT in figure 2.23.
keep_state_and_data.
set_other_procs0(OtherProcs, State) ->
#{sessions_sup := SessionsSup,
reader := Reader} = OtherProcs,
ReaderMRef = monitor(process, Reader),
amqp10_client_frame_reader:set_connection(Reader, self()),
State#state{sessions_sup = SessionsSup,
reader_m_ref = ReaderMRef,
reader = Reader}.
terminate(Reason, _StateName, #state{connection_sup = Sup,
config = Config}) ->
ok = notify_closed(Config, Reason),
case Reason of
normal -> sys:terminate(Sup, normal);
_ -> ok
end.
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
format_status(Context = #{data := ProcState}) ->
%% Note: Context.state here refers to the gen_statem state name,
%% so we need to use Context.data to get #state{}
Obfuscated = obfuscate_state(ProcState),
Context#{data => Obfuscated}.
%% -------------------------------------------------------------------
%% Internal functions.
%% -------------------------------------------------------------------
obfuscate_state(State = #state{config = Cfg0}) ->
Cfg1 = obfuscate_state_config_sasl(Cfg0),
Cfg2 = obfuscate_state_config_tls_opts(Cfg1),
State#state{config = Cfg2}.
-spec obfuscate_state_config_sasl(connection_config()) -> connection_config().
obfuscate_state_config_sasl(Cfg) ->
Sasl0 = maps:get(sasl, Cfg, none),
Sasl = case Sasl0 of
{plain, Username, _Password} ->
{plain, Username, <<"[redacted]">>};
Other ->
Other
end,
Cfg#{sasl => Sasl}.
-spec obfuscate_state_config_tls_opts(connection_config()) -> connection_config().
obfuscate_state_config_tls_opts(Cfg) ->
TlsOpts0 = maps:get(tls_opts, Cfg, undefined),
TlsOpts = case TlsOpts0 of
{secure_port, PropL0} ->
Obfuscated = proplists:delete(password, PropL0),
{secure_port, Obfuscated};
_ ->
TlsOpts0
end,
Cfg#{tls_opts => TlsOpts}.
handle_begin_session({FromPid, _Ref},
#state{sessions_sup = Sup, reader = Reader,
next_channel = Channel,
config = Config} = State) ->
Ret = supervisor:start_child(Sup, [FromPid, Channel, Reader, Config]),
State1 = case Ret of
{ok, _} -> State#state{next_channel = Channel + 1};
_ -> State
end,
{Ret, State1}.
send_open(#state{socket = Socket, config = Config0}) ->
{ok, Product} = application:get_key(description),
{ok, Version} = application:get_key(vsn),
Platform = "Erlang/OTP " ++ erlang:system_info(otp_release),
Props0 = #{<<"product">> => {utf8, list_to_binary(Product)},
<<"version">> => {utf8, list_to_binary(Version)},
<<"platform">> => {utf8, list_to_binary(Platform)}},
Config = maps:update_with(properties,
fun(Val) -> maps:merge(Props0, Val) end,
Props0,
Config0),
Props = amqp10_client_types:make_properties(Config),
ContainerId = maps:get(container_id, Config, generate_container_id()),
IdleTimeOut = maps:get(idle_time_out, Config, 0),
IncomingMaxFrameSize = maps:get(max_frame_size, Config),
Open0 = #'v1_0.open'{container_id = {utf8, ContainerId},
channel_max = {ushort, 100},
idle_time_out = {uint, IdleTimeOut},
properties = Props,
max_frame_size = {uint, IncomingMaxFrameSize}
},
Open = case Config of
#{hostname := Hostname} ->
Open0#'v1_0.open'{hostname = {utf8, Hostname}};
_ ->
Open0
end,
Encoded = amqp10_framing:encode_bin(Open),
Frame = amqp10_binary_generator:build_frame(0, Encoded),
?DBG("CONN <- ~tp", [Open]),
amqp10_client_socket:send(Socket, Frame).
send_close(#state{socket = Socket}, _Reason) ->
Close = #'v1_0.close'{},
Encoded = amqp10_framing:encode_bin(Close),
Frame = amqp10_binary_generator:build_frame(0, Encoded),
?DBG("CONN <- ~tp", [Close]),
amqp10_client_socket:send(Socket, Frame).
send_sasl_init(State, anon) ->
Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"ANONYMOUS">>}},
send(Frame, 1, State);
send_sasl_init(State, external) ->
Frame = #'v1_0.sasl_init'{
mechanism = {symbol, <<"EXTERNAL">>},
%% "This response is empty when the client is requesting to act
%% as the identity the server associated with its authentication
%% credentials."
%% https://datatracker.ietf.org/doc/html/rfc4422#appendix-A.1
initial_response = {binary, <<>>}},
send(Frame, 1, State);
send_sasl_init(State, {plain, User, Pass}) ->
Response = <<0:8, User/binary, 0:8, Pass/binary>>,
Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"PLAIN">>},
initial_response = {binary, Response}},
send(Frame, 1, State).
send(Record, FrameType, #state{socket = Socket}) ->
Encoded = amqp10_framing:encode_bin(Record),
Frame = amqp10_binary_generator:build_frame(0, FrameType, Encoded),
?DBG("CONN <- ~tp", [Record]),
amqp10_client_socket:send(Socket, Frame).
send_heartbeat(#state{socket = Socket}) ->
Frame = amqp10_binary_generator:build_heartbeat_frame(),
amqp10_client_socket:send(Socket, Frame).
notify_opened(#{notify_when_opened := none}, _) ->
ok;
notify_opened(#{notify_when_opened := Pid} = Config, Perf)
when is_pid(Pid) ->
notify_opened0(Config, Pid, Perf);
notify_opened(#{notify := Pid} = Config, Perf)
when is_pid(Pid) ->
notify_opened0(Config, Pid, Perf);
notify_opened(_, _) ->
ok.
notify_opened0(Config, Pid, Perf) ->
Evt = case Config of
#{notify_with_performative := true} ->
{opened, Perf};
_ ->
opened
end,
Pid ! amqp10_event(Evt),
ok.
notify_closed(#{notify_when_closed := none}, _Reason) ->
ok;
notify_closed(#{notify := none}, _Reason) ->
ok;
notify_closed(#{notify_when_closed := Pid} = Config, Reason)
when is_pid(Pid) ->
notify_closed0(Config, Pid, Reason);
notify_closed(#{notify := Pid} = Config, Reason)
when is_pid(Pid) ->
notify_closed0(Config, Pid, Reason).
notify_closed0(#{notify_with_performative := true}, Pid, Perf = #'v1_0.close'{}) ->
Pid ! amqp10_event({closed, Perf}),
ok;
notify_closed0(_, Pid, #'v1_0.close'{error = Error}) ->
Pid ! amqp10_event({closed, translate_err(Error)}),
ok;
notify_closed0(_, Pid, Reason) ->
Pid ! amqp10_event({closed, Reason}),
ok.
start_heartbeat_timer(Timeout) ->
timer:apply_after(Timeout, ?MODULE, heartbeat, [self()]).
unpack(V) -> amqp10_client_types:unpack(V).
-spec generate_container_id() -> binary().
generate_container_id() ->
Pre = atom_to_binary(node()),
Id = bin_to_hex(crypto:strong_rand_bytes(8)),
<<Pre/binary, <<"_">>/binary, Id/binary>>.
bin_to_hex(Bin) ->
<<<<if N >= 10 -> N -10 + $a;
true -> N + $0 end>>
|| <<N:4>> <= Bin>>.
translate_err(undefined) ->
none;
translate_err(#'v1_0.error'{condition = Cond, description = Desc}) ->
Err =
case Cond of
?V_1_0_AMQP_ERROR_INTERNAL_ERROR -> internal_error;
?V_1_0_AMQP_ERROR_NOT_FOUND -> not_found;
?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS -> unauthorized_access;
?V_1_0_AMQP_ERROR_DECODE_ERROR -> decode_error;
?V_1_0_AMQP_ERROR_RESOURCE_LIMIT_EXCEEDED -> resource_limit_exceeded;
?V_1_0_AMQP_ERROR_NOT_ALLOWED -> not_allowed;
?V_1_0_AMQP_ERROR_INVALID_FIELD -> invalid_field;
?V_1_0_AMQP_ERROR_NOT_IMPLEMENTED -> not_implemented;
?V_1_0_AMQP_ERROR_RESOURCE_LOCKED -> resource_locked;
?V_1_0_AMQP_ERROR_PRECONDITION_FAILED -> precondition_failed;
?V_1_0_AMQP_ERROR_RESOURCE_DELETED -> resource_deleted;
?V_1_0_AMQP_ERROR_ILLEGAL_STATE -> illegal_state;
?V_1_0_AMQP_ERROR_FRAME_SIZE_TOO_SMALL -> frame_size_too_small;
?V_1_0_CONNECTION_ERROR_CONNECTION_FORCED -> forced;
?V_1_0_CONNECTION_ERROR_FRAMING_ERROR -> framing_error;
?V_1_0_CONNECTION_ERROR_REDIRECT -> redirect;
_ -> Cond
end,
{Err, unpack(Desc)}.
amqp10_event(Evt) ->
{amqp10_event, {connection, self(), Evt}}.
decrypted_sasl_to_mechanism(anon) ->
<<"ANONYMOUS">>;
decrypted_sasl_to_mechanism(external) ->
<<"EXTERNAL">>;
decrypted_sasl_to_mechanism({plain, _, _}) ->
<<"PLAIN">>.
config_defaults() ->
#{sasl => none,
transfer_limit_margin => 0,
%% 1 MB
max_frame_size => 1_048_576}.