1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2013-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Tests of transport_opt() length_errors. 23%% 24 25-module(diameter_length_SUITE). 26 27-export([suite/0, 28 all/0, 29 groups/0, 30 init_per_suite/1, 31 end_per_suite/1, 32 init_per_group/2, 33 end_per_group/2, 34 init_per_testcase/2, 35 end_per_testcase/2]). 36 37%% testcases 38-export([start/1, 39 send/1, 40 stop/1]). 41 42%% diameter callbacks 43-export([peer_up/3, 44 peer_down/3, 45 pick_peer/5, 46 prepare_request/4, 47 handle_answer/5, 48 handle_error/5, 49 handle_request/3]). 50 51-include("diameter.hrl"). 52-include("diameter_gen_base_rfc3588.hrl"). 53 54%% =========================================================================== 55 56-define(util, diameter_util). 57 58-define(CLIENT, "CLIENT"). 59-define(SERVER, "SERVER"). 60-define(REALM, "erlang.org"). 61-define(HOST(Host, Realm), Host ++ [$.|Realm]). 62-define(DICT, diameter_gen_base_rfc3588). 63 64%% Config for diameter:start_service/2. 65-define(SERVICE(Name), 66 [{'Origin-Host', Name ++ "." ++ ?REALM}, 67 {'Origin-Realm', ?REALM}, 68 {'Host-IP-Address', [{127,0,0,1}]}, 69 {'Vendor-Id', 12345}, 70 {'Product-Name', "OTP/diameter"}, 71 {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, 72 {application, [{dictionary, ?DICT}, 73 {module, ?MODULE}, 74 {answer_errors, callback}]}]). 75 76-define(SUCCESS, 77 ?'DIAMETER_BASE_RESULT-CODE_SUCCESS'). 78-define(MISSING_AVP, 79 ?'DIAMETER_BASE_RESULT-CODE_MISSING_AVP'). 80-define(INVALID_MESSAGE_LENGTH, 81 ?'DIAMETER_BASE_RESULT-CODE_INVALID_MESSAGE_LENGTH'). 82 83-define(LOGOUT, 84 ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). 85 86-define(GROUPS, [exit, handle, discard]). 87 88-define(L, atom_to_list). 89 90%% =========================================================================== 91 92suite() -> 93 [{timetrap, {seconds, 60}}]. 94 95all() -> 96 [{group, G} || G <- ?GROUPS]. 97 98groups() -> 99 [{G, [], [start, send, stop]} || G <- ?GROUPS]. 100 101init_per_suite(Config) -> 102 ok = diameter:start(), 103 Config. 104 105end_per_suite(_Config) -> 106 ok = diameter:stop(). 107 108init_per_group(Group, Config) -> 109 [{group, Group} | Config]. 110 111end_per_group(_, _) -> 112 ok. 113 114init_per_testcase(_Name, Config) -> 115 Config. 116 117end_per_testcase(_, _) -> 118 ok. 119 120origin(exit) -> 0; 121origin(handle) -> 1; 122origin(discard) -> 2; 123 124origin(0) -> exit; 125origin(1) -> handle; 126origin(2) -> discard. 127 128%% =========================================================================== 129 130%% start/1 131 132start(Config) -> 133 Group = proplists:get_value(group, Config), 134 ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group))), 135 ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)), 136 LRef = ?util:listen(?SERVER, 137 tcp, 138 [{length_errors, Group}]), 139 ?util:connect(?CLIENT, 140 tcp, 141 LRef, 142 [{capabilities, [{'Origin-State-Id', origin(Group)}]}]). 143 144%% stop/1 145 146stop(_Config) -> 147 ok = diameter:remove_transport(?CLIENT, true), 148 ok = diameter:remove_transport(?SERVER, true), 149 ok = diameter:stop_service(?SERVER), 150 ok = diameter:stop_service(?CLIENT). 151 152%% send/1 153 154%% Server transport exits on messages of insuffient length. 155send(exit) -> 156 %% Transport exit is followed by failover but there's only one 157 %% transport to choose from. 158 {error, failover} = call(4); 159 160%% Server transport receives messages of insufficient length. 161send(handle) -> 162 %% Message Length too large: diameter_tcp flushes the request 163 %% when no additional bytes arrive. 164 #diameter_base_STA{'Result-Code' = ?INVALID_MESSAGE_LENGTH} 165 = call(4), 166 %% Another request answered as it should. 167 #diameter_base_STA{'Result-Code' = ?SUCCESS} 168 = call(0), 169 %% Message Length conveniently small: the trailing optional 170 %% Origin-State-Id isn't included in the received request. 171 #diameter_base_STA{'Result-Code' = ?SUCCESS} 172 = call(-12), 173 %% Server receives Origin-State-Id AVP as the first 12 bytes of 174 %% the next request: AVP <<Code:32, Flags:8, Len:24, Data:32>> is 175 %% interpreted as header <<Version:8, Len:24, Flags:8, Code:24, 176 %% ApplId: 32>>. In particular, the AVP Length 12 = 00001100 is 177 %% interpreted as Command Flags, so R=0 and the request is 178 %% interpreted as an unsolicited answer. Increase Message Length 179 %% to have the server receive all bytes sent thusfar. 180 {error, timeout} 181 = call(12), 182 %% Another request answered as it should. 183 #diameter_base_STA{'Result-Code' = ?SUCCESS} 184 = call(0), 185 %% Shorten Message Length so much that that the server doesn't 186 %% receive the required Termination-Cause AVP. 187 #diameter_base_STA{'Result-Code' = ?MISSING_AVP} 188 = call(-24); 189 190%% Server transport discards message of insufficient length. 191send(discard) -> 192 %% First request times out when the server discards it but a 193 %% second succeeds since the transport remains up. 194 {error, timeout} 195 = call(4), 196 #diameter_base_STA{'Result-Code' = ?SUCCESS} 197 = call(0); 198 199send(Config) -> 200 send(proplists:get_value(group, Config)). 201 202%% =========================================================================== 203 204call(Delta) -> 205 diameter:call(?CLIENT, 206 ?DICT, 207 #diameter_base_STR 208 {'Termination-Cause' = ?LOGOUT, 209 'Auth-Application-Id' = ?DIAMETER_APP_ID_COMMON, 210 'Origin-State-Id' = [7]}, 211 [{extra, [Delta]}]). 212 213%% =========================================================================== 214%% diameter callbacks 215 216%% peer_up/3 217 218peer_up(_SvcName, _Peer, State) -> 219 State. 220 221%% peer_down/3 222 223peer_down(_SvcName, _Peer, State) -> 224 State. 225 226%% pick_peer/5 227 228pick_peer([Peer], _, ?CLIENT, _State, _Delta) -> 229 {ok, Peer}. 230 231%% prepare_request/4 232 233prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Delta) -> 234 {send, resize(Delta, prepare(Pkt, Caps))}. 235 236prepare(#diameter_packet{msg = Req0} = Pkt, Caps) -> 237 #diameter_caps{origin_host = {OH, _}, 238 origin_realm = {OR, DR}} 239 = Caps, 240 Req = Req0#diameter_base_STR{'Session-Id' = diameter:session_id(OH), 241 'Origin-Host' = OH, 242 'Origin-Realm' = OR, 243 'Destination-Realm' = DR}, 244 diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req}). 245 246resize(0, Pkt) -> 247 Pkt; 248resize(Delta, #diameter_packet{bin = Bin} = Pkt) -> 249 Pkt#diameter_packet{bin = resize(Delta, Bin)}; 250 251resize(Delta, <<V, Len:24, T/binary>>) -> 252 <<V, (Len + Delta):24, T/binary>>. 253 254%% handle_answer/5 255 256handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Delta) -> 257 Pkt#diameter_packet.msg. 258 259%% handle_error/5 260 261handle_error(Reason, _Req, ?CLIENT, _Peer, _Delta) -> 262 {error, Reason}. 263 264%% handle_request/3 265 266handle_request(Pkt, ?SERVER, {_Ref, Caps}) -> 267 #diameter_caps{origin_host = {OH, _}, 268 origin_realm = {OR, _}, 269 origin_state_id = {_,[Id]}} 270 = Caps, 271 answer(origin(Id), 272 Pkt, 273 #diameter_base_STA{'Result-Code' = ?SUCCESS, 274 'Session-Id' = diameter:session_id(OH), 275 'Origin-Host' = OH, 276 'Origin-Realm' = OR}). 277 278answer(Group, #diameter_packet{errors = Es}, Ans) -> 279 answer(Group, Es, Ans); 280 281%% No errors: just answer. 282answer(_, [], Ans) -> 283 {reply, Ans}; 284 285%% Otherwise an invalid length should only reach the callback if 286%% length_errors = handle. 287answer(Group, [RC|_], Ans) 288 when RC == ?INVALID_MESSAGE_LENGTH, Group == handle; 289 RC /= ?INVALID_MESSAGE_LENGTH -> 290 {reply, Ans}. 291