aboutsummaryrefslogtreecommitdiffstats
path: root/library/rua/RUA_Emulation.ttcn
blob: a9c445e0153e6828e9eae27362033930fa590632 (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
module RUA_Emulation {

/* RUA_Emulation runs on top of Iuh_Emulation. It multiplexes/demultiplexes
 * the individuao connections, so there can be separate TTCN-3 components
 * handling each of the connections (one connection per UE).
 *
 * The RUA_Emulation.main() function processes RUA messages from the Iuh stack
 * via the RUA_PT, and dispatches them to the per-connection components.
 *
 * Outbound RUA connections are initiated by sending a FIXME primitive to the
 * RUA_Emulation component.
 *
 * For each new inbound connection, the RuaOps.create_cb() is called.  It can create
 * or resolve a TTCN-3 component, and returns a component reference to which that inbound
 * connection is routed/dispatched.
 *
 * If a pre-existing component wants to register to handle future inbound connection,
 * it can do so by registering an "expect" with the expected RANAP payload.

 * (C) 2022 by Harald Welte <laforge@gnumonks.org>
 * All rights reserved.
 *
 * Released under the terms of GNU General Public License, Version 2 or
 * (at your option) any later version.
 */

import from General_Types all;
import from Osmocom_Types all;

import from Iuh_Emulation all;

import from RUA_Templates all;
//import from RUA_Constants all;
import from RUA_PDU_Descriptions all;
import from RUA_IEs all;

import from RANAP_PDU_Descriptions all;
//import from RANAP_Constants all;
import from RANAP_IEs all;
import from RANAP_Types all;
import from RANAP_Templates all;

modulepar {
	integer mp_max_context_id := hex2int('FFFFFF'H);
}


/* General "base class" component definition, of which specific implementations
 * derive themselves by means of the "extends" feature */
type component RUA_ConnHdlr {
	port RUA_Conn_PT RUA;
}

/* port between individual per-connection components and this dispatcher */
type port RUA_Conn_PT message {
	inout RANAP_PDU,
	      RUA_Conn_Req,
	      RUA_Disc_Req,
	      RUA_Disc_Ind;
} with { extension "internal" };

type record RUA_Conn_Req {
	boolean ps_domain,
	RANAP_PDU ranap
};

type record RUA_Disc_Req {
	RANAP_PDU ranap,
	RUA_IEs.Cause cause
};

type record RUA_Disc_Ind {
	RUA_IEs.Cause cause
};

type bitstring ContextId length(24); // with { variant "FIELDLENGTH(24)" };

/* represents a single RANAP connection over RUA */
type record ConnectionData {
	RUA_ConnHdlr	comp_ref,
	RUA_IEs.CN_DomainIndicator domain,
	integer		context_id
}

type component RUA_Emulation_CT {
	/* port to the bottom side (Iuh) */
	port RUA_PT RUA;

	/* ports to the upper side (per-connection components) */
	port RUA_Conn_PT CLIENT;

	/* use 16 as this is also the number of SCCP connections that SCCP_Emulation can handle */
	var ConnectionData ConnectionTable[16];

	/* pending expected incoming connections */
	//var ExpectData ExpectTable[8];

	/* tables for mapping inbound unitdata (like paging) */
	//var ImsiMapping ImsiTable[16];

	/* procedure based port to register for incoming connections */
	//port RUA_PROC_PT PROC;

	var charstring g_rua_id;
	var RuaOps g_rua_ops;
}

type function RanapCreateCallback(ContextId context_id, RUA_IEs.CN_DomainIndicator domain, charstring id)
runs on RUA_Emulation_CT return RUA_ConnHdlr;

type function RanapUnitdataCallback(RANAP_PDU ranap)
runs on RUA_Emulation_CT return template RANAP_PDU;

type record RuaOps {
	RanapCreateCallback create_cb optional,
	RanapUnitdataCallback unitdata_cb optional
	//boolean deode_dtap
	//boolean role_ms
};

private function f_context_id_known(ContextId context_id)
runs on RUA_Emulation_CT return boolean {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].context_id == bit2int(context_id)){
			return true;
		}
	}
	return false;
}

private function f_comp_known(RUA_ConnHdlr client)
runs on RUA_Emulation_CT return boolean {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].comp_ref == client) {
			return true;
		}
	}
	return false;
}

/* resolve connection ID by component reference */
private function f_context_id_by_comp(RUA_ConnHdlr client)
runs on RUA_Emulation_CT return ContextId {
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].comp_ref == client) {
			return int2bit(ConnectionTable[i].context_id, 24);
		}
	}
	setverdict(fail, "RAN Connection table not found by component ", client);
	mtc.stop;
}

/* resolve ConnectionTable index component reference */
private function f_idx_by_comp(RUA_ConnHdlr client)
runs on RUA_Emulation_CT return integer {
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].comp_ref == client) {
			return i;
		}
	}
	setverdict(fail, "RAN Connection table not found by component ", client);
	mtc.stop;
}

