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.erl145
1 files changed, 123 insertions, 22 deletions
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 06a4f5de64..bf2fe8e7ca 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -22,6 +22,8 @@
-export([encode/2,
decode/2,
decode/3,
+ setopts/1,
+ getopt/1,
collect_avps/1,
decode_header/1,
sequence_numbers/1,
@@ -59,6 +61,50 @@
%% +-+-+-+-+-+-+-+-+-+-+-+-+-
%%% ---------------------------------------------------------------------------
+%%% # 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).
+
+%% Decode stringish types to string()? The default true is for
+%% backwards compatibility.
+setopt({string_decode = K, false = B}) ->
+ 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 ->
+ true;
+ undefined when Key == rfc ->
+ 6733;
+ V ->
+ V
+ end.
+
+%%% ---------------------------------------------------------------------------
%%% # encode/2
%%% ---------------------------------------------------------------------------
@@ -90,7 +136,7 @@ encode(Mod, Msg) ->
msg = Msg}).
e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
- try encode_avps(As) of
+ try encode_avps(reorder(As)) of
Avps ->
Length = size(Avps) + 20,
@@ -183,26 +229,50 @@ values(Avps) ->
%% Message as a list of #diameter_avp{} ...
encode_avps(_, _, [#diameter_avp{} | _] = Avps) ->
- encode_avps(reorder(Avps, [], Avps));
+ encode_avps(reorder(Avps));
%% ... or as a tuple list or record.
encode_avps(Mod, MsgName, Values) ->
Mod:encode_avps(MsgName, Values).
%% reorder/1
+%%
+%% Reorder AVPs for the relay case using the index field of
+%% diameter_avp records. Decode populates this field in collect_avps
+%% and presents AVPs in reverse order. A relay then sends the reversed
+%% list with a Route-Record AVP prepended. The goal here is just to do
+%% lists:reverse/1 in Grouped AVPs and the outer list, but only in the
+%% case there are indexed AVPs at all, so as not to reverse lists that
+%% have been explicilty sent (unindexed, in the desired order) as a
+%% diameter_avp list. The effect is the same as lists:keysort/2, but
+%% only on the cases we expect, not a general sort.
+
+reorder(Avps) ->
+ case reorder(Avps, []) of
+ false ->
+ Avps;
+ Sorted ->
+ Sorted
+ end.
+
+%% reorder/3
-reorder([#diameter_avp{index = 0} | _] = Avps, Acc, _) ->
+%% In case someone has reversed the list already. (Not likely.)
+reorder([#diameter_avp{index = 0} | _] = Avps, Acc) ->
Avps ++ Acc;
-reorder([#diameter_avp{index = N} = A | Avps], Acc, _)
+%% Assume indexed AVPs are in reverse order.
+reorder([#diameter_avp{index = N} = A | Avps], Acc)
when is_integer(N) ->
lists:reverse(Avps, [A | Acc]);
-reorder([H | T], Acc, Avps) ->
- reorder(T, [H | Acc], Avps);
+%% An unindexed AVP.
+reorder([H | T], Acc) ->
+ reorder(T, [H | Acc]);
-reorder([], Acc, _) ->
- Acc.
+%% No indexed members.
+reorder([], _) ->
+ false.
%% encode_avps/1
@@ -237,15 +307,35 @@ rec2msg(Mod, Rec) ->
%% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors.
--spec decode(module(), #diameter_packet{} | binary())
+-spec decode(module() | {module(), module()}, #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;
+
+%% Or not: a request, or an answer not setting the E-bit.
decode(Mod, Pkt) ->
decode(Mod:id(), Mod, Pkt).
-%% If we're a relay application then just extract the avp's without
-%% any decoding of their data since we don't know the application in
-%% question.
+%% decode/3
+
+%% 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) ->
case collect_avps(Pkt) of
{E, As} ->
@@ -274,6 +364,8 @@ decode(Id, Mod, Bin)
when is_binary(Bin) ->
decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}).
+%% decode_avps/4
+
decode_avps(MsgName, Mod, Pkt, {E, Avps}) ->
?LOG(invalid_avp_length, Pkt#diameter_packet.header),
#diameter_packet{errors = Failed}
@@ -368,6 +460,9 @@ sequence_numbers(#diameter_packet{bin = Bin})
sequence_numbers(#diameter_packet{header = #diameter_header{} = H}) ->
sequence_numbers(H);
+sequence_numbers(#diameter_packet{msg = [#diameter_header{} = H | _]}) ->
+ sequence_numbers(H);
+
sequence_numbers(#diameter_header{hop_by_hop_id = H,
end_to_end_id = E}) ->
{H,E};
@@ -539,14 +634,18 @@ split_data(Bin, Len) ->
<<Data:Len/binary, _:Pad/binary, Rest/binary>> ->
{Data, Rest};
_ ->
- %% Header length points past the end of the message. As
- %% stated in the 6733 text above, it's sufficient to
- %% return a zero-filled minimal payload if this is a
- %% request. Do this (in cases that we know the type) by
- %% inducing a decode failure and letting the dictionary's
- %% decode (in diameter_gen) deal with it. Here we don't
- %% know type. If the type isn't known, then the decode
- %% just strips the extra bit.
+ %% Header length points past the end of the message, or
+ %% doesn't span the header. As stated in the 6733 text
+ %% above, it's sufficient to return a zero-filled minimal
+ %% payload if this is a request. Do this (in cases that we
+ %% know the type) by inducing a decode failure and letting
+ %% the dictionary's decode (in diameter_gen) deal with it.
+ %%
+ %% Note that the extra bit can only occur in the trailing
+ %% AVP of a message or Grouped AVP, since a faulty AVP
+ %% Length is otherwise indistinguishable from a correct
+ %% one here, since we don't know the types of the AVPs
+ %% being extracted.
{<<0:1, Bin/binary>>, <<>>}
end.
@@ -560,6 +659,8 @@ split_data(Bin, Len) ->
%% dictionary doesn't know about specific AVP's.
%% Grouped AVP whose components need packing ...
+pack_avp([#diameter_avp{} = A | Avps]) ->
+ pack_avp(A#diameter_avp{data = Avps});
pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) ->
pack_avp(A#diameter_avp{data = encode_avps(Avps)});
@@ -593,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B})
Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
<<H/binary, Len:24, T/binary>>;
-%% ... from a dictionary compiled against old code in diameter_gen ...
%% ... when ignoring errors in Failed-AVP ...
+%% ... during a relay encode ...
pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
pack_avp(A#diameter_avp{data = B});