diff options
Diffstat (limited to 'lib')
55 files changed, 1438 insertions, 783 deletions
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index 54eac20ac4..9d6768b157 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -126,9 +126,11 @@ map_es/1, map_arg/1, update_c_map/3, + c_map/1, is_c_map_empty/1, ann_c_map/2, ann_c_map/3, map_pair_op/1,map_pair_key/1,map_pair_val/1, update_c_map_pair/4, + c_map_pair/2, ann_c_map_pair/4 ]). @@ -1582,9 +1584,20 @@ map_es(#c_map{es = Es}) -> -spec map_arg(c_map()) -> c_map() | c_literal(). -map_arg(#c_map{arg = M}) -> +map_arg(#c_map{arg=M}) -> M. +-spec c_map([c_map_pair()]) -> c_map(). + +c_map(Pairs) -> + #c_map{es=Pairs}. + +-spec is_c_map_empty(c_map() | c_literal()) -> boolean(). + +is_c_map_empty(#c_map{ es=[] }) -> true; +is_c_map_empty(#c_literal{val=M}) when is_map(M),map_size(M) =:= 0 -> true; +is_c_map_empty(_) -> false. + -spec ann_c_map([term()], [cerl()]) -> c_map() | c_literal(). ann_c_map(As,Es) -> @@ -1644,6 +1657,11 @@ map_pair_key(#c_map_pair{key=K}) -> K. map_pair_val(#c_map_pair{val=V}) -> V. map_pair_op(#c_map_pair{op=Op}) -> Op. +-spec c_map_pair(cerl(), cerl()) -> c_map_pair(). + +c_map_pair(Key,Val) -> + #c_map_pair{op=#c_literal{val=assoc},key=Key,val=Val}. + -spec ann_c_map_pair([term()], cerl(), cerl(), cerl()) -> c_map_pair(). @@ -4245,6 +4263,9 @@ ann_make_tree(As, bitstr, [[V],[S],[U],[T],[Fs]]) -> ann_c_bitstr(As, V, S, U, T, Fs); ann_make_tree(As, cons, [[H], [T]]) -> ann_c_cons(As, H, T); ann_make_tree(As, tuple, [Es]) -> ann_c_tuple(As, Es); +ann_make_tree(As, map, [Es]) -> ann_c_map(As, Es); +ann_make_tree(As, map, [[A], Es]) -> ann_c_map(As, A, Es); +ann_make_tree(As, map_pair, [[Op], [K], [V]]) -> ann_c_map_pair(As, Op, K, V); ann_make_tree(As, 'let', [Vs, [A], [B]]) -> ann_c_let(As, Vs, A, B); ann_make_tree(As, seq, [[A], [B]]) -> ann_c_seq(As, A, B); ann_make_tree(As, apply, [[Op], Es]) -> ann_c_apply(As, Op, Es); diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps_difftype b/lib/dialyzer/test/small_SUITE_data/results/maps_difftype new file mode 100644 index 0000000000..8980321135 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/results/maps_difftype @@ -0,0 +1,3 @@ + +maps_difftype.erl:10: Function empty_mismatch/1 has no local return +maps_difftype.erl:11: The pattern ~{}~ can never match the type tuple() diff --git a/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl b/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl new file mode 100644 index 0000000000..19e61a7944 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl @@ -0,0 +1,11 @@ +%% +%% File: maps_difftype.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-04-29 +%% +-module(maps_difftype). + +-export([empty_mismatch/1]). + +empty_mismatch(Tuple) when is_tuple(Tuple) -> + case Tuple of #{} -> ok end. diff --git a/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl b/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl new file mode 100644 index 0000000000..35687e22ec --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl @@ -0,0 +1,17 @@ +-module(remote_field2). + +-export([handle_cast/2]). + +-record(state, {tcp_socket :: inet:socket()}). + +-spec handle_cast(_,_) -> + {noreply,_} | + {stop,{shutdown,connection_closed}, + #state{tcp_socket :: port()}}. +handle_cast({send, Message}, #state{tcp_socket = TCPSocket} = State) -> + case gen_tcp:send(TCPSocket, Message) of + ok -> + {noreply, State}; + {error, closed} -> + {stop, {shutdown, connection_closed}, State} + end. diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 7d6a28e51c..ab9ad25a3a 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -227,7 +227,7 @@ question is as if a callback had taken place and returned <c>{error, failure}</c>.</p> <p> -Defaults to <c>report</c> if unspecified.</p> +Defaults to <c>discard</c> if unspecified.</p> </item> <tag><c>{request_errors, answer_3xxx|answer|callback}</c></tag> diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index 675ffcfd18..68e69dbfeb 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -238,7 +238,7 @@ first.</p> <section><title>diameter 1.4.4</title> - <section><title>Known Bugs and Problems</title> + <section><title>Fixed Bugs and Malfunctions</title> <list> <item> <p> diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl index bfe71b0e56..46eb4a55db 100644 --- a/lib/diameter/examples/code/client.erl +++ b/lib/diameter/examples/code/client.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 diff --git a/lib/diameter/examples/code/client_cb.erl b/lib/diameter/examples/code/client_cb.erl index ee3dcb2fec..843cdd9262 100644 --- a/lib/diameter/examples/code/client_cb.erl +++ b/lib/diameter/examples/code/client_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -77,23 +77,11 @@ prepare_retransmit(Packet, SvcName, Peer) -> %% handle_answer/4 -%% Since client.erl has detached the call when using the list -%% encoding and not otherwise, output to the terminal in the -%% the former case, return in the latter. - -handle_answer(#diameter_packet{msg = Msg}, Request, _SvcName, _Peer) - when is_list(Request) -> - io:format("answer: ~p~n", [Msg]); - handle_answer(#diameter_packet{msg = Msg}, _Request, _SvcName, _Peer) -> {ok, Msg}. %% handle_error/4 -handle_error(Reason, Request, _SvcName, _Peer) - when is_list(Request) -> - io:format("error: ~p~n", [Reason]); - handle_error(Reason, _Request, _SvcName, _Peer) -> {error, Reason}. diff --git a/lib/diameter/examples/code/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl index 69836774a1..8d98b0d2df 100644 --- a/lib/diameter/examples/code/redirect_cb.erl +++ b/lib/diameter/examples/code/redirect_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -34,12 +34,10 @@ -define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})). -peer_up(_SvcName, {PeerRef, _}, State) -> - io:format("up: ~p~n", [PeerRef]), +peer_up(_SvcName, _Peer, State) -> State. -peer_down(_SvcName, {PeerRef, _}, State) -> - io:format("down: ~p~n", [PeerRef]), +peer_down(_SvcName, _Peer, State) -> State. pick_peer(_, _, _SvcName, _State) -> diff --git a/lib/diameter/examples/code/relay_cb.erl b/lib/diameter/examples/code/relay_cb.erl index 9f9cd8d5ae..68798014e6 100644 --- a/lib/diameter/examples/code/relay_cb.erl +++ b/lib/diameter/examples/code/relay_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -32,12 +32,10 @@ handle_error/5, handle_request/3]). -peer_up(_SvcName, {PeerRef, _}, State) -> - io:format("up: ~p~n", [PeerRef]), +peer_up(_SvcName, _Peer, State) -> State. -peer_down(_SvcName, {PeerRef, _}, State) -> - io:format("down: ~p~n", [PeerRef]), +peer_down(_SvcName, _Peer, State) -> State. %% Returning 'relay' from handle_request causes diameter to resend the diff --git a/lib/diameter/examples/code/server_cb.erl b/lib/diameter/examples/code/server_cb.erl index 0f6eb32ed6..9d8d395d06 100644 --- a/lib/diameter/examples/code/server_cb.erl +++ b/lib/diameter/examples/code/server_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -38,12 +38,10 @@ -define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})). -peer_up(_SvcName, {PeerRef, _}, State) -> - io:format("up: ~p~n", [PeerRef]), +peer_up(_SvcName, _Peer, State) -> State. -peer_down(_SvcName, {PeerRef, _}, State) -> - io:format("down: ~p~n", [PeerRef]), +peer_down(_SvcName, _Peer, State) -> State. pick_peer(_, _, _SvcName, _State) -> @@ -68,10 +66,13 @@ handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps}) origin_realm = {OR,_}} = Caps, #diameter_base_RAR{'Session-Id' = Id, - 'Re-Auth-Request-Type' = RT} + 'Re-Auth-Request-Type' = Type} = Req, - {reply, answer(RT, Id, OH, OR)}; + {reply, #diameter_base_RAA{'Result-Code' = rc(Type), + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = Id}}; %% ... or one that wasn't. 3xxx errors are answered by diameter itself %% but these are 5xxx errors for which we must contruct a reply. @@ -84,32 +85,18 @@ handle_request(#diameter_packet{msg = Req}, _SvcName, {_, Caps}) #diameter_base_RAR{'Session-Id' = Id} = Req, - Ans = #diameter_base_RAA{'Origin-Host' = OH, - 'Origin-Realm' = OR, - 'Session-Id' = Id}, + {reply, #diameter_base_RAA{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = Id}}; - {reply, Ans}; +%% Answer that any other message is unsupported. +handle_request(#diameter_packet{}, _SvcName, _) -> + {answer_message, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED -%% Should really reply to other base messages that we don't support -%% but simply discard them instead. -handle_request(#diameter_packet{}, _SvcName, {_,_}) -> - discard. +%% Map Re-Auth-Request-Type to Result-Code just for the purpose of +%% generating different answers. -%% --------------------------------------------------------------------------- - -%% Answer using the record or list encoding depending on -%% Re-Auth-Request-Type. This is just as an example. You would -%% typically just choose one, and this has nothing to do with the how -%% client.erl sends. - -answer(0, Id, OH, OR) -> - #diameter_base_RAA{'Result-Code' = 2001, %% DIAMETER_SUCCESS - 'Origin-Host' = OH, - 'Origin-Realm' = OR, - 'Session-Id' = Id}; - -answer(_, Id, OH, OR) -> - ['RAA', {'Result-Code', 5012}, %% DIAMETER_UNABLE_TO_COMPLY - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Session-Id', Id}]. +rc(0) -> + 2001; %% DIAMETER_SUCCESS +rc(_) -> + 5012. %% DIAMETER_UNABLE_TO_COMPLY diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl index 5a40e42300..c2c271a9a3 100644 --- a/lib/diameter/include/diameter.hrl +++ b/lib/diameter/include/diameter.hrl @@ -126,7 +126,7 @@ default, extra = []}). -%% The diameter service and diameter_apps records are only passed +%% The diameter service and diameter_app 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 host_ip_address. @@ -143,6 +143,7 @@ init_state, %% option 'state', initial callback state id, %% 32-bit unsigned application identifier = Dict:id() mutable = false, %% boolean(), do traffic callbacks modify state? - options = [{answer_errors, report}, %% | callback | discard + options = [{answer_errors, discard}, %% | callback | report {request_errors, answer_3xxx}]}). %% | callback | answer + -endif. %% -ifdef(diameter_hrl). diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index c8f706dc3e..7e91ce375f 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -30,6 +30,10 @@ %% error or not. See is_strict/0. -define(STRICT_KEY, strict). +%% Key that says whether or not we should do a best-effort decode +%% within Failed-AVP. +-define(FAILED_KEY, failed). + -type parent_name() :: atom(). %% parent = Message or AVP -type parent_record() :: tuple(). %% -type avp_name() :: atom(). @@ -286,15 +290,7 @@ decode(Name, 'AVP', Avp, Acc) -> %% d/3 -%% Don't try to decode the value of a Failed-AVP component since it -%% probably won't. Note that matching on 'Failed-AVP' assumes that -%% this is the RFC AVP, with code 279. Strictly, this doesn't need to -%% be the case, so we're assuming no one defines another Failed-AVP. -d('Failed-AVP' = Name, Avp, Acc) -> - decode_AVP(Name, Avp, Acc); - -%% Or try to decode. -d(Name, Avp, {Avps, Acc}) -> +d(Name, Avp, Acc) -> #diameter_avp{name = AvpName, data = Data, type = Type, @@ -307,51 +303,81 @@ d(Name, Avp, {Avps, Acc}) -> %% value around through the entire decode. The solution here is %% simple in comparison, both to implement and to understand. - Reset = relax(Type, M), + Strict = relax(Type, M), + %% Use the process dictionary again to keep track of whether we're + %% decoding within Failed-AVP and should ignore decode errors + %% altogether. + + Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP + %% decode is packed into 'AVP'. try avp(decode, Data, AvpName) of V -> + {Avps, T} = Acc, {H, A} = ungroup(V, Avp), - {[H | Avps], pack_avp(Name, A, Acc)} + {[H | Avps], pack_avp(Name, A, T)} 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_lib:log({decode, failure}, - ?MODULE, - ?LINE, - {Reason, Avp, erlang:get_stacktrace()}), - {Rec, Failed} = Acc, - {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}} + d(undefined == Failed orelse is_failed(), Reason, Name, Avp, Acc) after - relax(Reset) + reset(?STRICT_KEY, Strict), + reset(?FAILED_KEY, Failed) end. +%% Ignore a decode error within Failed-AVP ... +d(true, _, Name, Avp, Acc) -> + decode_AVP(Name, Avp, Acc); + +%% ... or not. 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. +d(false, Reason, Name, Avp, {Avps, Acc}) -> + Stack = diameter_lib:get_stacktrace(), + diameter_lib:log(decode_error, + ?MODULE, + ?LINE, + {Reason, Name, Avp#diameter_avp.name, Stack}), + {Rec, Failed} = Acc, + {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}. + %% Set false in the process dictionary as soon as we see a Grouped AVP %% that doesn't set the M-bit, so that is_strict() can say whether or %% not to ignore the M-bit on an encapsulated AVP. relax('Grouped', M) -> - V = getr(?STRICT_KEY), - if V == undefined andalso not M -> + case getr(?STRICT_KEY) of + undefined when not M -> putr(?STRICT_KEY, M); - true -> + _ -> false end; relax(_, _) -> false. -%% Reset strictness. -relax(undefined) -> - eraser(?STRICT_KEY); -relax(false) -> - ok. - is_strict() -> false /= getr(?STRICT_KEY). +%% Set true in the process dictionary as soon as we see Failed-AVP. +%% Matching on 'Failed-AVP' assumes that this is the RFC AVP. +%% Strictly, this doesn't need to be the case. +relax('Failed-AVP') -> + case getr(?FAILED_KEY) of + undefined -> + putr(?FAILED_KEY, true); + true = Yes -> + Yes + end; +relax(_) -> + is_failed(). + +is_failed() -> + true == getr(?FAILED_KEY). + +reset(Key, undefined) -> + eraser(Key); +reset(_, _) -> + ok. + %% decode_AVP/3 %% %% Don't know this AVP: see if it can be packed in an 'AVP' field @@ -410,6 +436,23 @@ pack_avp(_, Arity, Avp, Acc) -> %% pack_AVP/3 +%% Length failure was induced because of a header/payload length +%% mismatch. The AVP Length is reset to match the received data if +%% this AVP is encoded in an answer message, since the length is +%% computed. +%% +%% Data is a truncated header if command_code = undefined, otherwise +%% payload bytes. The former is padded to the length of a header if +%% the AVP reaches an outgoing encode in diameter_codec. +%% +%% RFC 6733 says that an AVP returned with 5014 can contain a minimal +%% payload for the AVP's type, but in this case we don't know the +%% type. + +pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) -> + {Rec, Failed} = Acc, + {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]}; + pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> case pack_arity(Name, M) of 0 -> @@ -422,7 +465,15 @@ pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> %% Give Failed-AVP special treatment since it'll contain any %% unrecognized mandatory AVP's. pack_arity(Name, M) -> - case Name /= 'Failed-AVP' andalso M andalso is_strict() of + NF = Name /= 'Failed-AVP' andalso not is_failed(), + %% Not testing just Name /= 'Failed-AVP' means we're changing the + %% packing of AVPs nested within Failed-AVP, but the point of + %% ignoring errors within Failed-AVP is to decode as much as + %% possible, and failing because a mandatory AVP couldn't be + %% packed into a dedicated field defeats that point. Note that we + %% can't just test not is_failed() since this will be 'true' when + %% packing an unknown AVP directly within Failed-AVP. + case NF andalso M andalso is_strict() of true -> 0; false -> diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 0de4d53973..06a4f5de64 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-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -70,12 +70,15 @@ encode(Mod, #diameter_packet{} = Pkt) -> try e(Mod, Pkt) catch + exit: {Reason, Stack, #diameter_header{} = H} = T -> + %% Exit with a header in the reason to let the caller + %% count encode errors. + ?LOG(encode_error, {Reason, Stack, H}), + exit({?MODULE, encode, T}); error: Reason -> - %% Be verbose since a crash report may be truncated and - %% encode errors are self-inflicted. - X = {?MODULE, encode, {Reason, ?STACK}}, - diameter_lib:error_report(X, {?MODULE, encode, [Mod, Pkt]}), - exit(X) + T = {Reason, diameter_lib:get_stacktrace()}, + ?LOG(encode_error, T), + exit({?MODULE, encode, T}) end; encode(Mod, Msg) -> @@ -87,53 +90,62 @@ encode(Mod, Msg) -> msg = Msg}). e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> - Avps = encode_avps(As), - Length = size(Avps) + 20, - - #diameter_header{version = Vsn, - cmd_code = Code, - application_id = Aid, - hop_by_hop_id = Hid, - end_to_end_id = Eid} - = Hdr, - - Flags = make_flags(0, Hdr), - - Pkt#diameter_packet{header = Hdr, - bin = <<Vsn:8, Length:24, - Flags:8, Code:24, - Aid:32, - Hid:32, - Eid:32, - Avps/binary>>}; + try encode_avps(As) of + Avps -> + Length = size(Avps) + 20, + + #diameter_header{version = Vsn, + cmd_code = Code, + application_id = Aid, + hop_by_hop_id = Hid, + end_to_end_id = Eid} + = Hdr, + + Flags = make_flags(0, Hdr), + + Pkt#diameter_packet{header = Hdr, + bin = <<Vsn:8, Length:24, + Flags:8, Code:24, + Aid:32, + Hid:32, + Eid:32, + Avps/binary>>} + catch + error: Reason -> + exit({Reason, diameter_lib:get_stacktrace(), Hdr}) + end; -e(Mod, #diameter_packet{header = Hdr, msg = Msg} = Pkt) -> +e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> #diameter_header{version = Vsn, hop_by_hop_id = Hid, end_to_end_id = Eid} - = Hdr, + = Hdr0, MsgName = rec2msg(Mod, Msg), - {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr), - Flags = make_flags(Flags0, Hdr), - - Avps = encode_avps(Mod, MsgName, values(Msg)), - Length = size(Avps) + 20, - - Pkt#diameter_packet{header = Hdr#diameter_header - {length = Length, - cmd_code = Code, - application_id = Aid, - is_request = 0 /= ?MASK(7, Flags), - is_proxiable = 0 /= ?MASK(6, Flags), - is_error = 0 /= ?MASK(5, Flags), - is_retransmitted = 0 /= ?MASK(4, Flags)}, - bin = <<Vsn:8, Length:24, - Flags:8, Code:24, - Aid:32, - Hid:32, - Eid:32, - Avps/binary>>}. + {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr0), + Flags = make_flags(Flags0, Hdr0), + Hdr = Hdr0#diameter_header{cmd_code = Code, + application_id = Aid, + is_request = 0 /= ?MASK(7, Flags), + is_proxiable = 0 /= ?MASK(6, Flags), + is_error = 0 /= ?MASK(5, Flags), + is_retransmitted = 0 /= ?MASK(4, Flags)}, + Values = values(Msg), + + try encode_avps(Mod, MsgName, Values) of + Avps -> + Length = size(Avps) + 20, + Pkt#diameter_packet{header = Hdr#diameter_header{length = Length}, + bin = <<Vsn:8, Length:24, + Flags:8, Code:24, + Aid:32, + Hid:32, + Eid:32, + Avps/binary>>} + catch + error: Reason -> + exit({Reason, diameter_lib:get_stacktrace(), Hdr}) + end. %% make_flags/2 @@ -225,7 +237,7 @@ rec2msg(Mod, Rec) -> %% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors. --spec decode(module(), #diameter_packet{} | bitstring()) +-spec decode(module(), #diameter_packet{} | binary()) -> #diameter_packet{}. decode(Mod, Pkt) -> @@ -259,34 +271,34 @@ decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) -> decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt)); decode(Id, Mod, Bin) - when is_bitstring(Bin) -> + when is_binary(Bin) -> decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}). decode_avps(MsgName, Mod, Pkt, {E, Avps}) -> - ?LOG(invalid, Pkt#diameter_packet.bin), + ?LOG(invalid_avp_length, Pkt#diameter_packet.header), #diameter_packet{errors = Failed} = P = decode_avps(MsgName, Mod, Pkt, Avps), P#diameter_packet{errors = [E | Failed]}; -decode_avps('', Mod, Pkt, Avps) -> %% unknown message ... - ?LOG(unknown, {Mod, Pkt#diameter_packet.header}), +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, Failed} = Mod:decode_avps(MsgName, Avps), - ?LOGC([] /= Failed, failed, {Mod, Failed}), + {Rec, As, Errors} = Mod:decode_avps(MsgName, Avps), + ?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header), Pkt#diameter_packet{msg = Rec, - errors = Failed, + errors = Errors, avps = As}. %%% --------------------------------------------------------------------------- %%% # decode_header/1 %%% --------------------------------------------------------------------------- --spec decode_header(bitstring()) +-spec decode_header(binary()) -> #diameter_header{} | false. @@ -297,7 +309,7 @@ decode_header(<<Version:8, ApplicationId:32, HopByHopId:32, EndToEndId:32, - _/bitstring>>) -> + _/binary>>) -> <<R:1, P:1, E:1, T:1, _:4>> = CmdFlags, %% 3588 (ch 3) says that reserved bits MUST be set to 0 and ignored @@ -410,7 +422,7 @@ msg_id(#diameter_header{application_id = A, is_request = R}) -> {A, C, if R -> 1; true -> 0 end}; -msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) -> +msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) -> {ApplId, CmdCode, Rbit}. %%% --------------------------------------------------------------------------- @@ -421,17 +433,18 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) -> %% order in the binary. Note also that grouped avp's aren't unraveled, %% only those at the top level. --spec collect_avps(#diameter_packet{} | bitstring()) +-spec collect_avps(#diameter_packet{} | binary()) -> [Avp] | {Error, [Avp]} when Avp :: #diameter_avp{}, Error :: {5014, #diameter_avp{}}. collect_avps(#diameter_packet{bin = Bin}) -> - <<_:20/binary, Avps/bitstring>> = Bin, + <<_:20/binary, Avps/binary>> = Bin, collect_avps(Avps); -collect_avps(Bin) -> +collect_avps(Bin) + when is_binary(Bin) -> collect_avps(Bin, 0, []). collect_avps(<<>>, _, Acc) -> @@ -461,7 +474,9 @@ collect_avps(Bin, N, Acc) -> split_avp(Bin) -> {Code, V, M, P, Len, HdrLen} = split_head(Bin), - {Data, B} = split_data(Bin, HdrLen, Len - HdrLen), + + <<_:HdrLen/binary, Rest/binary>> = Bin, + {Data, B} = split_data(Rest, Len - HdrLen), {B, #diameter_avp{code = Code, vendor_id = V, @@ -471,17 +486,15 @@ split_avp(Bin) -> %% split_head/1 -split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/bitstring>>) -> +split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/binary>>) -> {Code, V, M, P, Len, 12}; -split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/bitstring>>) -> +split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/binary>>) -> {Code, undefined, M, P, Len, 8}; -%% Header is truncated: pack_avp/1 will pad to the minimum header -%% length. -split_head(B) - when is_bitstring(B) -> - ?THROW({5014, #diameter_avp{data = B}}). +%% Header is truncated. +split_head(Bin) -> + ?THROW({5014, #diameter_avp{data = Bin}}). %% 3588: %% @@ -516,34 +529,27 @@ split_head(B) %% split_data/3 -split_data(Bin, HdrLen, Len) - when 0 =< Len -> - split_data(Bin, HdrLen, Len, (4 - (Len rem 4)) rem 4); +split_data(Bin, Len) -> + Pad = (4 - (Len rem 4)) rem 4, -split_data(_, _, _) -> - invalid_avp_length(). + %% Len might be negative here, but that ensures the failure of the + %% binary match. -%% split_data/4 - -split_data(Bin, HdrLen, Len, Pad) -> case Bin of - <<_:HdrLen/binary, Data:Len/binary, _:Pad/binary, Rest/bitstring>> -> + <<Data:Len/binary, _:Pad/binary, Rest/binary>> -> {Data, Rest}; _ -> - invalid_avp_length() + %% 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. + {<<0:1, Bin/binary>>, <<>>} end. -%% invalid_avp_length/0 -%% -%% AVP Length doesn't mesh with payload. Induce a decode error by -%% returning a payload that no valid Diameter type can have. This is -%% so that a known AVP will result in 5014 error with a zero'd -%% payload. Here we simply don't know how to construct this payload. -%% (Yes, this solution is an afterthought.) - -invalid_avp_length() -> - {<<0:1>>, <<>>}. - %%% --------------------------------------------------------------------------- %%% # pack_avp/1 %%% --------------------------------------------------------------------------- @@ -575,17 +581,23 @@ pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) -> {Name, Type} = Dict:avp_name(Code, Vid), pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}}); +%% ... with a truncated header ... pack_avp(#diameter_avp{code = undefined, data = B}) - when is_bitstring(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 %% receiver can't correctly extract this and following AVP's %% without a correct length. On the downside, the header doesn't %% reveal if the received header has been padded. Pad = 8*header_length(B) - bit_size(B), - Len = size(<<H:5/binary, _:24, T/binary>> = <<B/bitstring, 0:Pad>>), + 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 ... +pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) -> + pack_avp(A#diameter_avp{data = B}); + %% ... or as an iolist. pack_avp(#diameter_avp{code = Code, vendor_id = V, diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index f5ea459fd0..dd1c9b73bb 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -753,7 +753,7 @@ app_acc({application, Opts} = T, Acc) -> Alias = get_opt(alias, Opts, Dict), ModS = get_opt(state, Opts, Alias), M = get_opt(call_mutates_state, Opts, false, [true]), - A = get_opt(answer_errors, Opts, report, [callback, discard]), + A = get_opt(answer_errors, Opts, discard, [callback, report]), P = get_opt(request_errors, Opts, answer_3xxx, [answer, callback]), [#diameter_app{alias = Alias, dictionary = Dict, diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl index 44d81e2778..5b3a2063f8 100644 --- a/lib/diameter/src/base/diameter_lib.erl +++ b/lib/diameter/src/base/diameter_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -25,6 +25,8 @@ now_diff/1, time/1, eval/1, + eval_name/1, + get_stacktrace/0, ipaddr/1, spawn_opts/2, wait/1, @@ -32,6 +34,22 @@ log/4]). %% --------------------------------------------------------------------------- +%% # get_stacktrace/0 +%% --------------------------------------------------------------------------- + +%% Return a stacktrace with a leading, potentially large, argument +%% list replaced by an arity. Trace on stacktrace/0 to see the +%% original. + +get_stacktrace() -> + stacktrace(erlang:get_stacktrace()). + +stacktrace([{M,F,A,L} | T]) when is_list(A) -> + [{M, F, length(A), L} | T]; +stacktrace(L) -> + L. + +%% --------------------------------------------------------------------------- %% # info_report/2 %% --------------------------------------------------------------------------- @@ -60,9 +78,17 @@ warning_report(Reason, T) -> report(fun error_logger:warning_report/1, Reason, T). report(Fun, Reason, T) -> - Fun([{why, Reason}, {who, self()}, {what, T}]), + Fun(io_lib:format("diameter: ~" ++ fmt(Reason) ++ "~n ~p~n", + [Reason, T])), false. +fmt(T) -> + if is_list(T) -> + "s"; + true -> + "p" + end. + %% --------------------------------------------------------------------------- %% # now_diff/1 %% --------------------------------------------------------------------------- @@ -129,8 +155,8 @@ eval({M,F,A}) -> eval([{M,F,A} | X]) -> apply(M, F, X ++ A); -eval([[F|A] | X]) -> - eval([F | X ++ A]); +eval([[F|X] | A]) -> + eval([F | A ++ X]); eval([F|A]) -> apply(F,A); @@ -142,6 +168,28 @@ eval(F) -> F(). %% --------------------------------------------------------------------------- +%% eval_name/1 +%% --------------------------------------------------------------------------- + +eval_name({M,F,A}) -> + {M, F, length(A)}; + +eval_name([{M,F,A} | X]) -> + {M, F, length(A) + length(X)}; + +eval_name([[F|A] | X]) -> + eval_name([F | X ++ A]); + +eval_name([F|_]) -> + F; + +eval_name({F}) -> + eval_name(F); + +eval_name(F) -> + F. + +%% --------------------------------------------------------------------------- %% # ipaddr/1 %% %% Parse an IP address. diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index f76bd96c3c..31e570ae20 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -283,7 +283,7 @@ handle_info(T, #state{} = State) -> ok -> {noreply, State}; #state{state = X} = S -> - ?LOGC(X =/= State#state.state, transition, X), + ?LOGC(X /= State#state.state, transition, X), {noreply, S}; {stop, Reason} -> ?LOG(stop, Reason), @@ -292,15 +292,12 @@ handle_info(T, #state{} = State) -> ?LOG(stop, T), {stop, {shutdown, T}, State} catch - exit: {diameter_codec, encode, _} = Reason -> + exit: {diameter_codec, encode, T} = Reason -> + incr_error(send, T, State#state.dictionary), ?LOG(stop, Reason), - %% diameter_codec:encode/2 emits an error report. Only - %% indicate the probable reason here. - diameter_lib:info_report(probable_configuration_error, - insufficient_capabilities), {stop, {shutdown, Reason}, State}; {?MODULE, Tag, Reason} -> - ?LOG(Tag, {Reason, T}), + ?LOG(stop, Tag), {stop, {shutdown, Reason}, State} end. %% The form of the throw caught here is historical. It's @@ -476,12 +473,12 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, orelse close({already_connected, Remote, LCaps}), CER = build_CER(S), - ?LOG(send, 'CER'), #diameter_packet{header = #diameter_header{end_to_end_id = Eid, hop_by_hop_id = Hid}} = Pkt = encode(CER, Dict), send(TPid, Pkt), + ?LOG(send, 'CER'), start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}). %% Register ourselves as connecting to the remote endpoint in @@ -526,7 +523,6 @@ recv(#diameter_packet{header = #diameter_header{} = Hdr} = S) -> Name = diameter_codec:msg_name(Dict0, Hdr), Pid ! {recv, self(), Name, Pkt}, - diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received rcv(Name, Pkt, S); recv(#diameter_packet{header = undefined, @@ -553,42 +549,30 @@ recv(#diameter_header{length = Len} recv(#diameter_header{} = H, #diameter_packet{bin = Bin}, - #state{length_errors = E} - = S) -> - invalid(E, - invalid_message_length, - recv, - [size(Bin), bit_size(Bin) rem 8, H, S]); + #state{length_errors = E}) -> + T = {size(Bin), bit_size(Bin) rem 8, H}, + invalid(E, message_length_mismatch, T); -recv(false, Pkt, #state{length_errors = E} = S) -> - invalid(E, truncated_header, recv, [Pkt, S]). +recv(false, #diameter_packet{bin = Bin}, #state{length_errors = E}) -> + invalid(E, truncated_header, Bin). %% Note that counters here only count discarded messages. -invalid(E, Reason, F, A) -> +invalid(E, Reason, T) -> diameter_stats:incr(Reason), - abort(E, Reason, F, A). - -abort(exit, Reason, F, A) -> - diameter_lib:warning_report(Reason, {?MODULE, F, A}), - throw({?MODULE, abort, Reason}); - -abort(_, _, _, _) -> + E == exit andalso close({Reason, T}), + ?LOG(Reason, T), ok. -msg_id({_,_,_} = T, _) -> - T; -msg_id(_, Hdr) -> - {_,_,_} = diameter_codec:msg_id(Hdr). - %% rcv/3 %% Incoming CEA. -rcv('CEA', +rcv('CEA' = N, #diameter_packet{header = #diameter_header{end_to_end_id = Eid, hop_by_hop_id = Hid}} = Pkt, #state{state = {'Wait-CEA', Hid, Eid}} = S) -> + ?LOG(recv, N), handle_CEA(Pkt, S); %% Incoming CER @@ -609,34 +593,71 @@ rcv('DPR' = N, Pkt, S) -> %% DPA in response to DPR and with the expected identifiers. rcv('DPA' = N, #diameter_packet{header = #diameter_header{end_to_end_id = Eid, - hop_by_hop_id = Hid}}, - #state{transport = TPid, + hop_by_hop_id = Hid} + = H} + = Pkt, + #state{dictionary = Dict0, + transport = TPid, dpr = {Hid, Eid}}) -> + ?LOG(recv, N), + incr(recv, H, Dict0), + incr_rc(recv, diameter_codec:decode(Dict0, Pkt), Dict0), diameter_peer:close(TPid), {stop, N}; %% Ignore anything else, an unsolicited DPA in particular. +rcv(N, #diameter_packet{header = H}, _) + when N == 'CER'; + N == 'CEA'; + N == 'DPR'; + N == 'DPA' -> + ?LOG(ignored, N), + %% Note that these aren't counted in the normal recv counter. + diameter_stats:incr({diameter_codec:msg_id(H), recv, ignored}), + ok; + rcv(_, _, _) -> ok. +%% incr/3 + +incr(Dir, Hdr, Dict0) -> + diameter_traffic:incr(Dir, Hdr, self(), Dict0). + +%% incr_rc/3 + +incr_rc(Dir, Pkt, Dict0) -> + diameter_traffic:incr_rc(Dir, Pkt, self(), Dict0). + +%% incr_error/3 + +incr_error(Dir, Pkt, Dict0) -> + diameter_traffic:incr_error(Dir, Pkt, self(), Dict0). + %% send/2 %% Msg here could be a #diameter_packet or a binary depending on who's %% sending. In particular, the watchdog will send DWR as a binary %% while messages coming from clients will be in a #diameter_packet. send(Pid, Msg) -> - diameter_stats:incr({diameter_codec:msg_id(Msg), send}), diameter_peer:send(Pid, Msg). %% handle_request/3 +%% +%% Incoming CER or DPR. -handle_request(Type, #diameter_packet{} = Pkt, #state{dictionary = D} = S) -> - ?LOG(recv, Type), - send_answer(Type, diameter_codec:decode(D, Pkt), S). +handle_request(Name, + #diameter_packet{header = H} = Pkt, + #state{dictionary = Dict0} = S) -> + ?LOG(recv, Name), + incr(recv, H, Dict0), + send_answer(Name, diameter_codec:decode(Dict0, Pkt), S). %% send_answer/3 send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) -> + incr_error(recv, ReqPkt, Dict), + #diameter_packet{header = H, transport_data = TD} = ReqPkt, @@ -653,13 +674,19 @@ send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) -> msg = Msg, transport_data = TD}, - send(TPid, diameter_codec:encode(Dict, Pkt)), + AnsPkt = diameter_codec:encode(Dict, Pkt), + + incr(send, AnsPkt, Dict), + incr_rc(send, AnsPkt, Dict), + send(TPid, AnsPkt), + ?LOG(send, ans(Type)), eval(PostF, S). +ans('CER') -> 'CEA'; +ans('DPR') -> 'DPA'. + eval([F|A], S) -> apply(F, A ++ [S]); -eval(ok, S) -> - S; eval(T, _) -> close(T). @@ -723,8 +750,8 @@ cea(CEA, RC, Dict0) -> post('CER' = T, RC, Pkt, S) -> {T, caps(S), {RC, Pkt}}; -post('DPR', _, _, _) -> - ok. +post('DPR' = T, _, _, #state{parent = Pid}) -> + [fun(S) -> Pid ! {T, self()}, S end]. rejected({capabilities_cb, _F, Reason}, T, S) -> rejected(Reason, T, S); @@ -734,7 +761,7 @@ rejected(discard, T, _) -> rejected({N, Es}, T, S) -> {answer('CER', N, failed_avp(N, Es), S), T}; rejected(N, T, S) -> - rejected({N, []}, T, S). + {answer('CER', N, [], S), T}. failed_avp(RC, [{RC, Avp} | _]) -> [{'Failed-AVP', [[{'AVP', [Avp]}]]}]; @@ -848,28 +875,27 @@ recv_CER(CER, #state{service = Svc, dictionary = Dict}) -> close({'CER', CER, Svc, Dict, Reason}) end. -%% handle_CEA/1 +%% handle_CEA/2 -handle_CEA(#diameter_packet{bin = Bin} +handle_CEA(#diameter_packet{header = H} = Pkt, #state{dictionary = Dict0, service = #diameter_service{capabilities = LCaps}} - = S) - when is_binary(Bin) -> - ?LOG(recv, 'CEA'), + = S) -> + incr(recv, H, Dict0), - #diameter_packet{msg = CEA} + #diameter_packet{} = DPkt = diameter_codec:decode(Dict0, Pkt), + RC = result_code(incr_rc(recv, DPkt, Dict0)), + {SApps, IS, RCaps} = recv_CEA(DPkt, S), #diameter_caps{origin_host = {OH, DH}} = Caps = capz(LCaps, RCaps), - RC = Dict0:'#get-'('Result-Code', CEA), - %% Ensure that we don't already have a connection to the peer in %% question. This isn't the peer election of 3588 except in the %% sense that, since we don't know who we're talking to until we @@ -877,7 +903,7 @@ handle_CEA(#diameter_packet{bin = Bin} %% connection with the peer. try - ?IS_SUCCESS(RC) + is_integer(RC) andalso ?IS_SUCCESS(RC) orelse ?THROW(RC), [] == SApps andalso ?THROW(no_common_application), @@ -897,6 +923,11 @@ handle_CEA(#diameter_packet{bin = Bin} %% capabilities exchange could send DIAMETER_LIMITED_SUCCESS = 2002, %% even if this isn't required by RFC 3588. +result_code({'Result-Code', N}) -> + N; +result_code(_) -> + undefined. + %% recv_CEA/2 recv_CEA(#diameter_packet{header = #diameter_header{version @@ -988,19 +1019,13 @@ capz(#diameter_caps{} = L, #diameter_caps{} = R) -> tl(tuple_to_list(R)))]). %% close/1 +%% +%% A good function to trace on in case of problems with capabilities +%% exchange. close(Reason) -> - report(Reason), throw({?MODULE, close, Reason}). -%% Could possibly log more here. -report({M, _, _, _, _} = T) - when M == 'CER'; - M == 'CEA' -> - diameter_lib:error_report(failure, T); -report(_) -> - ok. - %% dpr/2 %% %% The RFC isn't clear on whether DPR should be send in a non-Open @@ -1034,7 +1059,7 @@ dpr(_Reason, _S) -> %% process and contact it. (eg. diameter:service_info/2) dpr([CB|Rest], [Reason | _] = Args, S) -> - try diameter_lib:eval([CB | Args]) of + case diameter_lib:eval([CB | Args]) of {dpr, Opts} when is_list(Opts) -> send_dpr(Reason, Opts, S); dpr -> @@ -1044,14 +1069,7 @@ dpr([CB|Rest], [Reason | _] = Args, S) -> ignore -> dpr(Rest, Args, S); T -> - No = {disconnect_cb, T}, - diameter_lib:error_report(invalid, No), - {stop, No} - catch - E:R -> - No = {disconnect_cb, E, R, ?STACK}, - diameter_lib:error_report(failure, No), - {stop, No} + ?ERROR({disconnect_cb, CB, Args, T}) end; dpr([], [Reason | _], S) -> diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 8914992f17..b7cd311e02 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -499,9 +499,21 @@ transition(Req, S) -> %% # terminate/2 %% --------------------------------------------------------------------------- -terminate(Reason, #state{service_name = Name} = S) -> +terminate(Reason, #state{service_name = Name, peerT = PeerT} = S) -> send_event(Name, stop), ets:delete(?STATE_TABLE, Name), + + %% Communicate pending loss of any peers that connection_down/3 + %% won't. This is needed when stopping a service since we don't + %% wait for watchdog state changes to take care of if. That this + %% takes place after deleting the state entry ensures that the + %% resulting failover by request processes accomplishes nothing. + ets:foldl(fun(#peer{pid = TPid}, _) -> + diameter_traffic:peer_down(TPid) + end, + ok, + PeerT), + shutdown == Reason %% application shutdown andalso shutdown(application, S). @@ -719,14 +731,27 @@ remotes(F) -> L when is_list(L) -> L; T -> - diameter_lib:error_report({invalid_return, T}, F), + ?LOG(invalid_return, {F,T}), + error_report(invalid_return, share_peers, F), [] catch E:R -> - diameter_lib:error_report({failure, {E, R, ?STACK}}, F), + ?LOG(failure, {E, R, F, diameter_lib:get_stacktrace()}), + error_report(failure, share_peers, F), [] end. +%% error_report/3 + +error_report(T, What, F) -> + Reason = io_lib:format("~s from ~p callback", [reason(T), What]), + diameter_lib:error_report(Reason, diameter_lib:eval_name(F)). + +reason(invalid_return) -> + "invalid return"; +reason(failure) -> + "failure". + %% --------------------------------------------------------------------------- %% # start/3 %% --------------------------------------------------------------------------- @@ -869,7 +894,7 @@ watchdog(TPid, [], ?WD_OKAY, ?WD_SUSPECT = To, Wd, State) -> %% Watchdog has lost its connection. watchdog(TPid, [], _, ?WD_DOWN = To, Wd, #state{peerT = PeerT} = S) -> - close(Wd, S), + close(Wd), watchdog_down(Wd, To, S), ets:delete(PeerT, TPid); @@ -1026,8 +1051,11 @@ peer_cb(App, F, A) -> true catch E:R -> - diameter_lib:error_report({failure, {E, R, ?STACK}}, - {App, F, A}), + %% Don't include arguments since a #diameter_caps{} strings + %% from the peer, which could be anything (especially, large). + [Mod|X] = App#diameter_app.module, + ?LOG(failure, {E, R, Mod, F, diameter_lib:get_stacktrace()}), + error_report(failure, F, {Mod, F, A ++ X}), false end. @@ -1187,26 +1215,16 @@ tc(false = No, _, _) -> %% removed %% another watchdog to be able to detect that it should transition %% from initial into reopen rather than okay. That someone is either %% the accepting watchdog upon reception of a CER from the previously -%% connected peer, or us after connect_timer timeout. +%% connected peer, or us after connect_timer timeout or immediately. -close(#watchdog{type = connect}, _) -> +close(#watchdog{type = connect}) -> ok; + close(#watchdog{type = accept, pid = Pid, - ref = Ref, - options = Opts}, - #state{service_name = SvcName}) -> - c(Pid, diameter_config:have_transport(SvcName, Ref), Opts). - -%% Tell watchdog to (maybe) die later ... -c(Pid, true, Opts) -> + options = Opts}) -> Tc = connect_timer(Opts, 2*?DEFAULT_TC), - erlang:send_after(Tc, Pid, close); - -%% ... or now. -c(Pid, false, _Opts) -> - Pid ! close. - + erlang:send_after(Tc, Pid, close). %% The RFC's only document the behaviour of Tc, our connect_timer, %% for the establishment of connections but we also give %% connect_timer semantics for a listener, being the time within @@ -1260,13 +1278,14 @@ cm([#diameter_app{alias = Alias} = App], Req, From, Svc) -> mod_state(Alias, ModS), {T, RC}; T -> - diameter_lib:error_report({invalid, T}, - {App, handle_call, Args}), + ModX = App#diameter_app.module, + ?LOG(invalid_return, {ModX, handle_call, Args, T}), invalid catch E: Reason -> - diameter_lib:error_report({failure, {E, Reason, ?STACK}}, - {App, handle_call, Args}), + ModX = App#diameter_app.module, + Stack = diameter_lib:get_stacktrace(), + ?LOG(failure, {E, Reason, ModX, handle_call, Stack}), failure end; @@ -1424,13 +1443,16 @@ pick_peer(Local, T; %% Accept returned state in the immutable {false = No, S} -> %% case as long it isn't changed. No; - T -> - diameter_lib:error_report({invalid, T, App}, - {App, pick_peer, Args}) + T when M -> + ModX = App#diameter_app.module, + ?LOG(invalid_return, {ModX, pick_peer, T}), + false catch - E: Reason -> - diameter_lib:error_report({failure, {E, Reason, ?STACK}}, - {App, pick_peer, Args}) + E: Reason when M -> + ModX = App#diameter_app.module, + Stack = diameter_lib:get_stacktrace(), + ?LOG(failure, {E, Reason, ModX, pick_peer, Stack}), + false end. %% peers/4 diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 7fbb306b02..5fac61f416 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -31,6 +31,11 @@ %% towards diameter_watchdog -export([receive_message/4]). +%% towards diameter_peer_fsm and diameter_watchdog +-export([incr/4, + incr_error/4, + incr_rc/4]). + %% towards diameter_service -export([make_recvdata/1, peer_up/1, @@ -44,6 +49,8 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). +-define(LOGX(Reason, T), begin ?LOG(Reason, T), x({Reason, T}) end). + -define(RELAY, ?DIAMETER_DICT_RELAY). -define(BASE, ?DIAMETER_DICT_COMMON). %% Note: the RFC 3588 dictionary @@ -109,6 +116,67 @@ peer_down(TPid) -> failover(TPid). %% --------------------------------------------------------------------------- +%% incr/4 +%% --------------------------------------------------------------------------- + +incr(Dir, #diameter_packet{header = H}, TPid, Dict) -> + incr(Dir, H, TPid, Dict); + +incr(Dir, #diameter_header{} = H, TPid, Dict) -> + incr(TPid, {msg_id(H, Dict), Dir}). + +%% --------------------------------------------------------------------------- +%% incr_error/4 +%% --------------------------------------------------------------------------- + +%% Decoded message without errors. +incr_error(recv, #diameter_packet{errors = []}, _, _) -> + ok; + +incr_error(recv = D, #diameter_packet{header = H}, TPid, Dict) -> + incr_error(D, H, TPid, Dict); + +%% Encoded message with errors and an identifiable header ... +incr_error(send = D, {_, _, #diameter_header{} = H}, TPid, Dict) -> + incr_error(D, H, TPid, Dict); + +%% ... or not. +incr_error(send = D, {_,_}, TPid, _) -> + incr_error(D, unknown, TPid); + +incr_error(Dir, #diameter_header{} = H, TPid, Dict) -> + incr_error(Dir, msg_id(H, Dict), TPid); + +incr_error(Dir, Id, TPid, _) -> + incr_error(Dir, Id, TPid). + +incr_error(Dir, Id, TPid) -> + incr(TPid, {Id, Dir, error}). + +%% --------------------------------------------------------------------------- +%% incr_rc/4 +%% --------------------------------------------------------------------------- + +-spec incr_rc(send|recv, Pkt, TPid, Dict0) + -> {Counter, non_neg_integer()} + | Reason + when Pkt :: #diameter_packet{}, + TPid :: pid(), + Dict0 :: module(), + Counter :: {'Result-Code', integer()} + | {'Experimental-Result', integer(), integer()}, + Reason :: atom(). + +incr_rc(Dir, Pkt, TPid, Dict0) -> + try + incr_rc(Dir, Pkt, Dict0, TPid, Dict0) + catch + exit: {E,_} when E == no_result_code; + E == invalid_error_bit -> + E + end. + +%% --------------------------------------------------------------------------- %% pending/1 %% --------------------------------------------------------------------------- @@ -182,7 +250,7 @@ spawn_request(TPid, Pkt, Dict0, Opts, RecvData) -> spawn_opt(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end, Opts) catch error: system_limit = E -> %% discard - ?LOG({error, E}, now()) + ?LOG(error, E) end. %% --------------------------------------------------------------------------- @@ -211,7 +279,9 @@ recv_R({#diameter_app{id = Id, dictionary = Dict} = App, Caps}, Pkt0, Dict0, RecvData) -> + incr(recv, Pkt0, TPid, Dict), Pkt = errors(Id, diameter_codec:decode(Id, Dict, Pkt0)), + incr_error(recv, Pkt, TPid, Dict), {Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)}; %% Note that the decode is different depending on whether or not Id is %% ?APP_ID_RELAY. @@ -283,23 +353,25 @@ rc(N) -> %% This error is returned when a request is received with an invalid %% message length. -errors(_, #diameter_packet{header = #diameter_header{length = Len}, +errors(_, #diameter_packet{header = #diameter_header{length = Len} = H, bin = Bin, errors = Es} = Pkt) when Len < 20; 0 /= Len rem 4; 8*Len /= bit_size(Bin) -> + ?LOG(invalid_message_length, {H, bit_size(Bin)}), Pkt#diameter_packet{errors = [5015 | Es]}; %% DIAMETER_UNSUPPORTED_VERSION 5011 %% This error is returned when a request was received, whose version %% number is unsupported. -errors(_, #diameter_packet{header = #diameter_header{version = V}, +errors(_, #diameter_packet{header = #diameter_header{version = V} = H, errors = Es} = Pkt) when V /= ?DIAMETER_VERSION -> + ?LOG(unsupported_version, H), Pkt#diameter_packet{errors = [5011 | Es]}; %% DIAMETER_COMMAND_UNSUPPORTED 3001 @@ -307,12 +379,13 @@ errors(_, #diameter_packet{header = #diameter_header{version = V}, %% recognize or support. This MUST be used when a Diameter node %% receives an experimental command that it does not understand. -errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P}, +errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P} = H, msg = M, errors = Es} = Pkt) when ?APP_ID_RELAY /= Id, undefined == M; %% don't know the command ?APP_ID_RELAY == Id, not P -> %% command isn't proxiable + ?LOG(command_unsupported, H), Pkt#diameter_packet{errors = [3001 | Es]}; %% DIAMETER_INVALID_HDR_BITS 3008 @@ -321,9 +394,11 @@ errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P}, %% inconsistent with the command code's definition. errors(_, #diameter_packet{header = #diameter_header{is_request = true, - is_error = true}, + is_error = true} + = H, errors = Es} = Pkt) -> + ?LOG(invalid_hdr_bits, H), Pkt#diameter_packet{errors = [3008 | Es]}; %% Green. @@ -479,7 +554,6 @@ answer_message(RC, origin_realm = {OR,_}}, Dict0, Pkt) -> - ?LOG({error, RC}, Pkt), {Dict0, answer_message(OH, OR, RC, Dict0, Pkt)}. %% resend/7 @@ -595,9 +669,11 @@ reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt) reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) -> Pkt = encode(Dict, + TPid, reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0), Fs), - incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes + incr(send, Pkt, TPid, Dict), + incr_rc(send, Pkt, Dict, TPid, Dict0), %% count outgoing send(TPid, Pkt). %% reset/3 @@ -962,35 +1038,48 @@ find(Pred, [H|T]) -> %% code, the missing vendor id, and a zero filled payload of the minimum %% required length for the omitted AVP will be added. -%% incr/4 +%% incr_rc/5 %% %% Increment a stats counter for result codes in incoming and outgoing %% answers. %% Outgoing message as binary: don't count. (Sending binaries is only %% partially supported.) -incr(_, #diameter_packet{msg = undefined}, _, _, _) -> - ok; - -%% Incoming with decode errors. -incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid, _) -> - incr(TPid, {diameter_codec:msg_id(H), D, error}); +incr_rc(_, #diameter_packet{msg = undefined = No}, _, _, _) -> + No; -%% Incoming without errors or outgoing. Outgoing with encode errors -%% never gets here since encode fails. -incr(Dir, Pkt, Dict, TPid, Dict0) -> +%% Incoming or outgoing. Outgoing with encode errors never gets here +%% since encode fails. +incr_rc(Dir, Pkt, Dict, TPid, Dict0) -> #diameter_packet{header = #diameter_header{is_error = E} = Hdr, - msg = Rec} + msg = Msg, + errors = Es} = Pkt, - RC = int(get_avp_value(Dict, 'Result-Code', Rec)), + Id = msg_id(Hdr, Dict), + + %% Count incoming decode errors. + recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, Dict), - %% Exit on an improper Result-Code. + %% Exit on a missing result code. + T = rc_counter(Dict, Msg), + T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}), + {Ctr, RC} = T, + + %% Or on an inappropriate value. is_result(RC, E, Dict0) - orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]), + orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}), + + incr(TPid, {Id, Dir, Ctr}), + Ctr. - irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)). +%% Only count on known keeps so as not to be vulnerable to attack: +%% there are 2^32 (application ids) * 2^24 (command codes) * 2 (R-bits) +%% = 2^57 Ids for an attacker to choose from. +msg_id(Hdr, Dict) -> + {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr), + choose('' == Dict:msg_name(Code, 0 == R), unknown, Id). %% No E-bit: can't be 3xxx. is_result(RC, false, _Dict0) -> @@ -1006,12 +1095,6 @@ is_result(RC, true, _) -> orelse 5000 =< RC andalso RC < 6000. -irc(_, _, _, undefined) -> - false; - -irc(TPid, Hdr, Dir, Ctr) -> - incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}). - %% incr/2 incr(TPid, Counter) -> @@ -1024,14 +1107,16 @@ incr(TPid, Counter) -> %% All Diameter answer messages defined in vendor-specific %% applications MUST include either one Result-Code AVP or one %% Experimental-Result AVP. -%% -%% Maintain statistics assuming one or the other, not both, which is -%% surely the intent of the RFC. -rc_counter(Dict, Rec, undefined) -> - rcc(get_avp_value(Dict, 'Experimental-Result', Rec)); -rc_counter(_, _, RC) -> - {'Result-Code', RC}. +rc_counter(Dict, Msg) -> + rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))). + +rcc(Dict, Msg, undefined) -> + rcc(get_avp_value(Dict, 'Experimental-Result', Msg)); + +rcc(_, _, N) + when is_integer(N) -> + {{'Result-Code', N}, N}. %% Outgoing answers may be in any of the forms messages can be sent %% in. Incoming messages will be records. We're assuming here that the @@ -1039,12 +1124,12 @@ rc_counter(_, _, RC) -> rcc([{_,_,N} = T | _]) when is_integer(N) -> - T; + {T,N}; rcc({_,_,N} = T) when is_integer(N) -> - T; + {T,N}; rcc(_) -> - undefined. + false. %% Extract the first good looking integer. There's no guarantee %% that what we're looking for has arity 1. @@ -1057,13 +1142,6 @@ int(N) int(_) -> undefined. --spec x(any(), atom(), list()) -> no_return(). - -%% Warn and exit request process on errors in an incoming answer. -x(Reason, F, A) -> - diameter_lib:warning_report(Reason, {?MODULE, F, A}), - x(Reason). - x(T) -> exit(T). @@ -1305,7 +1383,7 @@ send_R(Pkt0, {Pid, Ref}, SvcName, Fs) -> - Pkt = encode(Dict, Pkt0, Fs), + Pkt = encode(Dict, TPid, Pkt0, Fs), #options{timeout = Timeout} = Opts, @@ -1370,11 +1448,19 @@ handle_answer(SvcName, %% want to examine the answer? handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> + incr(recv, Pkt, TPid, Dict), + try - incr(recv, Pkt, Dict, TPid, Dict0) %% count incoming result codes + incr_rc(recv, Pkt, Dict, TPid, Dict0) %% count incoming of _ -> answer(Pkt, SvcName, App, Req) catch + exit: {no_result_code, _} -> + %% RFC 6733 requires one of Result-Code or + %% Experimental-Result, but the decode will have detected + %% a missing AVP. If both are optional in the dictionary + %% then this isn't a decode error: just continue on. + answer(Pkt, SvcName, App, Req); exit: {invalid_error_bit, RC} -> #diameter_packet{errors = Es} = Pkt, @@ -1401,11 +1487,16 @@ a(#diameter_packet{errors = Es} callback == AE -> cb(ModX, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); -a(Pkt, SvcName, _, report, Req) -> - x(errors, handle_answer, [SvcName, Req, Pkt]); +a(Pkt, SvcName, _, AE, _) -> + a(Pkt#diameter_packet.header, SvcName, AE). + +a(Hdr, SvcName, report) -> + MFA = {?MODULE, handle_answer, [SvcName, Hdr]}, + diameter_lib:warning_report(errors, MFA), + a(Hdr, SvcName, discard); -a(Pkt, SvcName, _, discard, Req) -> - x({errors, handle_answer, [SvcName, Req, Pkt]}). +a(Hdr, SvcName, discard) -> + x({answer_errors, {SvcName, Hdr}}). %% Note that we don't check that the application id in the answer's %% header is what we expect. (TODO: Does the rfc says anything about @@ -1463,10 +1554,10 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) -> msg(#diameter_packet{msg = Msg}) -> Msg. -%% encode/3 +%% encode/4 -encode(Dict, Pkt, Fs) -> - P = encode(Dict, Pkt), +encode(Dict, TPid, Pkt, Fs) -> + P = encode(Dict, TPid, Pkt), eval_packet(P, Fs), P. @@ -1478,11 +1569,17 @@ encode(Dict, Pkt, Fs) -> %% support retransmission but is useful for test. %% A message to be encoded. -encode(Dict, #diameter_packet{bin = undefined} = Pkt) -> - diameter_codec:encode(Dict, Pkt); +encode(Dict, TPid, #diameter_packet{bin = undefined} = Pkt) -> + try + diameter_codec:encode(Dict, Pkt) + catch + exit: {diameter_codec, encode, T} = Reason -> + incr_error(send, T, TPid, Dict), + exit(Reason) + end; %% An encoded binary: just send. -encode(_, #diameter_packet{} = Pkt) -> +encode(_, _, #diameter_packet{} = Pkt) -> Pkt. %% send_request/5 @@ -1579,13 +1676,13 @@ resend_request(Pkt0, SvcName, Tmo, Fs) -> - Pkt = encode(Dict, Pkt0, Fs), + Pkt = encode(Dict, TPid, Pkt0, Fs), Req = Req0#request{transport = TPid, packet = Pkt0, caps = Caps}, - ?LOG(retransmission, Req), + ?LOG(retransmission, Pkt#diameter_packet.header), TRef = send_request(TPid, Pkt, Req, SvcName, Tmo), {TRef, Req}. diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 53e659e3f6..eff5096745 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -49,8 +49,6 @@ -define(IS_NATURAL(N), (is_integer(N) andalso 0 =< N)). --define(CHOOSE(B,T,F), if (B) -> T; true -> F end). - -record(config, {suspect = 1 :: non_neg_integer(), %% OKAY -> SUSPECT okay = 3 :: non_neg_integer()}). %% REOPEN -> OKAY @@ -221,7 +219,6 @@ dict0(_, _, Acc) -> Acc. config_error(T) -> - diameter_lib:error_report(configuration_error, T), exit({shutdown, {configuration_error, T}}). %% handle_call/3 @@ -270,7 +267,7 @@ event(Msg, TPid = tpid(F,T), E = {[TPid | data(Msg, TPid, From, To)], From, To}, send(Pid, {watchdog, self(), E}), - ?LOG(transition, {self(), E}). + ?LOG(transition, {From, To}). data(Msg, TPid, reopen, okay) -> {recv, TPid, 'DWA', _Pkt} = Msg, %% assert @@ -313,14 +310,13 @@ code_change(_, State, _) -> %% The state transitions documented here are extracted from RFC 3539, %% the commentary is ours. -%% Service or watchdog is telling the watchdog of an accepting -%% transport to die after connect_timer expiry or reestablished -%% connection (in another transport process) respectively. -transition(close, #watchdog{status = down}) -> +%% Service is telling the watchdog of an accepting transport to die +%% following transport death in state INITIAL, or after connect_timer +%% expiry; or another watchdog is saying the same after reestablishing +%% a connection previously had by this one. +transition(close, #watchdog{}) -> {{accept, _}, _, _} = getr(restart), %% assert stop; -transition(close, #watchdog{}) -> - ok; %% Service is asking for the peer to be taken down gracefully. transition({shutdown, Pid, _}, #watchdog{parent = Pid, @@ -332,6 +328,11 @@ transition({shutdown = T, Pid, Reason}, #watchdog{parent = Pid, send(TPid, {T, self(), Reason}), S#watchdog{shutdown = true}; +%% Transport is telling us that DPA has been sent in response to DPR: +%% its death should lead to ours. +transition({'DPR', TPid}, #watchdog{transport = TPid} = S) -> + S#watchdog{shutdown = true}; + %% Parent process has died, transition({'DOWN', _, process, Pid, _Reason}, #watchdog{parent = Pid}) -> @@ -403,18 +404,39 @@ transition({open = Key, TPid, _Hosts, T}, %% REOPEN Connection down CloseConnection() %% SetWatchdog() DOWN +%% Transport has died after DPA or service requested termination ... transition({'DOWN', _, process, TPid, _Reason}, #watchdog{transport = TPid, shutdown = true}) -> stop; +%% ... or not. transition({'DOWN', _, process, TPid, _Reason}, #watchdog{transport = TPid, - status = T} - = S) -> - set_watchdog(S#watchdog{status = ?CHOOSE(initial == T, T, down), - pending = false, - transport = undefined}); + status = T, + restrict = {_,R}} + = S0) -> + S = S0#watchdog{pending = false, + transport = undefined}, + {{M,_}, _, _} = getr(restart), + + %% Close an accepting watchdog immediately if there's no + %% restriction on the number of connections to the same peer: the + %% state machine never enters state REOPEN in this case. The + %% 'close' message (instead of stop) is so as not to bypass the + %% sending of messages to the service process in handle_info/2. + + if T /= initial, M == accept, not R -> + send(self(), close), + S#watchdog{status = down}; + T /= initial -> + set_watchdog(S#watchdog{status = down}); + M == connect -> + set_watchdog(S); + M == accept -> + send(self(), close), + S + end; %% Incoming message. transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) -> @@ -454,9 +476,7 @@ encode(dwr = M, Dict0, Mask) -> hop_by_hop_id = Seq}, Pkt = #diameter_packet{header = Hdr, msg = Msg}, - #diameter_packet{bin = Bin} = diameter_codec:encode(Dict0, Pkt), - Bin; - + diameter_codec:encode(Dict0, Pkt); encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD} = ReqPkt) -> @@ -525,10 +545,14 @@ send_watchdog(#watchdog{pending = false, dictionary = Dict0, sequence = Mask} = S) -> - send(TPid, {send, encode(dwr, Dict0, Mask)}), + #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Mask), + diameter_traffic:incr(send, EPkt, TPid, Dict0), + send(TPid, {send, Bin}), ?LOG(send, 'DWR'), S#watchdog{pending = true}. +%% Dont' count encode errors since we don't expect any on DWR/DWA. + %% recv/3 recv(Name, Pkt, S) -> @@ -545,13 +569,29 @@ recv(Name, Pkt, S) -> rcv('DWR', Pkt, #watchdog{transport = TPid, dictionary = Dict0}) -> - send(TPid, {send, encode(dwa, Dict0, Pkt)}), + ?LOG(recv, 'DWR'), + DPkt = diameter_codec:decode(Dict0, Pkt), + diameter_traffic:incr(recv, DPkt, TPid, Dict0), + diameter_traffic:incr_error(recv, DPkt, TPid, Dict0), + EPkt = encode(dwa, Dict0, Pkt), + diameter_traffic:incr(send, EPkt, TPid, Dict0), + diameter_traffic:incr_rc(send, EPkt, TPid, Dict0), + + send(TPid, {send, EPkt}), ?LOG(send, 'DWA'); +rcv('DWA', Pkt, #watchdog{transport = TPid, + dictionary = Dict0}) -> + ?LOG(recv, 'DWA'), + diameter_traffic:incr(recv, Pkt, TPid, Dict0), + diameter_traffic:incr_rc(recv, + diameter_codec:decode(Dict0, Pkt), + TPid, + Dict0); + rcv(N, _, _) when N == 'CER'; N == 'CEA'; - N == 'DWA'; N == 'DPR'; N == 'DPA' -> false; @@ -740,7 +780,7 @@ timeout(#watchdog{status = T} = S) restart(#watchdog{transport = undefined} = S) -> restart(getr(restart), S); -restart(S) -> +restart(S) -> %% reconnect has won race with timeout S. %% restart/2 @@ -770,9 +810,10 @@ restart({{connect, _} = T, Opts, Svc}, %% die. Note that a state machine never enters state REOPEN in this %% case. restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) -> - stop; + stop; %% 'DOWN' was in old code: 'close' was not sent -%% Otherwise hang around until told to die. +%% Otherwise hang around until told to die, either by the service or +%% by another watchdog. restart({{accept, _}, _, _}, S) -> S. diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index 0d421c229e..b7b9662383 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -34,7 +34,19 @@ {"1.4.2", [{restart_application, diameter}]}, %% R16B01 {"1.4.3", [{restart_application, diameter}]}, %% R16B02 {"1.4.4", [{restart_application, diameter}]}, - {"1.5", [{restart_application, diameter}]} %% R16B03 + {"1.5", [{restart_application, diameter}]}, %% R16B03 + {"1.6", [{load_module, diameter_lib}, %% 17.0 + {load_module, diameter_traffic}, + {load_module, diameter_watchdog}, + {load_module, diameter_peer_fsm}, + {load_module, diameter_service}, + {load_module, diameter_gen_base_rfc6733}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_accounting}, + {load_module, diameter_gen_relay}, + {load_module, diameter_codec}, + {load_module, diameter_sctp}]} ], [ {"0.9", [{restart_application, diameter}]}, @@ -51,6 +63,18 @@ {"1.4.2", [{restart_application, diameter}]}, {"1.4.3", [{restart_application, diameter}]}, {"1.4.4", [{restart_application, diameter}]}, - {"1.5", [{restart_application, diameter}]} + {"1.5", [{restart_application, diameter}]}, + {"1.6", [{load_module, diameter_sctp}, + {load_module, diameter_codec}, + {load_module, diameter_gen_relay}, + {load_module, diameter_gen_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, + {load_module, diameter_service}, + {load_module, diameter_peer_fsm}, + {load_module, diameter_watchdog}, + {load_module, diameter_traffic}, + {load_module, diameter_lib}]} ] }. diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index d0a01351f3..32e7aaca39 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -616,6 +616,8 @@ send(#diameter_packet{bin = Bin, transport_data = {outstream, SId}}, S; %% ... or not: rotate through all streams. +send(#diameter_packet{bin = Bin}, S) -> + send(Bin, S); send(Bin, #transport{streams = {_, OS}, os = N} = S) diff --git a/lib/diameter/test/diameter_dpr_SUITE.erl b/lib/diameter/test/diameter_dpr_SUITE.erl index 9252650bf7..f3f16b06e0 100644 --- a/lib/diameter/test/diameter_dpr_SUITE.erl +++ b/lib/diameter/test/diameter_dpr_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012. All Rights Reserved. +%% Copyright Ericsson AB 2012-2014. 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 @@ -73,7 +73,7 @@ %% Valid values for Disconnect-Cause. -define(CAUSES, [0, rebooting, 1, busy, 2, goaway]). -%% Establish one client connection for element of this list, +%% Establish one client connection for each element of this list, %% configured with disconnect/5 as disconnect_cb and returning the %% specified value. -define(RETURNS, @@ -129,8 +129,8 @@ stop_service(Config) -> service == group(Config) andalso (ok = diameter:stop_service(?CLIENT)). -%% Check for callbacks and stop the service. (Not the other way around -%% for the timing reason explained below.) +%% Check for callbacks before diameter:stop/0, not the other way around +%% for the timing reason explained below. check(Config) -> Grp = group(Config), [Pid | Refs] = ?util:read_priv(Config, config), diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl index 02c8d34361..aef4bc35ef 100644 --- a/lib/diameter/test/diameter_examples_SUITE.erl +++ b/lib/diameter/test/diameter_examples_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. 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 @@ -24,7 +24,10 @@ -module(diameter_examples_SUITE). -export([suite/0, - all/0]). + all/0, + groups/0, + init_per_group/2, + end_per_group/2]). %% testcases -export([dict/1, dict/0, @@ -46,7 +49,7 @@ %% The order here is significant and causes the server to listen %% before the clients connect. --define(NODES, [compile, server, client]). +-define(NODES, [server, client]). %% Options to ct_slave:start/2. -define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout, @@ -63,6 +66,9 @@ %% Common dictionaries to inherit from examples. -define(DICT0, [rfc3588_base, rfc6733_base]). +%% Transport protocols over which the example Diameter nodes are run. +-define(PROTS, [tcp, sctp]). + %% =========================================================================== suite() -> @@ -71,7 +77,34 @@ suite() -> all() -> [dict, code, - slave, + {group, all}]. + +groups() -> + Tc = tc(), + [{all, [parallel], [{group, P} || P <- ?PROTS]} + | [{P, [], Tc} || P <- ?PROTS]]. + +init_per_group(all, Config) -> + Config; + +init_per_group(tcp = N, Config) -> + [{group, N} | Config]; + +init_per_group(sctp = N, Config) -> + case gen_sctp:open() of + {ok, Sock} -> + gen_sctp:close(Sock), + [{group, N} | Config]; + {error, E} when E == eprotonosupport; + E == esocktnosupport -> %% fail on any other reason + {skip, no_sctp} + end. + +end_per_group(_, _) -> + ok. + +tc() -> + [slave, enslave, start, traffic, @@ -88,7 +121,7 @@ dict() -> dict(_Config) -> Dirs = [filename:join(H ++ ["examples", "dict"]) || H <- [[code:lib_dir(diameter)], [here(), ".."]]], - [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia")), + [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia$")), D <- ?DICT0, RC <- [make(F,D)], RC /= ok]. @@ -184,17 +217,18 @@ make_name(Dict) -> %% Compile example code under examples/code. code(Config) -> - Node = slave(hd(?NODES), here()), + Node = slave(compile, here()), [] = rpc:call(Node, ?MODULE, install, - [proplists:get_value(priv_dir, Config)]). + [proplists:get_value(priv_dir, Config)]), + {ok, Node} = ct_slave:stop(compile). %% Compile on another node since the code path may be modified. install(PrivDir) -> Top = install(here(), PrivDir), Src = filename:join([Top, "examples", "code"]), - Files = find_files([Src], ".*\\.erl"), + Files = find_files([Src], ".*\\.erl$"), [] = [{F,E} || {_,F} <- Files, {error, _, _} = E <- [compile:file(F, [warnings_as_errors, return_errors])]]. @@ -226,7 +260,7 @@ install(Dir, PrivDir) -> Inc = filename:join([Top, "include"]), Gen = filename:join([Top, "src", "gen"]), - Files = find_files([Inc, Gen], ".*\\.hrl"), + Files = find_files([Inc, Gen], ".*\\.hrl$"), [] = [{F,E} || {_,F} <- Files, B <- [filename:basename(F)], D <- [filename:join([TmpInc, B])], @@ -280,9 +314,10 @@ now_diff(_) -> %% Start two nodes: one for the server, one for the client. enslave(Config) -> + Prot = proplists:get_value(group, Config), Dir = here(), - Nodes = [{N, slave(N, Dir)} || N <- tl(?NODES)], - ?util:write_priv(Config, nodes, Nodes). + Nodes = [{S, slave(N, Dir)} || S <- ?NODES, N <- [concat(Prot, S)]], + ?util:write_priv(Config, Prot, Nodes). slave(Name, Dir) -> {ok, Node} = ct_slave:start(Name, ?TIMEOUTS), @@ -292,6 +327,9 @@ slave(Name, Dir) -> [[Dir, filename:join([Dir, "..", "ebin"])]]), Node. +concat(Prot, Svc) -> + list_to_atom(atom_to_list(Prot) ++ atom_to_list(Svc)). + here() -> filename:dirname(code:which(?MODULE)). @@ -304,24 +342,25 @@ top(Dir, LibDir) -> %% start/1 -start(server) -> +start({server, Prot}) -> ok = diameter:start(), ok = server:start(), - {ok, Ref} = server:listen(tcp), - [_] = ?util:lport(tcp, Ref), + {ok, Ref} = server:listen(Prot), + [_] = ?util:lport(Prot, Ref), ok; -start(client) -> +start({client = Svc, Prot}) -> ok = diameter:start(), - true = diameter:subscribe(client), + true = diameter:subscribe(Svc), ok = client:start(), - {ok, Ref} = client:connect(tcp), + {ok, Ref} = client:connect(Prot), receive #diameter_event{info = {up, Ref, _, _, _}} -> ok end; start(Config) -> - Nodes = ?util:read_priv(Config, nodes), + Prot = proplists:get_value(group, Config), + Nodes = ?util:read_priv(Config, Prot), [] = [RC || {T,N} <- Nodes, - RC <- [rpc:call(N, ?MODULE, start, [T])], + RC <- [rpc:call(N, ?MODULE, start, [{T, Prot}])], RC /= ok]. %% traffic/1 @@ -336,7 +375,8 @@ traffic(client) -> receive {'DOWN', MRef, process, _, Reason} -> Reason end; traffic(Config) -> - Nodes = ?util:read_priv(Config, nodes), + Prot = proplists:get_value(group, Config), + Nodes = ?util:read_priv(Config, Prot), [] = [RC || {T,N} <- Nodes, RC <- [rpc:call(N, ?MODULE, traffic, [T])], RC /= ok]. @@ -355,5 +395,6 @@ stop(Name) {ok, _Node} = ct_slave:stop(Name), ok; -stop(_Config) -> - [] = [RC || N <- ?NODES, RC <- [stop(N)], RC /= ok]. +stop(Config) -> + Prot = proplists:get_value(group, Config), + [] = [RC || N <- ?NODES, RC <- [stop(concat(Prot, N))], RC /= ok]. diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index dfd3253827..c1494dcdb1 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -47,6 +47,7 @@ send_discard_1/1, send_discard_2/1, stop_services/1, + empty/1, stop/1]). %% diameter callbacks @@ -121,6 +122,7 @@ all() -> send_discard_1, send_discard_2, stop_services, + empty, stop]. %% =========================================================================== @@ -147,6 +149,10 @@ stop_services(_Config) -> T <- [diameter:stop_service(H)], T /= ok]. +%% Ensure transports have been removed from request table. +empty(_Config) -> + [] = ets:tab2list(diameter_request). + stop(_Config) -> ok = diameter:stop(). diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index a97c54fc04..4b67372016 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -43,7 +43,9 @@ send_protocol_error/1, send_arbitrary/1, send_unknown/1, + send_unknown_short/1, send_unknown_mandatory/1, + send_unknown_short_mandatory/1, send_noreply/1, send_unsupported/1, send_unsupported_app/1, @@ -54,7 +56,8 @@ send_zero_avp_length/1, send_invalid_avp_length/1, send_invalid_reject/1, - send_unrecognized_mandatory/1, + send_unexpected_mandatory_decode/1, + send_unexpected_mandatory/1, send_long/1, send_nopeer/1, send_noapp/1, @@ -266,7 +269,9 @@ tc() -> send_protocol_error, send_arbitrary, send_unknown, + send_unknown_short, send_unknown_mandatory, + send_unknown_short_mandatory, send_noreply, send_unsupported, send_unsupported_app, @@ -277,7 +282,8 @@ tc() -> send_zero_avp_length, send_invalid_avp_length, send_invalid_reject, - send_unrecognized_mandatory, + send_unexpected_mandatory_decode, + send_unexpected_mandatory, send_long, send_nopeer, send_noapp, @@ -447,6 +453,24 @@ send_unknown(Config) -> data = <<17>>}]} = lists:last(Avps). +%% Ditto, and point the AVP length past the end of the message. Expect +%% 5014. +send_unknown_short(Config) -> + send_unknown_short(Config, false, ?INVALID_AVP_LENGTH). + +send_unknown_short(Config, M, RC) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = M, + data = <<17>>}]}], + ['ASA', _SessionId, {'Result-Code', RC} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), + [#diameter_avp{code = 999, + is_mandatory = M, + data = <<17, _/binary>>}] %% extra bits from padding + = As. + %% Ditto but set the M flag. send_unknown_mandatory(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, @@ -461,6 +485,27 @@ send_unknown_mandatory(Config) -> data = <<17>>}] = As. +%% Ditto, and point the AVP length past the end of the message. Expect +%% 5014 instead of 5001. +send_unknown_short_mandatory(Config) -> + send_unknown_short(Config, true, ?INVALID_AVP_LENGTH). + +%% Send an ACR containing an unexpected mandatory Session-Timeout. +%% Expect 5001, and check that the value in Failed-AVP was decoded. +send_unexpected_mandatory_decode(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout + is_mandatory = true, + data = <<12:32>>}]}], + ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), + [#diameter_avp{code = 27, + is_mandatory = true, + value = 12, + data = <<12:32>>}] + = As. + %% Send an STR that the server ignores. send_noreply(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], @@ -527,9 +572,9 @@ send_invalid_reject(Config) -> ?answer_message(?TOO_BUSY) = call(Config, Req). -%% Send an STR containing a known AVP, but one that's not allowed and -%% sets the M-bit. -send_unrecognized_mandatory(Config) -> +%% Send an STR containing a known AVP, but one that's not expected and +%% that sets the M-bit. +send_unexpected_mandatory(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _] @@ -836,6 +881,26 @@ log(#diameter_packet{bin = Bin} = P, T) %% prepare/4 prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) + when N == send_unknown_short_mandatory; + N == send_unknown_short -> + Req = prepare(Pkt, Caps, Group), + + #diameter_packet{header = #diameter_header{length = L}, + bin = Bin} + = E + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), + + %% Find the unknown AVP data at the end of the message and alter + %% its length header. + + {Padding, [17|_]} = lists:splitwith(fun(C) -> C == 0 end, + lists:reverse(binary_to_list(Bin))), + + Offset = L - length(Padding) - 4, + <<H:Offset/binary, Len:24, T/binary>> = Bin, + E#diameter_packet{bin = <<H/binary, (Len+9):24, T/binary>>}; + +prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) when N == send_long_avp_length; N == send_short_avp_length; N == send_zero_avp_length -> @@ -876,8 +941,8 @@ prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) <<V, L:24, H/binary>> = H0, %% assert E#diameter_packet{bin = <<V, (L+4):24, H/binary, 16:24, 0:32, T/binary>>}; -prepare(Pkt, Caps, send_unrecognized_mandatory, #group{client_dict0 = Dict0} - = Group) -> +prepare(Pkt, Caps, send_unexpected_mandatory, #group{client_dict0 = Dict0} + = Group) -> Req = prepare(Pkt, Caps, Group), #diameter_packet{bin = <<V, Len:24, T/binary>>} = E @@ -997,7 +1062,9 @@ answer(Rec, [_|_], N) N == send_short_avp_length; N == send_zero_avp_length; N == send_invalid_avp_length; - N == send_invalid_reject -> + N == send_invalid_reject; + N == send_unknown_short_mandatory; + N == send_unexpected_mandatory_decode -> Rec; answer(Rec, [], _) -> Rec. diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 54019fa46c..560c2aed50 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,5 +18,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.6 +DIAMETER_VSN = 1.7 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) diff --git a/lib/hipe/cerl/cerl_prettypr.erl b/lib/hipe/cerl/cerl_prettypr.erl index 22f5b8945a..9a3873f46d 100644 --- a/lib/hipe/cerl/cerl_prettypr.erl +++ b/lib/hipe/cerl/cerl_prettypr.erl @@ -63,7 +63,8 @@ seq_arg/1, seq_body/1, string_lit/1, try_arg/1, try_body/1, try_vars/1, try_evars/1, try_handler/1, tuple_es/1, type/1, values_es/1, var_name/1, - map_es/1, map_pair_key/1, map_pair_val/1, map_pair_op/1 + c_map/1, map_arg/1, map_es/1, is_c_map_empty/1, + c_map_pair/2, map_pair_key/1, map_pair_val/1, map_pair_op/1 ]). -define(PAPER, 76). @@ -489,7 +490,13 @@ lay_literal(Node, Ctxt) -> %% `lay_cons' will check for strings. lay_cons(Node, Ctxt); V when is_tuple(V) -> - lay_tuple(Node, Ctxt) + lay_tuple(Node, Ctxt); + M when is_map(M), map_size(M) =:= 0 -> + text("~{}~"); + M when is_map(M) -> + lay_map(c_map([c_map_pair(abstract(K),abstract(V)) + || {K,V} <- maps:to_list(M)]), + Ctxt) end. lay_var(Node, Ctxt) -> @@ -596,10 +603,17 @@ lay_tuple(Node, Ctxt) -> floating(text("}")))). lay_map(Node, Ctxt) -> + Arg = map_arg(Node), + After = case is_c_map_empty(Arg) of + true -> floating(text("}~")); + false -> + beside(floating(text(" | ")), + beside(lay(Arg,Ctxt), + floating(text("}~")))) + end, beside(floating(text("~{")), - beside(par(seq(map_es(Node), floating(text(",")), - Ctxt, fun lay/2)), - floating(text("}~")))). + beside(par(seq(map_es(Node), floating(text(",")), Ctxt, fun lay/2)), + After)). lay_map_pair(Node, Ctxt) -> K = map_pair_key(Node), diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 06c0d10296..67661130a5 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -4487,7 +4487,9 @@ get_mod_record([{FieldName, DeclType}|Left1], [{FieldName, ModType}|Left2], Acc) -> ModTypeNoVars = subst_all_vars_to_any(ModType), case - contains_remote(ModTypeNoVars) orelse t_is_subtype(ModTypeNoVars, DeclType) + contains_remote(ModTypeNoVars) + orelse contains_remote(DeclType) + orelse t_is_subtype(ModTypeNoVars, DeclType) of false -> {error, FieldName}; true -> get_mod_record(Left1, Left2, [{FieldName, ModType}|Acc]) diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index e29144f014..596c0d77f4 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2002</year><year>2013</year> + <year>2002</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -32,7 +32,37 @@ <file>notes.xml</file> </header> - <section><title>Inets 5.10</title> + <section><title>Inets 5.10.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct distirbing mode for httpd:reload_config/2</p> + <p> + Own Id: OTP-11914</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Improved handling of invalid strings in the HTTP request + line.</p> + <p> + Impact: May improve memory consumption</p> + <p> + Own Id: OTP-11925 Aux Id: Sequence 12601 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 5.10</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl index 97cf474ab9..53b776c4e7 100644 --- a/lib/inets/src/http_lib/http_internal.hrl +++ b/lib/inets/src/http_lib/http_internal.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2011. All Rights Reserved. +%% Copyright Ericsson AB 2002-2014. 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 @@ -26,6 +26,8 @@ -define(HTTP_MAX_BODY_SIZE, nolimit). -define(HTTP_MAX_HEADER_SIZE, 10240). -define(HTTP_MAX_URI_SIZE, nolimit). +-define(HTTP_MAX_VERSION_STRING, 8). +-define(HTTP_MAX_METHOD_STRING, 20). -ifndef(HTTP_DEFAULT_SSL_KIND). -define(HTTP_DEFAULT_SSL_KIND, essl). diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 5ba79b2706..712c73599f 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2011. All Rights Reserved. +%% Copyright Ericsson AB 2005-2014. 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 @@ -44,26 +44,26 @@ %%%========================================================================= parse([Bin, MaxSizes]) -> ?hdrt("parse", [{bin, Bin}, {max_sizes, MaxSizes}]), - parse_method(Bin, [], MaxSizes, []); + parse_method(Bin, [], 0, proplists:get_value(max_method, MaxSizes), MaxSizes, []); parse(Unknown) -> ?hdrt("parse", [{unknown, Unknown}]), exit({bad_args, Unknown}). %% Functions that may be returned during the decoding process %% if the input data is incompleate. -parse_method([Bin, Method, MaxSizes, Result]) -> - parse_method(Bin, Method, MaxSizes, Result). +parse_method([Bin, Method, Current, Max, MaxSizes, Result]) -> + parse_method(Bin, Method, Current, Max, MaxSizes, Result). -parse_uri([Bin, URI, CurrSize, MaxSizes, Result]) -> - parse_uri(Bin, URI, CurrSize, MaxSizes, Result). +parse_uri([Bin, URI, Current, Max, MaxSizes, Result]) -> + parse_uri(Bin, URI, Current, Max, MaxSizes, Result). -parse_version([Bin, Rest, Version, MaxSizes, Result]) -> - parse_version(<<Rest/binary, Bin/binary>>, Version, MaxSizes, +parse_version([Bin, Rest, Version, Current, Max, MaxSizes, Result]) -> + parse_version(<<Rest/binary, Bin/binary>>, Version, Current, Max, MaxSizes, Result). -parse_headers([Bin, Rest, Header, Headers, CurrSize, MaxSizes, Result]) -> +parse_headers([Bin, Rest, Header, Headers, Current, Max, MaxSizes, Result]) -> parse_headers(<<Rest/binary, Bin/binary>>, - Header, Headers, CurrSize, MaxSizes, Result). + Header, Headers, Current, Max, MaxSizes, Result). whole_body([Bin, Body, Length]) -> whole_body(<<Body/binary, Bin/binary>>, Length). @@ -107,8 +107,12 @@ validate("POST", Uri, "HTTP/1." ++ _N) -> validate("TRACE", Uri, "HTTP/1." ++ N) when hd(N) >= $1 -> validate_uri(Uri); validate(Method, Uri, Version) -> - {error, {not_supported, {Method, Uri, Version}}}. - + case validate_version(Version) of + true -> + {error, {not_supported, {Method, Uri, Version}}}; + false -> + {error, {bad_version, Version}} + end. %%---------------------------------------------------------------------- %% The request is passed through the server as a record of type mod %% create it. @@ -131,104 +135,75 @@ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)-> %%%======================================================================== %%% Internal functions %%%======================================================================== -parse_method(<<>>, Method, MaxSizes, Result) -> - ?hdrt("parse_method - empty bin", - [{method, Method}, {max_sizes, MaxSizes}, {result, Result}]), - {?MODULE, parse_method, [Method, MaxSizes, Result]}; -parse_method(<<?SP, Rest/binary>>, Method, MaxSizes, Result) -> - ?hdrt("parse_method - SP begin", - [{rest, Rest}, - {method, Method}, - {max_sizes, MaxSizes}, - {result, Result}]), - parse_uri(Rest, [], 0, MaxSizes, +parse_method(<<>>, Method, Current, Max, MaxSizes, Result) -> + {?MODULE, parse_method, [Method, Current, Max, MaxSizes, Result]}; +parse_method(<<?SP, Rest/binary>>, Method, _Current, _Max, MaxSizes, Result) -> + parse_uri(Rest, [], 0, proplists:get_value(max_uri, MaxSizes), MaxSizes, [string:strip(lists:reverse(Method)) | Result]); -parse_method(<<Octet, Rest/binary>>, Method, MaxSizes, Result) -> - ?hdrt("parse_method", - [{octet, Octet}, - {rest, Rest}, - {method, Method}, - {max_sizes, MaxSizes}, - {result, Result}]), - parse_method(Rest, [Octet | Method], MaxSizes, Result). - -parse_uri(_, _, CurrSize, {MaxURI, _}, _) - when (CurrSize > MaxURI) andalso (MaxURI =/= nolimit) -> - ?hdrt("parse_uri", - [{current_size, CurrSize}, - {max_uri, MaxURI}]), +parse_method(<<Octet, Rest/binary>>, Method, Current, Max, MaxSizes, Result) when Current =< Max -> + parse_method(Rest, [Octet | Method], Current + 1, Max, MaxSizes, Result); +parse_method(_, _, _, Max, _, _) -> + %% We do not know the version of the client as it comes after the + %% method send the lowest version in the response so that the client + %% will be able to handle it. + {error, {too_long, Max, 413, "Method unreasonably long"}, lowest_version()}. + +parse_uri(_, _, Current, MaxURI, _, _) + when (Current > MaxURI) andalso (MaxURI =/= nolimit) -> %% We do not know the version of the client as it comes after the %% uri send the lowest version in the response so that the client %% will be able to handle it. - HttpVersion = "HTTP/0.9", - {error, {uri_too_long, MaxURI}, HttpVersion}; -parse_uri(<<>>, URI, CurrSize, MaxSizes, Result) -> - ?hdrt("parse_uri - empty bin", - [{uri, URI}, - {current_size, CurrSize}, - {max_sz, MaxSizes}, - {result, Result}]), - {?MODULE, parse_uri, [URI, CurrSize, MaxSizes, Result]}; -parse_uri(<<?SP, Rest/binary>>, URI, _, MaxSizes, Result) -> - ?hdrt("parse_uri - SP begin", - [{uri, URI}, - {max_sz, MaxSizes}, - {result, Result}]), - parse_version(Rest, [], MaxSizes, + {error, {too_long, MaxURI, 414, "URI unreasonably long"},lowest_version()}; +parse_uri(<<>>, URI, Current, Max, MaxSizes, Result) -> + {?MODULE, parse_uri, [URI, Current, Max, MaxSizes, Result]}; +parse_uri(<<?SP, Rest/binary>>, URI, _, _, MaxSizes, Result) -> + parse_version(Rest, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes, [string:strip(lists:reverse(URI)) | Result]); %% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n" -parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, MaxSizes, Result) -> - ?hdrt("parse_uri - CR begin", - [{uri, URI}, - {max_sz, MaxSizes}, - {result, Result}]), - parse_version(Data, [], MaxSizes, +parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, _, MaxSizes, Result) -> + parse_version(Data, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes, [string:strip(lists:reverse(URI)) | Result]); -parse_uri(<<Octet, Rest/binary>>, URI, CurrSize, MaxSizes, Result) -> - ?hdrt("parse_uri", - [{octet, Octet}, - {uri, URI}, - {curr_sz, CurrSize}, - {max_sz, MaxSizes}, - {result, Result}]), - parse_uri(Rest, [Octet | URI], CurrSize + 1, MaxSizes, Result). - -parse_version(<<>>, Version, MaxSizes, Result) -> - {?MODULE, parse_version, [<<>>, Version, MaxSizes, Result]}; -parse_version(<<?LF, Rest/binary>>, Version, MaxSizes, Result) -> +parse_uri(<<Octet, Rest/binary>>, URI, Current, Max, MaxSizes, Result) -> + parse_uri(Rest, [Octet | URI], Current + 1, Max, MaxSizes, Result). + +parse_version(<<>>, Version, Current, Max, MaxSizes, Result) -> + {?MODULE, parse_version, [<<>>, Version, Current, Max, MaxSizes, Result]}; +parse_version(<<?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_version(<<?CR, ?LF, Rest/binary>>, Version, MaxSizes, Result); -parse_version(<<?CR, ?LF, Rest/binary>>, Version, MaxSizes, Result) -> - parse_headers(Rest, [], [], 0, MaxSizes, + parse_version(<<?CR, ?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result); +parse_version(<<?CR, ?LF, Rest/binary>>, Version, _, _, MaxSizes, Result) -> + parse_headers(Rest, [], [], 0, proplists:get_value(max_header, MaxSizes), MaxSizes, [string:strip(lists:reverse(Version)) | Result]); -parse_version(<<?CR>> = Data, Version, MaxSizes, Result) -> - {?MODULE, parse_version, [Data, Version, MaxSizes, Result]}; -parse_version(<<Octet, Rest/binary>>, Version, MaxSizes, Result) -> - parse_version(Rest, [Octet | Version], MaxSizes, Result). - -parse_headers(_, _, _, CurrSize, {_, MaxHeaderSize}, Result) - when CurrSize > MaxHeaderSize, MaxHeaderSize =/= nolimit -> +parse_version(<<?CR>> = Data, Version, Current, Max, MaxSizes, Result) -> + {?MODULE, parse_version, [Data, Version, Current, Max, MaxSizes, Result]}; +parse_version(<<Octet, Rest/binary>>, Version, Current, Max, MaxSizes, Result) when Current =< Max -> + parse_version(Rest, [Octet | Version], Current + 1, Max, MaxSizes, Result); +parse_version(_, _, _, Max,_,_) -> + {error, {too_long, Max, 413, "Version string unreasonably long"}, lowest_version()}. + +parse_headers(_, _, _, Current, Max, _, Result) + when Max =/= nolimit andalso Current > Max -> HttpVersion = lists:nth(3, lists:reverse(Result)), - {error, {header_too_long, MaxHeaderSize}, HttpVersion}; + {error, {too_long, Max, 413, "Headers unreasonably long"}, HttpVersion}; -parse_headers(<<>>, Header, Headers, CurrSize, MaxSizes, Result) -> - {?MODULE, parse_headers, [<<>>, Header, Headers, CurrSize, +parse_headers(<<>>, Header, Headers, Current, Max, MaxSizes, Result) -> + {?MODULE, parse_headers, [<<>>, Header, Headers, Current, Max, MaxSizes, Result]}; -parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], CurrSize, MaxSizes, Result) -> +parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], CurrSize, + parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result); -parse_headers(<<?LF,?LF,Body/binary>>, [], [], CurrSize, MaxSizes, Result) -> +parse_headers(<<?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], CurrSize, + parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result); -parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, Result) -> +parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, _, Result) -> NewResult = list_to_tuple(lists:reverse([Body, {#http_request_h{}, []} | Result])), {ok, NewResult}; -parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, +parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _, _, Result) -> HTTPHeaders = [lists:reverse(Header) | Headers], RequestHeaderRcord = @@ -238,52 +213,51 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, HTTPHeaders} | Result])), {ok, NewResult}; -parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, CurrSize, +parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_headers, [Data, Header, Headers, CurrSize, + {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, MaxSizes, Result]}; -parse_headers(<<?LF>>, [], [], CurrSize, MaxSizes, Result) -> +parse_headers(<<?LF>>, [], [], Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF>>, [], [], CurrSize, MaxSizes, Result); + parse_headers(<<?CR,?LF>>, [], [], Current, Max, MaxSizes, Result); %% There where no headers, which is unlikely to happen. -parse_headers(<<?CR,?LF>>, [], [], _, _, Result) -> +parse_headers(<<?CR,?LF>>, [], [], _, _, _, Result) -> NewResult = list_to_tuple(lists:reverse([<<>>, {#http_request_h{}, []} | Result])), {ok, NewResult}; -parse_headers(<<?LF>>, Header, Headers, CurrSize, +parse_headers(<<?LF>>, Header, Headers, Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF>>, Header, Headers, CurrSize, MaxSizes, Result); + parse_headers(<<?CR,?LF>>, Header, Headers, Current, Max, MaxSizes, Result); -parse_headers(<<?CR,?LF>> = Data, Header, Headers, CurrSize, +parse_headers(<<?CR,?LF>> = Data, Header, Headers, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_headers, [Data, Header, Headers, CurrSize, + {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, MaxSizes, Result]}; -parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, CurrSize, +parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, CurrSize, + parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max, MaxSizes, Result); -parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, CurrSize, +parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, _, Max, MaxSizes, Result) -> parse_headers(Rest, [Octet], [lists:reverse(Header) | Headers], - CurrSize + 1, MaxSizes, Result); - -parse_headers(<<?CR>> = Data, Header, Headers, CurrSize, + 0, Max, MaxSizes, Result); +parse_headers(<<?CR>> = Data, Header, Headers, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_headers, [Data, Header, Headers, CurrSize, + {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, MaxSizes, Result]}; -parse_headers(<<?LF>>, Header, Headers, CurrSize, +parse_headers(<<?LF>>, Header, Headers, Current, Max, MaxSizes, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR, ?LF>>, Header, Headers, CurrSize, + parse_headers(<<?CR, ?LF>>, Header, Headers, Current, Max, MaxSizes, Result); -parse_headers(<<Octet, Rest/binary>>, Header, Headers, - CurrSize, MaxSizes, Result) -> - parse_headers(Rest, [Octet | Header], Headers, CurrSize + 1, +parse_headers(<<Octet, Rest/binary>>, Header, Headers, Current, + Max, MaxSizes, Result) -> + parse_headers(Rest, [Octet | Header], Headers, Current + 1, Max, MaxSizes, Result). whole_body(Body, Length) -> @@ -326,6 +300,14 @@ validate_path([".." | Rest], N, RequestURI) -> validate_path([_ | Rest], N, RequestURI) -> validate_path(Rest, N + 1, RequestURI). +validate_version("HTTP/1.1") -> + true; +validate_version("HTTP/1.0") -> + true; +validate_version("HTTP/0.9") -> + true; +validate_version(_) -> + false. %%---------------------------------------------------------------------- %% There are 3 possible forms of the reuqest URI %% @@ -430,3 +412,5 @@ tag([$:|Rest], Tag) -> tag([Chr|Rest], Tag) -> tag(Rest, [Chr|Tag]). +lowest_version()-> + "HTTP/0.9". diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index bd37066ff6..b3c9cbc46a 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2013. All Rights Reserved. +%% Copyright Ericsson AB 1997-2014. 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 @@ -123,7 +123,8 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> {_, Status} = httpd_manager:new_connection(Manager), - MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]}, + MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, + {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]}, State = #state{mod = Mod, manager = Manager, @@ -207,23 +208,15 @@ handle_info({Proto, Socket, Data}, set_new_data_size(cancel_request_timeout(State), NewDataSize) end, handle_http_msg(Result, NewState); - - {error, {uri_too_long, MaxSize}, Version} -> - NewModData = ModData#mod{http_version = Version}, - httpd_response:send_status(NewModData, 414, "URI too long"), - Reason = io_lib:format("Uri too long, max size is ~p~n", - [MaxSize]), - error_log(Reason, NewModData), - {stop, normal, State#state{response_sent = true, - mod = NewModData}}; - {error, {header_too_long, MaxSize}, Version} -> + {error, {too_long, MaxSize, ErrCode, ErrStr}, Version} -> NewModData = ModData#mod{http_version = Version}, - httpd_response:send_status(NewModData, 413, "Header too long"), - Reason = io_lib:format("Header too long, max size is ~p~n", - [MaxSize]), + httpd_response:send_status(NewModData, ErrCode, ErrStr), + Reason = io_lib:format("~p: ~p max size is ~p~n", + [ErrCode, ErrStr, MaxSize]), error_log(Reason, NewModData), {stop, normal, State#state{response_sent = true, mod = NewModData}}; + NewMFA -> http_transport:setopts(SockType, Socket, [{active, once}]), case NewDataSize of @@ -382,6 +375,11 @@ handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body}, 400, URI), Reason = io_lib:format("Malformed syntax in URI: ~p~n", [URI]), error_log(Reason, ModData), + {stop, normal, State#state{response_sent = true}}; + {error, {bad_version, Ver}} -> + httpd_response:send_status(ModData#mod{http_version = "HTTP/0.9"}, 400, Ver), + Reason = io_lib:format("Malformed syntax version: ~p~n", [Ver]), + error_log(Reason, ModData), {stop, normal, State#state{response_sent = true}} end; handle_http_msg({ChunkedHeaders, Body}, @@ -549,7 +547,8 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData, MaxHeaderSize = max_header_size(ModData#mod.config_db), MaxURISize = max_uri_size(ModData#mod.config_db), - MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]}, + MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, + {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]}, TmpState = State#state{mod = NewModData, mfa = MFA, max_keep_alive_request = decrease(Max), diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index dd081962cc..5499596bbd 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -17,11 +17,20 @@ %% %CopyrightEnd% {"%VSN%", [ - {"5.9.8", [{load_module, ftp, soft_purge, soft_purge, []}]}, + {"5.10", + [{load_module, httpd, soft_purge, soft_purge, []}, + {load_module, httpd_manager, soft_purge, soft_purge, []}, + {load_module, httpd_request, soft_purge, soft_purge, []}, + {load_module, httpd_request_handler, soft_purge, soft_purge, + []}]}, {<<"5\\..*">>,[{restart_application, inets}]} ], [ - {"5.9.8", [{load_module, ftp, soft_purge, soft_purge, []}]}, + {"5.10", + [{load_module, httpd, soft_purge, soft_purge, []}, + {load_module, httpd_manager, soft_purge, soft_purge, []}, + {load_module, httpd_request, soft_purge, soft_purge, []}, + {load_module, httpd_request_handler, soft_purge, soft_purge, []}]}, {<<"5\\..*">>,[{restart_application, inets}]} ] }. diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl index c5920a3968..d4a3f28f38 100644 --- a/lib/inets/test/http_format_SUITE.erl +++ b/lib/inets/test/http_format_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2013. All Rights Reserved. +%% Copyright Ericsson AB 2004-2014. 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 @@ -356,7 +356,10 @@ http_request(Config) when is_list(Config) -> "HTTP/1.1", {#http_request_h{host = "www.erlang.org", te = []}, ["te: ","host:www.erlang.org"]}, <<>>} = - parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead), + parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version, ?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]], + HttpHead), HttpHead1 = ["GET http://www.erlang.org HTTP/1.1" ++ [?CR], [?LF, ?CR, ?LF]], @@ -364,7 +367,9 @@ http_request(Config) when is_list(Config) -> "http://www.erlang.org", "HTTP/1.1", {#http_request_h{}, []}, <<>>} = - parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead1), + parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version, ?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead1), HttpHead2 = ["GET http://www.erlang.org HTTP/1.1" ++ @@ -373,7 +378,9 @@ http_request(Config) when is_list(Config) -> "http://www.erlang.org", "HTTP/1.1", {#http_request_h{}, []}, <<>>} = - parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead2), + parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version, ?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead2), %% Note the following body is not related to the headers above HttpBody = ["<HTML>\n<HEAD>\n<TITLE> dummy </TITLE>\n</HEAD>\n<BODY>\n", diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 5efd73dc6f..f150cab877 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -27,15 +27,14 @@ -include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). -include("inets_test_lib.hrl"). - +-include("http_internal.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). -define(URL_START, "http://"). -define(TLS_URL_START, "https://"). -define(NOT_IN_USE_PORT, 8997). --define(LF, $\n). --define(HTTP_MAX_HEADER_SIZE, 10240). + -record(sslsocket, {fd = nil, pid = nil}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -1234,7 +1233,10 @@ dummy_server_init(Caller, ip_comm, Inet, _) -> {ok, ListenSocket} = gen_tcp:listen(0, [Inet | BaseOpts]), {ok, Port} = inet:port(ListenSocket), Caller ! {port, Port}, - dummy_ipcomm_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + dummy_ipcomm_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE}, + {max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version,?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]]}, [], ListenSocket); dummy_server_init(Caller, ssl, Inet, SSLOptions) -> @@ -1246,7 +1248,10 @@ dummy_ssl_server_init(Caller, BaseOpts, Inet) -> {ok, ListenSocket} = ssl:listen(0, [Inet | BaseOpts]), {ok, {_, Port}} = ssl:sockname(ListenSocket), Caller ! {port, Port}, - dummy_ssl_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + dummy_ssl_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE}, + {max_method, ?HTTP_MAX_METHOD_STRING}, + {max_version,?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]]}, [], ListenSocket). dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) -> @@ -1323,10 +1328,16 @@ handle_request(Module, Function, Args, Socket) -> stop -> stop; <<>> -> - {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]}; + {httpd_request, parse, [[<<>>, [{max_uri, ?HTTP_MAX_URI_SIZE}, + {max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version,?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]]]}; Data -> handle_request(httpd_request, parse, - [Data |[?HTTP_MAX_HEADER_SIZE]], Socket) + [Data, [{max_uri, ?HTTP_MAX_URI_SIZE}, + {max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version,?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}]], Socket) end; NewMFA -> NewMFA diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index fbe65145dc..1fcc5f257e 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -32,9 +32,9 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [ - uri_too_long_414, + [uri_too_long_414, header_too_long_413, + entity_too_long, erl_script_nocache_opt, script_nocache, escaped_url_in_error_body, @@ -63,15 +63,13 @@ end_per_group(_GroupName, Config) -> %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> - tsp("init_per_suite -> entry with" - "~n Config: ~p", [Config]), inets_test_lib:stop_apps([inets]), inets_test_lib:start_apps([inets]), PrivDir = ?config(priv_dir, Config), DataDir = ?config(data_dir, Config), - + Dummy = -"<HTML> + "<HTML> <HEAD> <TITLE>/index.html</TITLE> </HEAD> @@ -79,7 +77,7 @@ init_per_suite(Config) -> DUMMY </BODY> </HTML>", - + DummyFile = filename:join([PrivDir,"dummy.html"]), CgiDir = filename:join(PrivDir, "cgi-bin"), ok = file:make_dir(CgiDir), @@ -116,8 +114,6 @@ DUMMY %% Description: Cleanup after the whole suite %%-------------------------------------------------------------------- end_per_suite(_Config) -> - tsp("end_per_suite -> entry with" - "~n Config: ~p", [_Config]), inets:stop(), ok. @@ -134,8 +130,6 @@ end_per_suite(_Config) -> %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_testcase(Case, Config) -> - tsp("init_per_testcase(~w) -> entry with" - "~n Config: ~p", [Case, Config]), Config. @@ -147,22 +141,18 @@ init_per_testcase(Case, Config) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(Case, Config) -> - tsp("end_per_testcase(~w) -> entry with" - "~n Config: ~p", [Case, Config]), +end_per_testcase(_Case, Config) -> Config. %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -uri_too_long_414(doc) -> - ["Test that too long uri's get 414 HTTP code"]; -uri_too_long_414(suite) -> - []; +uri_too_long_414() -> + [{doc, "Test that too long uri's get 414 HTTP code"}]. uri_too_long_414(Config) when is_list(Config) -> HttpdConf = ?config(httpd_conf, Config), - {ok, Pid} = inets:start(httpd, [{port, 0}, {max_uri_size, 10} + {ok, Pid} = inets:start(httpd, [{max_uri_size, 10} | HttpdConf]), Info = httpd:info(Pid), Port = proplists:get_value(port, Info), @@ -178,17 +168,12 @@ uri_too_long_414(Config) when is_list(Config) -> {version, "HTTP/0.9"}]), inets:stop(httpd, Pid). - -%%------------------------------------------------------------------------- %%------------------------------------------------------------------------- - -header_too_long_413(doc) -> - ["Test that too long headers's get 413 HTTP code"]; -header_too_long_413(suite) -> - []; +header_too_long_413() -> + [{doc,"Test that too long headers's get 413 HTTP code"}]. header_too_long_413(Config) when is_list(Config) -> HttpdConf = ?config(httpd_conf, Config), - {ok, Pid} = inets:start(httpd, [{port, 0}, {max_header_size, 10} + {ok, Pid} = inets:start(httpd, [{max_header_size, 10} | HttpdConf]), Info = httpd:info(Pid), Port = proplists:get_value(port, Info), @@ -202,8 +187,72 @@ header_too_long_413(Config) when is_list(Config) -> inets:stop(httpd, Pid). %%------------------------------------------------------------------------- + +entity_too_long() -> + [{doc, "Test that too long versions and method strings are rejected"}]. +entity_too_long(Config) when is_list(Config) -> + HttpdConf = ?config(httpd_conf, Config), + {ok, Pid} = inets:start(httpd, HttpdConf), + Info = httpd:info(Pid), + Port = proplists:get_value(port, Info), + Address = proplists:get_value(bind_address, Info), + + %% Not so long but wrong + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + "GET / " ++ + lists:duplicate(5, $A) ++ "\r\n\r\n", + [{statuscode, 400}, + %% Server will send lowest version + %% as it will not get to the + %% client version + %% before aborting + {version, "HTTP/0.9"}]), + + %% Too long + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + "GET / " ++ + lists:duplicate(100, $A) ++ "\r\n\r\n", + [{statuscode, 413}, + %% Server will send lowest version + %% as it will not get to the + %% client version + %% before aborting + {version, "HTTP/0.9"}]), + %% Not so long but wrong + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + lists:duplicate(5, $A) ++ " / " + "HTTP/1.1\r\n\r\n", + [{statuscode, 501}, + %% Server will send lowest version + %% as it will not get to the + %% client version + %% before aborting + {version, "HTTP/1.1"}]), + %% Too long + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + lists:duplicate(100, $A) ++ " / " + "HTTP/1.1\r\n\r\n", + [{statuscode, 413}, + %% Server will send lowest version + %% as it will not get to the + %% client version + %% before aborting + {version, "HTTP/0.9"}]), + inets:stop(httpd, Pid). + %%------------------------------------------------------------------------- +script_nocache() -> + [{doc,"Test nocache option for mod_cgi and mod_esi"}]. +script_nocache(Config) when is_list(Config) -> + Normal = {no_header, "cache-control"}, + NoCache = {header, "cache-control", "no-cache"}, + verify_script_nocache(Config, false, false, Normal, Normal), + verify_script_nocache(Config, true, false, NoCache, Normal), + verify_script_nocache(Config, false, true, Normal, NoCache), + verify_script_nocache(Config, true, true, NoCache, NoCache). + +%%------------------------------------------------------------------------- erl_script_nocache_opt(doc) -> ["Test that too long headers's get 413 HTTP code"]; erl_script_nocache_opt(suite) -> @@ -225,155 +274,49 @@ erl_script_nocache_opt(Config) when is_list(Config) -> inets:stop(httpd, Pid). %%------------------------------------------------------------------------- -%%------------------------------------------------------------------------- -script_nocache(doc) -> - ["Test nocache option for mod_cgi and mod_esi"]; -script_nocache(suite) -> - []; -script_nocache(Config) when is_list(Config) -> - Normal = {no_header, "cache-control"}, - NoCache = {header, "cache-control", "no-cache"}, - verify_script_nocache(Config, false, false, Normal, Normal), - verify_script_nocache(Config, true, false, NoCache, Normal), - verify_script_nocache(Config, false, true, Normal, NoCache), - verify_script_nocache(Config, true, true, NoCache, NoCache), - ok. -verify_script_nocache(Config, CgiNoCache, EsiNoCache, CgiOption, EsiOption) -> - HttpdConf = ?config(httpd_conf, Config), - CgiScript = ?config(cgi_printenv, Config), - CgiDir = ?config(cgi_dir, Config), - {ok, Pid} = inets:start(httpd, [{port, 0}, - {script_alias, - {"/cgi-bin/", CgiDir ++ "/"}}, - {script_nocache, CgiNoCache}, - {erl_script_alias, - {"/cgi-bin/erl", [httpd_example,io]}}, - {erl_script_nocache, EsiNoCache} - | HttpdConf]), - Info = httpd:info(Pid), - Port = proplists:get_value(port, Info), - Address = proplists:get_value(bind_address, Info), - ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), - "GET /cgi-bin/" ++ CgiScript ++ - " HTTP/1.0\r\n\r\n", - [{statuscode, 200}, - CgiOption, - {version, "HTTP/1.0"}]), - ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), - "GET /cgi-bin/erl/httpd_example:get " - "HTTP/1.0\r\n\r\n", - [{statuscode, 200}, - EsiOption, - {version, "HTTP/1.0"}]), - inets:stop(httpd, Pid). - - -%%------------------------------------------------------------------------- %%------------------------------------------------------------------------- -escaped_url_in_error_body(doc) -> - ["Test Url-encoding see OTP-8940"]; -escaped_url_in_error_body(suite) -> - []; -escaped_url_in_error_body(Config) when is_list(Config) -> - %% <CONDITIONAL-SKIP> - %% This skip is due to a problem on windows with long path's - %% If a path is too long file:open fails with, for example, eio. - %% Until that problem is fixed, we skip this case... - Skippable = [win32], - Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, - ?NON_PC_TC_MAYBE_SKIP(Config, Condition), - %% </CONDITIONAL-SKIP> - - tsp("escaped_url_in_error_body -> entry"), +escaped_url_in_error_body() -> + [{doc, "Test Url-encoding see OTP-8940"}]. +escaped_url_in_error_body(Config) when is_list(Config) -> HttpdConf = ?config(httpd_conf, Config), {ok, Pid} = inets:start(httpd, [{port, 0} | HttpdConf]), Info = httpd:info(Pid), Port = proplists:get_value(port, Info), - _Address = proplists:get_value(bind_address, Info), - - %% Request 1 - tss(1000), - tsp("escaped_url_in_error_body -> request 1"), URL1 = ?URL_START ++ integer_to_list(Port), - %% Make sure the server is ok, by making a request for a valid page - case httpc:request(get, {URL1 ++ "/dummy.html", []}, - [{url_encode, false}, - {version, "HTTP/1.0"}], - [{full_result, false}]) of - {ok, {200, _}} -> - %% Don't care about the the body, just that we get a ok response - ok; - {ok, {StatusCode1, Body1}} -> - tsf({unexpected_ok_1, StatusCode1, Body1}) - end, - - %% Request 2 - tss(1000), - tsp("escaped_url_in_error_body -> request 2"), - %% Make sure the server is ok, by making a request for a valid page - case httpc:request(get, {URL1 ++ "/dummy.html", []}, - [{url_encode, true}, - {version, "HTTP/1.0"}], - [{full_result, false}]) of - {ok, {200, _}} -> - %% Don't care about the the body, just that we get a ok response - ok; - {ok, {StatusCode2, Body2}} -> - tsf({unexpected_ok_2, StatusCode2, Body2}) - end, - - %% Request 3 - tss(1000), - tsp("escaped_url_in_error_body -> request 3"), + + %% Sanity check + {ok, {200, _}} = httpc:request(get, {URL1 ++ "/dummy.html", []}, + [{url_encode, false}, + {version, "HTTP/1.0"}], + [{full_result, false}]), + {ok, {200, _}} = httpc:request(get, {URL1 ++ "/dummy.html", []}, + [{url_encode, true}, + {version, "HTTP/1.0"}], + [{full_result, false}]), + %% Ask for a non-existing page(1) Path = "/<b>this_is_bold<b>", HTMLEncodedPath = http_util:html_encode(Path), URL2 = URL1 ++ Path, - case httpc:request(get, {URL2, []}, - [{url_encode, true}, - {version, "HTTP/1.0"}], - [{full_result, false}]) of - {ok, {404, Body3}} -> - case find_URL_path(string:tokens(Body3, " ")) of - HTMLEncodedPath -> - ok; - BadPath3 -> - tsf({unexpected_path_3, HTMLEncodedPath, BadPath3}) - end; - {ok, UnexpectedOK3} -> - tsf({unexpected_ok_3, UnexpectedOK3}) - end, + {ok, {404, Body3}} = httpc:request(get, {URL2, []}, + [{url_encode, true}, + {version, "HTTP/1.0"}], + [{full_result, false}]), - %% Request 4 - tss(1000), - tsp("escaped_url_in_error_body -> request 4"), - %% Ask for a non-existing page(2) - case httpc:request(get, {URL2, []}, - [{url_encode, false}, - {version, "HTTP/1.0"}], - [{full_result, false}]) of - {ok, {404, Body4}} -> - case find_URL_path(string:tokens(Body4, " ")) of - HTMLEncodedPath -> - ok; - BadPath4 -> - tsf({unexpected_path_4, HTMLEncodedPath, BadPath4}) - end; - {ok, UnexpectedOK4} -> - tsf({unexpected_ok_4, UnexpectedOK4}) - end, - tss(1000), - tsp("escaped_url_in_error_body -> stop inets"), - inets:stop(httpd, Pid), - tsp("escaped_url_in_error_body -> done"), - ok. + HTMLEncodedPath = find_URL_path(string:tokens(Body3, " ")), + {ok, {404, Body4}} = httpc:request(get, {URL2, []}, + [{url_encode, false}, + {version, "HTTP/1.0"}], + [{full_result, false}]), + + HTMLEncodedPath = find_URL_path(string:tokens(Body4, " ")), + inets:stop(httpd, Pid). %%------------------------------------------------------------------------- -%%------------------------------------------------------------------------- keep_alive_timeout(doc) -> ["Test the keep_alive_timeout option"]; @@ -393,7 +336,6 @@ keep_alive_timeout(Config) when is_list(Config) -> inets:stop(httpd, Pid). %%------------------------------------------------------------------------- -%%------------------------------------------------------------------------- script_timeout(doc) -> ["Test the httpd script_timeout option"]; @@ -423,12 +365,10 @@ verify_script_timeout(Config, ScriptTimeout, StatusCode) -> {version, "HTTP/1.0"}]), inets:stop(httpd, Pid). - -%%------------------------------------------------------------------------- %%------------------------------------------------------------------------- -slowdose(doc) -> - ["Testing minimum bytes per second option"]; +slowdose() -> + [{doc, "Testing minimum bytes per second option"}]. slowdose(Config) when is_list(Config) -> HttpdConf = ?config(httpd_conf, Config), {ok, Pid} = inets:start(httpd, [{port, 0}, {minimum_bytes_per_second, 200}|HttpdConf]), @@ -439,6 +379,40 @@ slowdose(Config) when is_list(Config) -> after 6000 -> {error, closed} = gen_tcp:send(Socket, "Hey") end. + +%%------------------------------------------------------------------------- +%% Internal functions +%%------------------------------------------------------------------------- + +verify_script_nocache(Config, CgiNoCache, EsiNoCache, CgiOption, EsiOption) -> + HttpdConf = ?config(httpd_conf, Config), + CgiScript = ?config(cgi_printenv, Config), + CgiDir = ?config(cgi_dir, Config), + {ok, Pid} = inets:start(httpd, [{port, 0}, + {script_alias, + {"/cgi-bin/", CgiDir ++ "/"}}, + {script_nocache, CgiNoCache}, + {erl_script_alias, + {"/cgi-bin/erl", [httpd_example,io]}}, + {erl_script_nocache, EsiNoCache} + | HttpdConf]), + Info = httpd:info(Pid), + Port = proplists:get_value(port, Info), + Address = proplists:get_value(bind_address, Info), + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + "GET /cgi-bin/" ++ CgiScript ++ + " HTTP/1.0\r\n\r\n", + [{statuscode, 200}, + CgiOption, + {version, "HTTP/1.0"}]), + ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(), + "GET /cgi-bin/erl/httpd_example:get " + "HTTP/1.0\r\n\r\n", + [{statuscode, 200}, + EsiOption, + {version, "HTTP/1.0"}]), + inets:stop(httpd, Pid). + find_URL_path([]) -> ""; find_URL_path(["URL", URL | _]) -> @@ -446,21 +420,6 @@ find_URL_path(["URL", URL | _]) -> find_URL_path([_ | Rest]) -> find_URL_path(Rest). - -tsp(F) -> - inets_test_lib:tsp(F). -tsp(F, A) -> - inets_test_lib:tsp(F, A). - -tsf(Reason) -> - inets_test_lib:tsf(Reason). - -tss(Time) -> - inets_test_lib:tss(Time). - - - - skip(Reason) -> {skip, Reason}. diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index ed466fd727..36a5bb9e71 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -103,7 +103,7 @@ verify_request(SocketType, Host, Port, TranspOpts0, Node, RequestStr, Options, T try inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts) of {ok, Socket} -> - SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), + ok = inets_test_lib:send(SocketType, Socket, RequestStr), State = case inets_regexp:match(RequestStr, "printenv") of nomatch -> #state{}; diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index cbcf0362c9..bbd86c3eb3 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,6 +18,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.10 +INETS_VSN = 5.10.1 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index a83e55ac62..fe2fd67d71 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -1208,7 +1208,14 @@ handle_info(Done = #loader_done{worker_pid=WPid, table_name=Tab}, State0) -> {value,{_,Worker}} = lists:keysearch(WPid,1,get_loaders(State0)), add_loader(Tab,Worker,State1); _ -> - State1 + DelState = State1#state{late_loader_queue=gb_trees:delete_any(Tab, LateQueue0)}, + case ?catch_val({Tab, storage_type}) of + ram_copies -> + cast({disc_load, Tab, ram_only}), + DelState; + _ -> + DelState + end end end, State3 = opt_start_worker(State2), diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl index 81b435c6dc..c3846b00c0 100644 --- a/lib/mnesia/src/mnesia_locker.erl +++ b/lib/mnesia/src/mnesia_locker.erl @@ -131,9 +131,14 @@ send_release_tid(Nodes, Tid) -> receive_release_tid_acc([Node | Nodes], Tid) -> receive {?MODULE, Node, {tid_released, Tid}} -> - receive_release_tid_acc(Nodes, Tid); - {mnesia_down, Node} -> receive_release_tid_acc(Nodes, Tid) + after 0 -> + receive + {?MODULE, Node, {tid_released, Tid}} -> + receive_release_tid_acc(Nodes, Tid); + {mnesia_down, Node} -> + receive_release_tid_acc(Nodes, Tid) + end end; receive_release_tid_acc([], _Tid) -> ok. diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl index 5f46840ae9..9886754710 100644 --- a/lib/mnesia/test/mnesia_qlc_test.erl +++ b/lib/mnesia/test/mnesia_qlc_test.erl @@ -264,7 +264,7 @@ atomic_eval(Config) -> ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])), QC1 = ok(fun() -> qlc:cursor(Q1) end, []), - ?match({'EXIT', _}, qlc:next_answers(QC1)), + ?match({'EXIT', _}, (catch qlc:next_answers(QC1))), ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])), ?verify_mnesia(Ns, []). diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl index 281634c239..94a195f01f 100644 --- a/lib/mnesia/test/mnesia_test_lib.hrl +++ b/lib/mnesia/test/mnesia_test_lib.hrl @@ -46,15 +46,32 @@ -define(match(ExpectedRes,Expr), fun() -> - AcTuAlReS = (catch (Expr)), - case AcTuAlReS of - ExpectedRes -> - ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]), - {success,AcTuAlReS}; - _ -> - ?error("Not Matching Actual result was:~n ~p~n", - [AcTuAlReS]), - {fail,AcTuAlReS} + try Expr of + _AR_0 = ExpectedRes -> + ?verbose("ok, ~n Result as expected:~p~n",[_AR_0]), + {success,_AR_0}; + _AR_0 -> + ?error("Not Matching Actual result was:~n ~p~n",[_AR_0]), + {fail,_AR_0} + catch + exit:{aborted, _ER_1} when + element(1, _ER_1) =:= node_not_running; + element(1, _ER_1) =:= bad_commit; + element(1, _ER_1) =:= cyclic -> + %% Need to re-raise these to restart transaction + erlang:raise(exit, {aborted, _ER_1}, erlang:get_stacktrace()); + exit:_AR_1 -> + case fun(_AR_EXIT_) -> {'EXIT', _AR_EXIT_} end(_AR_1) of + _AR_2 = ExpectedRes -> + ?verbose("ok, ~n Result as expected:~p~n",[_AR_2]), + {success,_AR_2}; + _AR_2 -> + ?error("Not Matching Actual result was:~n ~p~n", [_AR_2]), + {fail,_AR_2} + end; + _:_AR_1 -> + ?error("Not Matching Actual result was:~n ~p~n", [_AR_1]), + {fail,_AR_1} end end()). diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl index 157e441b27..237984978e 100644 --- a/lib/mnesia/test/mnesia_trans_access_test.erl +++ b/lib/mnesia/test/mnesia_trans_access_test.erl @@ -677,7 +677,7 @@ check_res(sync_dirty, Res) when is_list(Res) -> check_res(ets, Res) when is_list(Res) -> Res; check_res(Type,Res) -> - ?match(bug,{Type,Res}). + ?match({bug, bug},{Type,Res}). read_op(Oid) -> case lists:reverse(mnesia:read(Oid)) of @@ -1118,10 +1118,7 @@ create_live_table_index(Config, Storage) -> ValPos = 3, mnesia:dirty_write({Tab, 1, 2}), - Fun = fun() -> - ?match(ok, mnesia:write({Tab, 2, 2})), - ok - end, + Fun = fun() -> mnesia:write({Tab, 2, 2}) end, ?match({atomic, ok}, mnesia:transaction(Fun)), ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)), IRead = fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos)) end, diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 59fe5b5670..7757dfea53 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. 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 @@ -409,7 +409,7 @@ handle_info({refresh, Min, Min}, State = #state{grid=Grid}) -> wxListCtrl:refreshItem(Grid, Min), %% Avoid assert in wx below if Max is 0 {noreply, State}; handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> - wxListCtrl:refreshItems(Grid, Min, Max), + Max > 0 andalso wxListCtrl:refreshItems(Grid, Min, Max), {noreply, State}; handle_info(refresh_interval, State = #state{pid=Pid}) -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index ced26f7119..03ca1bf9c1 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -112,7 +112,8 @@ setup(#state{frame = Frame} = State) -> observer_lib:create_menus(DefMenus, MenuBar, default), wxFrame:setMenuBar(Frame, MenuBar), - StatusBar = wxFrame:createStatusBar(Frame, []), + StatusBar = wxStatusBar:new(Frame), + wxFrame:setStatusBar(Frame, StatusBar), wxFrame:setTitle(Frame, atom_to_list(node())), wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), @@ -388,6 +389,7 @@ handle_info(_Info, State) -> terminate(_Reason, #state{frame = Frame}) -> wxFrame:destroy(Frame), + wx:destroy(), ok. code_change(_, _, State) -> diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index af07165456..5cf719acb1 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -45,7 +45,7 @@ all() -> groups() -> [{gui, [], [basic - %% , process_win, table_win + , process_win, table_win ] }]. @@ -107,6 +107,10 @@ appup_file(Config) when is_list(Config) -> basic(suite) -> []; basic(doc) -> [""]; basic(Config) when is_list(Config) -> + timer:send_after(100, "foobar"), %% Otherwise the timer sever gets added to procs + ProcsBefore = processes(), + NumProcsBefore = length(ProcsBefore), + ok = observer:start(), Notebook = setup_whitebox_testing(), @@ -116,11 +120,11 @@ basic(Config) when is_list(Config) -> 0 = wxNotebook:getSelection(Notebook), timer:sleep(500), Check = fun(N, TestMore) -> - ok = wxNotebook:advanceSelection(Notebook), TestMore andalso test_page(wxNotebook:getPageText(Notebook, N), wxNotebook:getCurrentPage(Notebook)), - timer:sleep(200) + timer:sleep(200), + ok = wxNotebook:advanceSelection(Notebook) end, %% Just verify that we can toogle trough all pages [_|_] = [Check(N, false) || N <- lists:seq(1, Count)], @@ -128,9 +132,22 @@ basic(Config) when is_list(Config) -> Frame = get_top_level_parent(Notebook), {W,H} = wxWindow:getSize(Frame), wxWindow:setSize(Frame, W+10, H+10), - [_|_] = [Check(N, true) || N <- lists:seq(1, Count)], - - ok = observer:stop(). + [_|_] = [Check(N, true) || N <- lists:seq(0, Count-1)], + + ok = observer:stop(), + timer:sleep(2000), %% stop is async + ProcsAfter = processes(), + NumProcsAfter = length(ProcsAfter), + if NumProcsAfter=/=NumProcsBefore -> + ct:log("Before but not after:~n~p~n", + [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]), + ct:log("After but not before:~n~p~n", + [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]), + ct:fail("leaking processes"); + true -> + ok + end, + ok. test_page("Load Charts" ++ _, _Window) -> %% Just let it display some info and hopefully it doesn't crash @@ -163,8 +180,11 @@ test_page("Processes" ++ _, _Window) -> timer:sleep(1000), %% Give it time to refresh ok; -test_page("Table" ++ _, _Window) -> +test_page(_Title = "Table" ++ _, _Window) -> Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)], + Table = lists:nth(3, Tables), + ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]), + Active = get_active(), Active ! refresh_interval, ChangeSort = fun(N) -> @@ -174,8 +194,6 @@ test_page("Table" ++ _, _Window) -> end, [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]], timer:sleep(1000), - Table = lists:nth(3, Tables), - ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]), Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}}, Active ! Focus, Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}}, @@ -226,14 +244,12 @@ table_win(Config) when is_list(Config) -> %% Modal can not test edit.. %% TPid = wx_object:get_pid(TObj), %% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}}, - timer:sleep(2000), + timer:sleep(3000), wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}}, observer:stop(), ok. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% get_top_level_parent(Window) -> diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index bce02966ae..84d5e5c86e 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2004</year><year>2013</year> + <year>2004</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -29,6 +29,36 @@ <file>notes.xml</file> </header> +<section><title>Ssh 3.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed timeout bug in ssh:connect.</p> + <p> + Own Id: OTP-11908</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Option <c>max_sessions</c> added to + <c>ssh:daemon/{2,3}</c>. This option, if set, limits the + number of simultaneous connections accepted by the + daemon.</p> + <p> + Own Id: OTP-11885</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 3.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 1917c95f5a..42eb2167e0 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,9 +19,13 @@ {"%VSN%", [ + {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []}, + {load_module, ssh_acceptor, soft_purge, soft_purge, []}]}, {<<".*">>, [{restart_application, ssh}]} ], [ + {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []}, + {load_module, ssh_acceptor, soft_purge, soft_purge, []}]}, {<<".*">>, [{restart_application, ssh}]} ] }. diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 37a307d783..ba38c1da40 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. 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 @@ -49,6 +49,7 @@ all() -> server_userpassword_option, double_close, ssh_connect_timeout, + ssh_connect_arg4_timeout, {group, hardening_tests} ]. diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index c8cac3e852..40ed27d8f5 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 3.1 +SSH_VSN = 3.0.2 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml index 76137e3dee..b37f7fd7fd 100644 --- a/lib/stdlib/doc/src/maps.xml +++ b/lib/stdlib/doc/src/maps.xml @@ -108,6 +108,26 @@ </func> <func> + <name name="get" arity="3"/> + <fsummary></fsummary> + <desc> + <p> + Returns the value <c><anno>Value</anno></c> associated with <c><anno>Key</anno></c> if + <c><anno>Map</anno></c> contains <c><anno>Key</anno></c>. + If no value is associated with <c><anno>Key</anno></c> then returns <c><anno>Default</anno></c>. + </p> + <p>Example:</p> + <code type="none"> +> Map = #{ key1 => val1, key2 => val2 }. +#{key1 => val1,key2 => val2} +> maps:get(key1, Map, "Default value"). +val1 +> maps:get(key3, Map, "Default value"). +"Default value"</code> + </desc> + </func> + + <func> <name name="is_key" arity="2"/> <fsummary></fsummary> <desc> diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl index fd6d56fa47..4ef1638e6d 100644 --- a/lib/stdlib/src/maps.erl +++ b/lib/stdlib/src/maps.erl @@ -23,7 +23,8 @@ fold/3, map/2, size/1, - without/2 + without/2, + get/3 ]). @@ -142,6 +143,21 @@ values(_) -> erlang:nif_error(undef). %%% End of BIFs +-spec get(Key, Map, Default) -> Value | Default when + Key :: term(), + Map :: map(), + Value :: term(), + Default :: term(). + +get(Key, Map, Default) -> + case maps:find(Key, Map) of + {ok, Value} -> + Value; + error -> + Default + end. + + -spec fold(Fun,Init,Map) -> Acc when Fun :: fun((K, V, AccIn) -> AccOut), Init :: term(), diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile index 39f6ce423a..a271229c59 100644 --- a/lib/stdlib/test/Makefile +++ b/lib/stdlib/test/Makefile @@ -85,7 +85,8 @@ MODULES= \ zip_SUITE \ random_unicode_list \ random_iolist \ - error_logger_forwarder + error_logger_forwarder \ + maps_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/stdlib/test/maps_SUITE.erl b/lib/stdlib/test/maps_SUITE.erl new file mode 100644 index 0000000000..c826ee731a --- /dev/null +++ b/lib/stdlib/test/maps_SUITE.erl @@ -0,0 +1,69 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2014. 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% +%% +%%%---------------------------------------------------------------- +%%% Purpose: Test suite for the 'maps' module. +%%%----------------------------------------------------------------- + +-module(maps_SUITE). + +-include_lib("test_server/include/test_server.hrl"). + +% Default timetrap timeout (set in init_per_testcase). +% This should be set relatively high (10-15 times the expected +% max testcasetime). +-define(default_timeout, ?t:minutes(4)). + +% Test server specific exports +-export([all/0]). +-export([suite/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([get3/1]). + +suite() -> + [{ct_hooks, [ts_install_cth]}]. + +all() -> + [get3]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_Case, Config) -> + ?line Dog=test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. + +end_per_testcase(_Case, Config) -> + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +get3(Config) when is_list(Config) -> + Map = #{ key1 => value1, key2 => value2 }, + DefaultValue = "Default value", + ?line value1 = maps:get(key1, Map, DefaultValue), + ?line value2 = maps:get(key2, Map, DefaultValue), + ?line DefaultValue = maps:get(key3, Map, DefaultValue), + ok. |