private function f_gen_context_id()
runs on RUA_Emulation_CT return ContextId {
	var ContextId context_id;

	do {
		context_id := int2bit(float2int(rnd()*int2float(mp_max_context_id)), 24);
	} while (f_context_id_known(context_id) == true);

	return context_id;
}

private function f_conn_table_init()
runs on RUA_Emulation_CT {
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		ConnectionTable[i].comp_ref := null;
		ConnectionTable[i].context_id := -1;
	}
/*
	for (var integer i := 0; i < sizeof(ImsiTable); i := i+1) {
		ImsiTable[i].comp_ref := null;
		ImsiTable[i].imsi := omit;
		ImsiTable[i].tmsi := 'FFFFFFFF'O;
	}
*/
}

private function f_conn_table_add(RUA_ConnHdlr comp_ref, RUA_IEs.CN_DomainIndicator domain, ContextId context_id)
runs on RUA_Emulation_CT {
	var integer int_context_id := bit2int(context_id);
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].context_id == -1) {
			ConnectionTable[i].comp_ref := comp_ref;
			ConnectionTable[i].domain := domain;
			ConnectionTable[i].context_id := int_context_id;
			log("Added conn table entry ", i, comp_ref, int_context_id);
			return;
		}
	}
	testcase.stop("RUA Connection table full!");
}

private function f_conn_table_del(ContextId context_id)
runs on RUA_Emulation_CT {
	var integer int_context_id := bit2int(context_id);
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].context_id == int_context_id) {
			log("Deleted conn table entry ", i,
			    ConnectionTable[i].comp_ref, int_context_id);
			ConnectionTable[i].context_id := -1;
			return
		}
	}
	setverdict(fail, "RUA Connection table attempt to delete non-existant ", int_context_id);
	mtc.stop;
}


/* resolve component reference by connection ID */
private function f_comp_by_context_id(ContextId context_id)
runs on RUA_Emulation_CT return RUA_ConnHdlr {
	var integer int_context_id := bit2int(context_id);
	for (var integer i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].context_id == int_context_id) {
			return ConnectionTable[i].comp_ref;
		}
	}
	setverdict(fail, "RUA Connection table not found by RUA Context ID ", int_context_id);
	mtc.stop;
}

private function CommonRanapUnitdataCallback(RANAP_PDU ranap)
runs on RUA_Emulation_CT return template RANAP_PDU {
	/* TODO: paging */
	return g_rua_ops.unitdata_cb.apply(ranap);
}

private function f_handle_userData_RANAP(RUA_ConnHdlr client, RANAP_PDU ranap)
runs on RUA_Emulation_CT {
	/* TODO: L3 decoding, if requested */
	CLIENT.send(ranap) to client;
}


private altstep as_reset_ack() runs on RUA_Emulation_CT {
	var RUA_PDU rua_clt;
	[] RUA.receive(tr_RUA_ConnectionlessTransfer(decmatch tr_RANAP_Reset)) -> value rua_clt {
		var RANAP_PDU rx := dec_RANAP_PDU(rua_clt.initiatingMessage.value_.connectionlessTransfer.protocolIEs[0].value_.rANAP_Message);
		var RANAP_IEs.CN_DomainIndicator dom;
		dom := rx.initiatingMessage.value_.Reset.protocolIEs[1].value_.cN_DomainIndicator;
		RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(valueof(ts_RANAP_ResetAck(dom)))));
	}
}

