module BTS_Tests_SMSCB { /* Integration Tests for OsmoBTS * (C) 2019 by Harald Welte * 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 * * This test suite tests the SMSCB (Cell Broadcast) related functionality of * OsmoBTS by attaching to the A-bis RSL and Um interface and emulating both * BSC and MS. */ import from Misc_Helpers all; import from General_Types all; import from Osmocom_Types all; import from GSM_Types all; import from L1CTL_PortType all; import from L1CTL_Types all; import from LAPDm_Types all; import from IPA_Emulation all; import from GSM_RR_Types all; import from L3_Templates all; import from RSL_Types all; import from PCUIF_Types all; import from PCUIF_CodecPort all; import from Osmocom_VTY_Functions all; import from BTS_Tests all; /*********************************************************************** * Cell Broadcast related tests ***********************************************************************/ /* Test parameters for one channel (e.g. Basic, Extended) */ type record CbchTestParsChan { /* list of "normal" (sent-once) SMSCB messages */ CbchTestMsgs msgs, /* default message, if any */ CbchTestMsg default_msg optional } private template (value) CbchTestParsChan t_CbchPC(template (value) CbchTestMsgs msgs, template (omit) CbchTestMsg def := omit) := { msgs := msgs, default_msg := def } /* CBCH test parameters for most of our tests */ type record CbchTestPars { /* Should we execute on SDCCH4 or SDCCH8? */ RslChannelNr chan_nr, /* Frequency Hopping parameters */ FreqHopPars fhp, /* Parameters for BASIC CBCH */ CbchTestParsChan basic, /* Parameters for EXTENDED CBCH */ CbchTestParsChan extended optional }; type record CbchTestMsg { /* config / input data */ RSL_CbCommand rsl_cb_cmd, uint2_t last_block, /* 0..3 */ octetstring payload, /* computed / result data */ CbchBlocks blocks optional }; type record of CbchTestMsg CbchTestMsgs; /* a single 22byte block within a CbchTestMsg */ type record CbchBlock { uint4_t seq_nr, /* as per TS 04.12 */ boolean is_last, OCT22 payload, boolean seen_once }; type record of CbchBlock CbchBlocks; /* compute the expected blocks for given test parameters */ private function f_cbch_compute_exp_blocks(inout CbchTestPars pars) { f_cbch_compute_exp_blocks_chan(pars.basic); if (ispresent(pars.extended)) { f_cbch_compute_exp_blocks_chan(pars.extended); } } private function f_cbch_compute_exp_blocks_chan(inout CbchTestParsChan pars_chan) { var integer i; for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { pars_chan.msgs[i].blocks := f_comp_blocks(pars_chan.msgs[i]); } if (ispresent(pars_chan.default_msg)) { pars_chan.default_msg.blocks := f_comp_blocks(pars_chan.default_msg); } } private function f_comp_blocks(in CbchTestMsg msg) return CbchBlocks { var CbchBlocks blocks := {}; var integer i; for (i := 0; i <= msg.last_block; i := i+1) { var CbchBlock block := { seq_nr := i, is_last := false, payload := substr(msg.payload, 22*i, 22), seen_once := false }; if (msg.rsl_cb_cmd == RSL_CB_CMD_SCHEDULE and i == 0) { block.seq_nr := 8; } if (i == msg.last_block) { block.is_last := true; } blocks := blocks & {block}; } return blocks; }; /* TS 48.058 Section 9.3.41 */ private function f_cbch_block_nr2rsl(uint2_t nr) return uint2_t { select (nr) { case (0) { return 1; } case (1) { return 2; } case (2) { return 3; } case (3) { return 0; } } setverdict(fail, "Invalid block number"); mtc.stop; } private function f_cbch_fn2tb(uint32_t fn) return integer { return (fn/51) mod 8; /* TS 05.02 Section 6.5.4 */ } /* Verify the CBCH TB scheduling rules of TS 05.02 Section 6.5.4 */ private function f_cbch_fn_verify(uint32_t fn, CBCH_Block cb) { var integer tb := f_cbch_fn2tb(fn); if (cb.block_type.seq_nr == 15 /* null */) { /* always permitted */ return; } else if (cb.block_type.seq_nr == 8 /* schedule */) { if (tb != 0) { setverdict(fail, "Schedule block at TB=", tb); } } else if (cb.block_type.seq_nr < 4) { if (cb.block_type.seq_nr != tb and cb.block_type.seq_nr+4 != tb) { setverdict(fail, "Normal block at wrong TB=", tb, ": ", cb); } } } private function f_rsl_smscb_default_null() runs on test_CT { var RSL_IE_CbCommandType cmd_type := valueof(ts_RSL_IE_CbCmdType(RSL_CB_CMD_DEFAULT, 1, true)); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_SMSCB_CMD(cmd_type, ''O))); } template RSL_IE t_RSL_IE_SMSCB_EXT := t_RSL_IE(RSL_IE_SMSCB_CHAN_INDICATOR, {smscb_chan_ind := 1}); private function f_smscb_setup_rsl_chan(inout CbchTestParsChan pars_chan, boolean extd := false) runs on test_CT { var integer i; var CbchTestMsg msg; var uint2_t rsl_last_block; var RSL_IE_CbCommandType cmd_type; var RSL_Message rsl; /* send SMSCB[s] via RSL */ for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { msg := pars_chan.msgs[i]; rsl_last_block := f_cbch_block_nr2rsl(msg.last_block); cmd_type := valueof(ts_RSL_IE_CbCmdType(msg.rsl_cb_cmd, rsl_last_block)); rsl := valueof(ts_RSL_SMSCB_CMD(cmd_type, msg.payload)); if (extd) { rsl.ies := rsl.ies & { valueof(t_RSL_IE_SMSCB_EXT) }; } RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); } if (ispresent(pars_chan.default_msg)) { msg := pars_chan.default_msg; rsl_last_block := f_cbch_block_nr2rsl(msg.last_block); cmd_type := valueof(ts_RSL_IE_CbCmdType(msg.rsl_cb_cmd, rsl_last_block, false)); rsl := valueof(ts_RSL_SMSCB_CMD(cmd_type, msg.payload)); if (extd) { rsl.ies := rsl.ies & { valueof(t_RSL_IE_SMSCB_EXT) }; } RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); } } private function f_vty_cbch_setup(in RslChannelNr chan_nr) runs on test_CT { if (match(chan_nr, t_RslChanNr_CBCH4(0))) { f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot 0"}, "phys_chan_config CCCH+SDCCH4+CBCH"); /* (Re)configure timeslots 1..3 as TCH/F */ for (var integer tn := 1; tn <= 3; tn := tn + 1) { f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot " & int2str(tn) }, "phys_chan_config TCH/F"); } } else if (match(chan_nr, t_RslChanNr_CBCH8(?))) { f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot 0"}, "phys_chan_config CCCH+SDCCH4"); f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot " & int2str(chan_nr.tn) }, "phys_chan_config SDCCH8+CBCH"); /* (Re)configure timeslots 1..3 (excluding the given one) as TCH/F */ for (var integer tn := 1; tn <= 3; tn := tn + 1) { if (tn == chan_nr.tn) { continue; } f_vty_config2(BSCVTY, { "network", "bts 0", "trx 0", "timeslot " & int2str(tn) }, "phys_chan_config TCH/F"); } } f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_sleep(2.0); } private function f_smscb_setup(inout CbchTestPars pars) runs on test_CT { /* Make sure we've got either SDCCH4+CBCH or SDCCH8+CBCH. * SDCCH4+CBCH can only be allocated on TS0, SDCCH8+CBCH on TS0..3. * On C0 the first timeslot shall always transmit BCCH, thus TS1..3.*/ if (not match(pars.chan_nr, (t_RslChanNr_CBCH4(0), t_RslChanNr_CBCH8(1), t_RslChanNr_CBCH8(2), t_RslChanNr_CBCH8(3)))) { setverdict(inconc, "Unhandled channel number: ", pars.chan_nr); mtc.stop; } /* Obtain frequency hopping parameters for a given timeslot */ if (mp_freq_hop_enabled and mp_transceiver_num > 1) { f_resolve_fh_params(pars.fhp, pars.chan_nr.tn); } else { pars.fhp.enabled := false; } f_cbch_compute_exp_blocks(pars); f_init_vty_bsc(); /* ensure that a CBCH is present in channel combination */ f_vty_cbch_setup(pars.chan_nr); f_init(); f_init_l1ctl(); /* Tune L1 to the given CBCH timeslot (SDCCH4+CBCH or SDCCH8+CBCH) */ if (match(pars.chan_nr, t_RslChanNr_CBCH4(0))) { f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); } else { f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED); if (pars.fhp.enabled) { L1CTL.send(ts_L1CTL_DM_EST_REQ_H1(pars.chan_nr, mp_tsc_def, pars.fhp.maio_hsn.hsn, pars.fhp.maio_hsn.maio, pars.fhp.ma)); } else { L1CTL.send(ts_L1CTL_DM_EST_REQ_H0(pars.chan_nr, mp_tsc_def, mp_trx_pars[0].arfcn)); } } /* send SMSCB[s] via RSL */ f_smscb_setup_rsl_chan(pars.basic, false); if (ispresent(pars.extended)) { f_smscb_setup_rsl_chan(pars.extended, true); } } /* construct a receive/match template for given block_nr in given msg */ private function f_get_block_template(CbchTestMsg msg, integer block_nr) return template CBCH_Block { var template CBCH_Block tr; if (block_nr < lengthof(msg.blocks)) { var CbchBlock b := msg.blocks[block_nr]; tr := tr_CBCH_Block(b.seq_nr, b.is_last, b.payload); } else { tr := tr_CBCH_Block(15, ?, ?); } return tr; } /* the heart of the CBCH test case matching engine for one channel (basic, extended) */ private function f_cbch_match(inout CbchTestParsChan pars_chan, CBCH_Block cb, integer tb) { var integer block_nr := tb mod 4; var integer i; if (not match(cb, tr_CBCH_Block)) { setverdict(fail, "Illegal CBCH Block received: ", cb); } else { var boolean matched := false; /* check if it's any of our expected blocks */ for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { if (block_nr < lengthof(pars_chan.msgs[i].blocks)) { if (match(cb, f_get_block_template(pars_chan.msgs[i], block_nr))) { log("Found block_nr ", block_nr, " of msg ", i); if (not pars_chan.msgs[i].blocks[block_nr].seen_once) { pars_chan.msgs[i].blocks[block_nr].seen_once := true; setverdict(pass); } else { setverdict(fail, "Received SMSCB twice! ", cb); } matched := true; break; } } } if (not matched) { var template CBCH_Block tr; if (ispresent(pars_chan.default_msg)) { /* it must be a block of the default message */ tr := f_get_block_template(pars_chan.default_msg, block_nr); } else { /* it must be a NULL block */ tr := tr_CBCH_Block(15, ?, ?); } if (not match(cb, tr)) { setverdict(fail, "Received unexpected CBCH block: ", cb); } else { log("Found block_nr ", block_nr, " of DEFAULT/NULL"); if (ispresent(pars_chan.default_msg) and block_nr < lengthof(pars_chan.default_msg.blocks)) { pars_chan.default_msg.blocks[block_nr].seen_once := true; } } } } } /* Report/Evaluate the per-channel CBCH test results */ private function f_cbch_report(CbchTestParsChan pars_chan, charstring id) { var integer i, j; /* verify that each block of each message has been seen once */ for (i := 0; i < lengthof(pars_chan.msgs); i := i+1) { for (j := 0; j < lengthof(pars_chan.msgs[i].blocks); j := j+1) { var CbchBlock b := pars_chan.msgs[i].blocks[j]; if (not b.seen_once) { setverdict(fail, "Timeout waiting for ", id, " CBCH block ", j, " of msg ", i); } } } if (ispresent(pars_chan.default_msg)) { /* verify that each block of default message has been seen at least once */ for (j := 0; j < lengthof(pars_chan.default_msg.blocks); j := j+1) { var CbchBlock b := pars_chan.default_msg.blocks[j]; if (not b.seen_once) { setverdict(fail, "Timeout waiting for at leaset one instance of ", "CBCH block ", j, " of DEFAULT msg"); } } } } /* shared function doing the heavy lifting for most CBCH tests */ private function f_TC_smscb(CbchTestPars pars) runs on test_CT { var L1ctlMessage dl; var integer msg_count; timer T; msg_count := lengthof(pars.basic.msgs); if (ispresent(pars.basic.default_msg)) { msg_count := msg_count + 1; } if (ispresent(pars.extended)) { msg_count := msg_count + lengthof(pars.extended.msgs); if (ispresent(pars.extended.default_msg)) { msg_count := msg_count + 1; } } f_smscb_setup(pars); /* dynamically adjust timeout based on number of messages */ T.start(5.0 + 3.0 * int2float(msg_count)); /* Expect this to show up exactly once on the basic CBCH (four blocks) */ alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(pars.chan_nr)) -> value dl { var integer tb := f_cbch_fn2tb(dl.dl_info.frame_nr); var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); log("Tb=", tb, ", CBCH: ", dl, ", block: ", cb); /* detect the proper CBCH messages; check frame number */ f_cbch_fn_verify(dl.dl_info.frame_nr, cb); if (tb < 4) { f_cbch_match(pars.basic, cb, tb); } else { if (not ispresent(pars.extended)) { /* no parameters for ext. BCCH given: ignore */ repeat; } f_cbch_match(pars.extended, cb, tb); } repeat; } [] L1CTL.receive { repeat; } [] T.timeout { f_cbch_report(pars.basic, "Basic"); if (ispresent(pars.extended)) { f_cbch_report(pars.extended, "Extended"); } } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); } private function f_TC_smscb_default_only(CbchTestPars pars) runs on test_CT { var L1ctlMessage dl; timer T := 5.0; f_smscb_setup(pars); /* ensure whatever initial NULL messages have all been drained */ f_sleep(5.0); L1CTL.clear; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(pars.chan_nr)) -> value dl { var integer tb := f_cbch_fn2tb(dl.dl_info.frame_nr); log("CBCH: ", dl); var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); /* detect the proper CBCH messages; check frame number */ f_cbch_fn_verify(dl.dl_info.frame_nr, cb); if (tb >= 4) { /* skip extended CBCH for now */ repeat; } if (not match(cb, tr_CBCH_Block)) { setverdict(fail, "Illegal CBCH Block received: ", cb); } else { var uint4_t rx_seq_nr := cb.block_type.seq_nr; var template CBCH_Block tr; if (rx_seq_nr < lengthof(pars.basic.msgs[0].blocks)) { var CbchBlock b := pars.basic.msgs[0].blocks[rx_seq_nr]; tr := tr_CBCH_Block(b.seq_nr, b.is_last, b.payload); } else { tr := tr_CBCH_Block(15, ?, ?); } if (match(cb, tr)) { setverdict(pass); /* FIXME: check that all blocks are received? */ repeat; } else { setverdict(fail, "Unexpected CBCH block ", cb, ", expected ", tr); } } } [] L1CTL.receive { repeat; } [] T.timeout {} } /* don't shut down; some tests still want to continue */ } private const CbchTestMsgs msgs_1m_1b_norm := { { RSL_CB_CMD_NORMAL, 0, '001000320f1141660c344dd3cba09a0c000000000000'O, omit } } private const CbchTestMsgs msgs_1m_2b_norm := { { RSL_CB_CMD_NORMAL, 1, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O, omit } } private const CbchTestMsgs msgs_1m_3b_norm := { { RSL_CB_CMD_NORMAL, 2, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O & '101112131415161718191a1b1c1d1e1f202223242526'O, omit } } private const CbchTestMsgs msgs_1m_4b_norm := { { RSL_CB_CMD_NORMAL, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O & '101112131415161718191a1b1c1d1e1f202223242526'O & '202122232425262728292a2b2c2d2e2f303233343536'O, omit } } private const CbchTestMsgs msgs_1m_4b_sched := { { RSL_CB_CMD_SCHEDULE, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O & '101112131415161718191a1b1c1d1e1f202223242526'O & '202122232425262728292a2b2c2d2e2f303233343536'O, omit } } private const CbchTestMsgs msgs_3m_4b_norm := { { RSL_CB_CMD_NORMAL, 3, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O & '101112131415161718191a1b1c1d1e1f202223242526'O & '201122232425262728292a2b2c2d2e2f303233343536'O, omit }, { RSL_CB_CMD_NORMAL, 3, '002000320f1141660c344dd3cba09a0c000000000000'O & '002102030405060708090a0b0c0d0e0f101213141516'O & '102112131415161718191a1b1c1d1e1f202223242526'O & '202122232425262728292a2b2c2d2e2f303233343536'O, omit }, { RSL_CB_CMD_NORMAL, 3, '003000320f1141660c344dd3cba09a0c000000000000'O & '003102030405060708090a0b0c0d0e0f101213141516'O & '103112131415161718191a1b1c1d1e1f202223242526'O & '203122232425262728292a2b2c2d2e2f303233343536'O, omit } } private const CbchTestMsgs msgs_1m_3b_default := { { RSL_CB_CMD_DEFAULT, 2, '001000320f1141660c344dd3cba09a0c000000000000'O & '000102030405060708090a0b0c0d0e0f101213141516'O & '101112131415161718191a1b1c1d1e1f202223242526'O, omit } } private const CbchTestMsg msg_default := { RSL_CB_CMD_DEFAULT, 0, '010203040506070708090a0b0c0d0e0f101112131415'O, omit } /* transmit single-block SMSCB COMMAND */ testcase TC_sms_cb_cmd_sdcch4_1block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_1b_norm)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_1block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_1b_norm)), extended := omit }; f_TC_smscb(pars); } /* transmit dual-block SMSCB COMMAND */ testcase TC_sms_cb_cmd_sdcch4_2block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_2b_norm)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_2block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_2b_norm)), extended := omit }; f_TC_smscb(pars); } /* transmit triple-block SMSCB COMMAND */ testcase TC_sms_cb_cmd_sdcch4_3block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_norm)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_3block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_3b_norm)), extended := omit }; f_TC_smscb(pars); } /* transmit quad-block SMSCB COMMAND */ testcase TC_sms_cb_cmd_sdcch4_4block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_4b_norm)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_4block() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_4b_norm)), extended := omit }; f_TC_smscb(pars); } /* transmit multiple commands of each 4 blocks */ testcase TC_sms_cb_cmd_sdcch4_multi() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_3m_4b_norm)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_multi() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_3m_4b_norm)), extended := omit }; f_TC_smscb(pars); } /* transmit multiple commands of each 4 blocks on CBCH EXTD */ testcase TC_sms_cb_cmd_sdcch4_extd_multi() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC({})), extended := valueof(t_CbchPC(msgs_3m_4b_norm)) }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_extd_multi() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC({})), extended := valueof(t_CbchPC(msgs_3m_4b_norm)) }; f_TC_smscb(pars); } /* transmit SMSCB COMMAND with SCHEDULE payload */ testcase TC_sms_cb_cmd_sdcch4_schedule() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_4b_sched)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_schedule() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_4b_sched)), extended := omit }; f_TC_smscb(pars); } /* set a DEFAULT message; verify it gets transmitted all the time */ testcase TC_sms_cb_cmd_sdcch4_default_only() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_default)), extended := omit }; f_TC_smscb_default_only(pars); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); } testcase TC_sms_cb_cmd_sdcch8_default_only() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_default)), extended := omit }; f_TC_smscb_default_only(pars); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); } testcase TC_sms_cb_cmd_sdcch4_default_and_normal() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_norm, msg_default)), extended := omit }; f_TC_smscb(pars); } testcase TC_sms_cb_cmd_sdcch8_default_and_normal() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH8(2)), basic := valueof(t_CbchPC(msgs_1m_3b_norm, msg_default)), extended := omit }; f_TC_smscb(pars); } /* first set a DEFAULT message, then disable it again */ testcase TC_sms_cb_cmd_sdcch4_default_then_null() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_default)), extended := omit }; var L1ctlMessage dl; timer T := 5.0; f_TC_smscb_default_only(pars); /* disable DEFAULT message; switch back to NULL */ f_rsl_smscb_default_null(); /* ensure whatever initial non-NULL messages have all been drained */ f_sleep(5.0); L1CTL.clear; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_CBCH4(0))) -> value dl { log("CBCH: ", dl); var CBCH_Block cb := dec_CBCH_Block(dl.payload.data_ind.payload); /* detect the proper CBCH messages; check frame number */ f_cbch_fn_verify(dl.dl_info.frame_nr, cb); if (not match(cb, tr_CBCH_Block)) { setverdict(fail, "Illegal CBCH Block received: ", cb); } else { if (not match(cb, tr_CBCH_Block(15, ?, ?))) { setverdict(fail, "Unexpected non-NULL CBCH block received"); } repeat; } } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(pass); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); } /* Verify there are no CBCH load indications if no CBCH is present */ testcase TC_cbch_load_idle_no_cbch() runs on test_CT { var ASP_RSL_Unitdata rx_ud; timer T := 10.0; f_init(); f_init_vty_bsc(); T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive { repeat; } [] T.timeout { setverdict(pass); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Verify the CBCH load indications of an idle cell (without CBCH load) */ function f_TC_cbc_load_idle() runs on test_CT { template integer tr_slot_count := (2 .. 15); const integer min_load_ind := 4; var integer basic_count := 0; var integer extd_count := 0; var ASP_RSL_Unitdata rx_ud; timer T := 10.0; f_init(); RSL_CCHAN.clear; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC(false, tr_slot_count))) { basic_count := basic_count + 1; repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD(false, tr_slot_count))) -> value rx_ud { extd_count := extd_count + 1; repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive { repeat; } [] T.timeout { if ((basic_count >= min_load_ind) and (extd_count >= min_load_ind)) { setverdict(pass); } else { setverdict(fail, "Insufficient number of CBCH LOAD IND: ", "BASIC=", basic_count, " EXTD=", extd_count); } } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_cbc_sdcch4_load_idle() runs on test_CT { f_init_vty_bsc(); f_vty_cbch_setup(valueof(ts_RslChanNr_CBCH4(0))); f_TC_cbc_load_idle(); } testcase TC_cbc_sdcch8_load_idle() runs on test_CT { f_init_vty_bsc(); f_vty_cbch_setup(valueof(ts_RslChanNr_CBCH8(2))); f_TC_cbc_load_idle(); } /* Verify CBCH overload indications are sent when sending too many SMS CB CMD */ function f_TC_cbc_load_overload(CbchTestPars pars) runs on test_CT { template integer tr_slot_count_basic := (11 .. 13); template integer tr_slot_count_extd := (2 .. 15); const integer min_load_ind := 4; var integer basic_count := 0; var integer extd_count := 0; var ASP_RSL_Unitdata rx_ud; timer T_total := 10.0; timer T_retransmit := 0.2; timer T_initial_guard := 2.0; var integer i; f_init(); /* send tons of SMSCB Command */ for (i := 0; i < 30; i := i+1) { f_smscb_setup_rsl_chan(pars.basic); } /* keep sending SMSCB Commands for another two seconds */ T_initial_guard.start; T_retransmit.start; alt { [] T_retransmit.timeout { f_smscb_setup_rsl_chan(pars.basic); T_retransmit.start; repeat; } [] T_initial_guard.timeout { } } /* clear any pending messages (where load may not have peaked yet) */ RSL_CCHAN.clear; /* keep sending SMSCB Commands while verifying LOAD INDICATIONS */ T_total.start; T_retransmit.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC(true, tr_slot_count_basic))) { basic_count := basic_count + 1; repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD(false, tr_slot_count_extd))) { extd_count := extd_count + 1; repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_BASIC)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CBCH_LOAD_IND_EXTD)) -> value rx_ud { setverdict(fail, "Received unexpected CBCH LOAD IND: ", rx_ud); } [] RSL_CCHAN.receive { repeat; } [] T_retransmit.timeout { f_smscb_setup_rsl_chan(pars.basic); T_retransmit.start; repeat; } [] T_total.timeout { if ((basic_count >= min_load_ind) and (extd_count >= min_load_ind)) { setverdict(pass); } else { setverdict(fail, "Insufficient number of CBCH LOAD IND: ", "BASIC=", basic_count, " EXTD=", extd_count); } } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_cbc_sdcch4_load_overload() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(0)), basic := valueof(t_CbchPC(msgs_1m_3b_norm)), extended := omit }; f_init_vty_bsc(); f_vty_cbch_setup(pars.chan_nr); f_TC_cbc_load_overload(pars); } testcase TC_cbc_sdcch8_load_overload() runs on test_CT { var CbchTestPars pars := { chan_nr := valueof(ts_RslChanNr_CBCH4(2)), basic := valueof(t_CbchPC(msgs_1m_3b_norm)), extended := omit }; f_init_vty_bsc(); f_vty_cbch_setup(pars.chan_nr); f_TC_cbc_load_overload(pars); } private template GsmRrMessage tr_PagingType1 := { header := t_RrHeader(PAGING_REQUEST_TYPE_1, ?), payload :=? }; /* we expect four blocks of 14 bytes, let's fill them with content easily distinguishable */ const octetstring c_etws_seg0 := '000102030405060708090a0b0c0d'O; const octetstring c_etws_seg1 := '101112131415161718191a1b1c1d'O; const octetstring c_etws_seg2 := '202122232425262728292a2b2c2d'O; const octetstring c_etws_seg3 := '303132333435363738393a3b3c3d'O; const octetstring c_etws := c_etws_seg0 & c_etws_seg1 & c_etws_seg2 & c_etws_seg3; /* Ensure only Paging Type 1 with segmented ETWS Primary Notification are sent after RSL_OSMO_ETWS_CMD */ testcase TC_etws_p1ro() runs on test_CT { /* decoding the actual entire P1 rest octets by manually generated code is * too much effort; instead simply do a binary compare to this constant */ const bitstring c_P1RO_hdr := '00101011101'B; var integer seg_received[4] := { 0, 0, 0, 0 }; var L1ctlMessage dl; timer T := 10.0; f_init(); f_init_l1ctl(); f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); /* wait for a bit until old non-ETWS Paging messages are gone */ f_sleep(1.0); L1CTL.clear; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { var GsmRrMessage l3 := dec_GsmRrMessage(dl.payload.data_ind.payload); select (l3) { case (tr_PAG_REQ1(tr_MI_LV(t_MI_NoIdentity()))) { var octetstring p1ro := l3.payload.pag_req_1.rest_octets; var bitstring midamble := oct2bit(substr(p1ro, 0, 3)); var octetstring segment := substr(p1ro, 3, lengthof(p1ro)-3); var BIT1 not_first := substr(midamble, 11, 1); var integer seg_nr := bit2int(substr(midamble, 12, 4)); var boolean err := false; if (substr(midamble, 0, 11) != c_P1RO_hdr) { setverdict(fail, "Received unexpected P1 RO header ", midamble); } if (not_first == '1'B) { select (seg_nr) { case (2) { if (segment != c_etws_seg1) { err := true } else { seg_received[1] := seg_received[1] + 1; }} case (3) { if (segment != c_etws_seg2) { err := true } else { seg_received[2] := seg_received[2] + 1; }} case (4) { if (segment != c_etws_seg3) { err := true } else { seg_received[3] := seg_received[3] + 1; }} case else { setverdict(fail, "Unknown segment Nr ", seg_nr); } } if (err) { setverdict(fail, "Unexpected segment ", seg_nr, ": ", segment); } } else { if (seg_nr != 4) { setverdict(fail, "Invalid number of segments ", seg_nr); err := true; } if (segment != c_etws_seg0) { setverdict(fail, "Invalid first segment ", segment); err := true; } if (not err) { seg_received[0] := seg_received[0] + 1; } } } case (tr_PagingType1) { setverdict(fail, "Received unexpected PAGING TYPE 1: ", l3); } } repeat; } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(pass); } } log("Quantity of received ETWS PN segments: ", seg_received); var integer i; for (i := 0; i < 4; i := i+1) { if (seg_received[i] < 15) { setverdict(fail, "Segment ", i, " not received often enough"); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Ensure only Paging Type 1 without ETWS Primary Notification are sent after disabling them */ testcase TC_etws_p1ro_end() runs on test_CT { /* we expect four blocks of 14 bytes, let's fill them with content easily * distinguishable */ /* decoding the actual entire P1 rest octets by manually generated code is * too much effort; instead simply do a binary compare to this constant */ const bitstring c_P1RO_hdr := '00101011101'B; var L1ctlMessage dl; timer T := 10.0; f_init(); f_init_l1ctl(); f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); /* wait for a bit until old non-ETWS Paging messages are gone */ f_sleep(3.0); /* disable the ETWS PN again */ RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(''O))); f_sleep(2.0); T.start; L1CTL.clear; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { var GsmRrMessage l3 := dec_GsmRrMessage(dl.payload.data_ind.payload); select (l3) { case (tr_PAG_REQ1(tr_MI_LV(t_MI_NoIdentity()))) { repeat; } case (tr_PagingType1) { setverdict(fail, "Received non-empty PT1 after disabling ETWS PN: ", l3); } } repeat; } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(pass); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Ensure ETWS Primary Notification is passed from RSL to PCU interface */ testcase TC_etws_pcu() runs on test_CT { timer T := 10.0; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL, ccch_mode := CCCH_MODE_COMBINED_CBCH); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_OSMO_ETWS_CMD(c_etws))); T.start; alt { [] PCU.receive(t_SD_PCUIF(g_pcu_conn_id, tr_PCUIF_APP_INFO_REQ(0, 0, c_etws))) { setverdict(pass); } [] PCU.receive(t_SD_PCUIF(g_pcu_conn_id, tr_PCUIF_APP_INFO_REQ(?, ?, ?))) { setverdict(fail, "PCU socket received invalid APP INFO"); } [] PCU.receive { repeat; } [] T.timeout { setverdict(fail, "PCU socket timeout receiving APP INFO (ETWS)"); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* SMSCB TODO: * multiple SMS BC CMD at the same time: Ensure all of them are sent exactly once * extended CBCH vs. normal CBCH * */ control { execute( TC_cbch_load_idle_no_cbch() ); execute( TC_sms_cb_cmd_sdcch4_1block() ); execute( TC_sms_cb_cmd_sdcch4_2block() ); execute( TC_sms_cb_cmd_sdcch4_3block() ); execute( TC_sms_cb_cmd_sdcch4_4block() ); execute( TC_sms_cb_cmd_sdcch4_multi() ); execute( TC_sms_cb_cmd_sdcch4_schedule() ); execute( TC_sms_cb_cmd_sdcch4_default_only() ); execute( TC_sms_cb_cmd_sdcch4_default_and_normal() ); execute( TC_sms_cb_cmd_sdcch4_default_then_null() ); execute( TC_cbc_sdcch4_load_idle() ); execute( TC_cbc_sdcch4_load_overload() ); execute( TC_sms_cb_cmd_sdcch8_1block() ); execute( TC_sms_cb_cmd_sdcch8_2block() ); execute( TC_sms_cb_cmd_sdcch8_3block() ); execute( TC_sms_cb_cmd_sdcch8_4block() ); execute( TC_sms_cb_cmd_sdcch8_multi() ); execute( TC_sms_cb_cmd_sdcch8_schedule() ); execute( TC_sms_cb_cmd_sdcch8_default_only() ); execute( TC_sms_cb_cmd_sdcch8_default_and_normal() ); execute( TC_cbc_sdcch8_load_idle() ); execute( TC_cbc_sdcch8_load_overload() ); execute( TC_etws_p1ro() ); execute( TC_etws_p1ro_end() ); execute( TC_etws_pcu() ); } }