aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/include
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2011-05-18 18:29:12 +0200
committerAnders Svensson <[email protected]>2011-05-18 18:29:12 +0200
commit3c15ff32e89e401b4dde2b8acc9699be2614b996 (patch)
tree184dc988fb2ab3af04a532bc59cc794a8d74fbd3 /lib/diameter/include
parentb1e768e86593178810c8a0b3c38443dcf6be5181 (diff)
downloadotp-3c15ff32e89e401b4dde2b8acc9699be2614b996.tar.gz
otp-3c15ff32e89e401b4dde2b8acc9699be2614b996.tar.bz2
otp-3c15ff32e89e401b4dde2b8acc9699be2614b996.zip
Initial commit of the diameter application.
The application provides an implementation of the Diameter protocol as defined in RFC 3588.
Diffstat (limited to 'lib/diameter/include')
-rw-r--r--lib/diameter/include/diameter.hrl130
-rw-r--r--lib/diameter/include/diameter_gen.hrl431
2 files changed, 561 insertions, 0 deletions
diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl
new file mode 100644
index 0000000000..8bd1ad1e51
--- /dev/null
+++ b/lib/diameter/include/diameter.hrl
@@ -0,0 +1,130 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(diameter_hrl).
+-define(diameter_hrl, true).
+
+%% RFC 3588, 2.4:
+-define(DIAMETER_APP_ID_COMMON, 0).
+-define(DIAMETER_APP_ID_ACCOUNTING, 3).
+-define(DIAMETER_APP_ID_RELAY, 16#FFFFFFFF).
+
+%% Corresponding dictionaries:
+-define(DIAMETER_DICT_COMMON, diameter_gen_base_rfc3588).
+-define(DIAMETER_DICT_ACCOUNTING, diameter_gen_base_accounting).
+-define(DIAMETER_DICT_RELAY, diameter_gen_relay).
+
+%% Events sent to processes that have subscribed with
+%% diameter:subscribe/1.
+%%
+-record(diameter_event,
+ {service, %% name
+ info}). %% tuple()
+
+%% diameter_packet records are passed through the encode/decode
+%% interface supplied by a dictionary module configured on a Diameter
+%% application. For an incoming message the bin field contains the
+%% received binary and the header, avps, msg and errors fields the
+%% result of decoding.
+
+-record(diameter_packet,
+ {header, %% #diameter_header{}
+ avps, %% deep list() of #diameter_avp{}
+ msg, %% fully decoded message
+ bin, %% binary received/sent over the wire
+ errors = [],%% list() of Result-Code | {Result-Code, #diameter_avp{}}
+ transport_data}).
+
+-record(diameter_header,
+ {version, %% 8-bit unsigned
+ length, %% 24-bit unsigned
+ cmd_code, %% 8-bit unsigned
+ application_id, %% 24-bit unsigned
+ hop_by_hop_id, %% 32-bit unsigned
+ end_to_end_id, %% 32-bit unsigned
+ is_request, %% boolean() R flag
+ is_proxiable, %% boolean() P flag
+ is_error, %% boolean() E flag
+ is_retransmitted}). %% boolean() T flag
+
+-record(diameter_avp,
+ {code, %% 32-bit unsigned
+ vendor_id, %% 32-bit unsigned
+ is_mandatory = false, %% boolean() M flag
+ need_encryption = false, %% boolean() P flag
+ data, %% encoded binary()
+ name, %% atom() AVP name
+ value, %% decoded term() decoded | undefined
+ type, %% atom() type name,
+ index}). %% non_neg_integer() | undefined
+
+%% A diameter_caps record corresponds to capabilities configuration on
+%% diameter:start_service/2. In application callbacks is identifies
+%% the peer connection for which the callback is taking place, and in
+%% this case each field is a 2-tuple specifying the host (ie. local)
+%% and peer (ie. remote) values, host values having been configured
+%% and peer values having been received at capabilities exchange.
+
+-record(diameter_caps,
+ {origin_host, %% 'DiameterIdentity'()
+ origin_realm, %% 'DiameterIdentity'()
+ host_ip_address = [], %% ['Address'()]
+ vendor_id, %% 'Unsigned32'()
+ product_name, %% 'OctetString'()
+ origin_state_id = [], %% ['Unsigned32'()]
+ supported_vendor_id = [], %% ['Unsigned32'()]
+ auth_application_id = [], %% ['Unsigned32'()]
+ inband_security_id = [], %% ['Unsigned32'()]
+ acct_application_id = [], %% ['Unsigned32'()]
+ vendor_specific_application_id = [], %% ['Grouped'()]
+ firmware_revision = [], %% ['Unsigned32()]
+ avp = []}).
+
+%% AVP's of type DiameterURI are encoded as a diameter_uri record.
+%% Note that AVP's of type IPFilterRule and QoSFilterRule are currently
+%% encoded simply as OctetString's.
+
+-record(diameter_uri,
+ {type, %% aaa | aaas
+ fqdn, %% string()
+ port = 3868, %% non_neg_integer(),
+ transport = sctp, %% | tcp,
+ protocol = diameter}). %% | radius | 'tacacs+'
+
+%% The diameter service and diameter_apps records are only passed
+%% through the transport interface when starting a transport process,
+%% although typically a transport implementation will (and probably
+%% should) only be interested diameter_service.host_ip_address.
+
+-record(diameter_service,
+ {pid,
+ capabilities, %% #diameter_caps{}
+ applications = []}). %% [#diameter_app{}]
+
+-record(diameter_app,
+ {alias, %% option 'alias'
+ dictionary, %% option 'dictionary', module()
+ module, %% [Mod | Args] callback module() and extra args
+ init_state, %% option 'state', initial callback state
+ id, %% 32-bit unsigned application identifier = Dict:id()
+ mutable = false, %% boolean(), do traffic callbacks modify state?
+ answer_errors = report}). %% | callback | discard
+ %% how to handle containing errors?
+
+-endif. %% -ifdef(diameter_hrl).
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
new file mode 100644
index 0000000000..4c91954a21
--- /dev/null
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -0,0 +1,431 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This file contains code that's included by encode/decode modules
+%% generated by diameter_codegen.erl. This code does most of the work, the
+%% generated code being kept simple.
+%%
+
+-define(THROW(T), throw({?MODULE, T})).
+
+%%% ---------------------------------------------------------------------------
+%%% # encode_avps/3
+%%%
+%%% Returns: binary()
+%%% ---------------------------------------------------------------------------
+
+encode_avps(Name, Vals)
+ when is_list(Vals) ->
+ encode_avps(Name, '#set-'(Vals, newrec(Name)));
+
+encode_avps(Name, Rec) ->
+ try
+ list_to_binary(encode(Name, Rec))
+ catch
+ throw: {?MODULE, Reason} ->
+ diameter_dbg:log({encode, error},
+ ?MODULE,
+ ?LINE,
+ {Reason, Name, Rec}),
+ erlang:error(list_to_tuple(Reason ++ [Name, Rec, ?MODULE]));
+ error: Reason ->
+ Stack = erlang:get_stacktrace(),
+ diameter_dbg:log({encode, failure},
+ ?MODULE,
+ ?LINE,
+ {Reason, Name, Rec, Stack}),
+ erlang:error({encode_failure, Reason, Name, Rec, ?MODULE, Stack})
+ end.
+
+%% encode/2
+
+encode(Name, Rec) ->
+ lists:flatmap(fun(A) -> encode(Name, A, '#get-'(A, Rec)) end,
+ '#info-'(element(1, Rec), fields)).
+
+%% encode/3
+
+encode(Name, AvpName, Values) ->
+ e(Name, AvpName, avp_arity(Name, AvpName), Values).
+
+%% e/4
+
+e(_, AvpName, 1, undefined) ->
+ ?THROW([mandatory_avp_missing, AvpName]);
+
+e(Name, AvpName, 1, Value) ->
+ e(Name, AvpName, [Value]);
+
+e(_, _, {0,_}, []) ->
+ [];
+
+e(_, AvpName, _, T)
+ when not is_list(T) ->
+ ?THROW([repeated_avp_as_non_list, AvpName, T]);
+
+e(_, AvpName, {Min, _}, L)
+ when length(L) < Min ->
+ ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]);
+
+e(_, AvpName, {_, Max}, L)
+ when Max < length(L) ->
+ ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]);
+
+e(Name, AvpName, _, Values) ->
+ e(Name, AvpName, Values).
+
+%% e/3
+
+e(Name, 'AVP', Values) ->
+ [pack_AVP(Name, A) || A <- Values];
+
+e(_, AvpName, Values) ->
+ e(AvpName, Values).
+
+%% e/2
+
+e(AvpName, Values) ->
+ H = avp_header(AvpName),
+ [diameter_codec:pack_avp(H, avp(encode, V, AvpName)) || V <- Values].
+
+%% pack_AVP/2
+
+%% No value: assume AVP data is already encoded. The normal case will
+%% be when this is passed back from #diameter_packet.errors as a
+%% consequence of a failed decode. Any AVP can be encoded this way
+%% however, which side-steps any arity checks for known AVP's and
+%% could potentially encode something unfortunate.
+pack_AVP(_, #diameter_avp{value = undefined} = A) ->
+ diameter_codec:pack_avp(A);
+
+%% Missing name for value encode.
+pack_AVP(_, #diameter_avp{name = N, value = V})
+ when N == undefined;
+ N == 'AVP' ->
+ ?THROW([value_with_nameless_avp, N, V]);
+
+%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we
+%% don't know this AVP at all then the encode will fail.
+pack_AVP(Name, #diameter_avp{name = AvpName,
+ value = Data}) ->
+ 0 == avp_arity(Name, AvpName)
+ orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]),
+ e(AvpName, [Data]).
+
+%%% ---------------------------------------------------------------------------
+%%% # decode_avps/2
+%%%
+%%% Returns: {Rec, Avps, Failed}
+%%%
+%%% Rec = decoded message record
+%%% Avps = list of Avp
+%%% Failed = list of {ResultCode, #diameter_avp{}}
+%%%
+%%% Avp = #diameter_avp{} if type is not Grouped
+%%% | list of Avp where first element has type Grouped
+%%% and following elements are its component
+%%% AVP's.
+%%% ---------------------------------------------------------------------------
+
+decode_avps(Name, Recs) ->
+ d_rc(Name, lists:foldl(fun(T,A) -> decode(Name, T, A) end,
+ {[], {newrec(Name), []}},
+ Recs)).
+
+newrec(Name) ->
+ '#new-'(name2rec(Name)).
+
+%% No errors so far: keep looking.
+d_rc(Name, {Avps, {Rec, [] = Failed}}) ->
+ try
+ true = have_required_avps(Rec, Name),
+ {Rec, Avps, Failed}
+ catch
+ throw: {?MODULE, {AvpName, Reason}} ->
+ diameter_dbg:log({decode, error},
+ ?MODULE,
+ ?LINE,
+ {AvpName, Reason, Rec}),
+ {Rec, Avps, [{5005, empty_avp(AvpName)}]}
+ end;
+%% 3588:
+%%
+%% DIAMETER_MISSING_AVP 5005
+%% The request did not contain an AVP that is required by the Command
+%% Code definition. If this value is sent in the Result-Code AVP, a
+%% Failed-AVP AVP SHOULD be included in the message. The Failed-AVP
+%% AVP MUST contain an example of the missing AVP complete with the
+%% Vendor-Id if applicable. The value field of the missing AVP
+%% should be of correct minimum length and contain zeroes.
+
+%% Or not. Only need to report the first error so look no further.
+d_rc(_, {Avps, {Rec, Failed}}) ->
+ {Rec, Avps, lists:reverse(Failed)}.
+
+%% empty_avp/1
+
+empty_avp(Name) ->
+ {Code, Flags, VId} = avp_header(Name),
+ {Name, Type} = avp_name(Code, VId),
+ #diameter_avp{name = Name,
+ code = Code,
+ vendor_id = VId,
+ is_mandatory = 0 /= (Flags band 2#01000000),
+ need_encryption = 0 /= (Flags band 2#00100000),
+ data = empty_value(Name),
+ type = Type}.
+
+%% have_required_avps/2
+
+have_required_avps(Rec, Name) ->
+ lists:foreach(fun(F) -> hra(Name, F, Rec) end,
+ '#info-'(element(1, Rec), fields)),
+ true.
+
+hra(Name, AvpName, Rec) ->
+ Arity = avp_arity(Name, AvpName),
+ hra(Arity, '#get-'(AvpName, Rec))
+ orelse ?THROW({AvpName, {insufficient_arity, Arity}}).
+
+%% Maximum arities have already been checked in building the record.
+
+hra({Min, _}, L) ->
+ Min =< length(L);
+hra(N, V) ->
+ N /= 1 orelse V /= undefined.
+
+%% 3588, ch 7:
+%%
+%% The Result-Code AVP describes the error that the Diameter node
+%% encountered in its processing. In case there are multiple errors,
+%% the Diameter node MUST report only the first error it encountered
+%% (detected possibly in some implementation dependent order). The
+%% specific errors that can be described by this AVP are described in
+%% the following section.
+
+%% decode/3
+
+decode(Name, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) ->
+ decode(Name, avp_name(Code, Vid), Avp, Acc).
+
+%% decode/4
+
+%% Don't know this AVP: see if it can be packed in an 'AVP' field
+%% undecoded, unless it's mandatory. Need to give Failed-AVP special
+%% treatment since it'll contain any unrecognized mandatory AVP's.
+decode(Name, 'AVP', #diameter_avp{is_mandatory = M} = Avp, {Avps, Acc}) ->
+ {[Avp | Avps], if M, Name /= 'Failed-AVP' ->
+ unknown(Avp, Acc);
+ true ->
+ pack_AVP(Name, Avp, Acc)
+ end};
+%% Note that the type field is 'undefined' in this case.
+
+%% Or try to decode.
+decode(Name, {AvpName, Type}, Avp, Acc) ->
+ d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc).
+
+%% d/3
+
+d(Name, Avp, {Avps, Acc}) ->
+ #diameter_avp{name = AvpName,
+ data = Data}
+ = Avp,
+
+ try avp(decode, Data, AvpName) of
+ V ->
+ {H, A} = ungroup(V, Avp),
+ {[H | Avps], pack_avp(Name, A, Acc)}
+ catch
+ error: Reason ->
+ %% Failures here won't be visible since they're a "normal"
+ %% occurrence if the peer sends a faulty AVP that we need to
+ %% respond sensibly to. Log the occurence for traceability,
+ %% but the peer will also receive info in the resulting
+ %% answer-message.
+ diameter_dbg:log({decode, failure},
+ ?MODULE,
+ ?LINE,
+ {Reason, Avp, erlang:get_stacktrace()}),
+ {Rec, Failed} = Acc,
+ {[Avp|Avps], {Rec, [{rc(Reason), Avp} | Failed]}}
+ end.
+
+%% rc/1
+
+%% diameter_types will raise an error of this form to communicate
+%% DIAMETER_INVALID_AVP_LENGTH (5014). A module specified to a
+%% @custom_types tag in a spec file can also raise an error of this
+%% form.
+rc({'DIAMETER', RC, _}) ->
+ RC;
+
+%% 3588:
+%%
+%% DIAMETER_INVALID_AVP_VALUE 5004
+%% The request contained an AVP with an invalid value in its data
+%% portion. A Diameter message indicating this error MUST include
+%% the offending AVPs within a Failed-AVP AVP.
+rc(_) ->
+ 5004.
+
+%% ungroup/2
+%%
+%% Returns: {Avp, Dec}
+%%
+%% Avp = #diameter_avp{} if type is not Grouped
+%% | list of Avp where first element has type Grouped
+%% and following elements are its component
+%% AVP's.
+%% = as for decode_avps/2
+%%
+%% Dec = #diameter_avp{}, either Avp or its head in the list case.
+
+%% The decoded value in the Grouped case is as returned by grouped_avp/3:
+%% a record and a list of component AVP's.
+ungroup(V, #diameter_avp{type = 'Grouped'} = Avp) ->
+ {Rec, As} = V,
+ A = Avp#diameter_avp{value = Rec},
+ {[A|As], A};
+
+%% Otherwise it's just a plain value.
+ungroup(V, #diameter_avp{} = Avp) ->
+ A = Avp#diameter_avp{value = V},
+ {A, A}.
+
+%% pack_avp/3
+
+pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Acc) ->
+ pack_avp(Name, avp_arity(Name, AvpName), Avp, Acc).
+
+%% pack_avp/4
+
+pack_avp(Name, 0, Avp, Acc) ->
+ pack_AVP(Name, Avp, Acc);
+
+pack_avp(_, Arity, Avp, Acc) ->
+ pack(Arity, Avp#diameter_avp.name, Avp, Acc).
+
+%% pack_AVP/3
+
+pack_AVP(Name, Avp, Acc) ->
+ case avp_arity(Name, 'AVP') of
+ 0 ->
+ unknown(Avp, Acc);
+ Arity ->
+ pack(Arity, 'AVP', Avp, Acc)
+ end.
+
+%% 3588:
+%%
+%% DIAMETER_AVP_UNSUPPORTED 5001
+%% The peer received a message that contained an AVP that is not
+%% recognized or supported and was marked with the Mandatory bit. A
+%% Diameter message with this error MUST contain one or more Failed-
+%% AVP AVP containing the AVPs that caused the failure.
+%%
+%% DIAMETER_AVP_NOT_ALLOWED 5008
+%% A message was received with an AVP that MUST NOT be present. The
+%% Failed-AVP AVP MUST be included and contain a copy of the
+%% offending AVP.
+%%
+unknown(#diameter_avp{is_mandatory = B} = Avp, {Rec, Failed}) ->
+ {Rec, [{if B -> 5001; true -> 5008 end, Avp} | Failed]}.
+
+%% pack/4
+
+pack(Arity, FieldName, Avp, {Rec, _} = Acc) ->
+ pack('#get-'(FieldName, Rec), Arity, FieldName, Avp, Acc).
+
+%% pack/5
+
+pack(undefined, 1, FieldName, Avp, Acc) ->
+ p(FieldName, fun(V) -> V end, Avp, Acc);
+
+%% 3588:
+%%
+%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
+%% A message was received that included an AVP that appeared more
+%% often than permitted in the message definition. The Failed-AVP
+%% AVP MUST be included and contain a copy of the first instance of
+%% the offending AVP that exceeded the maximum number of occurrences
+%%
+pack(_, 1, _, Avp, {Rec, Failed}) ->
+ {Rec, [{5009, Avp} | Failed]};
+pack(L, {_, Max}, _, Avp, {Rec, Failed})
+ when length(L) == Max ->
+ {Rec, [{5009, Avp} | Failed]};
+
+pack(L, _, FieldName, Avp, Acc) ->
+ p(FieldName, fun(V) -> [V|L] end, Avp, Acc).
+
+%% p/4
+
+p(F, Fun, Avp, {Rec, Failed}) ->
+ {'#set-'({F, Fun(value(F, Avp))}, Rec), Failed}.
+
+value('AVP', Avp) ->
+ Avp;
+value(_, Avp) ->
+ Avp#diameter_avp.value.
+
+%%% ---------------------------------------------------------------------------
+%%% # grouped_avp/3
+%%% ---------------------------------------------------------------------------
+
+grouped_avp(decode, Name, Data) ->
+ {Rec, Avps, []} = decode_avps(Name, diameter_codec:collect_avps(Data)),
+ {Rec, Avps};
+%% Note that a failed match here will result in 5004. Note that this is
+%% the only AVP type that doesn't just return the decoded value, also
+%% returning the list of component #diameter_avp{}'s.
+
+grouped_avp(encode, Name, Data) ->
+ encode_avps(Name, Data).
+
+%%% ---------------------------------------------------------------------------
+%%% # empty_group/1
+%%% ---------------------------------------------------------------------------
+
+empty_group(Name) ->
+ list_to_binary(empty_body(Name)).
+
+empty_body(Name) ->
+ [z(F, avp_arity(Name, F)) || F <- '#info-'(name2rec(Name), fields)].
+
+z(Name, 1) ->
+ z(Name);
+z(_, {0,_}) ->
+ [];
+z(Name, {Min, _}) ->
+ lists:duplicate(Min, z(Name)).
+
+z('AVP') ->
+ <<0:64/integer>>; %% minimal header
+z(Name) ->
+ Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)),
+ << <<0>> || <<_>> <= Bin >>.
+
+%%% ---------------------------------------------------------------------------
+%%% # empty/1
+%%% ---------------------------------------------------------------------------
+
+empty(AvpName) ->
+ avp(encode, zero, AvpName).