aboutsummaryrefslogtreecommitdiffstats
path: root/library/RSL_Emulation.ttcn
blob: 119a9be3497041c182d83900dcc49dd3de7d3583 (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
module RSL_Emulation {

/* RSL Emulation, runs on top of IPA_Emulation.  It multiplexes/demultiplexes
 * the individual connections (logical channels), so there can be separate TTCN-3 components
 * handling each of the connections.
 *
 * The RSL_Emulation.main() function processes RSL messages from the IPA demultiplex
 * stack via the IPA_RSL_PT, and dispatches them to the per-connection components.
 *
 * Outbound RSL connections are initiated by sending a RSLDC_ChanRqd primitive
 * to the component running the RSL_Emulation.main() function.
 *
 * in case we're emulating the BTS-side of RSL: We have no clue as to which particular logical
 * channel the BSC may decide to activate at which point, so we have to blindly acknowledge all
 * channel activations.  Still, a test case might be interested in the exact assignment message to
 * determine the type of activation, encryption, codec flags etc. at some later point, when it's
 * clear for which transaction this activation happened.  We keep this in the LastChanAct table.
 *
 * (C) 2017-2018 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 GSM_Types all;
import from GSM_RR_Types all;
import from RSL_Types all;
import from IPA_Types all;
import from IPA_Emulation all;


/* General "base class" component definition, of which specific implementations
 * derive themselves by means of the "extends" feature */
type component RSL_DchanHdlr {
	/* port facing up towards dedicated channel handler */
	port RSL_DCHAN_PT RSL;
	port RSLEM_PROC_PT RSL_PROC;
	var RslChannelNr g_chan_nr;
	/* second BTS / DChan during hand-over */
	port RSL_DCHAN_PT RSL1;
	port RSLEM_PROC_PT RSL1_PROC;
};

type record RSLDC_ChanRqd {
	OCT1		ra,
	GsmFrameNumber	fn
};

template RSLDC_ChanRqd ts_RSLDC_ChanRqd(OCT1 ra, GsmFrameNumber fn) := {
	ra := ra,
	fn := fn
}

type port RSL_DCHAN_PT message {
	inout RSLDC_ChanRqd, RSL_Message;
} with { extension "internal" };

type port RSL_CCHAN_PT message {
	inout ASP_RSL_Unitdata, ASP_IPA_Event;
} with { extension "internal" };


signature RSLEM_register(uint8_t trx_nr, RslChannelNr chan_nr, RSL_DchanHdlr hdlr);
signature RSLEM_unregister(uint8_t trx_nr, RslChannelNr chan_nr, RSL_DchanHdlr hdlr);
signature RSLEM_suspend(boolean suspend);
signature RSLEM_get_last_act(in uint8_t trx_nr, in RslChannelNr chan_nr, out RSL_Message chan_act);

type port RSLEM_PROC_PT procedure {
	inout RSLEM_register, RSLEM_unregister, RSLEM_suspend, RSLEM_get_last_act;
} with { extension "internal" };

/***********************************************************************
 * Client Component for a single dedicated channel
 ***********************************************************************/

private function f_rx_or_fail(template RSL_Message exp_rx) runs on RSL_DchanHdlr return RSL_Message
{
	var RSL_Message rx_rsl;
	timer T := 10.0;

	/* request a channel to be established */
	T.start;
	alt {
		[] RSL.receive(exp_rx) -> value rx_rsl {
			T.stop;
			return rx_rsl;
		}
		[] RSL.receive {
			setverdict(fail, "Unexpected RSL message on DCHAN");
			self.stop;
		}
		[] T.timeout {
			setverdict(fail, "Timeout waiting for RSL on DCHAN");
			self.stop;
		}
	}
	/* never reached */
	return rx_rsl;
}

/* establish a dedicated channel using 'ra' */
function f_chan_est(OCT1 ra, octetstring est_l3, template RslLinkId link_id, GsmFrameNumber fn := 23)
runs on RSL_DchanHdlr {
	var RSL_Message rx_rsl;
	var GsmRrMessage rr;

	/* request a channel to be established */
	RSL.send(ts_RSLDC_ChanRqd(ra, fn));
	/* expect immediate assignment */
	rx_rsl := f_rx_or_fail(tr_RSL_IMM_ASSIGN);
	rr := dec_GsmRrMessage(rx_rsl.ies[1].body.full_imm_ass_info.payload);
	g_chan_nr := rr.payload.imm_ass.chan_desc.chan_nr;
	RSL.send(ts_RSL_EST_IND(g_chan_nr, valueof(link_id), est_l3));
}

function f_deact_chan(RSL_Cause cause) runs on RSL_DchanHdlr
{
	var RSL_Message rx_rsl;

	RSL.send(ts_RSL_CONN_FAIL_IND(g_chan_nr, cause));
	rx_rsl := f_rx_or_fail(tr_RSL_MsgTypeD(RSL_MT_RF_CHAN_REL));
	/* FIXME RSL.send(ts_RSL_RF_CHAN_REL_ACK()) */
}



/***********************************************************************
 * Main Component
 ***********************************************************************/

private type record ConnectionData {
	/* component reference to the client component */
	RSL_DchanHdlr	comp_ref,
	/* RSL (dedicated) Channel number we're handling */
	uint8_t		trx_nr optional,
	IpaStreamId	stream_id optional,
	RslChannelNr	chan_nr optional,
	/* Random Reference */
	OCT1		ra optional,
	GsmFrameNumber	ra_fn optional
};

private function f_cid_by_comp_ref(RSL_DchanHdlr comp_ref)
runs on RSL_Emulation_CT return integer {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ispresent(ConnectionTable[i].comp_ref) and 
		    ConnectionTable[i].comp_ref == comp_ref) {
			return i;
		}
	}
	log("No Dchan handler for ", comp_ref);
	return -1;
}

