summaryrefslogtreecommitdiffstats
path: root/library/L1CTL_PortType.ttcn
blob: 7d69b4665d6d97127e11e25652523d223bc966fb (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
/* dual-faced port that wraps an Unixdomain port and encodes/decodes L1CTL
 * (C) 2017-2019 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 L1CTL_PortType {
	import from L1CTL_Types all;
	import from UD_PortType all;
	import from UD_Types all;
	import from Osmocom_Types all;
	import from Osmocom_Types all;
	import from GSM_Types all;
	import from GSM_RR_Types all;
	import from L1CTL_PortType_CtrlFunct all;

	type record L1CTL_connect {
		charstring	path
	}

	type record L1CTL_connect_result {
		UD_Result_code	result_code optional,
		charstring	err optional
	}

	modulepar {
		charstring	m_l1ctl_sock_path := "/tmp/osmocom_l2";
	}

	function f_L1CTL_getMsgLen(in octetstring stream, inout ro_integer args) return integer {
		var integer stream_len := lengthof(stream);
		var integer len;
		if (stream_len < 2) {
			return -1;
		}
		len := 2 + oct2int(substr(stream, 0, 2));
		return len;
	}

	function f_L1CTL_FBSB(L1CTL_PT pt, Arfcn arfcn, L1ctlCcchMode ccch_mode := CCCH_MODE_COMBINED, integer rxlev_exp := 57) {
		timer T := 15.0;
		for (var integer i := 0; i < 10; i := i+1) {
			var L1ctlDlMessage dl;
			pt.send(ts_L1CTL_FBSB_REQ(arfcn, valueof(t_L1CTL_FBSB_F_ALL), 0, ccch_mode, rxlev_exp));
			T.start
			alt {
			[] pt.receive(tr_L1CTL_FBSB_CONF(0)) { return; };
			[i >= 9] pt.receive(tr_L1CTL_FBSB_CONF(?)) -> value dl {
				setverdict(fail, "FBSB Failed with non-zero return code ", dl.payload.fbsb_conf.result);
				mtc.stop;
				};
			[] pt.receive(tr_L1CTL_FBSB_CONF(?)) {
				f_sleep(1.0);
				}
			[] pt.receive { repeat; };
			[] T.timeout {
				setverdict(fail, "Timeout waiting for L1CTL_FBSB_CONF");
				mtc.stop;
				};
			}
		}
	}

	function f_L1CTL_CCCH_MODE(L1CTL_PT pt, L1ctlCcchMode ccch_mode) {
		timer T := 2.0;
		pt.send(ts_L1CTL_CCCH_MODE_REQ(ccch_mode));
		T.start;
		alt {
		[] pt.receive(tr_L1CTL_CCCH_MODE_CONF) { }
		[] pt.receive { repeat; }
		[] T.timeout {
			setverdict(fail, "Timeout waiting for L1CTL_CCCH_MODE_CONF");
			mtc.stop;
			}
		}
	}

	function f_L1CTL_RACH(L1CTL_PT pt, uint8_t ra, uint8_t combined := 1, uint16_t offset := 0,
			      template (value) RslChannelNr chan_nr := ts_RslChanNr_RACH(0),
			      template (value) RslLinkId link_id := ts_RslLinkID_DCCH(0))
	return GsmFrameNumber {
		var L1ctlDlMessage rc;
		var GsmFrameNumber fn;
		timer T := 2.0;
		T.start
		pt.send(ts_L1CTL_RACH_REQ(ra, combined, offset, chan_nr, link_id))
		alt {
		[] pt.receive(tr_L1CTL_RACH_CONF) -> value rc { fn := rc.dl_info.frame_nr };
		[] pt.receive { repeat; };
		[] T.timeout {
			setverdict(fail, "Timeout waiting for L1CTL_RACH_CONF");
			mtc.stop;
			}
		}
		return fn;
	}

	function f_L1CTL_EXT_RACH(
		L1CTL_PT pt, uint16_t ra11, L1ctlRachSynchSeq seq,
		uint8_t combined := 1, uint16_t offset := 0
	) return GsmFrameNumber {
		var L1ctlDlMessage rc;
		var GsmFrameNumber fn;
		timer T := 2.0;

		T.start;
		pt.send(ts_L1CTL_EXT_RACH_REQ(ra11, seq, combined, offset));
		alt {
		[] pt.receive(tr_L1CTL_RACH_CONF) -> value rc { fn := rc.dl_info.frame_nr };
		[] pt.receive { repeat; };
		[] T.timeout {
			setverdict(fail, "Timeout waiting for (extended) L1CTL_RACH_CONF");
			mtc.stop;
			}
		}

		return fn;
	}

	function f_L1CTL_PARAM(L1CTL_PT pt, uint8_t ta, uint8_t tx_power) {
		pt.send(ts_L1CTL_PAR_REQ(ta, tx_power));
	}

	function f_L1CTL_WAIT_IMM_ASS(L1CTL_PT pt, uint8_t ra, GsmFrameNumber rach_fn) return ImmediateAssignment {
		var template GsmRrMessage rr_imm_ass;
		var L1ctlDlMessage dl;
		var GsmRrMessage rr;
		timer T := 10.0;

		/* Prepare generic template (for both CS and PS) */
		rr_imm_ass := tr_IMM_ASS(ra, rach_fn);
		rr_imm_ass.payload.imm_ass.ded_or_tbf := ?;
		rr_imm_ass.payload.imm_ass.pkt_chan_desc := *;
		rr_imm_ass.payload.imm_ass.chan_desc := *;

		T.start;
		alt {
		[] pt.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl {
			rr := dec_GsmRrMessage(dl.payload.data_ind.payload);
			log("PCH/AGCH DL RR: ", rr);
			if (match(rr, rr_imm_ass)) {
				log("Received IMM.ASS for our RACH!");
			} else {
				repeat;
			}
		};
		[] pt.receive { repeat };
		[] T.timeout {
			setverdict(fail, "Timeout waiting for IMM ASS");
			mtc.stop;
			}
		}
		T.stop;
		return rr.payload.imm_ass;
	}

	function f_L1CTL_WAIT_IMM_ASS_TBF_DL(L1CTL_PT pt, GprsTlli tlli) return ImmediateAssignment {
		var template PacketDlAssign dl_ass := tr_PacketDlAssign(tlli);
		var template IaRestOctets rest := tr_IaRestOctets_DLAss(dl_ass);
		var L1ctlDlMessage dl;
		var GsmRrMessage rr;
		timer T := 10.0;
		T.start;
		alt {
		[] pt.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl {
			/* TODO: use decmatch tr_IaRestOctets_DLAss(...) instead */
			rr := dec_GsmRrMessage(dl.payload.data_ind.payload);
			log("PCH/AGCN DL RR: ", rr);
			if (match(rr, tr_IMM_TBF_ASS(dl := true, rest := rest))) {
				log("Received IMM.ASS for our TLLI!");
			} else {
				repeat;
			}
		};
		[] pt.receive { repeat };
		[] T.timeout {
			setverdict(fail, "Timeout waiting for TBF IMM ASS");
			mtc.stop;
			}
		}
		T.stop;
		return rr.payload.imm_ass;
	}

	function f_L1CTL_TBF_CFG(L1CTL_PT pt, boolean is_uplink, TfiUsfArr tfi_usf) {
		timer T := 2.0;
		T.start;
		pt.send(ts_L1CTL_TBF_CFG_REQ(is_uplink, tfi_usf));
		alt {
		[] pt.receive(tr_L1CTL_TBF_CFG_CONF(is_uplink)) {}
		[] pt.receive { repeat };
		[] T.timeout {
			setverdict(fail, "Timeout waiting for L1CTL_TBF_CFG_CONF");
			mtc.stop;
			};
		}
		T.stop;
	}

	/* Send DM_EST_REQ from parameters derived from IMM ASS */
	function f_L1CTL_DM_EST_REQ_IA(L1CTL_PT pt, ImmediateAssignment imm_ass, L1ctlMA ma := {}) {
		/* FIXME: handle Packet Channel Description */
		if (imm_ass.ded_or_tbf.tbf == true) {
			setverdict(fail, "TBF assignment is not handled by ", __SCOPE__);
			mtc.stop;
		}

		/* Single channel or frequency hopping? */
		if (not imm_ass.chan_desc.h) {
			pt.send(ts_L1CTL_DM_EST_REQ_H0(imm_ass.chan_desc.chan_nr,
						       imm_ass.chan_desc.tsc,
						       imm_ass.chan_desc.arfcn));
		} else {
			/* TODO: we probably want to apply a bitmask from imm_ass.mobile_allocation
			 * on the list of channels, if it's present. Use all channels for now. */
			pt.send(ts_L1CTL_DM_EST_REQ_H1(imm_ass.chan_desc.chan_nr,
						       imm_ass.chan_desc.tsc,
						       imm_ass.chan_desc.maio_hsn.hsn,
						       imm_ass.chan_desc.maio_hsn.maio,
						       ma));
		}
	}

	/* Send DM_REL_REQ from parameters derived from IMM ASS */
	function f_L1CTL_DM_REL_REQ(L1CTL_PT pt, RslChannelNr chan_nr) {
		pt.send(ts_L1CTL_DM_REL_REQ(chan_nr));
	}

	function f_L1CTL_RESET(L1CTL_PT pt, L1ctlResetType res_type := L1CTL_RES_T_FULL) {
		timer T := 2.0;
		pt.send(t_L1ctlResetReq(res_type));
		T.start;
		alt {
		[] pt.receive(tr_L1CTL_MsgType(L1CTL_RESET_CONF)) { }
		[] pt.receive { repeat; }
		[] T.timeout {
			setverdict(fail, "Timeout waiting for L1CTL_RESET_CONF");
			mtc.stop;
			}
		}
	}

	function f_L1CTL_CRYPTO_REQ(L1CTL_PT pt, RslChannelNr chan_nr, uint8_t algo, octetstring key) {
		pt.send(ts_L1CTL_CRYPTO_REQ(chan_nr, algo, key));
	}

	function f_connect_reset(L1CTL_PT pt, charstring l1ctl_sock_path := m_l1ctl_sock_path) {
		var f_UD_getMsgLen vl_f := refers(f_L1CTL_getMsgLen);
		f_L1CTL_setGetMsgLen(pt, -1, vl_f, {});
		pt.send(L1CTL_connect:{path:=l1ctl_sock_path});
		pt.receive(L1CTL_connect_result:{result_code := SUCCESS, err:=omit});
		f_L1CTL_setGetMsgLen(pt, 0, vl_f, {});
		f_L1CTL_RESET(pt);
	}

	private function L1CTL_to_UD_connect(in L1CTL_connect pin, out UD_connect pout) {
		pout.path := pin.path;
		pout.id := 0;
	} with { extension "prototype(fast)" }

	private function UD_to_L1CTL_connect_result(in UD_connect_result pin, out L1CTL_connect_result pout) {
		pout.result_code := pin.result.result_code;
		pout.err := pin.result.err;
	} with { extension "prototype(fast)" }

	private function L1CTL_to_UD_ul(in L1ctlUlMessage pin, out UD_send_data pout) {
		var L1ctlUlMessageLV msg_lv := { msg := pin };
		pout.data := enc_L1ctlUlMessageLV(msg_lv);
		pout.id := 0;
	} with { extension "prototype(fast)" }

	private function UD_to_L1CTL_dl(in UD_send_data pin, out L1ctlDlMessage pout) {
		var L1ctlDlMessageLV msg_lv := dec_L1ctlDlMessageLV(pin.data);
		pout:= msg_lv.msg;
	} with { extension "prototype(fast)" }

	type port L1CTL_PT message {
		out L1ctlUlMessage
		out L1CTL_connect
		in L1ctlDlMessage
		in L1CTL_connect_result
		in UD_listen_result
		in UD_connected
	} with { extension "user UD_PT
		out(L1ctlUlMessage -> UD_send_data: function(L1CTL_to_UD_ul);
		    L1CTL_connect -> UD_connect: function(L1CTL_to_UD_connect))
		in(UD_send_data -> L1ctlDlMessage: function(UD_to_L1CTL_dl);
		   UD_connect_result -> L1CTL_connect_result: function(UD_to_L1CTL_connect_result);
		   UD_listen_result -> UD_listen_result: simple;
		   UD_connected -> UD_connected: simple
		)" }
}