summaryrefslogtreecommitdiffstats
path: root/library/NS_Emulation.ttcn
blob: 89e25eacacee8484c19289a5ab18cc39b05bc9ae (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
module NS_Emulation {
	import from NS_Types all;
	import from BSSGP_Types all;
	import from Osmocom_Gb_Types all;
	import from NS_CodecPort all;
	import from NS_CodecPort_CtrlFunct all;
	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,
			ASP_Event;
	} 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;
		/* Connect the UDP socket */
		res := f_IPL4_connect(NSCP, config.remote_ip, config.remote_udp_port, config.local_ip, config.local_udp_port, 0, { udp := {}});
		if (not ispresent(res.connId)) {
			setverdict(fail, "Could not connect NS UDP socket from " &
				   config.local_ip & ":"  & int2str(config.local_udp_port) &
				   " to " & config.remote_ip & ":" & int2str(config.remote_udp_port));
			mtc.stop;
		}
		g_conn_id := res.connId;
		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_CT {
		/* UDP port towards the bottom (IUT) */
		port NS_CODEC_PT NSCP;
		/* NS-User SAP towards the user */
		port NS_SP_PT NS_SP;

		var NSConfiguration config;

		var NseState		g_state := NSE_S_DEAD_BLOCKED;
		var ConnectionId	g_conn_id := -1;

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

	type record NSConfiguration {
		PortNumber local_udp_port,
		charstring local_ip,
		PortNumber remote_udp_port,
		charstring remote_ip,
		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(t_NS_Send(g_conn_id, 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_Send(g_conn_id, t_NS_ALIVE));
		Tns_alive.start;
	}

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

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

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

		/* transition to DEAD if t_alive times out */
		[Tns_alive.running] 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.running] Tns_test.timeout {
			log("Tns-test expired: sending NS-ALIVE");
			f_sendAlive();
		}

		/* Stop t_alive when receiving ALIVE-ACK */
		[] NSCP.receive(t_NS_RecvFrom(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_RecvFrom(t_NS_ALIVE)) {
			NSCP.send(t_NS_Send(g_conn_id, t_NS_ALIVE_ACK));
		}

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

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

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

		[config.role_sgsn and config.handle_sns] as_sns_sgsn();

		/* default case of handling unknown PDUs */
		[] NSCP.receive(t_NS_RecvFrom(?)) -> value rf {
			log("Rx Unexpected NS PDU ", rf.msg," in state ", g_state);
			NSCP.send(t_NS_Send(g_conn_id, ts_NS_STATUS(NS_CAUSE_PDU_NOT_COMPATIBLE_WITH_PROTOCOL_STATE, rf.msg)));
		}
		/* Forwarding of ASP_Evet to user */
		[] NSCP.receive(ASP_Event:?) -> value evt { NS_SP.send(evt); }
	}

	/* 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 NS_RecvFrom rf;
		[] NSCP.receive(t_NS_RecvFrom(tr_SNS_SIZE(config.nsei))) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(t_NS_Send(g_conn_id, ts_SNS_SIZE_ACK(config.nsei, omit)));
		}
		[] NSCP.receive(t_NS_RecvFrom(tr_SNS_SIZE(?))) {
			setverdict(fail, "SNS-SIZE from unexpected NSEI");
			self.stop;
		}
		[] NSCP.receive(t_NS_RecvFrom(tr_SNS_CONFIG(config.nsei, true,
				    {tr_SNS_IPv4(config.remote_ip, config.remote_udp_port)}))) -> value rf {
			/* blindly acknowledge whatever the PCU sends */
			NSCP.send(t_NS_Send(g_conn_id, 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.local_ip, config.local_udp_port)) };
			NSCP.send(t_NS_Send(g_conn_id, ts_SNS_CONFIG(config.nsei, true, v4)));
			alt {
			[] NSCP.receive(t_NS_RecvFrom(tr_SNS_CONFIG_ACK(config.nsei, omit))) {
				/* success */
				}
			[] NSCP.receive(t_NS_RecvFrom(tr_SNS_CONFIG_ACK(config.nsei, ?))) {
				setverdict(fail, "Unexpected SNS-CONFIG-NACK");
				self.stop;
				}
			}
		}
		[] NSCP.receive(t_NS_RecvFrom(tr_SNS_CONFIG(config.nsei, false, ?))) { /* ignore */}
		[] NSCP.receive(t_NS_RecvFrom(tr_SNS_CONFIG(config.nsei, true, ?))) {
			setverdict(fail, "Unexpected SNS-CONFIG content");
			self.stop;
		}
		[] NSCP.receive(t_NS_RecvFrom(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 NS_RecvFrom 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(t_NS_RecvFrom(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(t_NS_RecvFrom(tr_NS_BLOCK(?, config.nsvci))) -> value rf {
					NSCP.send(t_NS_Send(g_conn_id, ts_NS_BLOCK_ACK(config.nsvci)));
				}
				/* Respond to UNBLOCK with UNBLOCK-ACK + change state */
				[] NSCP.receive(t_NS_RecvFrom(t_NS_UNBLOCK)) -> value rf {
					NSCP.send(t_NS_Send(g_conn_id, t_NS_UNBLOCK_ACK));
					Tns_block.stop;
					f_change_state(NSE_S_ALIVE_UNBLOCKED);
				}
				[] NSCP.receive(t_NS_RecvFrom(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_RecvFrom(t_NS_UNBLOCK)) -> value rf {
					NSCP.send(t_NS_Send(g_conn_id, t_NS_UNBLOCK_ACK));
				}
				/* Respond to BLOCK with BLOCK-ACK + change state */
				[] NSCP.receive(t_NS_RecvFrom(tr_NS_BLOCK(?, config.nsvci))) -> value rf {
					NSCP.send(t_NS_Send(g_conn_id, ts_NS_BLOCK_ACK(config.nsvci)));
					Tns_block.stop;
					f_change_state(NSE_S_ALIVE_BLOCKED);
				}
				[] NSCP.receive(t_NS_RecvFrom(tr_NS_BLOCK_ACK(config.nsvci))) -> value rf {
					Tns_block.stop;
				}
				/* NS-UNITDATA PDU from network to NS-UNITDATA.ind to user */
				[] NSCP.receive(t_NS_RecvFrom(tr_NS_UNITDATA(?, ?, ?))) -> value rf {
					NS_SP.send(t_NsUdInd(config.nsei,
							     oct2int(rf.msg.pDU_NS_Unitdata.bVCI),
							     rf.msg.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(t_NS_Send(g_conn_id, 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(t_NS_Send(g_conn_id, ts_NS_UNITDATA(t_SduCtrlB, ud_req.bvci, enc)));
				}
			}
		}

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