private function f_cid_by_chan_nr(uint8_t trx_nr, RslChannelNr chan_nr)
runs on RSL_Emulation_CT return integer {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ispresent(ConnectionTable[i].chan_nr) and 
		    ConnectionTable[i].chan_nr == chan_nr and ConnectionTable[i].trx_nr == trx_nr) {
			return i;
		}
	}
	log("No Dchan handler for ", trx_nr, chan_nr);
	return -1;
}

private function f_cid_by_ra_fn(OCT1 ra, GsmFrameNumber fn)
runs on RSL_Emulation_CT return integer {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ispresent(ConnectionTable[i].ra) and
		    ConnectionTable[i].ra == ra and ConnectionTable[i].ra_fn == fn) {
			return i;
		}
	}
	log("No Dchan handler for ", ra, fn);
	return -1;
}

/* create an ew client with given RA and FN */
private function f_cid_create(OCT1 ra, GsmFrameNumber fn, RSL_DchanHdlr comp_ref)
runs on RSL_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (not ispresent(ConnectionTable[i].ra) and
		    not ispresent(ConnectionTable[i].trx_nr)) {
			ConnectionTable[i].ra := ra;
			ConnectionTable[i].ra_fn := fn;
			ConnectionTable[i].comp_ref := comp_ref;
			return;
		}
	}
	setverdict(fail, "No free entry in conn table for ", ra, fn);
}

/* create an ew client with given RA and FN */
private function f_cid_create_cnr(uint8_t trx_nr, RslChannelNr chan_nr,  RSL_DchanHdlr comp_ref)
runs on RSL_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (not ispresent(ConnectionTable[i].ra) and
		    not ispresent(ConnectionTable[i].trx_nr)) {
			ConnectionTable[i].stream_id := f_streamId_by_trx(trx_nr);
			ConnectionTable[i].trx_nr := trx_nr;
			ConnectionTable[i].chan_nr := chan_nr;
			ConnectionTable[i].comp_ref := comp_ref;
			return;
		}
	}
	setverdict(fail, "No free entry in conn table for ", trx_nr, chan_nr, comp_ref);
}


/* create an ew client with given RA and FN */
private function f_cid_delete_cnr(IpaStreamId stream_id, RslChannelNr chan_nr,  RSL_DchanHdlr comp_ref)
runs on RSL_Emulation_CT return integer {
	var integer i;
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		if (ConnectionTable[i].comp_ref == null) {
			continue;
		}
		if (ConnectionTable[i].stream_id == stream_id and
		    ConnectionTable[i].chan_nr == chan_nr and
		    ConnectionTable[i].comp_ref == comp_ref) {
			f_cid_clear(i);
		}
	}
	log("Unable to find entry to delete for ", stream_id, chan_nr, comp_ref);
	return -1;
}


private function f_cid_clear(integer cid)
runs on RSL_Emulation_CT {
	ConnectionTable[cid].ra := omit;
	ConnectionTable[cid].ra_fn := omit;
	ConnectionTable[cid].trx_nr := omit;
	ConnectionTable[cid].stream_id := omit;
	ConnectionTable[cid].chan_nr := omit;
	ConnectionTable[cid].comp_ref := null;
}

/* last activation for a given channel number */
type record LastActData {
	uint8_t		trx_nr optional,
	RslChannelNr	chan_nr optional,
	RSL_Message	chan_act optional
}

