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