summaryrefslogtreecommitdiffstats
path: root/library/NS_Emulation.ttcnpp
blob: 5872b000ae7ad9ffb637477d7da46a072c34b19b (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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/* GPRS-NS Emulation in TTCN-3
 * (C) 2018-2020 Harald Welte <laforge@gnumonks.org>
 * contributions by sysmocom - s.f.m.c. GmbH
 * 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
 */

module NS_Emulation {
	import from NS_Types all;
	import from BSSGP_Types all;
	import from Osmocom_Types all;
	import from Osmocom_Gb_Types all;
	import from NS_Provider_IPL4 all;
#ifdef NS_EMULATION_FR
	import from NS_Provider_FR all;
#endif
	import from IPL4asp_Types all;

	type record NsUnitdataRequest {
		BssgpBvci	bvci,
		Nsei		nsei,
		octetstring	sdu optional,
		PDU_BSSGP	bssgp optional
	}

	template NsUnitdataRequest t_NsUdReq(template Nsei nsei, template BssgpBvci bvci, template octetstring sdu,
					     template PDU_BSSGP bssgp) := {
		bvci := bvci,
		nsei := nsei,
		sdu := sdu,
		bssgp := bssgp
	}

	type record NsUnitdataIndication {
		BssgpBvci	bvci,
		Nsei		nsei,
		octetstring	sdu optional,
		PDU_BSSGP	bssgp optional
	}

	template NsUnitdataIndication t_NsUdInd(Nsei nsei, BssgpBvci bvci, octetstring sdu) := {
		bvci := bvci,
		nsei := nsei,
		sdu := sdu,
		bssgp := dec_PDU_BSSGP(sdu)
	}

	type record NsStatusIndication {
		Nsei		nsei,
		Nsvci		nsvci,
		NseState	old_state,
		NseState	new_state
	}

	template NsStatusIndication t_NsStsInd(Nsei nsei, Nsvci nsvci, NseState old_state, NseState state) := {
		nsei := nsei,
		nsvci := nsvci,
		old_state := old_state,
		new_state := state
	}

	type enumerated NseState {
		NSE_S_DEAD_BLOCKED,
		NSE_S_WAIT_RESET,
		NSE_S_ALIVE_BLOCKED,
		NSE_S_ALIVE_UNBLOCKED
	}

	/* port from our (internal) point of view */
	type port NS_SP_PT message {
		in	NsUnitdataRequest;
		out	NsUnitdataIndication,
			NsStatusIndication;
	} with { extension "internal" };

	/* port from the user point of view */
	type port NS_PT message {
		in	ASP_Event,
			NsStatusIndication,
			NsUnitdataIndication;
		out	NsUnitdataRequest;
	} with { extension "internal" };

	function NSStart(NSConfiguration init_config) runs on NS_CT {
		config := init_config;
		f_init();
		f_ScanEvents();
	}

	private function f_init() runs on NS_CT {
		var Result res;

		if (ischosen(config.provider.ip)) {
			/* Connect the UDP socket */
			vc_NSP_IP := NS_Provider_IPL4_CT.create;
			connect(self:NSCP, vc_NSP_IP:NSE);
			vc_NSP_IP.start(NS_Provider_IPL4.main(config));
#ifdef NS_EMULATION_FR
		} else if (ischosen(config.provider.fr)) {
			vc_NSP_FR := NS_Provider_FR_CT.create;
			connect(self:NSCP, vc_NSP_FR:NSE);
			vc_NSP_FR.start(NS_Provider_FR.main(config));
#endif
		}

		f_change_state(NSE_S_DEAD_BLOCKED);
		/* Send the first NS-ALIVE to test the connection */
		if (not config.role_sgsn) {
			f_sendReset();
		}
	}

	type component NS_Provider_CT {
		/* upper port, facing to NS_Emulation:NSCP */
		port NS_PROVIDER_PT NSE;
		/* lower layer ports (UDP/IP, Frame Relay) are added in derived components */
	};

	type enumerated NS_Provider_LinkStatus {
		NS_PROV_LINK_STATUS_UP,
		NS_PROV_LINK_STATUS_DOWN
	};
	type union NS_Provider_Evt {
		NS_Provider_LinkStatus link_status
	};

	/* port between NS_Provider and NS_CT */
	type port NS_PROVIDER_PT message {
		inout PDU_NS, NS_Provider_Evt;
	} with { extension "internal" };

	type component NS_CT {
		/* UDP port towards the bottom (IUT) */
		port NS_PROVIDER_PT NSCP;
		var NS_Provider_IPL4_CT vc_NSP_IP;
#ifdef NS_EMULATION_FR
		var NS_Provider_FR_CT vc_NSP_FR;
#endif

		/* NS-User SAP towards the user */
		port NS_SP_PT NS_SP;

		var NSConfiguration config;

		var NseState		g_state := NSE_S_DEAD_BLOCKED;

		timer Tns_alive := 3.0;
		timer Tns_test := 10.0;
		timer Tns_block := 10.0;
	}

	type record NSConfigurationIP {
		AddressFamily address_family,
		PortNumber local_udp_port,
		charstring local_ip,
		PortNumber remote_udp_port,
		charstring remote_ip
	};
	type record NSConfigurationFR {
		charstring netdev,	/* HDLC net-device for AF_PACKET socket */
		integer dlci
	};
	type union NSConfigurationP {
		NSConfigurationIP ip,
		NSConfigurationFR fr
	};
	type record NSConfiguration {
		NSConfigurationP provider,
		Nsvci nsvci,
		Nsvci nsei,
		boolean role_sgsn,
		boolean handle_sns
	}

	private function f_change_state(NseState new_state) runs on NS_CT {
		var NseState old_state := g_state;
		g_state := new_state;
		log("NS State Transition: ", old_state, " -> ", new_state);
		NS_SP.send(t_NsStsInd(config.nsei, config.nsvci, old_state, new_state));
	}

	private function f_sendReset() runs on NS_CT {
		NSCP.send(ts_NS_RESET(NS_CAUSE_OM_INTERVENTION, config.nsvci, config.nsei));
		g_state := NSE_S_WAIT_RESET;
	}

	private function f_sendAlive() runs on NS_CT {
		NSCP.send(t_NS_ALIVE);
		Tns_alive.start;
	}

	private function f_sendUnblock() runs on NS_CT {
		NSCP.send(t_NS_UNBLOCK);
		Tns_block.start;
	}

	private function f_sendBlock(NsCause cause) runs on NS_CT {
		NSCP.send(ts_NS_BLOCK(cause, config.nsvci));
		Tns_block.start;
	}

	altstep as_allstate() runs on NS_CT {
		var PDU_NS rf;
		var ASP_Event evt;

		/* transition to DEAD if t_alive times out */
		[] Tns_alive.timeout {
			log("Tns-alive expired: changing to DEAD_BLOCKED + starting Tns-test");
			f_change_state(NSE_S_DEAD_BLOCKED);
			Tns_test.start;
		}

		[] Tns_test.timeout {
			log("Tns-test expired: sending NS-ALIVE");
			f_sendAlive();
		}

		[] NSCP.receive(NS_Provider_Evt:{link_status:=NS_PROV_LINK_STATUS_UP}) {
			log("Provider Link came up: sending NS-ALIVE");
			f_sendAlive();
			Tns_test.start;
			}

		/* Stop t_alive when receiving ALIVE-ACK */
		[Tns_alive.running] NSCP.receive(t_NS_ALIVE_ACK) {
			log("NS-ALIVE-ACK received: stopping Tns-alive; starting Tns-test");
			Tns_alive.stop;
			Tns_test.start;
		}

		/* respond to NS-ALIVE with NS-ALIVE-ACK */
		[] NSCP.receive(t_NS_ALIVE) {
			NSCP.send(t_NS_ALIVE_ACK);
		}

		/* Respond to BLOCK for wrong NSVCI */
		[] NSCP.receive(tr_NS_BLOCK(?, ?)) -> value rf {
			log("Rx NS-BLOCK for unknown NSVCI");
			/* FIXME */
		}

		/* Respond to RESET with correct NSEI/NSVCI */
		[] NSCP.receive(tr_NS_RESET(?, config.nsvci, config.nsei)) -> value rf {
			f_change_state(NSE_S_ALIVE_BLOCKED);
			NSCP.send(ts_NS_RESET_ACK(config.nsvci, config.nsei));
		}

		/* Respond to RESET with wrong NSEI/NSVCI */
		[] NSCP.receive(tr_NS_RESET(?, ?, ?)) -> value rf {
			log("Rx NS-RESET for unknown NSEI/NSVCI");
			/* FIXME */
		}

		[config.role_sgsn and config.handle_sns and ischosen(config.provider.ip)] as_sns_sgsn();

		/* default case of handling unknown PDUs */
		[] NSCP.receive(PDU_NS: ?) -> value rf {
			log("Rx Unexpected NS PDU ", rf," in state ", g_state);
			NSCP.send(ts_NS_STATUS(NS_CAUSE_PDU_NOT_COMPATIBLE_WITH_PROTOCOL_STATE, rf));
		}
	}

	/* simple IP Sub-Network Service responder for the SGSN side. This is not a full implementation
	 * of the protocol, merely sufficient to make the PCU/BSS side happy to proceed */
	altstep as_sns_sgsn() runs on NS_CT {
		var PDU_NS rf;
		[] NSCP.receive(tr_SNS_SIZE(config.nsei)) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(ts_SNS_SIZE_ACK(config.nsei, omit));
		}
		[] NSCP.receive(tr_SNS_SIZE(?)) {
			setverdict(fail, "SNS-SIZE from unexpected NSEI");
			self.stop;
		}
		[] NSCP.receive(tr_SNS_CONFIG(config.nsei, true,
				    {tr_SNS_IPv4(config.provider.ip.remote_ip,
						 config.provider.ip.remote_udp_port)})) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(ts_SNS_CONFIG_ACK(config.nsei, omit));
			/* send a SNS-CONFIG in response and expect a SNS-CONFIG-ACK */
			var IP4_Elements v4 := { valueof(ts_SNS_IPv4(config.provider.ip.local_ip, config.provider.ip.local_udp_port)) };
			NSCP.send(ts_SNS_CONFIG(config.nsei, true, v4));
			alt {
			[] NSCP.receive(tr_SNS_CONFIG_ACK(config.nsei, omit)) {
				/* success */
				}
			[] NSCP.receive(tr_SNS_CONFIG_ACK(config.nsei, ?)) {
				setverdict(fail, "Unexpected SNS-CONFIG-NACK");
				self.stop;
				}
			}
		}
		[] NSCP.receive(tr_SNS_CONFIG(config.nsei, false, ?)) { /* ignore */}
		[] NSCP.receive(tr_SNS_CONFIG(config.nsei, true, ?)) {
			setverdict(fail, "Unexpected SNS-CONFIG content");
			self.stop;
		}
		[] NSCP.receive(tr_SNS_CONFIG(?, ?, ?)) {
			setverdict(fail, "SNS-CONFIG from unexpected NSEI");
			self.stop;
		}
	}

	private function f_ScanEvents() runs on NS_CT {
		var NsUnitdataRequest ud_req;
		var PDU_NS rf;
		var default d;

		d := activate(as_allstate());

		while (true) {
		if (g_state == NSE_S_DEAD_BLOCKED) {
			alt {
				[false] any timer.timeout {}
			}
		} else if (g_state == NSE_S_WAIT_RESET) {
			alt {
				[] NSCP.receive(tr_NS_RESET_ACK(config.nsvci, config.nsei)) -> value rf {
					f_change_state(NSE_S_ALIVE_BLOCKED);
					f_sendAlive();
					f_sendUnblock();
				}
			}
		} else if (g_state == NSE_S_ALIVE_BLOCKED) {
			alt {
				/* bogus block, just respond with ACK */
				[] NSCP.receive(tr_NS_BLOCK(?, config.nsvci)) -> value rf {
					NSCP.send(ts_NS_BLOCK_ACK(config.nsvci));
				}
				/* Respond to UNBLOCK with UNBLOCK-ACK + change state */
				[] NSCP.receive(t_NS_UNBLOCK) -> value rf {
					NSCP.send(t_NS_UNBLOCK_ACK);
					Tns_block.stop;
					f_change_state(NSE_S_ALIVE_UNBLOCKED);
				}
				[] NSCP.receive(t_NS_UNBLOCK_ACK) -> value rf {
					Tns_block.stop;
					f_change_state(NSE_S_ALIVE_UNBLOCKED);
				}
				[] Tns_block.timeout {
					/* repeat unblock transmission */
					f_sendUnblock();
				}
			}
		} else if (g_state == NSE_S_ALIVE_UNBLOCKED) {
			alt {
				/* bogus unblock, just respond with ACK */
				[] NSCP.receive(t_NS_UNBLOCK) -> value rf {
					NSCP.send(t_NS_UNBLOCK_ACK);
				}
				/* Respond to BLOCK with BLOCK-ACK + change state */
				[] NSCP.receive(tr_NS_BLOCK(?, config.nsvci)) -> value rf {
					NSCP.send(ts_NS_BLOCK_ACK(config.nsvci));
					Tns_block.stop;
					f_change_state(NSE_S_ALIVE_BLOCKED);
				}
				[] NSCP.receive(tr_NS_BLOCK_ACK(config.nsvci)) -> value rf {
					Tns_block.stop;
				}
				/* NS-UNITDATA PDU from network to NS-UNITDATA.ind to user */
				[] NSCP.receive(tr_NS_UNITDATA(?, ?, ?)) -> value rf {
					NS_SP.send(t_NsUdInd(config.nsei,
							     oct2int(rf.pDU_NS_Unitdata.bVCI),
							     rf.pDU_NS_Unitdata.nS_SDU));
				}
				/* NS-UNITDATA.req from user to NS-UNITDATA PDU on network */
				[] NS_SP.receive(t_NsUdReq(config.nsei, ?, ?, omit)) -> value ud_req {
					/* using raw octetstring PDU */
					NSCP.send(ts_NS_UNITDATA(t_SduCtrlB, ud_req.bvci, ud_req.sdu));
				}
				[] NS_SP.receive(t_NsUdReq(config.nsei, ?, omit, ?)) -> value ud_req {
					/* using decoded BSSGP PDU that we need to encode first */
					var octetstring enc := enc_PDU_BSSGP(ud_req.bssgp);
					NSCP.send(ts_NS_UNITDATA(t_SduCtrlB, ud_req.bvci, enc));
				}
			}
		}

		} /* while */
		//deactivate(d);
	}
}