private function f_store_last_act_data(uint8_t trx_nr, RslChannelNr chan_nr, RSL_Message chan_act)
runs on RSL_Emulation_CT {
	for (var integer i := 0; i < sizeof(LastActTable); i := i+1) {
		if (not ispresent(LastActTable[i].chan_nr) or
		    (LastActTable[i].chan_nr == chan_nr and LastActTable[i].trx_nr == trx_nr)) {
			LastActTable[i].trx_nr := trx_nr;
			LastActTable[i].chan_nr := chan_nr;
			LastActTable[i].chan_act := chan_act;
			return;
		}
	}
	setverdict(fail, "No space left in LastActTable to store chan_act for ", chan_nr);
	self.stop;
}

private function f_lookup_last_act(uint8_t trx_nr, RslChannelNr chan_nr)
runs on RSL_Emulation_CT return RSL_Message {
	for (var integer i := 0; i < sizeof(LastActTable); i := i+1) {
		if (ispresent(LastActTable[i].chan_nr) and LastActTable[i].chan_nr == chan_nr
		    and LastActTable[i].trx_nr == trx_nr) {
			return LastActTable[i].chan_act;
		}
	}
	setverdict(fail, "No LastActTable entry found for TRX ", trx_nr, " ", chan_nr);
	self.stop;
}

private function f_last_act_table_init() runs on RSL_Emulation_CT {
	for (var integer i := 0; i < sizeof(LastActTable); i := i+1) {
		LastActTable[i] := { omit, omit, omit };
	}
}

type component RSL_Emulation_CT {
	/* port facing down towards IPA emulation */
	port IPA_RSL_PT IPA_PT;
	/* port facing up towards dedicated channel handler */
	port RSL_DCHAN_PT CLIENT_PT;
	port RSLEM_PROC_PT RSL_PROC;

	/* port for Common Channel / TRX Management */
	port RSL_CCHAN_PT CCHAN_PT;

	/* state of all concurrent connections / dedicated channels */
	var ConnectionData ConnectionTable[64];

	/* last RSL CHAN ACT for each chan_nr */
	var LastActData LastActTable[64];
}


/* template for an ASP_RSL_Unitdata as we receive it from the IPA_Emulateion component */
private template ASP_RSL_Unitdata tr_RSL(template RSL_Message rsl, template IpaStreamId sid := ?) := {
	streamId := sid,
	rsl := rsl
}

private function f_trx_by_streamId(IpaStreamId id) return integer {
	return enum2int(id);
}

private function f_streamId_by_trx(uint8_t trx_nr) return IpaStreamId {
	select (trx_nr) {
	case (0) { return IPAC_PROTO_RSL_TRX0; }
	case (1) { return IPAC_PROTO_RSL_TRX1; }
	case (2) { return IPAC_PROTO_RSL_TRX2; }
	case (3) { return IPAC_PROTO_RSL_TRX3; }
	}
	self.stop;
}


