4
%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
20
-module(diameter_types).
23
%% Encode/decode of RFC 3588 Data Formats, Basic (section 4.2) and
24
%% Derived (section 4.3).
28
-export(['OctetString'/2,
45
%% Functions taking the AVP name in question as second parameter.
46
-export(['OctetString'/3,
61
-include_lib("diameter/include/diameter.hrl").
63
-define(UINT(N,X), ((0 =< X) andalso (X < 1 bsl N))).
64
-define(SINT(N,X), ((-1*(1 bsl (N-1)) < X) andalso (X < 1 bsl (N-1)))).
66
%% The Grouped and Enumerated types are dealt with directly in
67
%% generated decode modules by way of diameter_gen.hrl and
68
%% diameter_codec.erl. Padding and the setting of Length and other
69
%% fields are also dealt with there.
73
%% DIAMETER_INVALID_AVP_LENGTH 5014
74
%% The request contained an AVP with an invalid length. A Diameter
75
%% message indicating this error MUST include the offending AVPs
76
%% within a Failed-AVP AVP.
78
-define(INVALID_LENGTH(Bin), erlang:error({'DIAMETER', 5014, Bin})).
80
%% -------------------------------------------------------------------------
81
%% 3588, 4.2. Basic AVP Data Formats
83
%% The Data field is zero or more octets and contains information
84
%% specific to the Attribute. The format and length of the Data field
85
%% is determined by the AVP Code and AVP Length fields. The format of
86
%% the Data field MUST be one of the following base data types or a data
87
%% type derived from the base data types. In the event that a new Basic
88
%% AVP Data Format is needed, a new version of this RFC must be created.
89
%% --------------------
91
'OctetString'(decode, Bin)
92
when is_binary(Bin) ->
95
'OctetString'(encode = M, zero) ->
98
'OctetString'(encode, Str) ->
99
iolist_to_binary(Str).
101
%% --------------------
103
'Integer32'(decode, <<X:32/signed>>) ->
106
'Integer32'(decode, B) ->
109
'Integer32'(encode = M, zero) ->
112
'Integer32'(encode, I)
116
%% --------------------
118
'Integer64'(decode, <<X:64/signed>>) ->
121
'Integer64'(decode, B) ->
124
'Integer64'(encode = M, zero) ->
127
'Integer64'(encode, I)
131
%% --------------------
133
'Unsigned32'(decode, <<X:32>>) ->
136
'Unsigned32'(decode, B) ->
139
'Unsigned32'(encode = M, zero) ->
142
'Unsigned32'(encode, I)
146
%% --------------------
148
'Unsigned64'(decode, <<X:64>>) ->
151
'Unsigned64'(decode, B) ->
154
'Unsigned64'(encode = M, zero) ->
157
'Unsigned64'(encode, I)
161
%% --------------------
163
%% Decent summaries of the IEEE floating point formats can be
164
%% found at http://en.wikipedia.org/wiki/IEEE_754-1985 and
165
%% http://www.psc.edu/general/software/packages/ieee/ieee.php.
167
%% That the bit syntax uses these formats isn't well documented but
168
%% this does indeed appear to be the case. However, the bit syntax
169
%% only encodes numeric values, not the standard's (signed) infinity
170
%% or NaN. It also encodes any large value as 'infinity', never 'NaN'.
171
%% Treat these equivalently on decode for this reason.
173
%% An alternative would be to decode infinity/NaN to the largest
174
%% possible float but could likely lead to misleading results if
175
%% arithmetic is performed on the decoded value. Better to be explicit
176
%% that precision has been lost.
178
'Float32'(decode, <<S:1, 255:8, _:23>>) ->
179
choose(S, infinity, '-infinity');
181
'Float32'(decode, <<X:32/float>>) ->
184
'Float32'(decode, B) ->
187
'Float32'(encode = M, zero) ->
190
'Float32'(encode, infinity) ->
191
<<0:1, 255:8, 0:23>>;
193
'Float32'(encode, '-infinity') ->
194
<<1:1, 255:8, 0:23>>;
199
%% Note that this could also encode infinity/-infinity for large
200
%% (signed) numeric values. Note also that precision is lost just in
201
%% using the floating point syntax. For example:
203
%% 1> B = <<3.14159:32/float>>.
205
%% 2> <<F:32/float>> = B.
210
%% (The 64 bit type does better.)
212
%% --------------------
214
%% The 64 bit format is entirely analogous to the 32 bit format.
216
'Float64'(decode, <<S:1, 2047:11, _:52>>) ->
217
choose(S, infinity, '-infinity');
219
'Float64'(decode, <<X:64/float>>) ->
222
'Float64'(decode, B) ->
225
'Float64'(encode, infinity) ->
226
<<0:1, 2047:11, 0:52>>;
228
'Float64'(encode, '-infinity') ->
229
<<1:1, 2047:11, 0:52>>;
231
'Float64'(encode = M, zero) ->
238
%% -------------------------------------------------------------------------
239
%% 3588, 4.3. Derived AVP Data Formats
241
%% In addition to using the Basic AVP Data Formats, applications may
242
%% define data formats derived from the Basic AVP Data Formats. An
243
%% application that defines new AVP Derived Data Formats MUST include
244
%% them in a section entitled "AVP Derived Data Formats", using the same
245
%% format as the definitions below. Each new definition must be either
246
%% defined or listed with a reference to the RFC that defines the
248
%% --------------------
250
'Address'(encode, zero) ->
253
'Address'(decode, <<1:16, B/binary>>)
255
list_to_tuple(binary_to_list(B));
257
'Address'(decode, <<2:16, B/binary>>)
258
when size(B) == 16 ->
259
list_to_tuple(v6dec(B, []));
261
'Address'(decode, <<A:16, _/binary>> = B)
266
'Address'(encode, T) ->
267
ipenc(diameter_lib:ipaddr(T)).
270
when is_tuple(T), size(T) == 4 ->
271
B = list_to_binary(tuple_to_list(T)),
275
when is_tuple(T), size(T) == 8 ->
276
B = v6enc(lists:reverse(tuple_to_list(T)), <<>>),
279
v6dec(<<N:16, B/binary>>, Acc) ->
287
v6enc(Rest, <<N:16, B/binary>>);
292
%% --------------------
294
%% A DiameterIdentity is a FQDN as definined in RFC 1035, which is at
295
%% least one character.
297
'DiameterIdentity'(encode = M, zero) ->
298
'OctetString'(M, [0]);
300
'DiameterIdentity'(encode = M, X) ->
301
<<_,_/binary>> = 'OctetString'(M, X);
303
'DiameterIdentity'(decode = M, <<_,_/binary>> = X) ->
306
%% --------------------
308
'DiameterURI'(decode, Bin)
309
when is_binary(Bin) ->
312
%% The minimal DiameterURI is "aaa://x", 7 characters.
313
'DiameterURI'(encode = M, zero) ->
314
'OctetString'(M, lists:duplicate(0,7));
316
'DiameterURI'(encode, #diameter_uri{type = Type,
322
S = lists:append([atom_to_list(Type), "://", D,
323
":", integer_to_list(P),
324
";transport=", atom_to_list(T),
325
";protocol=", atom_to_list(Prot)]),
326
U = scan_uri(S), %% assert
329
'DiameterURI'(encode, Str) ->
330
Bin = iolist_to_binary(Str),
331
#diameter_uri{} = scan_uri(Bin), %% type check
334
%% --------------------
336
%% This minimal rule is "deny in 0 from 0.0.0.0 to 0.0.0.0", 33 characters.
337
'IPFilterRule'(encode = M, zero) ->
338
'OctetString'(M, lists:duplicate(0,33));
340
%% TODO: parse grammar.
341
'IPFilterRule'(M, X) ->
344
%% --------------------
346
%% This minimal rule is the same as for an IPFilterRule.
347
'QoSFilterRule'(encode = M, zero = X) ->
348
'IPFilterRule'(M, X);
350
%% TODO: parse grammar.
351
'QoSFilterRule'(M, X) ->
354
%% --------------------
356
'UTF8String'(decode, Bin) ->
359
'UTF8String'(encode = M, zero) ->
362
'UTF8String'(encode, S) ->
368
udec(<<C/utf8, Rest/binary>>, Acc) ->
369
udec(Rest, [C | Acc]).
374
list_to_binary(lists:reverse(Acc));
376
uenc(<<C/utf8, Rest/binary>>, Acc) ->
377
uenc(Rest, [<<C/utf8>> | Acc]);
379
uenc([[] | Rest], Acc) ->
382
uenc([[H|T] | Rest], Acc) ->
383
uenc([H, T | Rest], Acc);
385
uenc([C | Rest], Acc) ->
386
uenc(Rest, [<<C/utf8>> | Acc]).
388
%% --------------------
393
%% The Time format is derived from the OctetString AVP Base Format.
394
%% The string MUST contain four octets, in the same format as the
395
%% first four bytes are in the NTP timestamp format. The NTP
396
%% Timestamp format is defined in chapter 3 of [SNTP].
398
%% This represents the number of seconds since 0h on 1 January 1900
399
%% with respect to the Coordinated Universal Time (UTC).
401
%% On 6h 28m 16s UTC, 7 February 2036 the time value will overflow.
402
%% SNTP [SNTP] describes a procedure to extend the time to 2104.
403
%% This procedure MUST be supported by all DIAMETER nodes.
407
%% As the NTP timestamp format has been in use for the last 17 years,
408
%% it remains a possibility that it will be in use 40 years from now
409
%% when the seconds field overflows. As it is probably inappropriate
410
%% to archive NTP timestamps before bit 0 was set in 1968, a
411
%% convenient way to extend the useful life of NTP timestamps is the
412
%% following convention: If bit 0 is set, the UTC time is in the
413
%% range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1
414
%% January 1900. If bit 0 is not set, the time is in the range 2036-
415
%% 2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February
416
%% 2036. Note that when calculating the correspondence, 2000 is not a
417
%% leap year. Note also that leap seconds are not counted in the
420
%% The statement regarding year 2000 is wrong: errata id 518 at
421
%% http://www.rfc-editor.org/errata_search.php?rfc=2030 notes this.
423
-define(TIME_1900, 59958230400). %% {{1900,1,1},{0,0,0}}
424
-define(TIME_2036, 64253197696). %% {{2036,2,7},{6,28,16}}
425
%% TIME_2036 = TIME_1900 + (1 bsl 32)
427
%% Time maps [0, 1 bsl 31) onto [TIME_1900 + 1 bsl 31, TIME_2036 + 1 bsl 31)
428
%% by taking integers with the high-order bit set relative to TIME_1900
429
%% and those without relative to TIME_2036. This corresponds to the
431
-define(TIME_MIN, {{1968,1,20},{3,14,8}}). %% TIME_1900 + 1 bsl 31
432
-define(TIME_MAX, {{2104,2,26},{9,42,24}}). %% TIME_2036 + 1 bsl 31
434
'Time'(decode, <<Time:32>>) ->
435
Offset = msb(1 == Time bsr 31),
436
calendar:gregorian_seconds_to_datetime(Time + Offset);
441
'Time'(encode, {{_Y,_M,_D},{_HH,_MM,_SS}} = Datetime)
442
when ?TIME_MIN =< Datetime, Datetime < ?TIME_MAX ->
443
S = calendar:datetime_to_gregorian_seconds(Datetime),
444
T = S - msb(S < ?TIME_2036),
445
0 = T bsr 32, %% sanity check
448
'Time'(encode, zero) ->
451
%% -------------------------------------------------------------------------
453
'OctetString'(M, _, Data) ->
454
'OctetString'(M, Data).
456
'Integer32'(M, _, Data) ->
457
'Integer32'(M, Data).
459
'Integer64'(M, _, Data) ->
460
'Integer64'(M, Data).
462
'Unsigned32'(M, _, Data) ->
463
'Unsigned32'(M, Data).
465
'Unsigned64'(M, _, Data) ->
466
'Unsigned64'(M, Data).
468
'Float32'(M, _, Data) ->
471
'Float64'(M, _, Data) ->
474
'Address'(M, _, Data) ->
477
'Time'(M, _, Data) ->
480
'UTF8String'(M, _, Data) ->
481
'UTF8String'(M, Data).
483
'DiameterIdentity'(M, _, Data) ->
484
'DiameterIdentity'(M, Data).
486
'DiameterURI'(M, _, Data) ->
487
'DiameterURI'(M, Data).
489
'IPFilterRule'(M, _, Data) ->
490
'IPFilterRule'(M, Data).
492
'QoSFilterRule'(M, _, Data) ->
493
'QoSFilterRule'(M, Data).
495
%% ===========================================================================
496
%% ===========================================================================
498
choose(0, X, _) -> X;
499
choose(1, _, X) -> X.
501
msb(true) -> ?TIME_1900;
502
msb(false) -> ?TIME_2036.
506
%% The DiameterURI MUST follow the Uniform Resource Identifiers (URI)
507
%% syntax [URI] rules specified below:
509
%% "aaa://" FQDN [ port ] [ transport ] [ protocol ]
511
%% ; No transport security
513
%% "aaas://" FQDN [ port ] [ transport ] [ protocol ]
515
%% ; Transport security used
517
%% FQDN = Fully Qualified Host Name
519
%% port = ":" 1*DIGIT
521
%% ; One of the ports used to listen for
522
%% ; incoming connections.
524
%% ; the default Diameter port (3868) is
527
%% transport = ";transport=" transport-protocol
529
%% ; One of the transports used to listen
530
%% ; for incoming connections. If absent,
531
%% ; the default SCTP [SCTP] protocol is
532
%% ; assumed. UDP MUST NOT be used when
533
%% ; the aaa-protocol field is set to
536
%% transport-protocol = ( "tcp" / "sctp" / "udp" )
538
%% protocol = ";protocol=" aaa-protocol
540
%% ; If absent, the default AAA protocol
543
%% aaa-protocol = ( "diameter" / "radius" / "tacacs+" )
546
when is_binary(Bin) ->
547
scan_uri(binary_to_list(Bin));
548
scan_uri("aaa://" ++ Rest) ->
549
scan_fqdn(Rest, #diameter_uri{type = aaa});
550
scan_uri("aaas://" ++ Rest) ->
551
scan_fqdn(Rest, #diameter_uri{type = aaas}).
554
{[_|_] = F, Rest} = lists:splitwith(fun is_fqdn/1, S),
555
scan_opt_port(Rest, U#diameter_uri{fqdn = F}).
557
scan_opt_port(":" ++ S, U) ->
558
{[_|_] = P, Rest} = lists:splitwith(fun is_digit/1, S),
559
scan_opt_transport(Rest, U#diameter_uri{port = list_to_integer(P)});
560
scan_opt_port(S, U) ->
561
scan_opt_transport(S, U).
563
scan_opt_transport(";transport=" ++ S, U) ->
564
{P, Rest} = transport(S),
565
scan_opt_protocol(Rest, U#diameter_uri{transport = P});
566
scan_opt_transport(S, U) ->
567
scan_opt_protocol(S, U).
569
scan_opt_protocol(";protocol=" ++ S, U) ->
570
{P, ""} = protocol(S),
571
U#diameter_uri{protocol = P};
572
scan_opt_protocol("", U) ->
575
transport("tcp" ++ S) ->
577
transport("sctp" ++ S) ->
579
transport("udp" ++ S) ->
582
protocol("diameter" ++ S) ->
584
protocol("radius" ++ S) ->
586
protocol("tacacs+" ++ S) ->
590
is_digit(C) orelse is_alpha(C) orelse C == $. orelse C == $-.
593
($a =< C andalso C =< $z) orelse ($A =< C andalso C =< $Z).
596
$0 =< C andalso C =< $9.