summaryrefslogtreecommitdiffstats
path: root/library/NS_Emulation.ttcnpp
blob: 7958938495dfd0a9cc016c47fe157912448f4669 (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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/* 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 Misc_Helpers all;
	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 tr_NsUdReq(template Nsei nsei, template BssgpBvci bvci, template octetstring sdu,
					      template PDU_BSSGP bssgp) := {
		bvci := bvci,
		nsei := nsei,
		sdu := sdu,
		bssgp := bssgp
	}

	template (value) NsUnitdataRequest ts_NsUdReq(template (value) Nsei nsei,
						      template (value) BssgpBvci bvci,
						      template (omit) octetstring sdu,
						      template (omit) 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 (present) NsUnitdataIndication tr_NsUdInd(template (present) Nsei nsei,
							   template (present) BssgpBvci bvci,
							   template octetstring sdu) := {
		bvci := bvci,
		nsei := nsei,
		sdu := sdu,
		bssgp := ?
	}

	template (value) NsUnitdataIndication ts_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,
		NsvcState	old_state,
		NsvcState	new_state,
		boolean		first_or_last
	}

	template (present) NsStatusIndication tr_NsStsInd(template (present) Nsei nsei := ?,
							  template (present) Nsvci nsvci := ?,
							  template (present) NsvcState old_state := ?,
							  template (present) NsvcState state := ?,
							  template (present) boolean first_or_last := ?) := {
		nsei := nsei,
		nsvci := nsvci,
		old_state := old_state,
		new_state := state,
		first_or_last := first_or_last
	}


	template (value) NsStatusIndication ts_NsStsInd(Nsei nsei, Nsvci nsvci, NsvcState old_state, NsvcState state,
							boolean first_or_last := false) := {
		nsei := nsei,
		nsvci := nsvci,
		old_state := old_state,
		new_state := state,
		first_or_last := first_or_last
	}

	type enumerated NsvcState {
		NSVC_S_DEAD_BLOCKED,
		NSVC_S_WAIT_RESET,
		NSVC_S_ALIVE_BLOCKED,
		NSVC_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" };

	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 record NSVCConfigurationIP {
		AddressFamily address_family,
		PortNumber local_udp_port,
		charstring local_ip,
		PortNumber remote_udp_port,
		charstring remote_ip
	};
	type record NSVCConfigurationFR {
		charstring netdev,	/* HDLC net-device for AF_PACKET socket */
		integer dlci
	};
	type union NSVCConfigurationP {
		NSVCConfigurationIP ip,
		NSVCConfigurationFR fr
	};
	type record NSVCConfiguration {
		NSVCConfigurationP provider,
		Nsvci nsvci
	};
	type record of NSVCConfiguration NSVCConfigurations;
	type record NSConfiguration {
		Nsvci nsei,
		boolean role_sgsn,
		boolean handle_sns,
		NSVCConfigurations nsvc
	}

	/***********************************************************************
	 * per NS-VCG component. Exists once per [peer of] NSE
	 ***********************************************************************/

	type component NS_CT {
		/* NS-User SAP towards the user */
		port NS_SP_PT NS_SP;

		/* port towards the per-NSVC components */
		port NS_PT NSVC;

		/* all of the NS configuration a user passes to us */
		var NSConfiguration g_config;
		var charstring g_id;

		/* references to the per-NSVC components */
		var NsvcTable g_nsvcs := {};

	};
	type record NsvcTableEntry {
		Nsvci nsvci,
		NSVC_CT vc_conn,
		NsvcState state
	};
	type record of NsvcTableEntry NsvcTable;

	/* add one NSVC (component and table entry */
	function f_nsvc_add(NSVCConfiguration nsvc_cfg) runs on NS_CT {
		var charstring nsvc_id := g_id & "-NSVCI" & int2str(nsvc_cfg.nsvci);
		var NsvcTableEntry te;

		te.nsvci := nsvc_cfg.nsvci;
		te.vc_conn := NSVC_CT.create(nsvc_id);
		te.state := NSVC_S_DEAD_BLOCKED;

		connect(self:NSVC, te.vc_conn:NS_SP);
		te.vc_conn.start(NSVCStart(nsvc_cfg, g_config, nsvc_id));

		g_nsvcs := g_nsvcs & { te };
	}

	function f_nsvc_find_idx(Nsvci nsvci) runs on NS_CT return integer {
		var integer i;
		for (i := 0; i < lengthof(g_nsvcs); i := i+1) {
			if (g_nsvcs[i].nsvci == nsvci) {
				return i;
			}
		}
		return -1;
	}

	function f_nsvc_find(Nsvci nsvci) runs on NS_CT return NSVC_CT {
		var integer i := f_nsvc_find_idx(nsvci);
		if (i < 0) {
			return null;
		} else {
			return g_nsvcs[i].vc_conn;
		}
	}

	function f_nsvc_update_state(Nsvci nsvci, NsvcState state) runs on NS_CT {
		var integer i := f_nsvc_find_idx(nsvci);
		if (i < 0) {
			return;
		}
		g_nsvcs[i].state := state;
	}

	function NSStart(NSConfiguration init_config, charstring id := testcasename()) runs on NS_CT {
		g_config := init_config;
		g_id := id;

		/* iterate over list of NS-VCs and start per-NSVC components */
		for (var integer i := 0; i < lengthof(g_config.nsvc); i := i+1) {
			var NSVCConfiguration nsvc_cfg := g_config.nsvc[i];
			f_nsvc_add(nsvc_cfg);
		}

		while (true) {
			alt {
			[] as_ns_common() {}
			}
		}
	}

	function f_count_nsvcs_in_state(template NsvcState state := ?) runs on NS_CT return integer {
		var integer i;
		var integer res := 0;
		for (i := 0; i < lengthof(g_nsvcs); i := i+1) {
			if (match(g_nsvcs[i].state, state)) {
				res := res + 1;
			}
		}
		return res;
	}

	private altstep as_ns_common() runs on NS_CT {
		var NsStatusIndication rx_nssi;
		var NsUnitdataIndication rx_nsudi;
		var NsUnitdataRequest rx_nsudr;
		/* pass from NS-VCs up to user */
		[] NSVC.receive(tr_NsStsInd(g_config.nsei, ?, ?, NSVC_S_ALIVE_UNBLOCKED)) -> value rx_nssi {
			/* check if this one is the first to be unblocked */
			var integer num_nsvc_unblocked := f_count_nsvcs_in_state(NSVC_S_ALIVE_UNBLOCKED);
			f_nsvc_update_state(rx_nssi.nsvci, rx_nssi.new_state);
			if (num_nsvc_unblocked == 0) {
				rx_nssi.first_or_last := true;
			}
			NS_SP.send(rx_nssi);
			}
		[] NSVC.receive(tr_NsStsInd(g_config.nsei, ?, ?, NSVC_S_DEAD_BLOCKED)) -> value rx_nssi {
			f_nsvc_update_state(rx_nssi.nsvci, rx_nssi.new_state);
			/* check if this one is the last to be blocked */
			var integer num_nsvc_unblocked := f_count_nsvcs_in_state(NSVC_S_ALIVE_UNBLOCKED);
			if (num_nsvc_unblocked == 0) {
				rx_nssi.first_or_last := true;
			}
			NS_SP.send(rx_nssi);
			}
		[] NSVC.receive(tr_NsStsInd(g_config.nsei, ?, ?, ?)) -> value rx_nssi {
			f_nsvc_update_state(rx_nssi.nsvci, rx_nssi.new_state);
			NS_SP.send(rx_nssi);
			}
		[] NSVC.receive(tr_NsStsInd) -> value rx_nssi {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					log2str("Received NsStatusInd for invalid NSEI: ", rx_nssi));
			}
		[] NSVC.receive(tr_NsUdInd(g_config.nsei, ?, ?)) -> value rx_nsudi {
			NS_SP.send(rx_nsudi);
			}
		[] NSVC.receive(tr_NsUdInd(?, ?, ?)) -> value rx_nsudi {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					log2str("Received UnitDataInd for invalid NSEI: ", rx_nsudi));
			}
		/* from user down to NS-VC */
		[] NS_SP.receive(tr_NsUdReq(g_config.nsei, ?, ?, *)) -> value rx_nsudr {
			/* FIXME: load distribution function */
			NSVC.send(rx_nsudr) to g_nsvcs[0].vc_conn;
			}
		[] NS_SP.receive(tr_NsUdReq(?, ?, ?, *)) -> value rx_nsudr {
			Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					log2str("Received NsUnitdataReq for invalid NSEI: ", rx_nsudr));
			}
	}

	/***********************************************************************
	 per-NSVC component. Exists once for each NS-VC in the NS-VCG
	 ***********************************************************************/

	type component NSVC_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

		/* port towards the NS_CT */
		port NS_SP_PT NS_SP;

		/* configuration passed by the user */
		var NSVCConfiguration	g_nsvc_config;
		/* we cannot access the NS_CT.config and hence need to copy those */
		var NSConfiguration	g_config;

		var NsvcState		vc_state := NSVC_S_DEAD_BLOCKED;

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

	function NSVCStart(NSVCConfiguration init_config, NSConfiguration init_g_config, charstring id := testcasename()) runs on NSVC_CT {
		g_nsvc_config := init_config;
		g_config := init_g_config;
		f_init(id & "-NSVCemu" & int2str(g_nsvc_config.nsvci));
		f_ScanEvents();
	}

	private function f_init(charstring id) runs on NSVC_CT {
		var Result res;

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

		f_change_state(NSVC_S_DEAD_BLOCKED);
	}

	private function f_change_state(NsvcState new_state) runs on NSVC_CT {
		var NsvcState old_state := vc_state;
		vc_state := new_state;
		log("NSVC ", g_nsvc_config.nsvci, " State Transition: ", old_state, " -> ", new_state);
		NS_SP.send(ts_NsStsInd(g_config.nsei, g_nsvc_config.nsvci, old_state, new_state));
	}

	private function f_sendReset() runs on NSVC_CT {
		NSCP.send(ts_NS_RESET(NS_CAUSE_OM_INTERVENTION, g_nsvc_config.nsvci, g_config.nsei));
		Tns_reset.start;
		vc_state := NSVC_S_WAIT_RESET;
	}

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

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

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

	private altstep as_allstate() runs on NSVC_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(NSVC_S_DEAD_BLOCKED);
			Tns_test.start;
		}

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

		/* Stop t_alive when receiving ALIVE-ACK */
		[Tns_alive.running] NSCP.receive(t_NS_ALIVE_ACK) {
			log("Rx NS-ALIVE-ACK: 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 */
		}

		[not g_config.handle_sns] as_handle_reset();

		[g_config.role_sgsn and g_config.handle_sns and ischosen(g_nsvc_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 ", vc_state);
			NSCP.send(ts_NS_STATUS(NS_CAUSE_PDU_NOT_COMPATIBLE_WITH_PROTOCOL_STATE, rf));
		}
	}

	private altstep as_handle_reset() runs on NSVC_CT {
		var PDU_NS rf;

		[g_config.role_sgsn] NSCP.receive(NS_Provider_Evt:{link_status:=NS_PROV_LINK_STATUS_UP}) {
			log("Provider Link came up: waiting for NS-RESET");
			}

		[not g_config.role_sgsn] NSCP.receive(NS_Provider_Evt:{link_status:=NS_PROV_LINK_STATUS_UP}) {
			log("Provider Link came up: sending NS-RESET");
			f_sendReset();
			}

		/* Respond to RESET with correct NSEI/NSVCI */
		[] NSCP.receive(tr_NS_RESET(?, g_nsvc_config.nsvci, g_config.nsei)) -> value rf {
			f_change_state(NSVC_S_ALIVE_BLOCKED);
			NSCP.send(ts_NS_RESET_ACK(g_nsvc_config.nsvci, g_config.nsei));
			log("Rx NS-RESET: Sending NS-ALIVE");
			f_sendAlive();
			Tns_test.start;
		}

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

	/* 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 */
	private altstep as_sns_sgsn() runs on NSVC_CT {
		var PDU_NS rf;
		[g_config.role_sgsn] NSCP.receive(NS_Provider_Evt:{link_status:=NS_PROV_LINK_STATUS_UP}) {
			log("Provider Link came up: sending NS-ALIVE");
			f_sendAlive();
			}

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

		[] NSCP.receive(tr_SNS_SIZE(g_config.nsei)) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(ts_SNS_SIZE_ACK(g_config.nsei, omit));
		}
		[] NSCP.receive(tr_SNS_SIZE(?)) {
			setverdict(fail, "SNS-SIZE from unexpected NSEI");
			self.stop;
		}
		[] NSCP.receive(tr_SNS_CONFIG(g_config.nsei, true,
				    {tr_SNS_IPv4(g_nsvc_config.provider.ip.remote_ip,
						 g_nsvc_config.provider.ip.remote_udp_port)})) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(ts_SNS_CONFIG_ACK(g_config.nsei, omit));
			/* send a SNS-CONFIG in response and expect a SNS-CONFIG-ACK */
			var IP4_Elements v4 := { valueof(ts_SNS_IPv4(g_nsvc_config.provider.ip.local_ip,
								     g_nsvc_config.provider.ip.local_udp_port)) };
			NSCP.send(ts_SNS_CONFIG(g_config.nsei, true, v4));
			alt {
			[] NSCP.receive(tr_SNS_CONFIG_ACK(g_config.nsei, omit)) {
				/* success */
				}
			[] NSCP.receive(tr_SNS_CONFIG_ACK(g_config.nsei, ?)) {
				setverdict(fail, "Unexpected SNS-CONFIG-NACK");
				self.stop;
				}
			}
		}
		[] NSCP.receive(tr_SNS_CONFIG(g_config.nsei, false, ?)) { /* ignore */}
		[] NSCP.receive(tr_SNS_CONFIG(g_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 altstep as_alive_blocked() runs on NSVC_CT {
		var PDU_NS rf;
		/* bogus block, just respond with ACK */
		[] NSCP.receive(tr_NS_BLOCK(?, g_nsvc_config.nsvci)) -> value rf {
			NSCP.send(ts_NS_BLOCK_ACK(g_nsvc_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(NSVC_S_ALIVE_UNBLOCKED);
		}
		[] NSCP.receive(t_NS_UNBLOCK_ACK) -> value rf {
			Tns_block.stop;
			f_change_state(NSVC_S_ALIVE_UNBLOCKED);
		}
		[] Tns_block.timeout {
			/* repeat unblock transmission */
			f_sendUnblock();
		}
	}

	private altstep as_alive_unblocked() runs on NSVC_CT {
		var NsUnitdataRequest ud_req;
		var PDU_NS rf;
		/* 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(?, g_nsvc_config.nsvci)) -> value rf {
			NSCP.send(ts_NS_BLOCK_ACK(g_nsvc_config.nsvci));
			Tns_block.stop;
			f_change_state(NSVC_S_ALIVE_BLOCKED);
		}
		[] NSCP.receive(tr_NS_BLOCK_ACK(g_nsvc_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(ts_NsUdInd(g_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(tr_NsUdReq(g_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(tr_NsUdReq(g_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));
		}
	}

	private altstep as_wait_reset() runs on NSVC_CT {
		var PDU_NS rf;
		[] Tns_reset.timeout {
			/* If the sending entity of an NS-RESET PDU receives no NS-RESET-ACK PDU before timer
			 * Tns-reset expires the corresponding NS-VCs shall remain blocked and dead and the
			 * entire reset procedure shall be repeated */
			f_sendReset();
			}
		[] NSCP.receive(tr_NS_RESET_ACK(g_nsvc_config.nsvci, g_config.nsei)) -> value rf {
			Tns_reset.stop;
			f_change_state(NSVC_S_ALIVE_BLOCKED);
			f_sendAlive();
			f_sendUnblock();
		}
	}

	private function f_ScanEvents() runs on NSVC_CT {
		var PDU_NS rf;
		while (true) {
			alt {
			[vc_state == NSVC_S_WAIT_RESET] as_wait_reset();
			[vc_state == NSVC_S_ALIVE_BLOCKED] as_alive_blocked();
			[vc_state == NSVC_S_ALIVE_UNBLOCKED] as_alive_unblocked();
			[] as_allstate();
			}
		}
	}
}