function main(boolean bts_role := true) runs on RSL_Emulation_CT {
	var ASP_IPA_Event evt;
	var ASP_RSL_Unitdata rx_rsl;
	var RSL_Message rx_rsl_msg;
	var RSLDC_ChanRqd chan_rqd;
	var RSL_DchanHdlr vc_conn;
	var RslChannelNr chan_nr;
	var uint8_t trx_nr;
	var integer cid;
	var integer i;
	/* special synchronization handling during hand-over */
	var boolean dchan_suspended := false;

	f_conn_table_init();
	f_last_act_table_init();

	while (true) {
		alt {
		[bts_role] IPA_PT.receive(ASP_IPA_Event:{up_down := ASP_IPA_EVENT_UP}) {
			}
		[not bts_role] IPA_PT.receive(ASP_IPA_Event:{up_down := ASP_IPA_EVENT_UP}) -> value evt {
			CCHAN_PT.send(evt);
			}
		[bts_role] IPA_PT.receive(ASP_IPA_Event:{up_down := ASP_IPA_EVENT_ID_ACK}) {
			IPA_PT.send(ts_ASP_RSL_UD(IPAC_PROTO_RSL_TRX0,ts_RSL_PAGING_LOAD_IND(23)));
			}
		[not bts_role] IPA_PT.receive(ASP_IPA_Event:{up_down := ASP_IPA_EVENT_ID_ACK}) { }
		[bts_role] IPA_PT.receive(tr_RSL(tr_RSL_IMM_ASSIGN)) -> value rx_rsl {
			var GsmRrMessage rr;
			var OCT1 ra;
			var GsmFrameNumber fn;
			rr := dec_GsmRrMessage(rx_rsl.rsl.ies[1].body.full_imm_ass_info.payload);
			if (ischosen(rr.payload.imm_ass)) {
				ra := bit2oct(rr.payload.imm_ass.req_ref.ra);
				fn := 23; //FIXME(rr.payload.imm_ass);
				/* lookup client based on RA+time, deliver to client */
				cid := f_cid_by_ra_fn(ra, fn);
				if (cid == -1) {
					setverdict(fail, "IMM ASS for unknown DChan");
					self.stop;
				}
				/* update client with trx_nr */
				ConnectionTable[cid].trx_nr := f_trx_by_streamId(rx_rsl.streamId);
				ConnectionTable[cid].stream_id := rx_rsl.streamId;
				/* update client with chan_nr */
				ConnectionTable[cid].chan_nr := rr.payload.imm_ass.chan_desc.chan_nr;
				/* TODO: add timer to time-out ConnectionTable entries which
				 * never get followed-up to */
				CLIENT_PT.send(rx_rsl.rsl) to ConnectionTable[cid].comp_ref;
			} else if (ischosen(rr.payload.imm_ass_rej)) {
				for (i := 0; i < sizeof(rr.payload.imm_ass_rej.payload); i := i + 1) {
					ra := bit2oct(rr.payload.imm_ass_rej.payload[i].req_ref.ra);
					fn := 23; //FIXME();
					/* lookup client based on RA+time, deliver to client */
					cid := f_cid_by_ra_fn(ra, fn);
					if (cid != -1) {
						CLIENT_PT.send(rx_rsl.rsl) to ConnectionTable[cid].comp_ref;
						/* delete ClientTable entry, as it failed */
						f_cid_clear(cid);
					}
				}
			}
		}

		[bts_role] IPA_PT.receive(tr_RSL(tr_RSL_PAGING_CMD(?, ?))) -> value rx_rsl {
			/* broadcast to all clients? */
			for (i := 0; i < sizeof(ConnectionTable); i := i + 1) {
				if (ispresent(ConnectionTable[i].comp_ref)) {
					CLIENT_PT.send(rx_rsl.rsl) to ConnectionTable[i].comp_ref;
				}
			}
		}

		/* Forward common channel management to the special port for it */
		[] IPA_PT.receive(tr_RSL(tr_RSL_MsgTypeT(?))) -> value rx_rsl {
			CCHAN_PT.send(rx_rsl);
		}

		/* Forward common channel management to the special port for it */
		[] IPA_PT.receive(tr_RSL(tr_RSL_MsgTypeC(?))) -> value rx_rsl {
			CCHAN_PT.send(rx_rsl);
		}

		/* blindly acknowledge all channel activations */
		[bts_role] IPA_PT.receive(tr_RSL(tr_RSL_MsgTypeD(RSL_MT_CHAN_ACTIV))) -> value rx_rsl {
			chan_nr := rx_rsl.rsl.ies[0].body.chan_nr;
			trx_nr := f_trx_by_streamId(rx_rsl.streamId);
			f_store_last_act_data(trx_nr, chan_nr, rx_rsl.rsl);
			IPA_PT.send(ts_ASP_RSL_UD(rx_rsl.streamId, ts_RSL_CHAN_ACT_ACK(chan_nr, 23)));
		}

		[not dchan_suspended] IPA_PT.receive(tr_RSL(tr_RSL_MsgTypeDR(?))) -> value rx_rsl {
			/* dispatch to channel based on ChanId */
			cid := f_cid_by_chan_nr(f_trx_by_streamId(rx_rsl.streamId),
						rx_rsl.rsl.ies[0].body.chan_nr);
			if (cid != -1) {
				CLIENT_PT.send(rx_rsl.rsl) to ConnectionTable[cid].comp_ref;
			} else {
				setverdict(fail, "RSL for unknown Dchan");
			}
		}

		[not dchan_suspended] IPA_PT.receive {
			setverdict(fail, "Received unknown primitive from IPA");
			self.stop;
		}

		[bts_role] CLIENT_PT.receive(RSLDC_ChanRqd:?) -> value chan_rqd sender vc_conn {
			/* Store the knowledge that this sender has requested a certain RQ+time */
			f_cid_create(chan_rqd.ra, chan_rqd.fn, vc_conn);
			IPA_PT.send(ts_ASP_RSL_UD(IPAC_PROTO_RSL_TRX0,
						 ts_RSL_CHAN_RQD(chan_rqd.ra, chan_rqd.fn)));
			}

		[] CLIENT_PT.receive(tr_RSL_MsgType(?)) -> value rx_rsl_msg sender vc_conn {
			/* forward to BSC */
			cid := f_cid_by_comp_ref(vc_conn);
			IPA_PT.send(ts_ASP_RSL_UD(ConnectionTable[cid].stream_id, rx_rsl_msg));
			}

		[] CCHAN_PT.receive(tr_RSL(?)) -> value rx_rsl {
			IPA_PT.send(ts_ASP_RSL_UD(rx_rsl.streamId, rx_rsl.rsl));
			}

		/* explicit registration, e.g. in (non-immediate) assignment case */
		[] RSL_PROC.getcall(RSLEM_register:{?,?,?}) -> param(trx_nr, chan_nr, vc_conn) {
			f_cid_create_cnr(trx_nr, chan_nr, vc_conn);
			RSL_PROC.reply(RSLEM_register:{trx_nr, chan_nr, vc_conn}) to vc_conn;
			}

		[] RSL_PROC.getcall(RSLEM_unregister:{?,?,?}) -> param(trx_nr, chan_nr, vc_conn) {
			cid := f_cid_by_chan_nr(trx_nr, chan_nr);
			f_cid_clear(cid);
			RSL_PROC.reply(RSLEM_unregister:{trx_nr, chan_nr, vc_conn}) to vc_conn;
			}

		[] RSL_PROC.getcall(RSLEM_suspend:{true}) -> sender vc_conn {
			log("Suspending DChan");
			dchan_suspended := true;
			RSL_PROC.reply(RSLEM_suspend:{true}) to vc_conn;
			}

		[] RSL_PROC.getcall(RSLEM_suspend:{false}) -> sender vc_conn {
			log("Resuming DChan");
			dchan_suspended := false;
			RSL_PROC.reply(RSLEM_suspend:{false}) to vc_conn;
			}

		[] RSL_PROC.getcall(RSLEM_get_last_act:{?,?,?}) -> param(trx_nr, chan_nr) sender vc_conn {
			var RSL_Message last_chan_act := f_lookup_last_act(trx_nr, chan_nr);
			RSL_PROC.reply(RSLEM_get_last_act:{trx_nr, chan_nr, last_chan_act}) to vc_conn;
			}
		}
	}
}