private altstep as_main_rua() runs on RUA_Emulation_CT {
	var RANAP_PDU ranap;
	var RUA_PDU rua;
	var octetstring ranap_enc;
	var ContextId context_id;
	var RUA_IEs.CN_DomainIndicator domain_ind;
	var RUA_ConnHdlr vc_conn;
	var RUA_Conn_Req creq;
	var RUA_Disc_Req dreq;
	var RUA_IEs.Cause cause;

	/* RUA -> Client: UNIT-DATA (connectionless RUA) from CN */
	[] RUA.receive(tr_RUA_ConnectionlessTransfer) -> value rua {
		ranap := dec_RANAP_PDU(rua.initiatingMessage.value_.connectionlessTransfer.protocolIEs[0].value_.rANAP_Message);
		var template RANAP_PDU resp;
		resp := CommonRanapUnitdataCallback(ranap);
		if (isvalue(resp)) {
			RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(valueof(resp))));
		}
	}

	/* RUA -> Client: new connection from CN */
	[] RUA.receive(tr_RUA_Connect) -> value rua {
		domain_ind := rua.initiatingMessage.value_.connect_.protocolIEs[0].value_.cN_DomainIndicator;
		context_id := rua.initiatingMessage.value_.connect_.protocolIEs[1].value_.context_ID;
		ranap_enc := rua.initiatingMessage.value_.connect_.protocolIEs[3].value_.rANAP_Message;
		ranap := dec_RANAP_PDU(ranap_enc);
		vc_conn := g_rua_ops.create_cb.apply(context_id, domain_ind, g_rua_id);
		/* store mapping between client components and RUA contextId */
		f_conn_table_add(vc_conn, domain_ind, context_id);
		/* TODO: notify user about incoming connection? */
		/* handle user payload */
		f_handle_userData_RANAP(vc_conn, ranap);
	}

	/* RUA -> Client: connection-oriented data in existing connection */
	[] RUA.receive(tr_RUA_DirectTransfer) -> value rua {
		context_id := rua.initiatingMessage.value_.directTransfer.protocolIEs[1].value_.context_ID;
		vc_conn := f_comp_by_context_id(context_id);
		ranap_enc := rua.initiatingMessage.value_.directTransfer.protocolIEs[2].value_.rANAP_Message;
		f_handle_userData_RANAP(vc_conn, dec_RANAP_PDU(ranap_enc));
	}

	/* RUA -> Client: disconnect of an existing connection */
	[] RUA.receive(tr_RUA_Disconnect_opt_ranap) -> value rua {
		cause := rua.initiatingMessage.value_.disconnect_.protocolIEs[2].value_.cause;
		context_id := rua.initiatingMessage.value_.disconnect_.protocolIEs[1].value_.context_ID;
		vc_conn := f_comp_by_context_id(context_id);
		/* send contained RANAP message to user */
		if (lengthof(rua.initiatingMessage.value_.disconnect_.protocolIEs) > 3) {
			ranap_enc := rua.initiatingMessage.value_.disconnect_.protocolIEs[3].value_.rANAP_Message;
			f_handle_userData_RANAP(vc_conn, dec_RANAP_PDU(ranap_enc));
		}
		/* notify user of disconnect */
		if (CLIENT.checkstate("Connected")) {
			CLIENT.send(RUA_Disc_Ind:{cause});
		}
		f_conn_table_del(context_id);
	}

	/* RANAP from client through an existing RANAP connection */
	[] CLIENT.receive(RANAP_PDU:?) -> value ranap sender vc_conn {
		var integer idx := f_idx_by_comp(vc_conn);
		context_id := int2bit(ConnectionTable[idx].context_id, 24);
		domain_ind := ConnectionTable[idx].domain;
		RUA.send(ts_RUA_DirectTransfer(domain_ind, context_id, enc_RANAP_PDU(ranap)));
	}

	/* Disconnect request from client */
	[] CLIENT.receive(RUA_Disc_Req:?) -> value dreq sender vc_conn {
		var octetstring enc_ranap := enc_RANAP_PDU(dreq.ranap);
		var integer idx := f_idx_by_comp(vc_conn);
		context_id := int2bit(ConnectionTable[idx].context_id, 24);
		domain_ind := ConnectionTable[idx].domain;
		RUA.send(ts_RUA_Disconnect(domain_ind, context_id, dreq.cause, enc_ranap));
		f_conn_table_del(context_id);
	}

	/* RANAP from client, for a new RANAP connection */
	[] CLIENT.receive(RUA_Conn_Req:?) -> value creq sender vc_conn {
		var octetstring enc_ranap := enc_RANAP_PDU(creq.ranap);

		if (f_comp_known(vc_conn) == false) {
			/* unknown client, create new connection */
			context_id := f_gen_context_id();
			if (creq.ps_domain) {
				domain_ind := ps_domain;
			} else {
				domain_ind := cs_domain;
			}

			f_conn_table_add(vc_conn, domain_ind, context_id);
			RUA.send(ts_RUA_Connect(domain_ind, context_id, normal_call, enc_ranap));
		} else {
			/* known client, send via existing component */
			context_id := f_context_id_by_comp(vc_conn);
			RUA.send(ts_RUA_DirectTransfer(domain_ind, context_id, enc_ranap));
		}
	}

}



function f_ranap_reset(RANAP_IEs.CN_DomainIndicator dom) runs on RUA_Emulation_CT {
	timer T := 5.0;

	var RANAP_PDU tx := valueof(ts_RANAP_Reset(ts_RanapCause_om_intervention,dom));
	RUA.send(ts_RUA_ConnectionlessTransfer(enc_RANAP_PDU(tx)));
	T.start;
	alt {
	[] RUA.receive(tr_RUA_ConnectionlessTransfer(decmatch tr_RANAP_ResetAck)) {
		log("RUA-RANAP: Received RESET-ACK in response to RESET, we're reay to go!");
		}
	[] as_reset_ack();
	[] RUA.receive { repeat; }
	[] T.timeout {
		setverdict(fail, "RUA-RANAP: Timeout waiting for RESET-ACK after sending RESET");
		mtc.stop;
		}
	}
}

function main(RuaOps ops, charstring id) runs on RUA_Emulation_CT {
	g_rua_id := id;
	g_rua_ops := ops;
	f_conn_table_init();
	//f_expect_table_init();

	while (true) {
		alt {
		[] as_main_rua();

		/*
		[] PROC.getcall(RUA_Register:{?,?}) -> param(l3_info, vc_hdlr) {
			f_create_expect(l3_info, vc_hdlr);
			PROC.reply(RUA_register:{l3_info, vc_hdlr}) to vc_hdlr;
			}
		*/
		}
	}
}


}