aboutsummaryrefslogtreecommitdiffstats
path: root/library/IPA_Testing.ttcn
blob: c4097a5f8ed9f1cd1d10d8f7588ef3aba296b013 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 * Author: Stefan Sperling <ssperling@sysmocom.de>
 * All Rights Reserved
 *
 * Released under the terms of GNU General Public License, Version 2 or
 * (at your option) any later version.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * This module provides functions which implement IPA protocol tests.
 * There are no test cases defined here. Instead, there are test functions which
 * can be called by test cases in our test suites. Each such function will create
 * an IPA_CT component and execute a test on this component, and expects destination
 * IP address, TCP port, and connection mode parameters. Depending on the connection
 * mode, a test function will either connect to an IPA server on the specified
 * address and port, or listen for an IPA client on the specified address and port.
 * This allows IPA tests to be run against any IPA speakers used by various test suites.
 */

module IPA_Testing {

import from IPL4asp_Types all;
import from IPL4asp_PortType all;
import from IPA_Types all;
import from Osmocom_Types all;

type enumerated IPA_ConnectionMode {
	CONNECT_TO_SERVER,
	LISTEN_FOR_CLIENT
};

/* Encoded IPA messages (network byte order) */
const octetstring ipa_msg_ping := '0001FE00'O;
const octetstring ipa_msg_pong := '0001FE01'O;
const octetstring ipa_msg_id_req_hdr := '0007FE'O;
const octetstring ipa_msg_id_req_payload := '04010801070102'O;

/* A component which represents the system on which the IPA speaker is running. */
type component system_CT {
	port IPL4asp_PT IPL4;
}

/* Main component provided by this module. */
type component IPA_CT {
	port IPL4asp_PT IPL4;
	timer g_Tguard;
}

/* This guard timer prevents us from waiting too long if the IPA TCP connection hangs. */
private altstep as_Tguard() runs on IPA_CT {
	[] g_Tguard.timeout {
		setverdict(fail, "Tguard timeout");
		mtc.stop;
	}
}

/* Send an encoded IPA message across an IPA TCP connection. */
private function f_send_ipa_data(charstring ipa_ip, integer ipa_tcp_port, ConnectionId connId,
				 octetstring data) runs on IPA_CT {
	var IPL4asp_Types.Result res;
	var ASP_SendTo asp := {
		connId := connId,
		remName := ipa_ip,
		remPort := ipa_tcp_port,
		proto := {tcp := {}},
		msg := data
	};
	IPL4.send(asp);
}

/* Match an incoming IPA message. */
private template ASP_RecvFrom t_recvfrom(template octetstring msg) := {
	connId := ?,
	remName := ?,
	remPort := ?,
	locName := ?,
	locPort := ?,
	proto := {tcp := {}},
	userData := ?,
	msg := msg
}

/* Perform set up steps for a test function. */
private function f_init(charstring ipa_ip, integer ipa_tcp_port,
			IPA_ConnectionMode conmode) runs on IPA_CT return ConnectionId {
	var IPL4asp_Types.Result res;
	var ConnectionId connId;

	map(self:IPL4, system:IPL4);
	if (conmode == CONNECT_TO_SERVER) {
		/* Create an IPA connection over TCP. */
		res := IPL4asp_PortType.f_IPL4_connect(IPL4, ipa_ip, ipa_tcp_port, "", -1, 0, {tcp := {}});
		if (not ispresent(res.connId)) {
			setverdict(fail, "Could not connect IPA socket to ", ipa_ip, " port ",
				   ipa_tcp_port, "; check your configuration");
			mtc.stop;
		}
	} else {
		/* Listen for an incoming IPA connection on TCP. */
		res := IPL4asp_PortType.f_IPL4_listen(IPL4, ipa_ip, ipa_tcp_port, {tcp := {}});
		if (not ispresent(res.connId)) {
			setverdict(fail, "Could not listen on address ", ipa_ip, " port ",
				   ipa_tcp_port, "; check your configuration");
			mtc.stop;
		}
	}

	/*
	 * Activate guard timer. When changing the timeout value, keep in mind
	 * that test functions below may wait for some amount of time, which
	 * this guard timer should always exceed to avoid spurious failures.
	 */
	g_Tguard.start(60.0);
	activate(as_Tguard());

	return res.connId;
}

/*
 * Individual test case implementations.
 */

private function f_send_chopped_ipa_msg(charstring ipa_ip, integer ipa_tcp_port, ConnectionId connId,
					octetstring msg) runs on IPA_CT {
	const float delay := 6.0;
	for (var integer i := 0; i < lengthof(msg); i := i + 1) {
		log("sending byte ", msg[i]);
		f_send_ipa_data(ipa_ip, ipa_tcp_port, connId, msg[i]);
		f_sleep(delay);
	}
}

/* Send a ping message one byte at a time, waiting for TCP buffer to flush between each byte. */
private function f_TC_chopped_ipa_ping(charstring ipa_ip, integer ipa_tcp_port,
				       IPA_ConnectionMode conmode) runs on IPA_CT system system_CT {
	var ConnectionId connId;
	var ASP_RecvFrom asp_rx;

	connId := f_init(ipa_ip, ipa_tcp_port, conmode);

	if (conmode == CONNECT_TO_SERVER) {
		f_send_chopped_ipa_msg(ipa_ip, ipa_tcp_port, connId, ipa_msg_ping);
	} else {
		var PortEvent port_evt;
		IPL4.receive(PortEvent:{connOpened := ?}) -> value port_evt {
			var ConnectionOpenedEvent conn := port_evt.connOpened;
			f_send_chopped_ipa_msg(conn.remName, conn.remPort, conn.connId, ipa_msg_ping);
		}
	}

	/* Expect a pong response. */
	alt {
		[] IPL4.receive(t_recvfrom(ipa_msg_pong)) -> value asp_rx {
			log("received pong from ", asp_rx.remName, " port ", asp_rx.remPort, ": ", asp_rx.msg);
			setverdict(pass);
		}
		[] IPL4.receive {
			repeat;
		}
	}
}

/* Send a complete IPA "ID REQ" message header in one piece, and then send the payload one byte at a time,
 * waiting for TCP buffer to flush between each byte. */
private function f_TC_chopped_ipa_payload(charstring ipa_ip, integer ipa_tcp_port,
					  IPA_ConnectionMode conmode) runs on IPA_CT system system_CT {
	var ConnectionId connId;
	var ASP_RecvFrom asp_rx;

	connId := f_init(ipa_ip, ipa_tcp_port, conmode);

	if (conmode == CONNECT_TO_SERVER) {
		var PortEvent port_evt;
		f_send_ipa_data(ipa_ip, ipa_tcp_port, connId, ipa_msg_id_req_hdr);
		f_send_chopped_ipa_msg(ipa_ip, ipa_tcp_port, connId, ipa_msg_id_req_payload);
		/* Server will close the connection upon receiving an ID REQ. */
		alt {
			[] IPL4.receive(PortEvent:{connClosed := ?}) -> value port_evt {
				if (port_evt.connClosed.connId == connId) {
					setverdict(pass);
				} else {
					repeat;
				}
			}
			[] IPL4.receive {
				repeat;
			}
		}
	} else {
		var PortEvent port_evt;
		IPL4.receive(PortEvent:{connOpened := ?}) -> value port_evt {
			var ConnectionOpenedEvent conn := port_evt.connOpened;
			f_send_ipa_data(conn.remName, conn.remPort, conn.connId, ipa_msg_id_req_hdr);
			f_send_chopped_ipa_msg(conn.remName, conn.remPort, conn.connId, ipa_msg_id_req_payload);
		}

		/* Expect an encoded IPA ID RESP message from the client. */
		alt {
			[] IPL4.receive(t_recvfrom(?)) -> value asp_rx {
				log("received IPA message from ", asp_rx.remName, " port ", asp_rx.remPort, ": ",
				    asp_rx.msg);
				if (lengthof(asp_rx.msg) > 4
				    and asp_rx.msg[2] == 'FE'O /* PROTO_IPACCESS */
				    and asp_rx.msg[3] == '05'O /* ID RESP */) {
					setverdict(pass);
				} else {
					repeat;
				}
			}
			[] IPL4.receive {
				repeat;
			}
		}
	}
}

/*
 * Public functions.
 * Test suites may call these functions to create an IPA_CT component and run a test to completion.
 */

function f_run_TC_chopped_ipa_ping(charstring ipa_ip, integer ipa_tcp_port, IPA_ConnectionMode conmode) {
	var IPA_Testing.IPA_CT vc_IPA_Testing := IPA_Testing.IPA_CT.create;
	vc_IPA_Testing.start(IPA_Testing.f_TC_chopped_ipa_ping(ipa_ip, ipa_tcp_port, conmode));
	vc_IPA_Testing.done;
}

function f_run_TC_chopped_ipa_payload(charstring ipa_ip, integer ipa_tcp_port, IPA_ConnectionMode conmode) {
	var IPA_Testing.IPA_CT vc_IPA_Testing := IPA_Testing.IPA_CT.create;
	vc_IPA_Testing.start(IPA_Testing.f_TC_chopped_ipa_payload(ipa_ip, ipa_tcp_port, conmode));
	vc_IPA_Testing.done;
}

}