private function f_conn_table_init()
runs on RSL_Emulation_CT {
	var integer i;

	/* Initialize the ConnectionTable */
	for (i := 0; i < sizeof(ConnectionTable); i := i+1) {
		f_cid_clear(i);
	}
}

/* client/conn_hdlr side function to use procedure port to register stream_id/chan_nr */
function f_rslem_register(uint8_t trx_nr, RslChannelNr chan_nr, RSLEM_PROC_PT PT := RSL_PROC)
runs on RSL_DchanHdlr {
	PT.call(RSLEM_register:{trx_nr, chan_nr, self}) {
		[] PT.getreply(RSLEM_register:{?,?,?}) {};
	}
}

/* client/conn_hdlr side function to use procedure port to unregister stream_id/chan_nr */
function f_rslem_unregister(uint8_t trx_nr, RslChannelNr chan_nr, RSLEM_PROC_PT PT := RSL_PROC)
runs on RSL_DchanHdlr {
	PT.call(RSLEM_unregister:{trx_nr, chan_nr, self}) {
		[] PT.getreply(RSLEM_unregister:{?,?,?}) {};
	}
}

/* resume handling of RSL DChan messages from IPA until f_rslem_resume() is called */
function f_rslem_suspend(RSLEM_PROC_PT PT)
runs on RSL_DchanHdlr {
	PT.call(RSLEM_suspend:{true}) {
		[] PT.getreply(RSLEM_suspend:{true}) {};
	}
}

/* resume handling of RSL DChan messages after f_rslem_suspend() is called */
function f_rslem_resume(RSLEM_PROC_PT PT)
runs on RSL_DchanHdlr {
	PT.call(RSLEM_suspend:{false}) {
		[] PT.getreply(RSLEM_suspend:{false}) {};
	}
}

/* obtain the last RSL_CHAN_ACT message for the given chan_nr */
function f_rslem_get_last_act(RSLEM_PROC_PT PT, uint8_t trx_nr, RslChannelNr chan_nr)
runs on RSL_DchanHdlr return RSL_Message {
	var RSL_Message chan_act;
	PT.call(RSLEM_get_last_act:{trx_nr, chan_nr, -}) {
		[] PT.getreply(RSLEM_get_last_act:{trx_nr, chan_nr, ?}) -> param(chan_act) {};
	}
	return chan_act;
}




}