aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src/base/diameter_codec.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src/base/diameter_codec.erl')
-rw-r--r--lib/diameter/src/base/diameter_codec.erl232
1 files changed, 95 insertions, 137 deletions
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 789395abe9..52eb10b8c2 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -21,10 +21,8 @@
-module(diameter_codec).
-export([encode/2,
- decode/2,
decode/3,
- setopts/1,
- getopt/1,
+ decode/4,
collect_avps/1,
decode_header/1,
sequence_numbers/1,
@@ -33,7 +31,7 @@
msg_id/1]).
%% Towards generated encoders (from diameter_gen.hrl).
--export([pack_avp/1,
+-export([pack_data/2,
pack_avp/2]).
-include_lib("diameter/include/diameter.hrl").
@@ -66,52 +64,6 @@
%% +-+-+-+-+-+-+-+-+-+-+-+-+-
%%% ---------------------------------------------------------------------------
-%%% # setopts/1
-%%% # getopt/1
-%%% ---------------------------------------------------------------------------
-
-%% These functions are a compromise in the same vein as the use of the
-%% process dictionary in diameter_gen.hrl in generated codec modules.
-%% Instead of rewriting the entire dictionary generation to pass
-%% encode/decode options around, the calling process sets them by
-%% calling setopts/1. At current, the only option is whether or not to
-%% decode binaries as strings, which is used by diameter_types.
-
-setopts(Opts)
- when is_list(Opts) ->
- lists:foreach(fun setopt/1, Opts).
-
-%% The default string_decode true is for backwards compatibility.
-setopt({K, false = B})
- when K == string_decode;
- K == strict_mbit ->
- setopt(K, B);
-
-%% Regard anything but the generated RFC 3588 dictionary as modern.
-%% This affects the interpretation of defaults during the decode
-%% of values of type DiameterURI, this having changed from RFC 3588.
-%% (So much for backwards compatibility.)
-setopt({common_dictionary, diameter_gen_base_rfc3588}) ->
- setopt(rfc, 3588);
-
-setopt(_) ->
- ok.
-
-setopt(Key, Value) ->
- put({diameter, Key}, Value).
-
-getopt(Key) ->
- case get({diameter, Key}) of
- undefined when Key == string_decode;
- Key == strict_mbit ->
- true;
- undefined when Key == rfc ->
- 6733;
- V ->
- V
- end.
-
-%%% ---------------------------------------------------------------------------
%%% # encode/2
%%% ---------------------------------------------------------------------------
@@ -121,7 +73,7 @@ getopt(Key) ->
encode(Mod, #diameter_packet{} = Pkt) ->
try
- e(Mod, Pkt)
+ encode(Mod, _Opts = [], Pkt)
catch
exit: {Reason, Stack, #diameter_header{} = H} = T ->
%% Exit with a header in the reason to let the caller
@@ -139,11 +91,14 @@ encode(Mod, Msg) ->
Hdr = #diameter_header{version = ?DIAMETER_VERSION,
end_to_end_id = Seq,
hop_by_hop_id = Seq},
- encode(Mod, #diameter_packet{header = Hdr,
- msg = Msg}).
+ encode(Mod, #diameter_packet{header = Hdr,
+ msg = Msg}).
-e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
- try encode_avps(reorder(As)) of
+%% encode/3
+
+encode(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]}
+ = Pkt) ->
+ try encode_avps(reorder(As), Opts) of
Avps ->
Bin = list_to_binary(Avps),
Len = 20 + size(Bin),
@@ -171,7 +126,7 @@ e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
exit({Reason, diameter_lib:get_stacktrace(), Hdr})
end;
-e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
+encode(Mod, Opts, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
MsgName = rec2msg(Mod, Msg),
{Code, Flags, Aid} = msg_header(Mod, MsgName, Hdr0),
@@ -191,7 +146,7 @@ e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
Values = values(Msg),
- try encode_avps(Mod, MsgName, Values) of
+ try encode_avps(Mod, MsgName, Values, Opts) of
Avps ->
Bin = list_to_binary(Avps),
Len = 20 + size(Bin),
@@ -230,7 +185,7 @@ values([H|T])
values(Avps) ->
Avps.
-%% encode_avps/3
+%% encode_avps/4
%% Specifying values as a #diameter_avp list bypasses arity and other
%% checks: the values are expected to be already encoded and the AVP's
@@ -238,12 +193,12 @@ values(Avps) ->
%% these have to be able to resend whatever comes.
%% Message as a list of #diameter_avp{} ...
-encode_avps(_, _, [#diameter_avp{} | _] = Avps) ->
- encode_avps(reorder(Avps));
+encode_avps(_, _, [#diameter_avp{} | _] = Avps, Opts) ->
+ encode_avps(reorder(Avps), Opts);
%% ... or as a tuple list or record.
-encode_avps(Mod, MsgName, Values) ->
- Mod:encode_avps(MsgName, Values).
+encode_avps(Mod, MsgName, Values, Opts) ->
+ Mod:encode_avps(MsgName, Values, Opts).
%% reorder/1
%%
@@ -284,10 +239,10 @@ reorder([H | T], Acc) ->
reorder([], _) ->
false.
-%% encode_avps/1
+%% encode_avps/2
-encode_avps(Avps) ->
- lists:map(fun pack_avp/1, Avps).
+encode_avps(Avps, Opts) ->
+ [pack_avp(A, Opts) || A <- Avps].
%% msg_header/3
@@ -312,41 +267,39 @@ rec2msg(Mod, Rec) ->
Mod:rec2msg(element(1, Rec)).
%%% ---------------------------------------------------------------------------
-%%% # decode/2
+%%% # decode/3
%%% ---------------------------------------------------------------------------
%% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors.
--spec decode(module() | {module(), module()}, #diameter_packet{} | binary())
+-spec decode(module() | {module(), module()},
+ map(),
+ #diameter_packet{} | binary())
-> #diameter_packet{}.
%% An Answer setting the E-bit. The application dictionary is needed
-%% for the best-effort decode of Failed-AVP, and the best way to make
-%% this available to the AVP decode in diameter_gen.hrl, without
-%% having to rewrite the entire codec generation, is to place it in
-%% the process dictionary. It's the code in diameter_gen.hrl (that's
-%% included by every generated codec module) that looks for the entry.
-%% Not ideal, but it solves the problem relatively simply.
-decode({Mod, Mod}, Pkt) ->
- decode(Mod, Pkt);
-decode({Mod, AppMod}, Pkt) ->
- Key = {?MODULE, dictionary},
- put(Key, AppMod),
- try
- decode(Mod, Pkt)
- after
- erase(Key)
- end;
+%% for the best-effort decode of Failed-AVP.
+decode({Mod, AppMod}, Opts, Pkt) ->
+ decode(Mod, AppMod, Opts, Pkt);
%% Or not: a request, or an answer not setting the E-bit.
-decode(Mod, Pkt) ->
- decode(Mod:id(), Mod, Pkt).
+decode(Mod, Opts, Pkt) ->
+ decode(Mod, Mod, Opts, Pkt).
+
+%% decode/4
-%% decode/3
+decode(Id, Mod, Opts, Pkt)
+ when is_integer(Id) ->
+ decode(Id, Mod, Mod, Opts, Pkt);
+
+decode(Mod, AppMod, Opts, Pkt) ->
+ decode(Mod:id(), Mod, AppMod, Opts, Pkt).
+
+%% decode/5
%% Relay application: just extract the avp's without any decoding of
%% their data since we don't know the application in question.
-decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) ->
+decode(?APP_ID_RELAY, _, _, _, #diameter_packet{} = Pkt) ->
case collect_avps(Pkt) of
{E, As} ->
Pkt#diameter_packet{avps = As,
@@ -356,7 +309,7 @@ decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) ->
end;
%% Otherwise decode using the dictionary.
-decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) ->
+decode(_, Mod, AppMod, Opts, #diameter_packet{header = Hdr} = Pkt) ->
#diameter_header{cmd_code = CmdCode,
is_request = IsRequest,
is_error = IsError}
@@ -368,29 +321,33 @@ decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) ->
Mod:msg_name(CmdCode, IsRequest)
end,
- decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt));
+ decode_avps(MsgName, Mod, AppMod, Opts, Pkt, collect_avps(Pkt));
-decode(Id, Mod, Bin)
+decode(Id, Mod, AppMod, Opts, Bin)
when is_binary(Bin) ->
- decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}).
+ decode(Id, Mod, AppMod, Opts, #diameter_packet{header = decode_header(Bin),
+ bin = Bin}).
-%% decode_avps/4
+%% decode_avps/6
-decode_avps(MsgName, Mod, Pkt, {E, Avps}) ->
+decode_avps(MsgName, Mod, AppMod, Opts, Pkt, {E, Avps}) ->
?LOG(invalid_avp_length, Pkt#diameter_packet.header),
#diameter_packet{errors = Failed}
= P
- = decode_avps(MsgName, Mod, Pkt, Avps),
+ = decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps),
P#diameter_packet{errors = [E | Failed]};
-decode_avps('', _, Pkt, Avps) -> %% unknown message ...
+decode_avps('', _, _, _, Pkt, Avps) -> %% unknown message ...
?LOG(unknown_message, Pkt#diameter_packet.header),
Pkt#diameter_packet{avps = lists:reverse(Avps),
errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED
%% msg = undefined identifies this case.
-decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not
- {Rec, As, Errors} = Mod:decode_avps(MsgName, Avps),
+decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps) -> %% ... or not
+ {Rec, As, Errors} = Mod:decode_avps(MsgName,
+ Avps,
+ Opts#{dictionary => AppMod,
+ failed_avp => false}),
?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header),
Pkt#diameter_packet{msg = Rec,
errors = Errors,
@@ -655,7 +612,7 @@ collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) ->
%% (2) the last sentence covers this case.
%%% ---------------------------------------------------------------------------
-%%% # pack_avp/1
+%%% # pack_avp/2
%%% ---------------------------------------------------------------------------
%% The normal case here is data as an #diameter_avp{} list or an
@@ -665,34 +622,36 @@ collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) ->
%% Decoded Grouped AVP with decoded components: ignore components
%% since they're already encoded in the Grouped AVP.
-pack_avp([#diameter_avp{} = Grouped | _Components]) ->
- pack_avp(Grouped);
+pack_avp([#diameter_avp{} = Grouped | _Components], Opts) ->
+ pack_avp(Grouped, Opts);
%% Grouped AVP whose components need packing. It's intentional that
%% this isn't equivalent to [Grouped | Components]: here the
%% components need to be encoded before wrapping with the Grouped AVP,
%% and the list is flat, nesting being accomplished in the data
%% fields.
-pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} = Grouped) ->
- pack_data(encode_avps(Components), Grouped);
+pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components}
+ = Grouped,
+ Opts) ->
+ pack_data(Grouped, encode_avps(Components, Opts));
%% Data as a type/value tuple ...
-pack_avp(#diameter_avp{data = {Type, Value}} = A)
+pack_avp(#diameter_avp{data = {Type, Value}} = A, Opts)
when is_atom(Type) ->
- pack_data(diameter_types:Type(encode, Value), A);
+ pack_data(A, diameter_types:Type(encode, Value, Opts));
%% ... with a header in various forms ...
-pack_avp(#diameter_avp{data = {T, {Type, Value}}}) ->
- pack_avp(T, diameter_types:Type(encode, Value));
+pack_avp(#diameter_avp{data = {T, {Type, Value}}}, Opts) ->
+ pack_data(T, diameter_types:Type(encode, Value, Opts));
-pack_avp(#diameter_avp{data = {T, Data}}) ->
- pack_avp(T, Data);
+pack_avp(#diameter_avp{data = {T, Data}}, _) ->
+ pack_data(T, Data);
-pack_avp(#diameter_avp{data = {Dict, Name, Data}}) ->
- pack_avp(Dict:avp_header(Name), Dict:avp(encode, Data, Name));
+pack_avp(#diameter_avp{data = {Dict, Name, Data}}, Opts) ->
+ pack_data(Dict:avp_header(Name), Dict:avp(encode, Data, Name, Opts));
%% ... with a truncated header ...
-pack_avp(#diameter_avp{code = undefined, data = B})
+pack_avp(#diameter_avp{code = undefined, data = B}, _)
when is_binary(B) ->
%% Reset the AVP Length of an AVP Header resulting from a 5014
%% error. The RFC doesn't explicitly say to do this but the
@@ -706,54 +665,53 @@ pack_avp(#diameter_avp{code = undefined, data = B})
<<B:Sz/binary, 0:(5-Sz)/unit:8, Len:24, 0:(Len-8)/unit:8>>;
%% Ignoring errors in Failed-AVP or during a relay encode.
-pack_avp(#diameter_avp{data = {5014, Data}} = A) ->
- pack_data(Data, A);
+pack_avp(#diameter_avp{data = {5014, Data}} = A, _) ->
+ pack_data(A, Data);
-pack_avp(#diameter_avp{data = Data} = A) ->
- pack_data(Data, A).
+pack_avp(#diameter_avp{data = Data} = A, _) ->
+ pack_data(A, Data).
header_length(<<_:32, 1:1, _/bits>>) ->
12;
header_length(_) ->
8.
-%% pack_data/2
+%%% ---------------------------------------------------------------------------
+%%% # pack_data/2
+%%% ---------------------------------------------------------------------------
-pack_data(Data, #diameter_avp{code = Code,
- vendor_id = V,
- is_mandatory = M,
- need_encryption = P}) ->
+pack_data(#diameter_avp{code = Code,
+ vendor_id = V,
+ is_mandatory = M,
+ need_encryption = P},
+ Data) ->
Flags = ?BIT(V /= undefined, 2#10000000)
bor ?BIT(M, 2#01000000)
bor ?BIT(P, 2#00100000),
- pack_avp(Code, Flags, V, Data).
-
-%%% ---------------------------------------------------------------------------
-%%% # pack_avp/2
-%%% ---------------------------------------------------------------------------
+ pack(Code, Flags, V, Data);
-pack_avp({Code, Flags, VendorId}, Data) ->
- pack_avp(Code, Flags, VendorId, Data).
+pack_data({Code, Flags, VendorId}, Data) ->
+ pack(Code, Flags, VendorId, Data).
-%% pack_avp/4
+%% pack/4
-pack_avp(Code, Flags, VendorId, Data) ->
+pack(Code, Flags, VendorId, Data) ->
Sz = iolist_size(Data),
- pack_avp(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)).
+ pack(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)).
%% Padding is not included in the length field, as mandated by the RFC.
-%% pack_avp/6
+%% pack/6
%%
%% Prepend the vendor id as required.
-pack_avp(Code, Flags, Sz, _Vid, Data, Pad)
+pack(Code, Flags, Sz, _Vid, Data, Pad)
when 0 == Flags band 2#10000000 ->
- pack_avp(Code, Flags, Sz, 0, 0, Data, Pad);
+ pack(Code, Flags, Sz, 0, 0, Data, Pad);
-pack_avp(Code, Flags, Sz, Vid, Data, Pad) ->
- pack_avp(Code, Flags, Sz+4, Vid, 1, Data, Pad).
+pack(Code, Flags, Sz, Vid, Data, Pad) ->
+ pack(Code, Flags, Sz+4, Vid, 1, Data, Pad).
-%% pack_avp/7
+%% pack/7
-pack_avp(Code, Flags, Sz, VId, V, Data, Pad) ->
+pack(Code, Flags, Sz, VId, V, Data, Pad) ->
[<<Code:32, Flags:8, (8+Sz):24, VId:V/unit:32>>, Data, <<0:Pad/unit:8>>].