| module BTS_Tests_SMSCB { |
| |
| /* Integration Tests for OsmoBTS |
| * (C) 2019 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. |
| * |
| * 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 RSL_Types all; |
| |
| import from Osmocom_VTY_Functions all; |
| |
| import from BTS_Tests all; |
| |
| /*********************************************************************** |
| * Cell Broadcast related tests |
| ***********************************************************************/ |
| |
| type record CbchTestPars { |
| boolean use_sdcch4, |
| CbchTestMsgs msgs |
| }; |
| |
| 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) { |
| var integer i; |
| |
| for (i := 0; i < lengthof(pars.msgs); i := i+1) { |
| pars.msgs[i].blocks := f_comp_blocks(pars.msgs[i]); |
| } |
| } |
| 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_RSL_UD(ts_RSL_SMSCB_CMD(cmd_type, ''O))); |
| } |
| |
| private function f_smscb_setup(inout CbchTestPars pars) runs on test_CT { |
| var integer i; |
| |
| f_cbch_compute_exp_blocks(pars); |
| |
| f_init_vty_bsc(); |
| /* ensure that a CBCH is present in channel combination */ |
| if (pars.use_sdcch4) { |
| f_vty_config2(BSCVTY, {"network", "bts 0", "trx 0", "timeslot 0"}, |
| "phys_chan_config CCCH+SDCCH4+CBCH"); |
| f_vty_config2(BSCVTY, {"network", "bts 0", "trx 0", "timeslot 6"}, |
| "phys_chan_config SDCCH8"); |
| } else { |
| 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 6"}, |
| "phys_chan_config SDCCH8+CBCH"); |
| } |
| f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); |
| f_sleep(2.0); |
| f_init(testcasename()); |
| |
| f_init_l1ctl(); |
| f_l1_tune(L1CTL); |
| /* FIXME: switch to dedicated mode for SDCCH/8 */ |
| |
| /* send SMSCB[s] via RSL */ |
| for (i := 0; i < lengthof(pars.msgs); i := i+1) { |
| var CbchTestMsg msg := pars.msgs[i]; |
| var uint2_t rsl_last_block := f_cbch_block_nr2rsl(msg.last_block); |
| var RSL_IE_CbCommandType cmd_type := |
| valueof(ts_RSL_IE_CbCmdType(msg.rsl_cb_cmd, rsl_last_block)); |
| RSL_CCHAN.send(ts_RSL_UD(ts_RSL_SMSCB_CMD(cmd_type, msg.payload))); |
| } |
| } |
| |
| private function f_smscb_cleanup() runs on test_CT { |
| /* reset timeslot 0 channel combination to default */ |
| 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 6"}, |
| "phys_chan_config SDCCH8"); |
| } |
| |
| /* shared function doing the heavy lifting for most CBCH tests */ |
| private function f_TC_smscb(CbchTestPars pars) runs on test_CT { |
| var L1ctlDlMessage dl; |
| var integer i, j; |
| timer T := 5.0 * int2float(lengthof(pars.msgs)); |
| |
| f_smscb_setup(pars); |
| |
| T.start; |
| /* Expect this to show up exactly once on the basic CBCH (four blocks) */ |
| alt { |
| /* FIXME: Channel Nr for SDCCH/8 */ |
| [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_CBCH(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 { |
| var boolean matched := false; |
| /* ignore NULL messages */ |
| if (match(cb, tr_CBCH_Block(15, ?, ?))) { repeat; } |
| for (i := 0; i < lengthof(pars.msgs); i := i+1) { |
| for (j := 0; j < lengthof(pars.msgs[i].blocks); j := j+1) { |
| var CbchBlock b := pars.msgs[i].blocks[j]; |
| if (match(cb, tr_CBCH_Block(b.seq_nr, b.is_last, b.payload))) { |
| if (not pars.msgs[i].blocks[j].seen_once) { |
| pars.msgs[i].blocks[j].seen_once := true; |
| setverdict(pass); |
| } else { |
| setverdict(fail, "Received SMSCB twice! ", cb); |
| } |
| matched := true; |
| continue; |
| } |
| } |
| } |
| if (not matched) { |
| setverdict(fail, "Received unexpected CBCH block: ", cb); |
| } |
| repeat; |
| } |
| } |
| [] L1CTL.receive { repeat; } |
| [] T.timeout { |
| for (i := 0; i < lengthof(pars.msgs); i := i+1) { |
| for (j := 0; j < lengthof(pars.msgs[i].blocks); j := j+1) { |
| var CbchBlock b := pars.msgs[i].blocks[j]; |
| if (not b.seen_once) { |
| setverdict(fail, "Timeout waiting for CBCH"); |
| } |
| } |
| } |
| } |
| } |
| |
| f_smscb_cleanup(); |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| |
| private function f_TC_smscb_default_only(CbchTestPars pars) runs on test_CT { |
| var L1ctlDlMessage 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(t_RslChanNr_CBCH(0))) -> 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.msgs[0].blocks)) { |
| var CbchBlock b := pars.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 {} |
| } |
| |
| f_smscb_cleanup(); |
| /* 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 } |
| } |
| |
| /* transmit single-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_1block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_1b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_1block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := msgs_1m_1b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit dual-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_2block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_2b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_2block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := msgs_1m_2b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit triple-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_3block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_3b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_3block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := msgs_1m_3b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| |
| /* transmit quad-block SMSCB COMMAND */ |
| testcase TC_sms_cb_cmd_sdcch4_4block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_4b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_4block() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := msgs_1m_4b_norm |
| }; |
| 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 := { |
| use_sdcch4 := true, |
| msgs := msgs_3m_4b_norm |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_multi() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := 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 := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_4b_sched |
| }; |
| f_TC_smscb(pars); |
| } |
| testcase TC_sms_cb_cmd_sdcch8_schedule() runs on test_CT { |
| var CbchTestPars pars := { |
| use_sdcch4 := false, |
| msgs := msgs_1m_4b_sched |
| }; |
| 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 := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_3b_default |
| }; |
| 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 := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_3b_default |
| }; |
| f_TC_smscb_default_only(pars); |
| Misc_Helpers.f_shutdown(__BFILE__, __LINE__, pass); |
| } |
| |
| /* 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 := { |
| use_sdcch4 := true, |
| msgs := msgs_1m_3b_default |
| }; |
| var L1ctlDlMessage 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_CBCH(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); |
| } |
| |
| |
| |
| /* 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_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_then_null() ); |
| if (false) { /* FIXME: SDCCH/8 support broken, needs trxcon + L1CTL work */ |
| 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() ); |
| } |
| } |
| |
| |
| } |