blob: 29e56693c1ed8d7a3260f3919259ab09d84a2e54 [file] [log] [blame]
Alexander Couzens20cd41e2021-01-11 02:56:03 +01001module NS_Tests {
2
3/* Osmocom NS test suite for NS over framerelay or udp ip.access style in TTCN-3
4 * (C) 2021 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
5 * Author: Alexander Couzens <lynxis@fe80.eu>
6 * All rights reserved.
7 *
8 * Released under the terms of GNU General Public License, Version 2 or
9 * (at your option) any later version.
10 *
11 * SPDX-License-Identifier: GPL-2.0-or-later
12 */
13
14import from General_Types all;
15import from Osmocom_Types all;
16import from Osmocom_Gb_Types all;
17import from NS_Types all;
18import from BSSGP_Types all;
19import from UD_Types all;
20import from NS_Emulation all;
21import from Native_Functions all;
22import from IPL4asp_Types all;
23import from RAW_NS all;
24import from Osmocom_VTY_Functions all;
25import from TELNETasp_PortType all;
26
27modulepar {
28 OsmoNsDialect mp_dialect := NS2_DIALECT_IPACCESS;
29 NSConfiguration mp_nsconfig := {
30 nsei := 96,
31 role_sgsn := false,
32 handle_sns := false,
33 nsvc := {
34 {
35 provider := {
36 ip := {
37 address_family := AF_INET,
38 local_udp_port := 21000,
39 local_ip := "127.0.0.1",
40 remote_udp_port := 23000,
Alexander Couzens87e44cf2021-02-03 15:15:27 +010041 remote_ip := "127.0.0.1",
42 data_weight := 2,
43 signalling_weight := 2
Alexander Couzens20cd41e2021-01-11 02:56:03 +010044 }
45 },
46 nsvci := 97
47 }
48 }
49 };
50}
51
52type component RAW_Test_CT extends RAW_NS_CT {
53 port TELNETasp_PT NSVTY;
54}
55
56private function f_init_vty() runs on RAW_Test_CT {
57 map(self:NSVTY, system:NSVTY);
58 f_vty_set_prompts(NSVTY);
59 f_vty_transceive(NSVTY, "enable");
60 f_vty_transceive(NSVTY, "nsvc nsei " & int2str(mp_nsconfig.nsei) & " force-unconfigured");
61}
62
63/* ensure no matching message is received within 'tout' */
64function f_ensure_no_ns(integer idx := 0, boolean answer_alive := false, float tout := 3.0)
65runs on RAW_Test_CT {
66 var PDU_NS nrf;
67
68 timer T := tout;
69 var default d := activate(ax_rx_fail_on_any_ns(idx));
70 T.start;
71 alt {
72 [answer_alive] as_rx_alive_tx_ack();
73 [] T.timeout {
74 setverdict(pass);
75 }
76 }
77 deactivate(d);
78}
79
80function f_fails_except_reset(integer idx := 0, float tout := 15.0)
81runs on RAW_Test_CT {
82 var PDU_NS nrf;
83 timer T := 15.0;
84 T.start;
85 alt {
86 [] NSCP[idx].receive(tr_NS_RESET(*, *, *)) {
87 repeat;
88 }
89 [] NSCP[idx].receive(PDU_NS: ?) -> value nrf {
90 setverdict(fail, "Received unexpected NS: ", nrf);
91 mtc.stop;
92 }
93 [] T.timeout {
94 setverdict(pass);
95 }
96 }
97}
98
99testcase TC_tx_reset() runs on RAW_Test_CT {
100 f_init_vty();
101 f_init_ns_codec(mp_nsconfig, guard_secs := 10.0);
102
103 /* do a NS Reset procedure */
104 f_outgoing_ns_reset();
105
106 setverdict(pass);
107 f_sleep(1.0);
108}
109
110testcase TC_tx_reset_tx_alive() runs on RAW_Test_CT {
111 f_init_vty();
112 f_init_ns_codec(mp_nsconfig, guard_secs := 10.0);
113
114 /* do a NS Reset procedure */
115 f_outgoing_ns_reset();
116
117 /* check outgoing NS procedure */
118 f_outgoing_ns_alive();
119
120 setverdict(pass);
121 f_sleep(1.0);
122}
123
124testcase TC_tx_reset_rx_alive() runs on RAW_Test_CT {
125 f_init_vty();
126 f_init_ns_codec(mp_nsconfig, guard_secs := 10.0);
127
128 /* do a NS Reset procedure */
129 f_outgoing_ns_reset();
130
131 activate(as_rx_ns_unblock_ack());
132 /* check outgoing NS procedure */
133 as_rx_alive_tx_ack(oneshot := true);
134
135 setverdict(pass);
136 f_sleep(1.0);
137}
138
139/* 48.016 7.2 unblock procedure
140 *
141 * TTCN -> NS: reset
142 * TTCN <- NS: reset ack
143 * TTCN -> NS: unblock
144 * TTCN <- NS: unblock ack
145 */
146testcase TC_tx_unblock() runs on RAW_Test_CT {
147 f_init_vty();
148 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
149
150 /* do a NS Reset procedure */
151 f_outgoing_ns_reset();
152 /* send alive acks */
153 activate(as_rx_alive_tx_ack());
154
155 f_outgoing_ns_unblock();
156 setverdict(pass);
157 f_sleep(1.0);
158}
159
160/* 48.016 7.2 tx unblock retries
161 *
162 * TTCN -> NS: reset
163 * TTCN <- NS: reset ack
164 * TTCN -> NS: unblock
165 * TTCN <- NS: unblock ack
166 * TTCN -> NS: unblock
167 * TTCN <- NS: unblock ack
168 * TTCN -> NS: unblock
169 * TTCN <- NS: unblock ack
170 */
171testcase TC_tx_unblock_retries() runs on RAW_Test_CT {
172 f_init_vty();
173 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
174
175 /* do a NS Reset procedure */
176 f_outgoing_ns_reset();
177 /* send alive acks */
178 activate(as_rx_alive_tx_ack());
179
180 f_outgoing_ns_unblock();
181 f_outgoing_ns_unblock();
182 f_outgoing_ns_unblock();
183 setverdict(pass);
184 f_sleep(1.0);
185}
186
187/* 48.016 7.2 block procedure
188 *
189 * TTCN -> NS: reset
190 * TTCN <- NS: reset ack
191 * TTCN -> NS: unblock
192 * TTCN <- NS: unblock ack
193 * TTCN -> NS: block
194 * TTCN <- NS: block ack
195 */
196testcase TC_tx_block() runs on RAW_Test_CT {
197 f_init_vty();
198 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
199
200 /* do a NS Reset procedure */
201 f_outgoing_ns_reset();
202 activate(as_rx_alive_tx_ack());
203
204 f_outgoing_ns_unblock();
205 f_sleep(1.0);
206
207 f_outgoing_ns_block(NS_CAUSE_EQUIPMENT_FAILURE);
208 setverdict(pass);
209 f_sleep(1.0);
210}
211
212/* 48.016 7.2 block procedure by vty
213 *
214 * TTCN -> NS: reset
215 * TTCN <- NS: reset ack
216 * TTCN -> NS: unblock
217 * TTCN <- NS: unblock ack
218 * vty: block nsvc
219 * TTCN <- NS: block
220 * TTCN -> NS: block ack
221 */
222function tx_block_by_vty(float guard_secs := 30.0) runs on RAW_Test_CT {
223 f_init_vty();
224 f_init_ns_codec(mp_nsconfig, guard_secs := guard_secs);
225
226 /* do a NS Reset procedure */
227 f_outgoing_ns_reset();
228 activate(as_rx_alive_tx_ack());
229
230 f_outgoing_ns_unblock();
231 f_sleep(1.0);
232
233 f_vty_transceive(NSVTY, "nsvc " & int2str(mp_nsconfig.nsvc[0].nsvci) & " block");
234
235 alt {
236 [] as_rx_ns_block_ack(oneshot := true, nsvci := mp_nsconfig.nsvc[0].nsvci);
237 }
238 setverdict(pass);
239}
240
241testcase TC_tx_block_by_vty() runs on RAW_Test_CT {
242 tx_block_by_vty(30.0);
243 f_sleep(1.0);
244}
245
246/* 48.016 7.2 block precedure by vty and reset the NSVC.
247 * The NSVC should be still blocked after the reset.
248 */
249testcase TC_tx_block_by_vty_reset() runs on RAW_Test_CT {
250 timer T := 10.0;
251
252 tx_block_by_vty(60.0);
253 f_outgoing_ns_reset();
254
255 var default d := activate(ax_rx_fail_on_any_ns());
256 T.start;
257 alt {
258 [] as_rx_alive_tx_ack();
259 [] T.timeout { setverdict(pass); }
260 }
261 deactivate(d);
262}
263
264/* 48.016 7.4.1 ignore unexpected NS_ALIVE ACK */
265testcase TC_no_reset_alive_ack() runs on RAW_Test_CT {
266 f_init_vty();
267 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
268
269 NSCP[0].send(t_NS_ALIVE_ACK);
270 f_fails_except_reset();
271 setverdict(pass);
272 f_sleep(1.0);
273}
274
275/* 48.016 7.3.1 NS_RESET with wrong nsei */
276testcase TC_reset_wrong_nsei() runs on RAW_Test_CT {
277 f_init_vty();
278 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
279
280 NSCP[0].send(ts_NS_RESET(NS_CAUSE_EQUIPMENT_FAILURE, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei + 20));
281 NSCP[0].receive(tr_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
282 f_fails_except_reset();
283 setverdict(pass);
284 f_sleep(1.0);
285}
286
287/* 48.016 7.3.1 NS_RESET with wrong nsvci */
288testcase TC_reset_wrong_nsvci() runs on RAW_Test_CT {
289 f_init_vty();
290 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
291
292 NSCP[0].send(ts_NS_RESET(NS_CAUSE_EQUIPMENT_FAILURE, g_nsconfig.nsvc[0].nsvci + 20, g_nsconfig.nsei));
293 NSCP[0].receive(tr_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
294 f_fails_except_reset();
295 setverdict(pass);
296 f_sleep(1.0);
297}
298
299/* 48.016 7.3.1 NS_RESET with wrong nsvci + nsei */
300testcase TC_reset_wrong_nsei_nsvci() runs on RAW_Test_CT {
301 f_init_vty();
302 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
303
304 NSCP[0].send(ts_NS_RESET(NS_CAUSE_EQUIPMENT_FAILURE, g_nsconfig.nsvc[0].nsvci + 20, g_nsconfig.nsei + 20));
305 NSCP[0].receive(tr_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
306 f_fails_except_reset();
307 setverdict(pass);
308 f_sleep(1.0);
309}
310
311/* 48.016 7.3.1 NS_RESET_ACK with wrong nsei */
312testcase TC_reset_ack_wrong_nsei() runs on RAW_Test_CT {
313 f_init_vty();
314 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
315
316 NSCP[0].receive(tr_NS_RESET(*, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
317 NSCP[0].send(ts_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei + 20));
318 f_fails_except_reset();
319 setverdict(pass);
320 f_sleep(1.0);
321}
322
323/* 48.016 7.3.1 NS_RESET_ACK with wrong nsvci */
324testcase TC_reset_ack_wrong_nsvci() runs on RAW_Test_CT {
325 f_init_vty();
326 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
327
328 NSCP[0].receive(tr_NS_RESET(*, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
329 NSCP[0].send(ts_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci + 20, g_nsconfig.nsei));
330 f_fails_except_reset();
331 setverdict(pass);
332 f_sleep(1.0);
333}
334
335/* 48.016 7.3.1 NS_RESET_ACK with wrong nsvci + nsei */
336testcase TC_reset_ack_wrong_nsei_nsvci() runs on RAW_Test_CT {
337 f_init_vty();
338 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
339
340 NSCP[0].receive(tr_NS_RESET(*, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
341 NSCP[0].send(ts_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci + 20, g_nsconfig.nsei + 20));
342 f_fails_except_reset();
343 setverdict(pass);
344 f_sleep(1.0);
345}
346
347/* 48.016 7.3.1 ignore unexpected NS_RESET_ACK after NS_RESET+ALIVE */
348testcase TC_ignore_reset_ack() runs on RAW_Test_CT {
349 f_init_vty();
350 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
351
352 /* do a NS Reset procedure */
353 f_outgoing_ns_reset();
354
355 /* unblock and alive */
356 alt {
357 [] as_rx_ns_unblock_ack(oneshot := true);
358 [] as_rx_alive_tx_ack();
359 }
360
361 NSCP[0].send(ts_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
362 f_ensure_no_ns(answer_alive := true, tout := 15.0);
363 setverdict(pass);
364 f_sleep(1.0);
365}
366
367/* 48.016 7.3 NS_RESET retries */
368testcase TC_reset_retries() runs on RAW_Test_CT {
369 var integer reset := 0;
370
371 f_init_vty();
372 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
373
374 alt {
375 [] NSCP[0].receive(tr_NS_RESET(*, *, *)) {
376 reset := reset + 1;
377 if (reset <= 3) {
378 repeat;
379 } else {
380 setverdict(pass);
381 }
382 }
383 }
384
385 f_sleep(1.0);
386}
387
388/* 48.016 behave RESET_ACK got dropped
389 * TTCN -> NS: reset
390 * TTCN <- NS: reset ack
391 * TTCN <- NS: unblock
392 * TTCN -> NS: reset
393 * TTCN <- NS: reset ack
394 * TTCN <- NS: unblock
395 */
396testcase TC_reset_on_block_reset() runs on RAW_Test_CT {
397 var integer i := 0;
398
399 f_init_vty();
400 f_init_ns_codec(mp_nsconfig, guard_secs := 30.0);
401
402 f_outgoing_ns_reset();
403 activate(as_rx_alive_tx_ack());
404
405 alt {
406 [] NSCP[0].receive(t_NS_UNBLOCK) {
407 NSCP[0].send(ts_NS_RESET(NS_CAUSE_EQUIPMENT_FAILURE, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
408 i := i + 1;
409 if (i < 3) {
410 repeat;
411 } else {
412 setverdict(pass);
413 }
414 }
415 [] NSCP[0].receive(tr_NS_RESET_ACK(g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei)) { repeat; }
416 }
417
418 f_sleep(1.0);
419}
420
421/* 48.016 7.4 test procedure for frame relay with a single nsvci */
422function f_alive_retries_single(boolean reset := false) runs on RAW_Test_CT {
423 f_init_vty();
424 f_init_ns_codec(mp_nsconfig, guard_secs := 60.0);
425
426 /* do a NS Reset procedure */
427 f_outgoing_ns_reset();
428
429 alt {
430 [] as_rx_ns_unblock_ack(oneshot := true);
431 [] as_rx_alive_tx_ack();
432 }
433
434 /* wait for one alive and answer it */
435 as_rx_alive_tx_ack(oneshot := true);
436 NSCP[0].receive(t_NS_ALIVE);
437 NSCP[0].receive(t_NS_ALIVE);
438 NSCP[0].receive(t_NS_ALIVE);
439 NSCP[0].receive(t_NS_ALIVE);
440 if (reset) {
441 NSCP[0].receive(tr_NS_RESET(*, g_nsconfig.nsvc[0].nsvci, g_nsconfig.nsei));
442 } else {
443 f_ensure_no_ns(tout := 10.0);
444 }
445
446 setverdict(pass);
447 f_sleep(1.0);
448}
449
450testcase TC_alive_retries_single_reset() runs on RAW_Test_CT {
451 f_alive_retries_single(reset := true);
452}
453
454testcase TC_alive_retries_single_no_resp() runs on RAW_Test_CT {
455 f_alive_retries_single(reset := false);
456}
457
Alexander Couzenscbee32a2021-02-27 20:50:20 +0100458/* 48.016 SNS test cases */
459
460/* do a succesful SNS configuration */
461testcase TC_sns_config_success() runs on RAW_Test_CT {
462 f_init_vty();
463 f_init_ns_codec(mp_nsconfig);
464 f_incoming_sns_size();
465 f_incoming_sns_config();
466 f_outgoing_sns_config();
467 setverdict(pass);
468 f_clean_ns_codec();
469}
470
Alexander Couzensd5ac5102021-02-27 20:53:37 +0100471testcase TC_sns_bss_change_weight() runs on RAW_Test_CT {
472 f_init_vty();
473 f_init_ns_codec(mp_nsconfig);
474 f_incoming_sns_size();
475 f_incoming_sns_config();
476 f_outgoing_sns_config();
477 activate(as_rx_alive_tx_ack());
478 f_vty_config2(NSVTY, {"ns", "bind udp local"}, "ip-sns signalling-weight 99 data-weight 99");
479 f_incoming_sns_chg_weight();
480 setverdict(pass);
481 f_clean_ns_codec();
482}
483
Alexander Couzens6c723fb2021-03-01 00:12:00 +0100484/* receive 3x SNS_CHG_WEIGHT but never answer on it */
485testcase TC_sns_bss_change_weight_timeout() runs on RAW_Test_CT {
486 var integer i := 0;
487 var template PDU_NS rx;
488 var NSVCConfiguration nsvc_cfg;
489
490 f_init_vty();
491 f_init_ns_codec(mp_nsconfig);
492 f_incoming_sns_size();
493 f_incoming_sns_config();
494 f_outgoing_sns_config();
495 activate(as_rx_alive_tx_ack());
496 f_vty_config2(NSVTY, {"ns", "bind udp local"}, "ip-sns signalling-weight 99 data-weight 99");
497
498 nsvc_cfg := g_nsconfig.nsvc[0];
499 if (nsvc_cfg.provider.ip.address_family == AF_INET) {
500 var template IP4_Elements v4_elem := { tr_SNS_IPv4(nsvc_cfg.provider.ip.remote_ip,
501 nsvc_cfg.provider.ip.remote_udp_port) };
502
503 rx := tr_SNS_CHG_WEIGHT(g_nsconfig.nsei, ?, v4 := v4_elem);
504 } else {
505 var template IP6_Elements v6_elem := { tr_SNS_IPv6(nsvc_cfg.provider.ip.remote_ip,
506 nsvc_cfg.provider.ip.remote_udp_port) };
507 rx := tr_SNS_CHG_WEIGHT(g_nsconfig.nsei, ?, v4 := omit, v6 := v6_elem);
508 }
509
510 alt {
511 [] NSCP[0].receive(rx) {
512 i := i + 1;
513 if (i < 3) {
514 repeat;
515 }
516 }
517 [] as_rx_alive_tx_ack();
518 }
519
Alexander Couzensa93cc362021-04-14 19:39:18 +0200520 if (nsvc_cfg.provider.ip.address_family == AF_INET) {
521 /* expect one single SNS-SIZE with RESET flag; 4x v4 EP; no v6 EP */
522 rx := f_ns_exp(tr_SNS_SIZE(g_nsconfig.nsei, rst_flag := true, max_nsvcs := ?,
523 num_v4 := ?, num_v6 := omit), 0);
524 } else {
525 /* expect one single SNS-SIZE with RESET flag; no v4 EP; 4x v6 EP */
526 rx := f_ns_exp(tr_SNS_SIZE(g_nsconfig.nsei, rst_flag := true, max_nsvcs := ?,
527 num_v4 := omit, num_v6 := ?), 0);
528 }
529
Alexander Couzens6c723fb2021-03-01 00:12:00 +0100530 setverdict(pass);
531 f_clean_ns_codec();
532}
533
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100534control {
Alexander Couzense48d3552021-02-27 20:01:16 +0100535 if (mp_dialect == NS2_DIALECT_STATIC_RESETBLOCK or mp_dialect == NS2_DIALECT_IPACCESS) {
536 execute( TC_tx_reset() );
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100537
Alexander Couzense48d3552021-02-27 20:01:16 +0100538 /* 48.016 7.2 Block procedure */
539 execute( TC_tx_block() );
540 execute( TC_tx_block_by_vty() );
541 execute( TC_tx_block_by_vty_reset() );
542 // execute( TC_block_other_nsvc() ); // reset, unblock, sleep(1), block over another nsvci
543 /* 48.016 7.2 Unblock procedure */
544 execute( TC_tx_unblock() );
545 execute( TC_tx_unblock_retries() );
546 // execute( TC_rx_unblock_tx_unblock() ); // wait for an rx unblock pdu, send an unblock pdu, expect unblock ack pdu
547 // execute( TC_unblockable() ); // block a NS-VCI via vty, try block procedure
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100548
Alexander Couzense48d3552021-02-27 20:01:16 +0100549 /* 48.016 7.2.1 Block Abnormal Condition */
550 /* 48.016 7.2.1 Unblock Abnormal Condition */
551 /* 48.016 7.3.1 Abnormal Condition */
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100552 execute( TC_reset_wrong_nsei() );
553 execute( TC_reset_wrong_nsvci() );
554 execute( TC_reset_wrong_nsei_nsvci() );
555 execute( TC_reset_ack_wrong_nsei() );
556 execute( TC_reset_ack_wrong_nsvci() );
557 execute( TC_reset_ack_wrong_nsei_nsvci() );
558 execute( TC_reset_retries() );
559 execute( TC_reset_on_block_reset() );
Alexander Couzense48d3552021-02-27 20:01:16 +0100560 execute( TC_ignore_reset_ack() );
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100561
Alexander Couzense48d3552021-02-27 20:01:16 +0100562 /* 48.016 7.4 Test procedure on frame relay */
563 execute( TC_tx_reset_tx_alive() );
564 execute( TC_tx_reset_rx_alive() );
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100565
Alexander Couzense48d3552021-02-27 20:01:16 +0100566 /* 48.016 7.4.1 Abnormal Condition */
567 if (mp_dialect == NS2_DIALECT_STATIC_RESETBLOCK) {
568 // execute( TC_alive_retries_multi() ); // check if alive retries works and block over an alive nsvc
569 execute( TC_alive_retries_single_reset() );
570 } else if (mp_dialect == NS2_DIALECT_IPACCESS) {
571 execute( TC_alive_retries_single_no_resp() );
572 }
573
574 execute( TC_no_reset_alive_ack() );
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100575 }
Alexander Couzenscbee32a2021-02-27 20:50:20 +0100576
577 if (mp_dialect == NS2_DIALECT_SNS) {
578 execute( TC_sns_config_success() );
Alexander Couzensd5ac5102021-02-27 20:53:37 +0100579 execute( TC_sns_bss_change_weight() );
Alexander Couzens6c723fb2021-03-01 00:12:00 +0100580 execute( TC_sns_bss_change_weight_timeout() );
Alexander Couzenscbee32a2021-02-27 20:50:20 +0100581 }
Alexander Couzens20cd41e2021-01-11 02:56:03 +0100582}
583
584}