Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 1 | /* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> |
| 2 | * Author: Stefan Sperling <ssperling@sysmocom.de> |
| 3 | * All Rights Reserved |
| 4 | * |
| 5 | * Released under the terms of GNU General Public License, Version 2 or |
| 6 | * (at your option) any later version. |
Harald Welte | 34b5a95 | 2019-05-27 11:54:11 +0200 | [diff] [blame] | 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 9 | */ |
| 10 | |
| 11 | /* |
| 12 | * This module provides functions which implement IPA protocol tests. |
| 13 | * There are no test cases defined here. Instead, there are test functions which |
| 14 | * can be called by test cases in our test suites. Each such function will create |
| 15 | * an IPA_CT component and execute a test on this component, and expects destination |
| 16 | * IP address, TCP port, and connection mode parameters. Depending on the connection |
| 17 | * mode, a test function will either connect to an IPA server on the specified |
| 18 | * address and port, or listen for an IPA client on the specified address and port. |
| 19 | * This allows IPA tests to be run against any IPA speakers used by various test suites. |
| 20 | */ |
| 21 | |
| 22 | module IPA_Testing { |
| 23 | |
| 24 | import from IPL4asp_Types all; |
| 25 | import from IPL4asp_PortType all; |
| 26 | import from IPA_Types all; |
| 27 | import from Osmocom_Types all; |
| 28 | |
| 29 | type enumerated IPA_ConnectionMode { |
| 30 | CONNECT_TO_SERVER, |
| 31 | LISTEN_FOR_CLIENT |
| 32 | }; |
| 33 | |
| 34 | /* Encoded IPA messages (network byte order) */ |
| 35 | const octetstring ipa_msg_ping := '0001FE00'O; |
| 36 | const octetstring ipa_msg_pong := '0001FE01'O; |
Stefan Sperling | aa1e60f | 2018-10-15 16:34:07 +0200 | [diff] [blame] | 37 | const octetstring ipa_msg_id_req_hdr := '0007FE'O; |
| 38 | const octetstring ipa_msg_id_req_payload := '04010801070102'O; |
Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 39 | |
| 40 | /* A component which represents the system on which the IPA speaker is running. */ |
| 41 | type component system_CT { |
| 42 | port IPL4asp_PT IPL4; |
| 43 | } |
| 44 | |
| 45 | /* Main component provided by this module. */ |
| 46 | type component IPA_CT { |
| 47 | port IPL4asp_PT IPL4; |
| 48 | timer g_Tguard; |
| 49 | } |
| 50 | |
| 51 | /* This guard timer prevents us from waiting too long if the IPA TCP connection hangs. */ |
| 52 | private altstep as_Tguard() runs on IPA_CT { |
| 53 | [] g_Tguard.timeout { |
| 54 | setverdict(fail, "Tguard timeout"); |
| 55 | mtc.stop; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | /* Send an encoded IPA message across an IPA TCP connection. */ |
| 60 | private function f_send_ipa_data(charstring ipa_ip, integer ipa_tcp_port, ConnectionId connId, |
| 61 | octetstring data) runs on IPA_CT { |
| 62 | var IPL4asp_Types.Result res; |
| 63 | var ASP_SendTo asp := { |
| 64 | connId := connId, |
| 65 | remName := ipa_ip, |
| 66 | remPort := ipa_tcp_port, |
| 67 | proto := {tcp := {}}, |
| 68 | msg := data |
| 69 | }; |
| 70 | IPL4.send(asp); |
| 71 | } |
| 72 | |
| 73 | /* Match an incoming IPA message. */ |
| 74 | private template ASP_RecvFrom t_recvfrom(template octetstring msg) := { |
| 75 | connId := ?, |
| 76 | remName := ?, |
| 77 | remPort := ?, |
| 78 | locName := ?, |
| 79 | locPort := ?, |
| 80 | proto := {tcp := {}}, |
| 81 | userData := ?, |
| 82 | msg := msg |
| 83 | } |
| 84 | |
| 85 | /* Perform set up steps for a test function. */ |
| 86 | private function f_init(charstring ipa_ip, integer ipa_tcp_port, |
| 87 | IPA_ConnectionMode conmode) runs on IPA_CT return ConnectionId { |
| 88 | var IPL4asp_Types.Result res; |
| 89 | var ConnectionId connId; |
| 90 | |
| 91 | map(self:IPL4, system:IPL4); |
| 92 | if (conmode == CONNECT_TO_SERVER) { |
| 93 | /* Create an IPA connection over TCP. */ |
| 94 | res := IPL4asp_PortType.f_IPL4_connect(IPL4, ipa_ip, ipa_tcp_port, "", -1, 0, {tcp := {}}); |
| 95 | if (not ispresent(res.connId)) { |
| 96 | setverdict(fail, "Could not connect IPA socket to ", ipa_ip, " port ", |
| 97 | ipa_tcp_port, "; check your configuration"); |
| 98 | mtc.stop; |
| 99 | } |
| 100 | } else { |
| 101 | /* Listen for an incoming IPA connection on TCP. */ |
| 102 | res := IPL4asp_PortType.f_IPL4_listen(IPL4, ipa_ip, ipa_tcp_port, {tcp := {}}); |
| 103 | if (not ispresent(res.connId)) { |
| 104 | setverdict(fail, "Could not listen on address ", ipa_ip, " port ", |
| 105 | ipa_tcp_port, "; check your configuration"); |
| 106 | mtc.stop; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | /* |
| 111 | * Activate guard timer. When changing the timeout value, keep in mind |
| 112 | * that test functions below may wait for some amount of time, which |
| 113 | * this guard timer should always exceed to avoid spurious failures. |
| 114 | */ |
| 115 | g_Tguard.start(60.0); |
| 116 | activate(as_Tguard()); |
| 117 | |
| 118 | return res.connId; |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | * Individual test case implementations. |
| 123 | */ |
| 124 | |
| 125 | private function f_send_chopped_ipa_msg(charstring ipa_ip, integer ipa_tcp_port, ConnectionId connId, |
| 126 | octetstring msg) runs on IPA_CT { |
| 127 | const float delay := 6.0; |
| 128 | for (var integer i := 0; i < lengthof(msg); i := i + 1) { |
| 129 | log("sending byte ", msg[i]); |
| 130 | f_send_ipa_data(ipa_ip, ipa_tcp_port, connId, msg[i]); |
| 131 | f_sleep(delay); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | /* Send a ping message one byte at a time, waiting for TCP buffer to flush between each byte. */ |
| 136 | private function f_TC_chopped_ipa_ping(charstring ipa_ip, integer ipa_tcp_port, |
| 137 | IPA_ConnectionMode conmode) runs on IPA_CT system system_CT { |
| 138 | var ConnectionId connId; |
| 139 | var ASP_RecvFrom asp_rx; |
| 140 | |
| 141 | connId := f_init(ipa_ip, ipa_tcp_port, conmode); |
| 142 | |
| 143 | if (conmode == CONNECT_TO_SERVER) { |
| 144 | f_send_chopped_ipa_msg(ipa_ip, ipa_tcp_port, connId, ipa_msg_ping); |
| 145 | } else { |
Stefan Sperling | 0ec1c26 | 2018-10-15 15:12:52 +0200 | [diff] [blame] | 146 | var PortEvent port_evt; |
| 147 | IPL4.receive(PortEvent:{connOpened := ?}) -> value port_evt { |
| 148 | var ConnectionOpenedEvent conn := port_evt.connOpened; |
| 149 | f_send_chopped_ipa_msg(conn.remName, conn.remPort, conn.connId, ipa_msg_ping); |
Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 150 | } |
| 151 | } |
| 152 | |
| 153 | /* Expect a pong response. */ |
| 154 | alt { |
| 155 | [] IPL4.receive(t_recvfrom(ipa_msg_pong)) -> value asp_rx { |
| 156 | log("received pong from ", asp_rx.remName, " port ", asp_rx.remPort, ": ", asp_rx.msg); |
| 157 | setverdict(pass); |
| 158 | } |
| 159 | [] IPL4.receive { |
| 160 | repeat; |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
Stefan Sperling | aa1e60f | 2018-10-15 16:34:07 +0200 | [diff] [blame] | 165 | /* Send a complete IPA "ID REQ" message header in one piece, and then send the payload one byte at a time, |
| 166 | * waiting for TCP buffer to flush between each byte. */ |
| 167 | private function f_TC_chopped_ipa_payload(charstring ipa_ip, integer ipa_tcp_port, |
| 168 | IPA_ConnectionMode conmode) runs on IPA_CT system system_CT { |
| 169 | var ConnectionId connId; |
| 170 | var ASP_RecvFrom asp_rx; |
| 171 | |
| 172 | connId := f_init(ipa_ip, ipa_tcp_port, conmode); |
| 173 | |
| 174 | if (conmode == CONNECT_TO_SERVER) { |
| 175 | var PortEvent port_evt; |
| 176 | f_send_ipa_data(ipa_ip, ipa_tcp_port, connId, ipa_msg_id_req_hdr); |
| 177 | f_send_chopped_ipa_msg(ipa_ip, ipa_tcp_port, connId, ipa_msg_id_req_payload); |
| 178 | /* Server will close the connection upon receiving an ID REQ. */ |
| 179 | alt { |
| 180 | [] IPL4.receive(PortEvent:{connClosed := ?}) -> value port_evt { |
| 181 | if (port_evt.connClosed.connId == connId) { |
| 182 | setverdict(pass); |
| 183 | } else { |
| 184 | repeat; |
| 185 | } |
| 186 | } |
| 187 | [] IPL4.receive { |
| 188 | repeat; |
| 189 | } |
| 190 | } |
| 191 | } else { |
| 192 | var PortEvent port_evt; |
| 193 | IPL4.receive(PortEvent:{connOpened := ?}) -> value port_evt { |
| 194 | var ConnectionOpenedEvent conn := port_evt.connOpened; |
| 195 | f_send_ipa_data(conn.remName, conn.remPort, conn.connId, ipa_msg_id_req_hdr); |
| 196 | f_send_chopped_ipa_msg(conn.remName, conn.remPort, conn.connId, ipa_msg_id_req_payload); |
| 197 | } |
| 198 | |
| 199 | /* Expect an encoded IPA ID RESP message from the client. */ |
| 200 | alt { |
| 201 | [] IPL4.receive(t_recvfrom(?)) -> value asp_rx { |
| 202 | log("received IPA message from ", asp_rx.remName, " port ", asp_rx.remPort, ": ", |
| 203 | asp_rx.msg); |
| 204 | if (lengthof(asp_rx.msg) > 4 |
| 205 | and asp_rx.msg[2] == 'FE'O /* PROTO_IPACCESS */ |
| 206 | and asp_rx.msg[3] == '05'O /* ID RESP */) { |
| 207 | setverdict(pass); |
| 208 | } else { |
| 209 | repeat; |
| 210 | } |
| 211 | } |
| 212 | [] IPL4.receive { |
| 213 | repeat; |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | |
Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 219 | /* |
| 220 | * Public functions. |
| 221 | * Test suites may call these functions to create an IPA_CT component and run a test to completion. |
| 222 | */ |
| 223 | |
| 224 | function f_run_TC_chopped_ipa_ping(charstring ipa_ip, integer ipa_tcp_port, IPA_ConnectionMode conmode) { |
| 225 | var IPA_Testing.IPA_CT vc_IPA_Testing := IPA_Testing.IPA_CT.create; |
| 226 | vc_IPA_Testing.start(IPA_Testing.f_TC_chopped_ipa_ping(ipa_ip, ipa_tcp_port, conmode)); |
| 227 | vc_IPA_Testing.done; |
| 228 | } |
| 229 | |
Stefan Sperling | aa1e60f | 2018-10-15 16:34:07 +0200 | [diff] [blame] | 230 | function f_run_TC_chopped_ipa_payload(charstring ipa_ip, integer ipa_tcp_port, IPA_ConnectionMode conmode) { |
| 231 | var IPA_Testing.IPA_CT vc_IPA_Testing := IPA_Testing.IPA_CT.create; |
| 232 | vc_IPA_Testing.start(IPA_Testing.f_TC_chopped_ipa_payload(ipa_ip, ipa_tcp_port, conmode)); |
| 233 | vc_IPA_Testing.done; |
| 234 | } |
| 235 | |
Stefan Sperling | 0796a82 | 2018-10-05 13:01:39 +0200 | [diff] [blame] | 236 | } |