module BTS_Tests { /* Integration Tests for OsmoBTS * (C) 2018-2019 by Harald Welte * contributions by Vadim Yanitskiy and 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 * * This test suite tests OsmoBTS by attaching to the external interfaces * such as Abis RSL, PCU, VTY as well as by attaching to a MS L1 implementation * using the L1CTL protocol/interface. * * You can run the tests with * a) osmo-bts-trx + trxcon + fake_trx (without any hardware) * b) any osmo-bts-* + OsmocomBB layer1 + osmocon (with real BTS hardware) * c) osmo-bts-virtual + virt_phy (without any hardware) * * Some of the tests will only run on a subset of those three configurations * due to limitations in the respective L1. */ import from Misc_Helpers all; import from General_Types all; import from GSM_Types all; import from GSM_RR_Types all; import from Osmocom_Types all; import from GSM_Types all; import from GSM_RestOctets all; import from GSM_SystemInformation all; import from L1CTL_PortType all; import from L1CTL_Types all; import from LAPDm_Types all; import from LAPDm_RAW_PT all; import from Native_Functions all; import from Osmocom_CTRL_Adapter all; import from Osmocom_CTRL_Functions all; import from RSL_Types all; import from RTP_Types all; import from IPA_Types all; import from IPA_Emulation all; import from IPA_Testing all; import from RSL_Emulation all; import from RTP_Emulation all; import from AMR_Types all; import from OSMUX_Types all; import from OSMUX_CodecPort all; import from OSMUX_CodecPort_CtrlFunct all; import from OSMUX_Emulation all; import from IPL4asp_Types all; import from TRXC_Types all; import from TRXC_CodecPort all; import from TRXC_CodecPort_CtrlFunct all; import from PCUIF_Types all; import from PCUIF_CodecPort all; import from UD_Types all; import from MobileL3_CommonIE_Types all; import from MobileL3_RRM_Types all; import from MobileL3_Types all; import from L3_Templates all; import from L3_Common all; import from MobileL3_GMM_SM_Types all; import from Osmocom_VTY_Functions all; import from TELNETasp_PortType all; import from BTS_Tests_LAPDm all; friend module BTS_Tests_ASCI; friend module BTS_Tests_SMSCB; friend module BTS_Tests_VAMOS; friend module BTS_Tests_virtphy; friend module BTS_Tests_LAPDm; friend module BTS_Tests_perf; friend module BTS_Tests_OML; /* The tests assume a BTS with the following timeslot configuration: * TS0 : Combined CCCH + SDCCH/4 * TS1 : TCH/F * TS2 : TCH/F * TS3 : TCH/F_PDCH (IPA Style) * TS4 : TCH/F_TCH/H_PDCH (Osmocom Style) * TS5 : TCH/H * TS6 : SDCCH/8 * TS7 : PDCH */ modulepar { charstring mp_rsl_ip := "127.0.0.2"; integer mp_rsl_port := 3003; charstring mp_bts_trxc_ip := "127.0.0.1"; integer mp_bts_trxc_port := 5701; charstring mp_pcu_socket := PCU_SOCK_DEFAULT; charstring mp_ctrl_ip := "127.0.0.1"; integer mp_ctrl_port := 4238; charstring mp_bsc_ctrl_ip := "127.0.0.1"; integer mp_bsc_ctrl_port := 4249; charstring mp_rtpem_bind_ip := "127.0.0.1"; integer mp_rtpem_bind_port := 6766; charstring mp_osmuxem_bind_ip := "127.0.0.1"; integer mp_osmuxem_bind_port := 1984; integer mp_tolerance_rxqual := 1; integer mp_tolerance_rxlev := 3; integer mp_tolerance_timing_offset_256syms := 0; integer mp_rxlev_exp := 57; integer mp_ul_rxlev_exp := 10; integer mp_ms_power_level_exp := 7; integer mp_bts_tx_nom_pwr_exp := 50; /* Expected Tx Nominal Output Power of the BTS, in dBm */ integer mp_bts_tx_pwr_att_exp := 20; /* Expected Tx Power attenuation wrt to Tx Nominal Output Power, in dB */ integer mp_ms_actual_ta_exp := 0; integer mp_timing_offset_256syms_exp := 512; integer mp_uplink_power_target := -75; integer mp_uplink_power_hysteresis := 8; /* -83 .. -67 */ /* Time to wait for RSL conn from BTS during startup of test */ float mp_ipa_up_timeout := 15.0; float mp_ipa_up_delay := 0.0; /* false for now, as only virtphy supports it, not calypso-l1 nor trxcon */ boolean mp_l1_supports_gprs := false; /* how many transceivers do we expect to connect */ integer mp_transceiver_num := 1; /* frequency hopping status */ boolean mp_freq_hop_enabled := false; /* frequency hopping parameters */ FreqHopConfig mp_fh_config; /* configuration for each individual transceiver */ TrxPars mp_trx_pars; /* see BTS_Tests.default */ /* default Training Sequence Code */ GsmTsc mp_tsc_def := 7; /* Default interference boundaries in osmo-bsc (in dBm): * 0(-115) X1(-109) X2(-103) X3(-97) X4(-91) X5(-85) * Default interference level reported by fake_trx.py: * 0: -109 < -115 dBm * X1: -109 < -109 dBm * X2: -109 < -103 dBm <-- stop here * X3: -109 < -97 dBm * X4: -109 < -91 dBm * X5: -109 < -85 dBm * So all channels should be in band 2. */ uint3_t mp_interf_band := 2; /* T3105 timer (see 3GPP TS 44.018, section 11.1.2) */ integer mp_ho_t3105_ms := 100; /* Ny1 counter (see 3GPP TS 44.018, section 11.1.3) */ integer mp_ho_ny1 := 10; } type record of RslChannelNr ChannelNrs; type component test_CT extends CTRL_Adapter_CT { /* IPA Emulation component underneath RSL */ var IPA_Emulation_CT vc_IPA; /* RSL Emulation component (for ConnHdlr tests) */ var RSL_Emulation_CT vc_RSL; /* Direct RSL_CCHAN_PT */ port RSL_CCHAN_PT RSL_CCHAN; /* L1CTL port (for classic tests) */ port L1CTL_PT L1CTL; /* Optional TRXC connection to FakeTRX (BTS side) */ port TRXC_CODEC_PT BTS_TRXC; var integer g_bts_trxc_conn_id; /* VTY connections to both BTS and BSC */ port TELNETasp_PT BTSVTY; port TELNETasp_PT BSCVTY; /* PCU Interface of BTS */ port PCUIF_CODEC_PT PCU; var integer g_pcu_conn_id; /* Last PCU INFO IND we received */ var PCUIF_Message g_pcu_last_info; /* SI configuration */ var SystemInformationConfig si_cfg := { bcch_extended := false, si1_present := false, si2bis_present := false, si2ter_present := false, si2quater_present := false, si7_present := false, si8_present := false, si9_present := false, si13_present := false, si13alt_present := false, si15_present := false, si16_present := false, si17_present := false, si2n_present := false, si21_present := false, si22_present := false }; /* all logical channels available on the BTS */ var ChannelNrs g_AllChannels; var ChannelNrs g_AllChanTypes; } /* an individual call / channel */ type component ConnHdlr extends RSL_DchanHdlr, lapdm_test_CT { port L1CTL_PT L1CTL; /* Optional TRXC connection to FakeTRX (BTS side) */ port TRXC_CODEC_PT BTS_TRXC; var integer g_bts_trxc_conn_id; /* port to be initialized optionally to access BSC VTY */ port TELNETasp_PT BSCVTY; timer g_Tguard; timer g_Tmeas_exp := 2.0; /* >= 103 SACCH multiframe ~ 500ms */ var ConnHdlrPars g_pars; var uint8_t g_next_meas_res_nr := 0; var boolean g_first_meas_res := true; /* PCU Interface of BTS */ port PCUIF_CODEC_PT PCU; var RTP_Emulation_CT vc_RTPEM; port RTPEM_CTRL_PT RTPEM_CTRL; port RTPEM_DATA_PT RTPEM_DATA; var OSMUX_Emulation_CT vc_OsmuxEM; port OsmuxEM_CTRL_PT OsmuxEM_CTRL; port OsmuxEM_DATA_PT OsmuxEM_DATA; } private function f_init_rsl(charstring id) runs on test_CT { var bitstring trx_mask := '00000000'B; var bitstring rfind_mask := '00000000'B; var integer trx_count := 0; var integer rfind_count := 0; var RSLEm_Event ev; var ASP_RSL_Unitdata rx_ud; timer T; vc_IPA := IPA_Emulation_CT.create(id & "-RSL-IPA"); vc_RSL := RSL_Emulation_CT.create(id & "-RSL"); map(vc_IPA:IPA_PORT, system:IPA_CODEC_PT); connect(vc_IPA:IPA_RSL_PORT, vc_RSL:IPA_PT); connect(self:RSL_CCHAN, vc_RSL:CCHAN_PT); vc_IPA.start(IPA_Emulation.main_server(mp_rsl_ip, mp_rsl_port)); vc_RSL.start(RSL_Emulation.main(false)); /* We expect (N = mp_transceiver_num) IPA/RSL transceiver connections here. * See https://gerrit.osmocom.org/q/Ib5ad31388ae25399ad09739aac3fdcb0b3a1f78b. */ T.start(mp_ipa_up_timeout); alt { /* These events are sent by the RSL_Emulation_CT */ [] RSL_CCHAN.receive(tr_RSLEm_EV(RSLEM_EV_TRX_UP, ?)) -> value ev { /* Make sure that all transceivers use unique stream ID */ if (trx_mask[enum2int(ev.sid)] == '1'B) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Duplicate RSL stream ID (", ev.sid, ")")); } trx_mask[enum2int(ev.sid)] := '1'B; trx_count := trx_count + 1; log(trx_count, "/", mp_transceiver_num, " transceiver(s) connected"); repeat; } /* This message (RF RESource INDication) is sent by the IUT itself */ [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RF_RES_IND, ?)) -> value rx_ud { if (trx_mask[enum2int(rx_ud.streamId)] == '0'B) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Got RF Resource Indication before RSLEM_EV_TRX_UP (", rx_ud.streamId, ")")); } if (rfind_mask[enum2int(rx_ud.streamId)] == '1'B) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Duplicate RF Resource Indication ID (", rx_ud.streamId, ")")); } rfind_mask[enum2int(rx_ud.streamId)] := '1'B; rfind_count := rfind_count + 1; log(rfind_count, "/", mp_transceiver_num, " RF Resource Indication(s) received"); if (rfind_count < mp_transceiver_num) { repeat; } } /* osmo-bts may send us CCCH LOAD INDication or whatever else */ [] RSL_CCHAN.receive(ASP_RSL_Unitdata:?) { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RSL bring up"); } } } friend type record length(1 .. 256) of TrxParsItem TrxPars; friend type record TrxParsItem { GsmArfcn arfcn, TrxTsPars ts }; friend type record length(8) of TrxTsParsItem TrxTsPars; friend type record TrxTsParsItem { PchanConfig config }; friend type record FreqHopPars { /* Whether frequency hopping is in use */ boolean enabled, /* Mobile Allocation Index Offset / Hopping Sequence Number */ MaioHsn maio_hsn, /* MA bitmap to be indicated in RR Immediate Assignment */ MobileAllocationLV ma_map, /* The actual Mobile Allocation (ARFCN list) to be used */ L1ctlMA ma }; type record ConnHdlrPars { uint8_t trx_nr, RslChannelNr chan_nr, RSL_IE_ChannelMode chan_mode, RSL_IE_MultirateCfg mr_conf optional, float t_guard, ConnL1Pars l1_pars, TestSpecUnion spec optional, RSL_IE_EncryptionInfo encr optional, BtsBand bts0_band optional, /* Training Sequence Code */ GsmTsc tsc, /* Frequency hopping parameters */ FreqHopPars fhp, OsmuxCID loc_osmux_cid, OsmuxCID rem_osmux_cid optional }; /* Test-specific parameters */ friend type union TestSpecUnion { RllTestCase rll, TopTestCase top } private template (value) RachControlParameters ts_RachCtrl_default := { max_retrans := RACH_MAX_RETRANS_7, tx_integer := '1001'B, /* 12 slots */ cell_barr_access := false, re_not_allowed := true, acc := '0000010000000000'B }; private template (value) CellSelectionParameters ts_CellSelPar_default := { cell_resel_hyst_2dB := 2, ms_txpwr_max_cch := mp_ms_power_level_exp, acs := '0'B, neci := true, rxlev_access_min := 0 } private template (value) LocationAreaIdentification ts_LAI_default := { mcc_mnc := '262F42'H, lac := 42 } private template (value) GPRSIndicator ts_GPRSIndicator_def := { ra_colour := 0, si13_pos := '0'B } private template (value) SI3RestOctets ts_SI3RestOctets_def modifies ts_SI3RestOctets := { gprs_ind := { presence := CSN1_H, ind := ts_GPRSIndicator_def } } private template (value) SI4RestOctets ts_SI4RestOctets_def modifies ts_SI4RestOctets := { gprs_ind := { presence := CSN1_H, ind := ts_GPRSIndicator_def } } /* Default SYSTEM INFORMATION 3 */ private template (value) SystemInformation ts_SI3_default := { header := ts_RrHeader(SYSTEM_INFORMATION_TYPE_3, 18), payload := { si3 := { cell_id := 23, lai := ts_LAI_default, ctrl_chan_desc := { msc_r99 := true, att := true, bs_ag_blks_res := 1, ccch_conf := CCHAN_DESC_1CCCH_COMBINED, si22ind := false, cbq3 := CBQ3_IU_MODE_NOT_SUPPORTED, spare := '00'B, bs_pa_mfrms := 0, /* 2 multiframes */ t3212 := 1 /* 6 minutes */ }, cell_options := { dn_ind := false, pwrc := false, dtx := MS_MAY_USE_UL_DTX, radio_link_tout_div4 := (32/4)-1 }, cell_sel_par := ts_CellSelPar_default, rach_control := ts_RachCtrl_default, rest_octets := valueof(ts_SI3RestOctets_def) } } } private template (value) SystemInformation ts_SI2_default := { header := ts_RrHeader(SYSTEM_INFORMATION_TYPE_2, 22), payload := { si2 := { bcch_freq_list := '00000000000000000000000000000000'O, ncc_permitted := '11111111'B, rach_control := ts_RachCtrl_default } } } private template (value) SystemInformation ts_SI4_default := { header := ts_RrHeader(SYSTEM_INFORMATION_TYPE_4, 12), /* no CBCH / restoct */ payload := { si4 := { lai := ts_LAI_default, cell_sel_par := ts_CellSelPar_default, rach_control := ts_RachCtrl_default, cbch_chan_desc := omit, cbch_mobile_alloc := omit, rest_octets := valueof(ts_SI4RestOctets_def) } } } friend function f_rsl_bcch_fill_raw(RSL_IE_SysinfoType rsl_si_type, octetstring si_enc) runs on test_CT { log("Setting ", rsl_si_type, ": ", si_enc); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_BCCH_INFO(rsl_si_type, si_enc))); } private function f_rsl_bcch_fill(RSL_IE_SysinfoType rsl_si_type, template (value) SystemInformation si_dec) runs on test_CT { var octetstring si_enc := enc_SystemInformation(valueof(si_dec)); log("Setting ", rsl_si_type, ": ", si_dec); f_rsl_bcch_fill_raw(rsl_si_type, si_enc); } friend function f_init_vty(charstring id) runs on test_CT { map(self:BTSVTY, system:BTSVTY); f_vty_set_prompts(BTSVTY); f_vty_transceive(BTSVTY, "enable"); } friend function f_init_vty_bsc() runs on test_CT { map(self:BSCVTY, system:BSCVTY); f_vty_set_prompts(BSCVTY, "OsmoBSC"); f_vty_transceive(BSCVTY, "enable"); } friend function f_connhdlr_init_vty_bsc() runs on ConnHdlr { map(self:BSCVTY, system:BSCVTY); f_vty_set_prompts(BSCVTY, "OsmoBSC"); f_vty_transceive(BSCVTY, "enable"); } /* PCU socket may at any time receive a new INFO.ind */ friend altstep as_pcu_info_ind(PCUIF_CODEC_PT pt, integer pcu_conn_id, out PCUIF_Message pcu_last_info) { var PCUIF_send_data sd; [] pt.receive(t_SD_PCUIF(pcu_conn_id, tr_PCUIF_INFO_IND(0, ?))) -> value sd { pcu_last_info := sd.data; } [] pt.receive(t_SD_PCUIF(pcu_conn_id, tr_PCUIF_INFO_IND(?, ?, ?))) -> value sd { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Invalid PCU Version/BTS Number received"); } } private function f_init_pcu(PCUIF_CODEC_PT pt, charstring id, out integer pcu_conn_id, out PCUIF_Message pcu_last_info) { timer T := 2.0; var PCUIF_send_data sd; if (mp_pcu_socket == "") { pcu_conn_id := -1; return; } pcu_conn_id := f_pcuif_connect(pt, mp_pcu_socket); T.start; alt { [] as_pcu_info_ind(pt, pcu_conn_id, pcu_last_info); [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU INFO_IND"); } } } private function f_init_trxc(TRXC_CODEC_PT pt, charstring id, out integer trxc_conn_id) { var Result res; res := TRXC_CodecPort_CtrlFunct.f_IPL4_connect(pt, mp_bts_trxc_ip, mp_bts_trxc_port, "", -1, -1, { udp := {} }, {}); if (not ispresent(res.connId)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Could not connect to the control (TRXC) interface " & "of FakeTRX, check your configuration"); } trxc_conn_id := res.connId; } private function f_gen_chans_for_ts(uint3_t tn, PchanConfig config) return ChannelNrs { select (config) { case (GSM_PCHAN_CCCH_SDCCH4) { return { valueof(ts_RslChanNr_SDCCH4(tn, 0)), valueof(ts_RslChanNr_SDCCH4(tn, 1)), valueof(ts_RslChanNr_SDCCH4(tn, 2)), valueof(ts_RslChanNr_SDCCH4(tn, 3)) }; } case (GSM_PCHAN_SDCCH8) { return { valueof(ts_RslChanNr_SDCCH8(tn, 0)), valueof(ts_RslChanNr_SDCCH8(tn, 1)), valueof(ts_RslChanNr_SDCCH8(tn, 2)), valueof(ts_RslChanNr_SDCCH8(tn, 3)), valueof(ts_RslChanNr_SDCCH8(tn, 4)), valueof(ts_RslChanNr_SDCCH8(tn, 5)), valueof(ts_RslChanNr_SDCCH8(tn, 6)), valueof(ts_RslChanNr_SDCCH8(tn, 7)) }; } case (GSM_PCHAN_TCHH_TCHF_PDCH) { return { valueof(ts_RslChanNr_Lm(tn, 0)), valueof(ts_RslChanNr_Lm(tn, 1)), valueof(ts_RslChanNr_Bm(tn)) }; } case (GSM_PCHAN_TCHH) { return { valueof(ts_RslChanNr_Lm(tn, 0)), valueof(ts_RslChanNr_Lm(tn, 1)) }; } case (GSM_PCHAN_TCHF, GSM_PCHAN_TCHF_PDCH) { return { valueof(ts_RslChanNr_Bm(tn)) }; } } return { }; } /* global init function (without PCUIF connection) */ friend function f_init(uint8_t trx_nr := 0) runs on test_CT { var TrxParsItem trx_pars := mp_trx_pars[trx_nr]; var charstring id := testcasename(); /* FIXME: FACCH/H is unreliable with calypso firmware, see OS#3653. * TODO: also generate this list dynamically from module parameters. */ if (mp_bts_trxc_port != -1) { g_AllChanTypes := { /* TS 1..4: TCH/F */ valueof(ts_RslChanNr_Bm(1)), /* TS 5: TCH/H */ valueof(ts_RslChanNr_Lm(5,1)), /* TS 0: SDCCH/4 */ valueof(ts_RslChanNr_SDCCH4(0,2)), /* TS 6: SDCCH/8 */ valueof(ts_RslChanNr_SDCCH8(6,4)) }; } else { g_AllChanTypes := { /* TS 1..4: TCH/F */ valueof(ts_RslChanNr_Bm(1)), /* TS 0: SDCCH/4 */ valueof(ts_RslChanNr_SDCCH4(0,2)), /* TS 6: SDCCH/8 */ valueof(ts_RslChanNr_SDCCH8(6,4)) }; } g_AllChannels := { }; /* Generate list of all logical channels from module parameters */ for (var integer tn := 0; tn < lengthof(trx_pars.ts); tn := tn + 1) { var PchanConfig config := trx_pars.ts[tn].config; g_AllChannels := g_AllChannels & f_gen_chans_for_ts(tn, config); } f_init_rsl(id); f_sleep(0.5); /* workaround for OS#3000 */ f_init_vty(id); f_ipa_ctrl_start_client(mp_ctrl_ip, mp_ctrl_port); /* Send SI3 to the BTS, it is needed for various computations */ f_rsl_bcch_fill(RSL_SYSTEM_INFO_3, ts_SI3_default); /* SI2 + SI4 are required for SI testing as they are mandatory defaults */ f_rsl_bcch_fill(RSL_SYSTEM_INFO_2, ts_SI2_default); f_rsl_bcch_fill(RSL_SYSTEM_INFO_4, ts_SI4_default); if (mp_bts_trxc_port != -1) { var TrxcMessage ret; /* Init TRXC interface to FakeTRX */ map(self:BTS_TRXC, system:BTS_TRXC); f_init_trxc(BTS_TRXC, id, g_bts_trxc_conn_id); /* Start with a default moderate timing offset equalling TA=2, and RSSI=-60 */ ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TIMING(2*256)); ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_RSSI(-60)); /* OsmoBTS may have different AB / NB threshold (see MIN_QUAL_NORM, MIN_QUAL_RACH) */ ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_CI(60)); } /* Wait some extra time to make sure the BTS emits a stable carrier. * (this is only relevant when running the tests with a physical BTS.) */ f_sleep(mp_ipa_up_delay); } /* Attach L1CTL to master test_CT (classic tests, non-handler mode) */ friend function f_init_l1ctl() runs on test_CT { map(self:L1CTL, system:L1CTL); f_connect_reset(L1CTL); } friend type function void_fn(charstring id) runs on ConnHdlr; private type record length(8) of FreqHopGroups FreqHopConfig; private type record of FreqHopGroup FreqHopGroups; private type record FreqHopGroup { uint6_t hsn, FreqHopGroupItems trx_maio }; private type record of FreqHopGroupItem FreqHopGroupItems; private type record FreqHopGroupItem { uint8_t trx_nr, uint6_t maio }; friend function f_resolve_fh_params(inout FreqHopPars fhp, uint8_t tn, uint8_t trx_nr := 0) { var FreqHopGroups groups := mp_fh_config[tn]; var integer i, j; fhp.enabled := false; for (i := 0; i < lengthof(groups); i := i + 1) { var FreqHopGroup g := groups[i]; for (j := 0; j < lengthof(g.trx_maio); j := j + 1) { var FreqHopGroupItem gi := g.trx_maio[j]; if (gi.trx_nr == trx_nr) { fhp.maio_hsn.maio := gi.maio; fhp.maio_hsn.hsn := g.hsn; fhp.enabled := true; break; } } if (fhp.enabled) { /* Prepare the Mobile Allocation bitmask (length & padding) */ fhp.ma_map.len := (mp_transceiver_num + 8 - 1) / 8; /* in bytes */ fhp.ma_map.ma := f_pad_bit('0'B, fhp.ma_map.len * 8, '0'B); fhp.ma := { }; /* to be composed below */ /* Compose the actual Mobile Allocation and the bitmask */ for (j := 0; j < lengthof(g.trx_maio); j := j + 1) { var FreqHopGroupItem gi := g.trx_maio[j]; var GsmArfcn arfcn := mp_trx_pars[gi.trx_nr].arfcn; fhp.ma := fhp.ma & { valueof(ts_GsmBandArfcn(arfcn)) }; fhp.ma_map.ma[gi.trx_nr] := '1'B; } log("Freq. hopping parameters: ", fhp); break; /* We're done */ } } } /* create a new test component */ friend function f_start_handler(void_fn fn, ConnHdlrPars pars, boolean pcu_comp := false, boolean trxc_comp := false, boolean l1ctl := true) runs on test_CT return ConnHdlr { var charstring id := testcasename(); var ConnHdlr vc_conn; vc_conn := ConnHdlr.create(id); /* connect to RSL Emulation main component */ connect(vc_conn:RSL, vc_RSL:CLIENT_PT); connect(vc_conn:RSL_PROC, vc_RSL:RSL_PROC); /* The ConnHdlr component may want to talk to some ports directly, * so we disconnect it from the test_CT and connect it to the component. * This obviously only works for one component, i.e. no concurrency. */ if (pcu_comp) { unmap(self:PCU, system:PCU); map(vc_conn:PCU, system:PCU); } if (trxc_comp) { unmap(self:BTS_TRXC, system:BTS_TRXC); map(vc_conn:BTS_TRXC, system:BTS_TRXC); } if (l1ctl) { map(vc_conn:L1CTL, system:L1CTL); } /* 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); } vc_conn.start(f_handler_init(fn, id, pars)); return vc_conn; } private altstep as_Tguard() runs on ConnHdlr { [] g_Tguard.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Tguard timeout"); } } friend function f_l1_tune(L1CTL_PT L1CTL, L1ctlCcchMode ccch_mode := CCCH_MODE_COMBINED) { var GsmBandArfcn arfcn := valueof(ts_GsmBandArfcn(mp_trx_pars[0].arfcn)); f_L1CTL_FBSB(L1CTL, arfcn, ccch_mode, mp_rxlev_exp); } private function f_trxc_fake_rssi(TRXC_RSSI rssi) runs on ConnHdlr { var TrxcMessage ret; ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_RSSI(rssi)); } private function f_trxc_fake_toffs256(int16_t toffs256) runs on ConnHdlr { var TrxcMessage ret; ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TIMING(toffs256)); } /* first function started in ConnHdlr component */ private function f_handler_init(void_fn fn, charstring id, ConnHdlrPars pars) runs on ConnHdlr { g_pars := pars; g_chan_nr := pars.chan_nr; if (L1CTL.checkstate("Mapped")) { f_connect_reset(L1CTL); } if (mp_bts_trxc_port != -1 and BTS_TRXC.checkstate("Mapped")) { f_init_trxc(BTS_TRXC, id, g_bts_trxc_conn_id); } g_Tguard.start(pars.t_guard); activate(as_Tguard()); f_rslem_register(pars.trx_nr, pars.chan_nr); /* call the user-supplied test case function */ fn.apply(id); } private function f_rsl_transceive_ret(template RSL_Message tx, template RSL_Message exp_rx, charstring id, boolean ignore_other := false) runs on ConnHdlr return RSL_Message { var RSL_Message rx; timer T := 3.0; RSL.send(tx); T.start; alt { [] RSL.receive(exp_rx) -> value rx { T.stop; setverdict(pass); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout expecting " & id); } [not ignore_other] as_l1_sacch_loop(); [not ignore_other] as_meas_res(); [not ignore_other] as_l1_dcch_loop(); [not ignore_other] as_l1_tch_loop(); [not ignore_other] RSL.receive(RSL_Message:?) -> value rx { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Rx unexpected RSL port message: ", rx)); } [not ignore_other] RSL.receive { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Rx unexpected RSL port message"); } [ignore_other] RSL.receive { repeat; } } return rx; } friend function f_rsl_transceive(template RSL_Message tx, template RSL_Message exp_rx, charstring id, boolean ignore_other := false) runs on ConnHdlr { var RSL_Message rx := f_rsl_transceive_ret(tx, exp_rx, id, ignore_other); } /* Send the given measurement results to the IUT over the Um interface */ friend function f_send_meas_rep(template (value) MeasurementResults meas_res) runs on ConnHdlr { var template (value) SacchL1Header l1h; l1h := ts_SacchL1Header(g_pars.l1_pars.ms_power_level, g_pars.l1_pars.ms_actual_ta); f_send_meas_rep_l1h(meas_res, l1h); } /* Send the given measurement results and L1H to the IUT over the Um interface */ friend function f_send_meas_rep_l1h(template (value) MeasurementResults meas_res, template (value) SacchL1Header l1h) runs on ConnHdlr { var octetstring l2; /* RR Measurement Report to be sent */ var GsmRrL3Message meas_rep := { header := valueof(t_RrL3Header(MEASUREMENT_REPORT)), payload := { meas_rep := { meas_res := valueof(meas_res) } } }; /* TITAN has weird (and often unusable) padding model, so we pad here manaully */ l2 := f_pad_oct(enc_LapdmFrameAB(valueof(ts_LAPDm_AB(0, meas_rep))), 21, '00'O); log(%definitionId, "(): Tx SACCH L1 header: ", l1h); /* Send RR Measurement Report over the Um interface */ L1CTL.send(ts_L1CTL_DATA_REQ_SACCH(g_chan_nr, ts_RslLinkID_SACCH(0), l1h, l2)); } /* Send the given measurement results to the IUT over the Um interface, * wait for the IUT to receive then and forward over the A-bis/RSL interface. */ friend function f_transceive_meas_rep(template (value) MeasurementResults meas_res) runs on ConnHdlr { var octetstring l3; timer T; /* RR Measurement Report to be sent */ var GsmRrL3Message meas_rep := { header := valueof(t_RrL3Header(MEASUREMENT_REPORT)), payload := { meas_rep := { meas_res := valueof(meas_res) } } }; /* TITAN has weird (and often unusable) padding model, so we pad here manaully */ l3 := f_pad_oct(enc_GsmRrL3Message(meas_rep), 18, '00'O); f_send_meas_rep(meas_res); /* Expect MEASurement RESult on the A-bis/RSL interface */ T.start(2.0); alt { [] RSL.receive(tr_RSL_MEAS_RES_OSMO(g_chan_nr, l3_info := l3)) { setverdict(pass); } [] RSL.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for RSL MEASurement RESult"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } } friend function f_rsl_chan_act(RSL_IE_ChannelMode mode, boolean encr_enable := false, RSL_IE_List more_ies := {}, RSL_IE_ActivationType act_type := t_RSL_IE_ActType_IA) runs on ConnHdlr { var RSL_Message ch_act := valueof(ts_RSL_CHAN_ACT(g_chan_nr, mode, act_type)); if (encr_enable) { /* append encryption related IEs, if requested */ var RSL_IE_EncryptionInfo encr_info; encr_info := valueof(ts_RSL_IE_EncrInfo(g_pars.encr.alg_id, g_pars.encr.key)); ch_act.ies := ch_act.ies & { valueof(t_RSL_IE(RSL_IE_ENCR_INFO, RSL_IE_Body:{encr_info := encr_info})) }; } if (ischosen(mode.u.speech) and mode.u.speech == RSL_CMOD_SP_GSM3) { ch_act.ies := ch_act.ies & { valueof(t_RSL_IE(RSL_IE_MR_CONFIG, RSL_IE_Body:{multirate_cfg := g_pars.mr_conf})) }; } ch_act.ies := ch_act.ies & more_ies; f_rsl_transceive(ch_act, tr_RSL_CHAN_ACT_ACK(g_chan_nr), "RSL CHAN ACT"); } friend function f_rsl_chan_deact() runs on ConnHdlr { f_rsl_transceive(ts_RSL_RF_CHAN_REL(g_chan_nr), tr_RSL_RF_CHAN_REL_ACK(g_chan_nr), "RF CHAN REL", true); } friend template ConnHdlrPars t_Pars(template RslChannelNr chan_nr, template RSL_IE_ChannelMode chan_mode, template (omit) TestSpecUnion spec := omit, uint8_t trx_nr := 0, float t_guard := 20.0) := { trx_nr := trx_nr, chan_nr := valueof(chan_nr), chan_mode := valueof(chan_mode), mr_conf := omit, t_guard := t_guard, l1_pars := { dtx_enabled := false, toa256_enabled := false, meas_valid := true, meas_ul := { full := { rxlev := mp_ul_rxlev_exp, rxqual := 0 }, sub := { rxlev := mp_ul_rxlev_exp, rxqual := 0 } }, timing_offset_256syms := mp_timing_offset_256syms_exp, bs_power_level := 0, ms_power_level := mp_ms_power_level_exp, ms_actual_ta := mp_ms_actual_ta_exp, facch_enabled := false }, spec := spec, encr := omit, bts0_band := omit, tsc := mp_tsc_def, fhp := { enabled := false, maio_hsn := ts_HsnMaio(0, 0), ma_map := c_MA_null, ma := { } }, loc_osmux_cid := trx_nr, rem_osmux_cid := omit } /* This altstep triggers on receipt of a L1CTL DATA.ind matching the given * RSL chan_nr/link_id and data templates. Used as a base for other altsteps. */ private altstep as_l1ctl_dl_msg(out L1ctlMessage msg, template (present) octetstring tr_data := ?, template (present) RslChannelNr chan_nr := ?, template (present) RslLinkId link_id := ?) runs on ConnHdlr { [] L1CTL.receive(tr_L1CTL_DATA_IND(chan_nr, link_id, tr_data)) -> value msg; } /* This altstep is built on top of as_l1ctl_dl_msg(), and triggers on receipt * of dummy LAPDm func=UI frames with empty payload. Repeats by default. */ private altstep as_dl_lapdm_dummy(template (present) RslChannelNr chan_nr := ?, template (present) RslLinkId link_id := ?, template (present) LapdmSapi sapi := ?, boolean do_repeat := true) runs on ConnHdlr { template (present) LapdmFrame tr_frame := tr_LAPDm_UI(sapi, true, ''O); var L1ctlMessage dl_msg; [] as_l1ctl_dl_msg(dl_msg, decmatch tr_frame, chan_nr, link_id) { if (do_repeat) { repeat; } } } /* This altstep triggers on receipt of a Downlink DCCH containing a L2 payload * that matches the given LAPDm frame. The L2 is treated as LapdmFrameAB. */ private altstep as_dl_dcch_lapdm_ab(out LapdmFrameAB frame, template (present) LapdmFrameAB tr_frame := ?, template (present) RslLinkId link_id := ?) runs on ConnHdlr { var L1ctlMessage dl_msg; [] as_l1ctl_dl_msg(dl_msg, decmatch tr_frame, g_chan_nr, link_id) { frame := dec_LapdmFrameAB(dl_msg.payload.data_ind.payload); } } /* This altstep triggers on receipt of a Downlink SACCH containing a L2 payload * that matches the given LAPDm frame, as well as the given SACCH L1 header. */ private altstep as_dl_sacch_lapdm_ab(out SacchL1Header l1h, out LapdmFrameAB frame, template (present) LapdmFrameAB tr_frame := ?, template (present) SacchL1Header tr_l1h := ?, template (present) GsmSapi sapi := ?) runs on ConnHdlr { var L1ctlMessage dl_msg; template (present) RslLinkId link_id := tr_RslLinkID_SACCH(sapi); template (present) L1ctlDataReq tr_data := { l1header := tr_l1h, l2_payload := decmatch tr_frame }; [] as_l1ctl_dl_msg(dl_msg, decmatch tr_data, g_chan_nr, link_id) { var octetstring data := dl_msg.payload.data_ind.payload; l1h := dec_SacchL1Header(substr(data, 0, 2)); frame := dec_LapdmFrameAB(substr(data, 2, lengthof(data) - 2)); } } /* This altstep is built on top of as_dl_dcch_lapdm_ab(), and triggers on receipt * of a LAPDm AB frame with a L3 payload matching the given template. The L3 * payload is treated as PDU_ML3_NW_MS. */ private altstep as_dl_dcch_pdu(out PDU_ML3_NW_MS pdu, template (present) PDU_ML3_NW_MS tr_pdu := ?, template (present) GsmSapi sapi := 0) runs on ConnHdlr { template (present) LapdmFrame tr_frame := tr_LAPDm_UI(sapi, true, decmatch tr_pdu); template (present) RslLinkId link_id := tr_RslLinkID_DCCH(sapi); var LapdmFrameAB lapdm_frame; [] as_dl_dcch_lapdm_ab(lapdm_frame, tr_frame.ab, link_id) { pdu := dec_PDU_ML3_NW_MS(lapdm_frame.payload); } } /*********************************************************************** * Channel Activation / Deactivation ***********************************************************************/ /* Stress test: Do 500 channel activations/deactivations in rapid succession */ private function f_TC_chan_act_stress(charstring id) runs on ConnHdlr { for (var integer i := 0; i < 500; i := i+1) { f_rsl_chan_act(g_pars.chan_mode); f_rsl_chan_deact(); } setverdict(pass); } testcase TC_chan_act_stress() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); vc_conn := f_start_handler(refers(f_TC_chan_act_stress), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test if re-activation of an already active channel fails as expected */ private function f_TC_chan_act_react(charstring id) runs on ConnHdlr { f_rsl_chan_act(g_pars.chan_mode); /* attempt to activate the same lchan again -> expect reject */ RSL.send(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode)); alt { [] RSL.receive(tr_RSL_CHAN_ACT_ACK(g_chan_nr)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected CHAN ACT ACK on double activation"); } [] RSL.receive(tr_RSL_CHAN_ACT_NACK(g_chan_nr)) { setverdict(pass); } } f_rsl_chan_deact(); } testcase TC_chan_act_react() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); vc_conn := f_start_handler(refers(f_TC_chan_act_react), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Attempt to de-activate a channel that's not active */ private function f_TC_chan_deact_not_active(charstring id) runs on ConnHdlr { timer T := 3.0; RSL.send(ts_RSL_RF_CHAN_REL(g_chan_nr)); T.start; alt { [] RSL.receive(tr_RSL_RF_CHAN_REL_ACK(g_chan_nr)) { setverdict(pass); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout expecting RF_CHAN_REL_ACK"); } } } testcase TC_chan_deact_not_active() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); var ConnHdlr vc_conn := f_start_handler(refers(f_TC_chan_deact_not_active), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* attempt to activate channel with wrong RSL Channel Nr IE; expect NACK */ private function f_TC_chan_act_wrong_nr(charstring id) runs on ConnHdlr { RSL.send(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode)); alt { [] RSL.receive(tr_RSL_CHAN_ACT_ACK(g_chan_nr)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected CHAN ACT ACK"); } [] RSL.receive(tr_RSL_CHAN_ACT_NACK(g_chan_nr)) { setverdict(pass); } } } private type record WrongChanNrCase { RslChannelNr chan_nr, charstring description } private type record of WrongChanNrCase WrongChanNrCases; private template WrongChanNrCase t_WCN(template RslChannelNr chan_nr, charstring desc) := { chan_nr := chan_nr, description := desc } testcase TC_chan_act_wrong_nr() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); var WrongChanNrCases wrong := { valueof(t_WCN(t_RslChanNr_RACH(0), "RACH is not a dedicated channel")), valueof(t_WCN(t_RslChanNr_RACH(1), "RACH doesn't exist on timeslot")), valueof(t_WCN(t_RslChanNr_BCCH(0), "BCCH is not a dedicated channel")), valueof(t_WCN(t_RslChanNr_PCH_AGCH(0), "PCH/AGCH is not a dedicated channel")), valueof(t_WCN(t_RslChanNr_Bm(0), "TS0 cannot be TCH/F")), valueof(t_WCN(t_RslChanNr_Lm(0, 0), "TS0 cannot be TCH/H")), valueof(t_WCN(t_RslChanNr_Lm(0, 1), "TS0 cannot be TCH/H")), valueof(t_WCN(t_RslChanNr_PDCH(0), "TS0 cannot be PDCH")), valueof(t_WCN(t_RslChanNr_SDCCH8(0, 0), "TS0 cannot be SDCCH/8")), valueof(t_WCN(t_RslChanNr_SDCCH8(0, 7), "TS0 cannot be SDCCH/8")), valueof(t_WCN(t_RslChanNr_SDCCH4(7, 0), "TS7 cannot be SDCCH/4")), valueof(t_WCN(t_RslChanNr_SDCCH4(7, 3), "TS7 cannot be SDCCH/4")), valueof(t_WCN(t_RslChanNr_Lm(1, 0), "TS1 cannot be TCH/H")) }; for (var integer i := 0; i < sizeof(wrong); i := i+1) { pars := valueof(t_Pars(wrong[i].chan_nr, ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_chan_act_wrong_nr), pars); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* execute the same callback function on a variety of logical channels */ friend function f_testmatrix_each_chan(ConnHdlrPars pars, void_fn fn) runs on test_CT { var ConnHdlr vc_conn; f_init(); /* test on each of the channels we have */ for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i+1) { pars.chan_nr := valueof(g_AllChanTypes[i]); log(testcasename(), ": XXX Starting on ", g_AllChanTypes[i]); vc_conn := f_start_handler(fn, pars); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * SACCH handling ***********************************************************************/ private function f_exp_sacch(boolean exp) runs on ConnHdlr { timer T_sacch := 3.0; T_sacch.start; alt { [not exp] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(0))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Received SACCH when not expecting it"); } [not exp] T_sacch.timeout { setverdict(pass); } [exp] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(0))) { setverdict(pass); } [exp] T_sacch.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for SACCH on ", g_chan_nr)); } [] L1CTL.receive { repeat; } [] RSL.receive { repeat; } } } /* Test if DEACTIVATE SACCH actualy deactivates its transmission (TS 48.058 4.6) */ private function f_TC_deact_sacch(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* check that SACCH actually are received as expected */ f_exp_sacch(true); /* deactivate SACCH on the logical channel */ RSL.send(ts_RSL_DEACT_SACCH(g_chan_nr)); f_sleep(1.0); L1CTL.clear; /* check that no SACCH are received anymore */ f_exp_sacch(false); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_deact_sacch() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { //for (var integer i := 0; i < 1; i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_deact_sacch), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* verify that given SACCH payload is present */ private function f_sacch_present(template octetstring l3_exp, boolean do_fail := true) runs on ConnHdlr { var L1ctlMessage dl; timer T_sacch := 3.0; L1CTL.clear; T_sacch.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(0))) -> value dl { var octetstring l3 := substr(dl.payload.data_ind.payload, 4, 19); if (match(l3, l3_exp)) { setverdict(pass); } else { repeat; } } [] L1CTL.receive { repeat; } [not do_fail] T_sacch.timeout { log("Timeout waiting for SACCH ", l3_exp); } [do_fail] T_sacch.timeout { setverdict(fail, "Timeout waiting for SACCH ", l3_exp); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } } /* verify that given SACCH payload is not present */ private function f_sacch_missing(template octetstring l3_exp) runs on ConnHdlr { var L1ctlMessage dl; timer T_sacch := 3.0; L1CTL.clear; T_sacch.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(0))) -> value dl { var octetstring l3 := substr(dl.payload.data_ind.payload, 4, 19); if (match(l3, l3_exp)) { setverdict(fail, "Received unexpected SACCH ", dl); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } else { repeat; } } [] L1CTL.receive { repeat; } [] T_sacch.timeout { setverdict(pass); } } } /* Test for default SACCH FILL transmitted in DL SACCH (all channel types) */ private function f_TC_sacch_filling(charstring id) runs on ConnHdlr { /* Set a known default SACCH filling for SI5 */ var octetstring si5 := f_rnd_octstring(19); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); /* check that the specified SI5 value is actually sent */ f_sacch_present(si5); /* release the channel */ RSL.clear; f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_filling() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_sacch_filling), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test for lchan-specific SACCH INFO MODIFY (TS 48.058 4.12) */ private function f_TC_sacch_info_mod(charstring id) runs on ConnHdlr { /* Set a known default SACCH filling for SI5 */ var octetstring si5 := f_rnd_octstring(19); var octetstring si5_diff := f_rnd_octstring(19); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); f_l1_tune(L1CTL); RSL.clear; log("Activating channel, expecting standard SI5"); /* activate the logical channel */ f_est_dchan(); /* check that the specified SI5 value is actually sent */ f_sacch_present(si5); /* set channel-specific different SI5 */ log("Setting channel specific SACCH INFO, expecting it"); RSL.send(ts_RSL_SACCH_INF_MOD(g_chan_nr, RSL_SYSTEM_INFO_5, si5_diff)) /* check that the specified lchan-specific value is now used */ f_sacch_present(si5_diff); /* deactivate the channel and re-activate it, this should result in default SI5 */ log("De-activating and re-activating channel, expecting standard SI5"); f_rsl_chan_deact(); f_rsl_chan_act(valueof(ts_RSL_ChanMode_SIGN)); /* Verify that the TRX-wide default SACCH filling is present again */ f_sacch_present(si5); /* release the channel */ RSL.clear; f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_info_mod() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_sacch_info_mod), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test SACCH scheduling of multiple different SI message types */ private function f_TC_sacch_multi(charstring id) runs on ConnHdlr { var octetstring si5 := f_rnd_octstring(19); var octetstring si5bis := f_rnd_octstring(19); var octetstring si5ter := f_rnd_octstring(19); var octetstring si6 := f_rnd_octstring(19); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5bis, si5bis)); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5ter, si5ter)); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_6, si6)); f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* check that SACCH actually are received as expected */ f_sacch_present(si5); f_sacch_present(si5bis); f_sacch_present(si5ter); f_sacch_present(si6); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_multi() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_sacch_multi), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test if SACH information is modified as expected */ private function f_TC_sacch_multi_chg(charstring id) runs on ConnHdlr { var octetstring si5 := f_rnd_octstring(19); var octetstring si6 := f_rnd_octstring(19); /* First, configure both SI5 and SI6 to be transmitted */ RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_6, si6)); f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* check that SACCH actually are received as expected */ f_sacch_present(si5); f_sacch_present(si6); /* disable SI6 */ RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_6, ''O)); /* check that SI5 is still transmitted */ f_sacch_present(si5); /* check if SI6 is now gone */ f_sacch_missing(si6); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_multi_chg() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_sacch_multi_chg), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test for SACCH information present in RSL CHAN ACT (overrides FILLING) */ private function f_TC_sacch_chan_act(charstring id) runs on ConnHdlr { var octetstring si5 := f_rnd_octstring(19); var octetstring si6 := f_rnd_octstring(19); var octetstring si5_specific := f_rnd_octstring(19); var octetstring si6_specific := f_rnd_octstring(19); /* First, configure both SI5 and SI6 to be transmitted */ RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_6, si6)); f_l1_tune(L1CTL); RSL.clear; /* activate channel with different SACCH filling */ var RSL_SacchInfo sacch_info := valueof(ts_RSL_SacchInfo({ ts_RSL_SacchInfoElem(RSL_SYSTEM_INFO_5, si5_specific), ts_RSL_SacchInfoElem(RSL_SYSTEM_INFO_6, si6_specific) })); var RSL_IE_List addl_ies := { valueof(t_RSL_IE(RSL_IE_SACCH_INFO, RSL_IE_Body:{sacch_info := sacch_info})) }; f_est_dchan(more_ies := addl_ies); /* check that SACCH actually are received as expected */ f_sacch_present(si5_specific); f_sacch_present(si6_specific); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_chan_act() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i+1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_sacch_chan_act), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* verify that SACCH DL transmission is started only if MS power IE present * see section 4.1.3 of 3GPP TS 48.058 */ private function f_TC_sacch_chan_act_ho_async(charstring id) runs on ConnHdlr { var octetstring si5 := f_rnd_octstring(19); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); f_l1_tune(L1CTL); RSL.clear; /* Step 1: Activate ASYNC HO channel without MS power IE */ var integer ho_ref := oct2int(f_rnd_octstring(1)); var RSL_IE ho_ref_ie := valueof(t_RSL_IE(RSL_IE_HANDO_REF, RSL_IE_Body:{ handover_ref := ho_ref })); var RSL_IE_List addl_ies := { ho_ref_ie }; /* Activate channel on BTS side */ f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_ASYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* Verify that no DL SACCH is being received */ f_sacch_missing(?); /* Send handover Access Burst and expect SACCH to start */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_L1CTL_RESET(L1CTL); /* Tune back to CCCH/BCCH */ f_l1_tune(L1CTL); /* Step 2: Activate ASYNC HO channel with MS power IE */ /* Activate channel on BTS side */ addl_ies := { ho_ref_ie, valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ts_RSL_IE_MS_Power(0)})) }; f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_ASYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* 4.1.3 "If the MS Power IE is present the BTS *may* start transmission * also on the SACCH". "May" does not mean "shall", so osmo-bts does not. */ f_sacch_present(si5, do_fail := false); /* Send handover Access Burst and expect SACCH to remain present */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_chan_act_ho_async() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i+1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChanTypes[i]); vc_conn := f_start_handler(refers(f_TC_sacch_chan_act_ho_async), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* verify that SACCH DL transmission is started only if TA + MS power IEs present, * see section 4.1.4 of 3GPP TS 48.058 */ private function f_TC_sacch_chan_act_ho_sync(charstring id) runs on ConnHdlr { var octetstring si5 := f_rnd_octstring(19); RSL.send(ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, si5)); var RSL_IE_List addl_ies; f_l1_tune(L1CTL); RSL.clear; /* Step 1: Activate SYNC HO channel without MS power IE */ var integer ho_ref := oct2int(f_rnd_octstring(1)); var RSL_IE ho_ref_ie := valueof(t_RSL_IE(RSL_IE_HANDO_REF, RSL_IE_Body:{ handover_ref := ho_ref })); addl_ies := { ho_ref_ie }; /* Activate channel on BTS side */ f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_SYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* Verify that no DL SACCH is being received */ f_sacch_missing(?); /* Send handover Access Burst and expect SACCH to start */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_L1CTL_RESET(L1CTL); /* Tune back to CCCH/BCCH */ f_l1_tune(L1CTL); /* Step 2a: Activate SYNC HO channel with only MS power IE */ /* Activate channel on BTS side */ addl_ies := { ho_ref_ie, valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ts_RSL_IE_MS_Power(0)})) }; f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_SYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* 4.1.4 "If only the MS Power IE is present the BTS *may* start transmission * also on the SACCH". "May" does not mean "shall", so osmo-bts does not. */ f_sacch_present(si5, do_fail := false); /* Send handover Access Burst and expect SACCH to remain present */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_L1CTL_RESET(L1CTL); /* Tune back to CCCH/BCCH */ f_l1_tune(L1CTL); /* Step 2b: Activate SYNC HO channel with TA IE */ /* Activate channel on BTS side */ addl_ies := { ho_ref_ie, valueof(t_RSL_IE(RSL_IE_TIMING_ADVANCE, RSL_IE_Body:{timing_adv := 0})) }; f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_SYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* Verify that no DL SACCH is being received */ f_sacch_missing(?); /* Send handover Access Burst and expect SACCH to start */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_L1CTL_RESET(L1CTL); /* Tune back to CCCH/BCCH */ f_l1_tune(L1CTL); /* Step 3: Activate SYNC HO channel with MS power IE and TA IE */ /* Activate channel on BTS side */ addl_ies := { ho_ref_ie, valueof(t_RSL_IE(RSL_IE_TIMING_ADVANCE, RSL_IE_Body:{timing_adv := 0})), valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ts_RSL_IE_MS_Power(0)})) }; f_rsl_chan_act(g_pars.chan_mode, more_ies := addl_ies, act_type := t_RSL_IE_ActType_HO_SYNC); /* don't perform immediate assignment here, as we're testing non-IA case */ /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* Verify that DL SACCH is being received */ f_sacch_present(si5); /* Send handover Access Burst and expect SACCH to remain present */ f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); f_sacch_present(si5); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_sacch_chan_act_ho_sync() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i+1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChanTypes[i]); vc_conn := f_start_handler(refers(f_TC_sacch_chan_act_ho_sync), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * RACH Handling ***********************************************************************/ /* Send 1000 RACH requests and check their RA+FN on the RSL side */ private function f_TC_rach_content(boolean emerg) runs on test_CT { f_init(); f_init_l1ctl(); f_l1_tune(L1CTL); var GsmFrameNumber fn_last := 0; var boolean test_failed := false; for (var integer i := 0; i < 1000; i := i+1) { var OCT1 ra; if (emerg == true) { ra := f_rnd_ra_emerg(); } else { ra := f_rnd_ra_cs(); } var GsmFrameNumber fn := f_L1CTL_RACH(L1CTL, oct2int(ra)); if (fn == fn_last) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Two RACH in same FN?!?"); } fn_last := fn; timer T := 5.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CHAN_RQD(ra, fn, t_RslChanNr_RACH(0)))) { T.stop; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CHAN_RQD(?, ?, ?, ?))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected CHAN RQD"); } [] RSL_CCHAN.receive { repeat; } [] T.timeout { test_failed := true; log("[", i, "] Timeout waiting for CHAN RQD FN=", fn, " RA=", ra); } } } if (test_failed) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Some out of 1000 RACH requests timed out")); } setverdict(pass); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Normal variant */ testcase TC_rach_content() runs on test_CT { f_TC_rach_content(emerg := false); } /* Emergency call variant */ testcase TC_rach_content_emerg() runs on test_CT { f_TC_rach_content(emerg := true); } /* Send 1000 RACH Requests (flood ~ 89/s) and count if count(Abis) == count(Um) */ testcase TC_rach_count() runs on test_CT { f_init(); f_init_l1ctl(); f_sleep(1.0); f_l1_tune(L1CTL); var GsmFrameNumber fn_last := 0; for (var integer i := 0; i < 1000; i := i+1) { var OCT1 ra := f_rnd_ra_cs(); var GsmFrameNumber fn := f_L1CTL_RACH(L1CTL, oct2int(ra)); if (fn == fn_last) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Two RACH in same FN?!?"); } fn_last := fn; } var integer rsl_chrqd := 0; timer T := 3.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CHAN_RQD(?,?))) { rsl_chrqd := rsl_chrqd + 1; f_timer_safe_restart(T); repeat; } [] RSL_CCHAN.receive { repeat; } [] T.timeout { } } if (rsl_chrqd == 1000) { setverdict(pass); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received only ", rsl_chrqd, " out of 1000 RACH")); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_vty_load_ind_thresh(integer period := 10, integer bts_nr := 0) runs on test_CT { var charstring bts_str := "bts " & int2str(bts_nr); f_vty_config2(BSCVTY, {"network", bts_str}, "ccch load-indication-threshold " & int2str(period)); } /* empirical value: Number of RACH slots per reporting interval (1s) on combined CCCH */ private template integer tr_rach_slots_per_interval := (90 .. 130); /* Expect 0 RACH load on an idle BTS that has just started up */ testcase TC_rach_load_idle_thresh0() runs on test_CT { var ASP_RSL_Unitdata rx_ud; f_init_vty_bsc(); /* send load indications even at 0% load */ f_vty_load_ind_thresh(0); f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_sleep(2.0); f_init(); /* Skip the first RACH LOAD IND, as it may not have the full slot count (BTS started less than 1s before) */ alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(?, ?, ?))) { } [] RSL_CCHAN.receive { repeat } } timer T := 5.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(tr_rach_slots_per_interval, 0, 0))) { setverdict(pass); repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(?, ?, ?))) -> value rx_ud { setverdict(fail, "Unexpected RACH LOAD IND: ", rx_ud); repeat; } [] RSL_CCHAN.receive { repeat; } [] T.timeout { } } f_vty_load_ind_thresh(10); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Expect no RACH load indications on an idle BTS that has just started up (default threshold 10%) */ testcase TC_rach_load_idle_below_thresh() runs on test_CT { var ASP_RSL_Unitdata rx_ud; f_init_vty_bsc(); f_init(); timer T := 5.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(?, ?, ?))) -> value rx_ud { setverdict(fail, "Unexpected RACH LOAD IND: ", rx_ud); repeat; } [] RSL_CCHAN.receive { repeat; } [] T.timeout { setverdict(pass); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Expect 0 RACH load on an idle BTS that has just started up */ testcase TC_rach_load_count() runs on test_CT { var ASP_RSL_Unitdata rx_ud; var integer load_access_count := 0; f_init_vty_bsc(); /* send load indications even at 0% load */ f_vty_load_ind_thresh(0); f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_sleep(2.0); f_init(); f_init_l1ctl(); f_sleep(1.0); f_l1_tune(L1CTL); /* Skip the first RACH LOAD IND, as it may not have the full slot count (BTS started less than 1s before) */ alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(?, ?, ?))) { } [] RSL_CCHAN.receive { repeat } } var GsmFrameNumber fn_last := 0; for (var integer i := 0; i < 1000; i := i+1) { var OCT1 ra := f_rnd_ra_cs(); var GsmFrameNumber fn := f_L1CTL_RACH(L1CTL, oct2int(ra)); if (fn == fn_last) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Two RACH in same FN?!?"); } fn_last := fn; } timer T := 5.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(tr_rach_slots_per_interval, ?, ?))) -> value rx_ud { var RSL_IE_Body ie; f_rsl_find_ie(rx_ud.rsl, RSL_IE_RACH_LOAD, ie); load_access_count := load_access_count + ie.rach_load.access_count; if (ie.rach_load.busy_count < ie.rach_load.access_count) { setverdict(fail, "Access count cannot be < Busy count"); } repeat; } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RACH_LOAD_IND(?, ?, ?))) -> value rx_ud { setverdict(fail, "Unexpected RACH LOAD IND: ", rx_ud); repeat; } [] RSL_CCHAN.receive { repeat; } [] T.timeout { } } if (load_access_count == 1000) { setverdict(pass); } else { setverdict(fail, "Load reports state ", load_access_count, " RACH, but we sent 1000"); } f_vty_load_ind_thresh(10); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_rach_toffs(int16_t toffs256, boolean expect_pass) runs on test_CT { var TrxcMessage ret; /* tell fake_trx to use a given timing offset for all bursts */ ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TIMING(toffs256)); f_sleep(0.5); /* Transmit RACH request + wait for confirmation */ var OCT1 ra := f_rnd_ra_cs(); var GsmFrameNumber fn := f_L1CTL_RACH(L1CTL, oct2int(ra)); /* Check for expected result */ timer T := 1.5; T.start; alt { [expect_pass] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CHAN_RQD(ra, fn))) { setverdict(pass); } [not expect_pass] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_CHAN_RQD(ra, fn))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("RACH passed but was expected to be dropped: ", toffs256)); } [] RSL_CCHAN.receive { repeat; } [not expect_pass] T.timeout { setverdict(pass); } [expect_pass] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for CHAN RQD: FN=", fn, " RA=", ra)); } } } /* Test if dropping of RACH Based on NM_ATT_MAX_TA works */ testcase TC_rach_max_ta() runs on test_CT { f_init(); f_init_l1ctl(); f_l1_tune(L1CTL); f_sleep(1.0); /* default max-ta is 63 (full range of GSM timing advance */ /* We allow early arrival up to 2 symbols */ f_rach_toffs(-1*256, true); f_rach_toffs(-2*256, true); f_rach_toffs(-10*256, false); /* 0 / 32 / 63 bits is legal / permitted */ f_rach_toffs(0, true); f_rach_toffs(32*256, true); f_rach_toffs(63*256, true); /* more than 63 bits is not legal / permitted */ f_rach_toffs(64*256, false); f_rach_toffs(127*256, false); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_TC_ho_rach(charstring id) runs on ConnHdlr { var GsmFrameNumber fn; var RSL_Message rm; f_l1_tune(L1CTL); RSL.clear; /* Generate a random Handover Reference */ var integer ho_ref := oct2int(f_rnd_octstring(1)); /* Handover Reference IE (see 3GPP TS 48.058, 9.3.9) */ var RSL_IE ho_ref_ie := valueof(t_RSL_IE(RSL_IE_HANDO_REF, RSL_IE_Body:{ handover_ref := ho_ref })); /* Activate a channel on the BTS side (no encryption) */ f_rsl_chan_act(g_pars.chan_mode, more_ies := { ho_ref_ie }, act_type := t_RSL_IE_ActType_HO_SYNC); /* Switch the MS side (e.g. trxcon) to a dedicated channel without * waiting for Immediate Assignment and sending Access Burst */ f_l1ctl_est_dchan(L1CTL, g_pars); /* Send handover Access Burst */ fn := f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); /* TODO: test mismatching Handover Reference, and missing IE */ /* Wait for handover detection */ timer T := 3.0; T.start; alt { [] RSL.receive(tr_RSL_HANDO_DET(g_pars.chan_nr)) -> value rm { log("Handover RACH has been detected: ", rm); setverdict(pass); } [] RSL.receive(tr_RSL_CHAN_RQD(?, ?, ?, ?)) -> value rm { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("RSL_CHAN_RQD was not expected: ", rm)); } [] RSL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for handover RACH: FN=", fn, " RA=", ho_ref)); } } /* Release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* Test handover RACH detection */ testcase TC_ho_rach() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i + 1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChannels[i]); vc_conn := f_start_handler(refers(f_TC_ho_rach), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test sending of the RR Physical Information message on DCCH */ private function f_TC_ho_physical_info(charstring id) runs on ConnHdlr { var PDU_ML3_NW_MS dcch_pdu; var integer count := 0; var L1ctlMessage dl; timer T; f_l1_tune(L1CTL); RSL.clear; /* Generate a random Handover Reference */ var integer ho_ref := oct2int(f_rnd_octstring(1)); /* Handover Reference IE (see 3GPP TS 48.058, 9.3.9) */ var RSL_IE ho_ref_ie := valueof(t_RSL_IE(RSL_IE_HANDO_REF, RSL_IE_Body:{ handover_ref := ho_ref })); /* Activate a channel on the BTS side (no encryption) */ f_rsl_chan_act(g_pars.chan_mode, more_ies := { ho_ref_ie }, act_type := t_RSL_IE_ActType_HO_SYNC); /* Switch the MS side (e.g. trxcon) to a dedicated channel without * waiting for Immediate Assignment and sending of an Access Burst. */ f_l1ctl_est_dchan(L1CTL, g_pars); /* FIXME: disable Uplink Tx */ /* Expect nothing to be transmitted on Downlink DCCH. Nothing in this * context means any L2 frames, not only the RR Physical Information. */ T.start(2.0); L1CTL.clear; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_DCCH(?))) -> value dl { setverdict(fail, "Rx unexpected Downlink DCCH (before handover RACH): ", dl); T.stop; } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(pass); } } /* Send handover Access Burst */ log("Sending handover Access Burst"); f_L1CTL_RACH(L1CTL, ho_ref, chan_nr := g_pars.chan_nr); /* Expect RR Physical Information being sent on Downlink DCCH Ny1 times */ T.start(int2float(mp_ho_t3105_ms * mp_ho_ny1) / 1000.0 + 1.0); L1CTL.clear; alt { [] as_dl_lapdm_dummy(); /* Ignore empty LAPDm func=UI frames */ [] as_dl_dcch_pdu(dcch_pdu, tr_RRM_PhysicalInfo) { log("Rx RR Physical Information: ", dcch_pdu); /* Wait until the count reaches Ny1 */ count := count + 1; if (count < mp_ho_ny1) { repeat; } setverdict(pass); } [] as_dl_dcch_pdu(dcch_pdu, ?) { setverdict(fail, "Rx unexpected DCCH PDU: ", dcch_pdu); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for RR Physical Information"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* Release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_ho_physical_info() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i + 1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": Starting for ", g_AllChanTypes[i]); vc_conn := f_start_handler(refers(f_TC_ho_physical_info), pars); vc_conn.done; } /* TODO: do the above in parallel, rather than sequentially? */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * Measurement Processing / Reporting ***********************************************************************/ private template (value) LapdmAddressField ts_LapdmAddr(template (value) LapdmSapi sapi, template (value) boolean c_r) := { spare := '0'B, lpd := 0, sapi := sapi, c_r := c_r, ea := true } private template (value) LapdmFrameAB ts_LAPDm_AB(template (value) LapdmSapi sapi, template (value) GsmRrL3Message l3, template (value) boolean c_r := false, template (value) boolean p := false) := { addr := ts_LapdmAddr(sapi, c_r), ctrl := ts_LapdmCtrlUI(valueof(p)), len := 0, /* overwritten */ m := false, el := 1, payload := enc_GsmRrL3Message(valueof(l3)), padding := ''O } /* handle incoming downlink SACCH and respond with uplink SACCH (meas res) */ private altstep as_l1_sacch_loop() runs on ConnHdlr { var template (value) LapdmFrameAB lb; var L1ctlMessage l1_dl; [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(?))) -> value l1_dl { log("SACCH received: ", l1_dl.payload.data_ind.payload); lb := ts_LAPDm_AB(0, ts_MEAS_REP(g_pars.l1_pars.meas_valid, g_pars.l1_pars.meas_ul.full.rxlev, g_pars.l1_pars.meas_ul.sub.rxlev, g_pars.l1_pars.meas_ul.full.rxqual, g_pars.l1_pars.meas_ul.sub.rxqual)); log("LAPDm: ", lb); var template (value) SacchL1Header l1h := ts_SacchL1Header( g_pars.l1_pars.ms_power_level, g_pars.l1_pars.ms_actual_ta); /* According to 3GPP TS 44.018, section 10.5.2.20, we should pad with zeroes */ var octetstring l2 := f_pad_oct(enc_LapdmFrameAB(valueof(lb)), 21, '00'O); log("Sending Measurement Report: ", l1h, l2); L1CTL.send(ts_L1CTL_DATA_REQ_SACCH(g_chan_nr, ts_RslLinkID_SACCH(0), l1h, l2)); repeat; } /* dequeue (ignore) L1CTL DATA.cnf for UL SACCH (SAPI=0) */ [] L1CTL.receive(tr_L1CTL_DATA_CONF(g_chan_nr, tr_RslLinkID_SACCH(0))) { repeat; } } /* handle incoming downlink SACCH, decode the L1 header into the given record */ private altstep as_l1_sacch_l1h(inout SacchL1Header l1h, boolean do_apply := true) runs on ConnHdlr { var L1ctlMessage l1_dl; [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(?))) -> value l1_dl { /* Parse the L1 SACCH header (MS Power Level & Timing Advance) */ l1h := dec_SacchL1Header(substr(l1_dl.payload.data_ind.payload, 0, 2)); log(%definitionId, "(): Rx SACCH L1 header: ", l1h); if (do_apply) { /* Update TA and MS power to follow what BTS requests */ f_L1CTL_PARAM(L1CTL, l1h.actual_ta, l1h.ms_power_lvl); } } } private altstep as_l1_dcch_loop() runs on ConnHdlr { var L1ctlMessage l1_dl; [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_DCCH(?))) -> value l1_dl { log("DCCH received: ", l1_dl.payload.data_ind.payload); var octetstring pl := '010301'O; L1CTL.send(ts_L1CTL_DATA_REQ(g_chan_nr, ts_RslLinkID_DCCH(0), f_pad_oct(pl, 23, '2B'O))); repeat; } /* dequeue (ignore) L1CTL DATA.cnf for UL DCCH (SAPI=0) */ [] L1CTL.receive(tr_L1CTL_DATA_CONF(g_chan_nr, tr_RslLinkID_DCCH(0))) { repeat; } } private altstep as_l1_tch_loop() runs on ConnHdlr { var L1ctlMessage l1_dl; [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr)) -> value l1_dl { var octetstring data := l1_dl.payload.traffic_ind.data; log("TCH received (len=", lengthof(data), "): ", data); /* occasionaly inject FACCH frames into the uplink */ if (g_pars.l1_pars.facch_enabled == true and l1_dl.dl_info.frame_nr mod 5 == 0) { var octetstring pl := '010301'O; L1CTL.send(ts_L1CTL_DATA_REQ(g_chan_nr, ts_RslLinkID_DCCH(0), f_pad_oct(pl, 23, '2B'O))); } else { L1CTL.send(ts_L1CTL_TRAFFIC_REQ(g_chan_nr, l1_dl.dl_info.link_id, l1_dl.payload.traffic_ind.data)); } repeat; } /* dequeue (ignore) L1CTL TRAFFIC.cnf for UL TCH */ [] L1CTL.receive(tr_L1CTL_TRAFFIC_CONF(g_chan_nr, tr_RslLinkID_DCCH(0))) { repeat; } } private type record MeasElem { uint6_t rxlev, uint3_t rxqual } private type record MeasElemFS { MeasElem full, MeasElem sub } private type record ConnL1Pars { boolean dtx_enabled, boolean toa256_enabled, boolean meas_valid, MeasElemFS meas_ul, int16_t timing_offset_256syms, uint4_t bs_power_level, uint5_t ms_power_level, uint8_t ms_actual_ta, boolean facch_enabled } /* Convert tiing offset from 1/256th symbol to RSL Timing Offset */ private function toffs256s_to_rsl(int16_t toffs256s) return uint8_t { return 63 + (toffs256s/256); } private function f_max(integer a, integer b) return integer { if (a > b) { return a; } else { return b; } } private function f_min(integer a, integer b) return integer { if (a < b) { return a; } else { return b; } } /* compute negative tolerance val-tolerance, ensure >= min */ private function f_tolerance_neg(integer val, integer min, integer tolerance) return integer { val := val - tolerance; return f_max(val, min); } /* compute positive tolerance val+tolerance, ensure <= max */ private function f_tolerance_pos(integer val, integer max, integer tolerance) return integer { val := val + tolerance; return f_min(val, max); } /* return a template of (val-tolerance .. val+tolerance) ensuring it is within (min .. max) */ private function f_tolerance(integer val, integer min, integer max, integer tolerance) return template integer { var template integer ret; ret := (f_tolerance_neg(val, min, tolerance) .. f_tolerance_pos(val, max, tolerance)); return ret; } /* build a template for matching measurement results against */ private function f_build_meas_res_tmpl() runs on ConnHdlr return template RSL_Message { var ConnL1Pars l1p := g_pars.l1_pars; var template RSL_IE_UplinkMeas ul_meas := { len := 3, rfu := '0'B, dtx_d := l1p.dtx_enabled, rxlev_f_u := f_tolerance(l1p.meas_ul.full.rxlev, 0, 63, mp_tolerance_rxlev), reserved1 := '00'B, rxlev_s_u := f_tolerance(l1p.meas_ul.sub.rxlev, 0, 63, mp_tolerance_rxlev), reserved2 := '00'B, rxq_f_u := f_tolerance(l1p.meas_ul.full.rxqual, 0, 7, mp_tolerance_rxqual), rxq_s_u := f_tolerance(l1p.meas_ul.sub.rxqual, 0, 7, mp_tolerance_rxqual), supp_meas_info := omit }; if (l1p.toa256_enabled) { ul_meas.len := (3+8); ul_meas.supp_meas_info := { toa256_mean := f_tolerance(l1p.timing_offset_256syms, -63*256, 192*256, mp_tolerance_timing_offset_256syms), toa256_min := ?, toa256_max := ?, toa256_std_dev := ? } } var template RSL_IE_BS_Power bs_power := { reserved := 0, epc := false, fpc := false, power_level := l1p.bs_power_level }; var template RSL_IE_L1Info l1_info := { ms_power_lvl := l1p.ms_power_level, fpc := false, reserved := 0, actual_ta := f_tolerance(l1p.ms_actual_ta, 0, 63, mp_tolerance_timing_offset_256syms/256) }; var uint8_t offs := toffs256s_to_rsl(l1p.timing_offset_256syms); var template uint8_t t_toffs := f_tolerance(offs, 0, 255, mp_tolerance_timing_offset_256syms/256); return tr_RSL_MEAS_RES_OSMO(g_chan_nr, g_next_meas_res_nr, ul_meas, bs_power, l1_info, ?, t_toffs); } /* build a template for matching measurement results that do not contain any * MS related measurement (l1_info, l3_info and ms timing offset). */ private function f_build_meas_res_tmpl_empty() runs on ConnHdlr return template RSL_Message { var ConnL1Pars l1p := g_pars.l1_pars; var template RSL_IE_UplinkMeas ul_meas := { len := 3, rfu := '0'B, dtx_d := l1p.dtx_enabled, rxlev_f_u := ?, reserved1 := '00'B, rxlev_s_u := ?, reserved2 := '00'B, rxq_f_u := ?, rxq_s_u := ?, supp_meas_info := omit }; if (l1p.toa256_enabled) { ul_meas.len := (3+8); ul_meas.supp_meas_info := { toa256_mean := f_tolerance(l1p.timing_offset_256syms, -63*256, 192*256, mp_tolerance_timing_offset_256syms), toa256_min := ?, toa256_max := ?, toa256_std_dev := ? } } var template RSL_IE_BS_Power bs_power := { reserved := 0, epc := false, fpc := false, power_level := l1p.bs_power_level }; return tr_RSL_MEAS_RES_EMPTY(g_chan_nr, g_next_meas_res_nr, ul_meas, bs_power); } /* verify we regularly receive measurement reports with incrementing numbers */ private altstep as_meas_res(boolean verify_meas := true) runs on ConnHdlr { var RSL_Message rsl; var boolean chan_est := false; [not verify_meas] RSL.receive(tr_RSL_MEAS_RES(?)) { repeat; } /* Receive osmocom specific measurement reports. This is the normal * case. Here we verify that the measurement reports we sent are * comming back as we expect them. */ [] RSL.receive(f_build_meas_res_tmpl()) -> value rsl { /* increment counter of next to-be-expected meas rep */ g_next_meas_res_nr := (g_next_meas_res_nr + 1) mod 256; /* Re-start the timer expecting the next MEAS RES */ f_timer_safe_restart(g_Tmeas_exp); /* The following two cases may only happen in the beginning * of the channel establishment phase. Once we have received * the "our" measurement report the first time, the channel * is established and empty or hardcoded TRXCON reports must * not occur anymore. */ chan_est := true; repeat; } /* When the BTS has established the channel, the MS might need slightly * more time to establish the channel and actually start sending. The * result is then a measurement report that just lacks the measurement * information of the MS. This is normal and we tolerate this behavior. */ [chan_est == false] RSL.receive(f_build_meas_res_tmpl_empty()) -> value rsl { /* increment counter of next to-be-expected meas rep */ g_next_meas_res_nr := (g_next_meas_res_nr + 1) mod 256; /* Re-start the timer expecting the next MEAS RES */ f_timer_safe_restart(g_Tmeas_exp); repeat; } /* Due to the TDMA nature of GSM, TRXCON implements a way to emit dummy * measurements if the TTCN3 side does not supply measurement input in * time. In those cases TRXCON will either use a cached measurement * report or a hardcoded one. If TRXCON picks the hardcoded measurement * report the templates above will not match. We tolerate this * behavior, but only once. */ [chan_est == false] RSL.receive(tr_RSL_MEAS_RES(g_chan_nr, g_next_meas_res_nr)) -> value rsl { /* increment counter of next to-be-expected meas rep */ g_next_meas_res_nr := (g_next_meas_res_nr + 1) mod 256; if (g_first_meas_res) { g_first_meas_res := false; repeat; } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received unspecific MEAS RES ", rsl)); } } [] RSL.receive(tr_RSL_MEAS_RES(?)) -> value rsl { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received unexpected MEAS RES ", rsl)); } [g_Tmeas_exp.running] g_Tmeas_exp.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Didn't receive expected measurement result") } } private function f_alg_id_to_l1ctl(RSL_AlgId rsl_alg_id) return uint8_t { select (rsl_alg_id) { case (RSL_ALG_ID_A5_0) { return 0; } case (RSL_ALG_ID_A5_1) { return 1; } case (RSL_ALG_ID_A5_2) { return 2; } case (RSL_ALG_ID_A5_3) { return 3; } case (RSL_ALG_ID_A5_4) { return 4; } case (RSL_ALG_ID_A5_5) { return 5; } case (RSL_ALG_ID_A5_6) { return 6; } case (RSL_ALG_ID_A5_7) { return 7; } case else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unknwon Algorithm ID"); /* Make compiler happy by calling mtc.stop here. It is already * called in f_shutdown */ mtc.stop; } } } private function f_alg_id_to_l3(RSL_AlgId rsl_alg_id) return BIT3 { select (rsl_alg_id) { case (RSL_ALG_ID_A5_1) { return '000'B; } case (RSL_ALG_ID_A5_2) { return '001'B; } case (RSL_ALG_ID_A5_3) { return '010'B; } case (RSL_ALG_ID_A5_4) { return '011'B; } case (RSL_ALG_ID_A5_5) { return '100'B; } case (RSL_ALG_ID_A5_6) { return '101'B; } case (RSL_ALG_ID_A5_7) { return '110'B; } case else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unknwon Algorithm ID"); /* Make compiler happy by calling mtc.stop here. It is already * called in f_shutdown */ mtc.stop; } } } /* Send RACH request through l1CTL and wait for ChanReq on RSL BST->BSC */ private function f_rach_req_wait_chan_rqd(integer ra) runs on ConnHdlr return GsmFrameNumber { var GsmFrameNumber fn; timer T := 8.0; /* advertise to RSL Emulation that we expect to receive confirmation from RACH */ RSL.send(ts_RSLDC_ChanRqd_anyFN(int2oct(ra,1))); f_L1CTL_PARAM(L1CTL, g_pars.l1_pars.ms_actual_ta, g_pars.l1_pars.ms_power_level); /* Send the actual RACH */ fn := f_L1CTL_RACH(L1CTL, ra); T.start; alt { [] RSL.receive(tr_RSL_CHAN_RQD(int2oct(ra,1), fn)) { setverdict(pass, "Received CHAN-RQD from RACH REQ") } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for CHAN-RQD from RACH REQ <", ra, ", ", fn, ">")); } } T.stop return fn; } /* Tune to a dedicated channel: L1CTL only */ friend function f_l1ctl_est_dchan(L1CTL_PT pt, ConnHdlrPars pars) { if (not pars.fhp.enabled) { var TrxParsItem trx_pars := mp_trx_pars[pars.trx_nr]; pt.send(ts_L1CTL_DM_EST_REQ_H0(pars.chan_nr, pars.tsc, trx_pars.arfcn)); } else { pt.send(ts_L1CTL_DM_EST_REQ_H1(pars.chan_nr, pars.tsc, pars.fhp.maio_hsn.hsn, pars.fhp.maio_hsn.maio, pars.fhp.ma)); } } /* Establish dedicated channel: L1CTL + RSL side */ private function f_est_dchan(boolean encr_enable := false, RSL_IE_List more_ies := {}, RSL_IE_ActivationType act_type := t_RSL_IE_ActType_IA) runs on ConnHdlr { var ChannelDescription ch_desc; /* Activate channel on BTS side */ f_rsl_chan_act(g_pars.chan_mode, encr_enable, more_ies, act_type := act_type); /* Craft channel description (with or without frequency hopping parameters) */ if (g_pars.fhp.enabled) { ch_desc := valueof(ts_ChanDescH1(g_pars.chan_nr, g_pars.fhp.maio_hsn, g_pars.tsc)); } else { var TrxParsItem trx_pars := mp_trx_pars[g_pars.trx_nr]; ch_desc := valueof(ts_ChanDescH0(g_pars.chan_nr, trx_pars.arfcn, g_pars.tsc)); } /* enable dedicated mode */ f_l1ctl_est_dchan(L1CTL, g_pars); /* enable encryption, if requested */ if (encr_enable) { var uint8_t alg_id := f_alg_id_to_l1ctl(g_pars.encr.alg_id); f_L1CTL_CRYPTO_REQ(L1CTL, g_pars.chan_nr, alg_id, g_pars.encr.key); } /* Send TCH Mode Request to the L1 if needed */ if (match(g_pars.chan_mode.spd_ind, (RSL_SPDI_SPEECH, RSL_SPDI_DATA))) { var L1ctlTchMode tch_mode; var uint8_t amr_start_codec := 0; var BIT8 amr_codecs_bitmask := '00000000'B; select (g_pars.chan_mode) { case (tr_RSL_ChanMode_SIGN) { tch_mode := L1CTL_CHAN_MODE_SIGN; } /* Speech modes */ case (tr_RSL_ChanMode((RSL_CHRT_TCH_F, RSL_CHRT_TCH_H), RSL_CMOD_SP_GSM1)) { tch_mode := L1CTL_CHAN_MODE_SPEECH_V1; } case (tr_RSL_ChanMode((RSL_CHRT_TCH_F, RSL_CHRT_TCH_H), RSL_CMOD_SP_GSM2)) { tch_mode := L1CTL_CHAN_MODE_SPEECH_V2; } case (tr_RSL_ChanMode((RSL_CHRT_TCH_F, RSL_CHRT_TCH_H), RSL_CMOD_SP_GSM3)) { tch_mode := L1CTL_CHAN_MODE_SPEECH_V3; amr_codecs_bitmask := g_pars.mr_conf.codec_modes; } /* Data modes */ case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, /* TCH/F14.4 */ (RSL_CMOD_CSD_T_14k4, RSL_CMOD_CSD_NT_14k5))) { tch_mode := L1CTL_CHAN_MODE_DATA_14k5; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, /* TCH/F9.6 */ (RSL_CMOD_CSD_T_9k6, RSL_CMOD_CSD_NT_12k0))) { tch_mode := L1CTL_CHAN_MODE_DATA_12k0; } case (tr_RSL_ChanMode_DATA((RSL_CHRT_TCH_F, RSL_CHRT_TCH_H), /* TCH/[FH]4.8 */ (RSL_CMOD_CSD_T_4k8, RSL_CMOD_CSD_NT_6k0))) { tch_mode := L1CTL_CHAN_MODE_DATA_6k0; } case (tr_RSL_ChanMode_DATA((RSL_CHRT_TCH_F, RSL_CHRT_TCH_H), /* TCH/[FH]2.4 */ (RSL_CMOD_CSD_T_2k4, RSL_CMOD_CSD_T_1k2, RSL_CMOD_CSD_T_600, RSL_CMOD_CSD_T_1200_75))) { tch_mode := L1CTL_CHAN_MODE_DATA_3k6; } case else { log("RSL channel mode is not supported by the L1, falling back to signalling"); tch_mode := L1CTL_CHAN_MODE_SIGN; } } f_L1CTL_TCH_MODE(L1CTL, ts_L1CTL_TCH_MODE_REQ(tch_mode, amr_start_codec := amr_start_codec, amr_codecs_bitmask := amr_codecs_bitmask)); } g_first_meas_res := true; } private function get_start_amr_ft() runs on ConnHdlr return integer { var integer start_nth; if (g_pars.mr_conf.icmi) { start_nth := 0; /* FIXME: implement 3GPP TS 45.009 3.4.3 */ } else { start_nth := g_pars.mr_conf.start_mode; } var integer n := 0; for (var integer i:= 7; i >= 0; i := i - 1) { if (g_pars.mr_conf.codec_modes[i] == '1'B) { if (n == start_nth) { return 7 - i; } n := n + 1; } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("mr_conf is wrong! ", g_pars.mr_conf)); return 0; } /* Initialize and start the RTP emulation component for a ConnHdlr */ friend function f_rtpem_activate(RtpemConfig cfg := c_RtpemDefaultCfg, RtpemMode mode := RTPEM_MODE_LOOPBACK, uint7_t rtp_pt := 0) runs on ConnHdlr { /* Step 0: initialize, connect and start the emulation component */ vc_RTPEM := RTP_Emulation_CT.create(testcasename() & "-RTPEM"); map(vc_RTPEM:RTP, system:RTP); map(vc_RTPEM:RTCP, system:RTCP); connect(vc_RTPEM:CTRL, self:RTPEM_CTRL); connect(vc_RTPEM:DATA, self:RTPEM_DATA); vc_RTPEM.start(RTP_Emulation.f_main()); /* Step 1: configure the RTP parameters */ f_rtpem_configure(RTPEM_CTRL, cfg); /* Step 2: bind the RTP emulation to the configured address */ var PortNumber rtpem_bind_port := mp_rtpem_bind_port; f_rtpem_bind(RTPEM_CTRL, mp_rtpem_bind_ip, rtpem_bind_port); /* Step 3a: send CRCX to create an RTP connection at the IUT */ var RSL_Message crcx_ack := f_rsl_transceive_ret( /* FIXME (OS#5242): do not include Remote IP/Port IEs because * osmo-bts would respond with nonsense listen addr='0.0.0.0'. */ ts_RSL_IPA_CRCX(g_chan_nr, omit, omit), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?, omit), "IPA CRCX ACK"); var uint16_t conn_id := crcx_ack.ies[1].body.ipa_conn_id; /* Step 3b: send MDCX with the configured address/port to the IUT */ var RSL_Message mdcx_ack := f_rsl_transceive_ret( ts_RSL_IPA_MDCX(g_chan_nr, conn_id, remote_ip := f_inet_addr(mp_rtpem_bind_ip), remote_port := rtpem_bind_port, rtp_pt2 := rtp_pt), tr_RSL_IPA_MDCX_ACK(g_chan_nr, conn_id, ?, ?, ?), "IPA MDCX ACK"); /* Step 4: connect to the IUT's address/port parsed from MDCX ACK */ var HostName bts_bind_ip := f_inet_ntoa(mdcx_ack.ies[2].body.ipa_local_ip); var PortNumber bts_bind_port := mdcx_ack.ies[3].body.ipa_local_port; f_rtpem_connect(RTPEM_CTRL, bts_bind_ip, bts_bind_port); /* Step 5: set the given RTP emulation mode */ f_rtpem_mode(RTPEM_CTRL, mode); } /* Initialize and start the RTP emulation component for a ConnHdlr */ friend function f_osmuxem_activate(inout octetstring payload, OsmuxemConfig cfg := c_OsmuxemDefaultCfg, OsmuxemMode mode := OSMUXEM_MODE_BIDIR) runs on ConnHdlr { var RSL_IE_Body ie; var OsmuxTxHandle tx_hdl; var OsmuxRxHandle rx_hdl; /* Step 0: initialize, connect and start the emulation component */ vc_OsmuxEM := OSMUX_Emulation_CT.create(testcasename() & "-OsmuxEM"); map(vc_OsmuxEM:OSMUX, system:OSMUX); connect(vc_OsmuxEM:CTRL, self:OsmuxEM_CTRL); connect(vc_OsmuxEM:DATA, self:OsmuxEM_DATA); vc_OsmuxEM.start(OSMUX_Emulation.f_main()); /* Step 1: configure the RTP parameters */ var integer payload_len := 31; var octetstring hdr := ''O; /* Pad the payload to conform the expected length */ payload := f_pad_oct(hdr & payload, payload_len, '00'O); cfg.tx_fixed_payload := payload; f_osmuxem_configure(OsmuxEM_CTRL, cfg); /* Step 2: bind the RTP emulation to the configured address */ var PortNumber osmuxem_bind_port := mp_osmuxem_bind_port; f_osmuxem_bind(OsmuxEM_CTRL, mp_osmuxem_bind_ip, osmuxem_bind_port); rx_hdl := c_OsmuxemDefaultRxHandle; rx_hdl.cid := g_pars.loc_osmux_cid; f_osmuxem_register_rxhandle(OsmuxEM_CTRL, rx_hdl); /* Step 3a: send CRCX to create an RTP connection at the IUT */ var RSL_Message crcx_ack := f_rsl_transceive_ret( /* FIXME (OS#5242): do not include Remote IP/Port IEs because * osmo-bts would respond with nonsense listen addr='0.0.0.0'. */ ts_RSL_IPA_CRCX(g_chan_nr, omit, omit, g_pars.loc_osmux_cid), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?, ?), "IPA CRCX ACK"); var uint16_t conn_id := crcx_ack.ies[1].body.ipa_conn_id; f_rsl_find_ie(crcx_ack, RSL_IE_OSMO_OSMUX_CID, ie); g_pars.rem_osmux_cid := ie.osmux_cid.cid; /* Step 3b: send MDCX with the configured address/port to the IUT */ var RSL_Message mdcx_ack := f_rsl_transceive_ret( ts_RSL_IPA_MDCX(g_chan_nr, conn_id, remote_ip := f_inet_addr(mp_osmuxem_bind_ip), remote_port := osmuxem_bind_port, rtp_pt2 := 0, osmux_cid := g_pars.loc_osmux_cid), tr_RSL_IPA_MDCX_ACK(g_chan_nr, conn_id, ?, ?, ?, g_pars.rem_osmux_cid), "IPA MDCX ACK"); tx_hdl := valueof(t_TxHandleAMR590(g_pars.rem_osmux_cid)); f_osmuxem_register_txhandle(OsmuxEM_CTRL, tx_hdl); /* Step 4: connect to the IUT's address/port parsed from MDCX ACK */ var HostName bts_bind_ip := f_inet_ntoa(mdcx_ack.ies[2].body.ipa_local_ip); var PortNumber bts_bind_port := mdcx_ack.ies[3].body.ipa_local_port; f_osmuxem_connect(OsmuxEM_CTRL, bts_bind_ip, bts_bind_port); /* Step 5: set the given RTP emulation mode */ f_osmuxem_mode(OsmuxEM_CTRL, mode); } /* establish DChan, verify existance + contents of measurement reports */ private function f_TC_meas_res_periodic(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; if (mp_bts_trxc_port != -1) { f_trxc_fake_rssi(rxlev2dbm(mp_ul_rxlev_exp)); f_trxc_fake_toffs256(g_pars.l1_pars.timing_offset_256syms); } f_est_dchan(); /* run for a number of seconds, send SACCH + FACCH from MS side and verify * RSL measurement reports on Abis side */ timer T := 8.0; T.start; alt { [] as_l1_sacch_loop(); [] as_meas_res(); [] as_l1_dcch_loop(); [] as_l1_tch_loop(); [] L1CTL.receive { repeat; } [g_Tmeas_exp.running] T.timeout { /* as_meas_res() would have done Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail) in case * of any earlier errors, so if we reach this timeout, we're good */ setverdict(pass); } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "No MEAS RES received at all"); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* Wait until the BTS has reached full tx power (nominal tx power minus configured attenuation) */ private function f_wait_ramp_up() runs on ConnHdlr return integer { var L1ctlMessage l1_dl; var integer max_rx_lvl := mp_bts_tx_nom_pwr_exp - mp_bts_tx_pwr_att_exp; timer Tup := 10.0; Tup.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { var GsmRxLev rx_lvl := l1_dl.dl_info.rx_level; log("Received rx_level=", rx_lvl); if (rx_lvl != max_rx_lvl) { repeat; } Tup.stop; } [] L1CTL.receive { repeat; } [] Tup.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Didn't reach full power ", max_rx_lvl)); } } return max_rx_lvl; } /* verify BTS ramps power up to full tx power (nominal tx power minus configured attenuation) */ private function f_verify_ramp_up() runs on ConnHdlr { var L1ctlMessage l1_dl; var integer initial_rx_lvl := -1; var integer last_rx_lvl := -1; var integer max_rx_lvl := mp_bts_tx_nom_pwr_exp - mp_bts_tx_pwr_att_exp; timer T := 2.0; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { var GsmRxLev rx_lvl := l1_dl.dl_info.rx_level; log("Received rx_level=", rx_lvl); if (initial_rx_lvl == -1) { initial_rx_lvl := rx_lvl; last_rx_lvl := rx_lvl; /* Expect a somehow low value during first received messages */ if (initial_rx_lvl >= max_rx_lvl / 2) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected high initial tx power during ramp up: ", initial_rx_lvl , ", full power is", max_rx_lvl)); } } /* received Rx level bigger than maximum allowed power by CN */ if (rx_lvl > max_rx_lvl) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power higher than full power: ", rx_lvl , " > ", max_rx_lvl)); } /* Make sure it never decreases, since we are rumping up */ if (last_rx_lvl > rx_lvl) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power decrease during ramp up: ", last_rx_lvl , " -> ", rx_lvl)); } if (rx_lvl == max_rx_lvl and not T.running) { /* We reached the maximum power, start timer and receive /* a few more to make sure we don't surpass it */ log("Reached full power, wating a bit more until success"); T.start; } last_rx_lvl := rx_lvl; repeat; } [] L1CTL.receive { repeat; } [] T.timeout { } } /* We didn't increase tx power during ramp up */ if (initial_rx_lvl < last_rx_lvl) { log("Tx power increased during ramp up: ", initial_rx_lvl , " -> ", last_rx_lvl); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No Tx power increase during whole ramp up: ", initial_rx_lvl , " -> ", last_rx_lvl)); } } /* verify BTS ramps power down to rx_level 0 */ private function f_verify_ramp_down(integer max_rx_lvl) runs on ConnHdlr { var L1ctlMessage l1_dl; var integer last_rx_lvl := max_rx_lvl; timer Tdown := 5.0; Tdown.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { var GsmRxLev rx_lvl := l1_dl.dl_info.rx_level; log("Received rx_level=", rx_lvl); /* received Rx level bigger than maximum allowed power by CN */ if (rx_lvl > max_rx_lvl) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power higher than full power: ", rx_lvl , " > ", max_rx_lvl)); } /* Make sure it never increases, since we are rumping down */ if (last_rx_lvl < rx_lvl) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power increase during ramp up: ", last_rx_lvl , " -> ", rx_lvl)); } last_rx_lvl := rx_lvl; if (last_rx_lvl != 0) { repeat; } /* we reached power level 0, we are done */ Tdown.stop; } [] L1CTL.receive { repeat; } [] Tdown.timeout { } } /* We didn't increase tx power during ramp down */ if (max_rx_lvl > last_rx_lvl) { log("Tx power decreased during ramp down: ", max_rx_lvl , " -> ", last_rx_lvl); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No Tx power decrease during whole ramp down: ", max_rx_lvl , " -> ", last_rx_lvl)); } } /* verify BTS power down to rx_level 0 without ramping */ private function f_verify_power_down(integer max_rx_lvl) runs on ConnHdlr { var L1ctlMessage l1_dl; var boolean first_data_ind := true; timer Tdown := 5.0; Tdown.start; alt { [first_data_ind == true] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { first_data_ind := false; var GsmRxLev rx_lvl := l1_dl.dl_info.rx_level; log("Received rx_level=", rx_lvl); /* The first data indication could still have the original power level */ if (rx_lvl != 0 and rx_lvl != max_rx_lvl) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power: ", rx_lvl , " should be 0 or ", max_rx_lvl)); } repeat; } [first_data_ind == false] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { var GsmRxLev rx_lvl := l1_dl.dl_info.rx_level; log("Received rx_level=", rx_lvl); /* Expect immediate power off so either rx_level == 0 or no report at all because TRX was shut down already */ if (rx_lvl != 0) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Detected Tx power: ", rx_lvl , " should be 0")); } repeat; } [] L1CTL.receive { repeat; } [] Tdown.timeout { } } } /* Verify Tx power reduction and ramping up during BTS bring up */ private function f_TC_tx_power_start_ramp_up_bcch(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; f_verify_ramp_up(); setverdict(pass); } testcase TC_tx_power_start_ramp_up_bcch() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_tx_power_start_ramp_up_bcch), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Verify Tx power reduction without ramping down during BTS shutdown due to Abis link failure */ private function f_TC_tx_power_down_bcch(charstring id) runs on ConnHdlr { f_connhdlr_init_vty_bsc(); f_l1_tune(L1CTL); RSL.clear; /* Wait until BTS is started and at full power */ var integer max_rx_lvl := f_wait_ramp_up(); log("Reached nominal level ", max_rx_lvl, ", shutting down OML link"); f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_verify_power_down(max_rx_lvl); setverdict(pass); } testcase TC_tx_power_down_bcch() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_tx_power_down_bcch), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Verify Tx power: * + ramping down during ADM state UNLOCKED->LOCKED * + ramping up during ADM state LOCKED->UNLOCKED */ private function f_TC_tx_power_ramp_adm_state_change(charstring id) runs on ConnHdlr { var L1ctlMessage l1_dl; var integer last_rx_lvl; f_connhdlr_init_vty_bsc(); f_l1_tune(L1CTL); RSL.clear; /* Wait until BTS is started and at full power */ var integer max_rx_lvl := f_wait_ramp_up(); log("Reached nominal level ", max_rx_lvl, ", changing ADM state to LOCKED"); log("ADM STATE UNLOCKED->LOCKED"); f_vty_enter_cfg_trx(BSCVTY); f_vty_transceive(BSCVTY, "rf_locked 1"); last_rx_lvl := max_rx_lvl; f_verify_ramp_down(max_rx_lvl); /* Let some time after we received 0dBm, then check we don't receive BCCH * anymore because scheduler has stopped after ramping down */ f_sleep(2.0); L1CTL.clear; timer Tlocked := 2.0; Tlocked.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received data_ind during rf_locked: ", l1_dl)); } [] L1CTL.receive { repeat; } [] Tlocked.timeout { setverdict(pass, "Didn't receive data_ind while in rf_locked state."); } } log("ADM STATE LOCKED->UNLOCKED"); f_vty_transceive(BSCVTY, "rf_locked 0"); f_verify_ramp_up(); setverdict(pass); } testcase TC_tx_power_ramp_adm_state_change() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_tx_power_ramp_adm_state_change), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_check_meas_bs_power_level(integer level) runs on ConnHdlr { timer T := 8.0; T.start; var RSL_Message rsl; alt { [] as_l1_sacch_loop(); [] L1CTL.receive { repeat; } [] RSL.receive(tr_RSL_MEAS_RES(g_chan_nr, ?, ?, ?)) -> value rsl { if (rsl.ies[3].body.bs_power.power_level == level) { setverdict(pass) } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Received wrong BS power level in MEAS RES ", rsl)); } } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "No MEAS RES received at all"); } } } /* See if the RSL MEASurement RESult contains expeced BS power level * set _during_ the CHANnel ACTIVation procedure. */ private function f_TC_rsl_bs_pwr_static_ass(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; if (mp_bts_trxc_port != -1) { f_trxc_fake_rssi(rxlev2dbm(mp_ul_rxlev_exp)); f_trxc_fake_toffs256(g_pars.l1_pars.timing_offset_256syms); } var uint4_t pwr_var := 1; var template (value) RSL_IE_BS_Power bs_power := ts_RSL_IE_BS_Power(pwr_var); var template (value) RSL_IE pwr := t_RSL_IE(RSL_IE_BS_POWER, RSL_IE_Body:{bs_power := bs_power}); f_est_dchan(more_ies :={valueof(pwr)}); f_check_meas_bs_power_level(pwr_var); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* See if the RSL MEASurement RESult contains expeced BS power level * set _after_ the CHANnel ACTIVation procedure. */ private function f_TC_rsl_bs_pwr_static_power_control(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; if (mp_bts_trxc_port != -1) { f_trxc_fake_rssi(rxlev2dbm(mp_ul_rxlev_exp)); f_trxc_fake_toffs256(g_pars.l1_pars.timing_offset_256syms); } var uint4_t pwr_var := 1; var template (value) RSL_IE_BS_Power bs_power := ts_RSL_IE_BS_Power(pwr_var); f_est_dchan(); RSL.send(ts_RSL_BS_PWR_CTRL(g_chan_nr, bs_power)); f_check_meas_bs_power_level(pwr_var); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_rsl_bs_pwr_static_ass() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_rsl_bs_pwr_static_ass), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_bs_pwr_static_power_control() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_rsl_bs_pwr_static_power_control), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Target level -100, first rssi -90, ms power 7, expected increase to 7+6 within 6 seconds, * second rssi -110, ms power 7+6, expected decrease to 7 within 6 seconds. * These power levels are valid for all bands and require no special handling. */ private function f_TC_rsl_ms_pwr_dyn_ass_updown(charstring id) runs on ConnHdlr { var uint5_t pwr_var := 7; var SacchL1Header l1h; f_trxc_fake_rssi(rxlev2dbm(10)); f_l1_tune(L1CTL); RSL.clear; var RSL_IE_List addl_ies; var template (value) RSL_IE_MS_Power_Parameters pp := (ts_RSL_IE_MS_Power_Parameters(''O)); addl_ies := { valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ts_RSL_IE_MS_Power(pwr_var)})), valueof(t_RSL_IE(RSL_IE_MS_POWER_PARAM, RSL_IE_Body:{ms_power_params := pp})) }; /* establish with power parameters */ f_est_dchan(more_ies := addl_ies); /* set a high value to ensure L1 power control level increases */ f_trxc_fake_rssi(rxlev2dbm(20)); timer T2 := 6.0; T2.start; alt { [] as_l1_sacch_l1h(l1h) { f_send_meas_rep_l1h(ts_MeasurementResults, l1h); if (l1h.ms_power_lvl < (pwr_var + 6)) { repeat; } T2.stop; } [] L1CTL.receive { repeat; } [] T2.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Power Level in L1 header has not increased sufficiently"); } } /* set a low value to ensure L1 power control level decreases */ f_trxc_fake_rssi(rxlev2dbm(0)); timer T4 := 6.0; T4.start; alt { [] as_l1_sacch_l1h(l1h) { f_send_meas_rep_l1h(ts_MeasurementResults, l1h); if (l1h.ms_power_lvl > pwr_var) { repeat; } T4.stop; setverdict(pass, "Power level in L1 decreased/increased as expected"); } [] L1CTL.receive { repeat; } [] T4.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Power Level in L1 header has not decreased sufficiently"); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* check that we do not exceed the max power */ private function f_TC_rsl_ms_pwr_dyn_max(charstring id) runs on ConnHdlr { var uint5_t pwr_var := 7; var SacchL1Header l1h; /* set a low value to ensure power increases */ f_trxc_fake_rssi(rxlev2dbm(10)); f_l1_tune(L1CTL); RSL.clear; var RSL_IE_List addl_ies; var template (value) RSL_IE_MS_Power_Parameters pp := (ts_RSL_IE_MS_Power_Parameters(''O)); addl_ies := { valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ts_RSL_IE_MS_Power(pwr_var)})), valueof(t_RSL_IE(RSL_IE_MS_POWER_PARAM, RSL_IE_Body:{ms_power_params := pp})) }; /* establish with power parameters */ f_est_dchan(more_ies := addl_ies); timer T1 := 10.0; T1.start; alt { [] as_l1_sacch_l1h(l1h) { f_send_meas_rep_l1h(ts_MeasurementResults, l1h); repeat; } [] L1CTL.receive { repeat; } [] T1.timeout { if (not isbound(l1h)) { setverdict(fail, "No SACCH blocks were received"); } else if (l1h.ms_power_lvl != pwr_var) { setverdict(fail, "Power level in L1 header should not have changed"); } } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* see if we reach the band max power */ private function f_TC_rsl_ms_pwr_dyn_up(charstring id) runs on ConnHdlr { var SacchL1Header l1h; var uint5_t pwr_var := 15; var uint5_t pwr_max_var := f_get_max_power_from_band(); /* set a low value to ensure power increases */ f_trxc_fake_rssi(rxlev2dbm(10)); f_l1_tune(L1CTL); RSL.clear; var template (value) RSL_IE_MS_Power ms_power := ts_RSL_IE_MS_Power(pwr_var); var template (value) RSL_IE pwr := t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ms_power}); /* establish with fixed power level */ f_est_dchan(more_ies :={valueof(pwr)}); /* check our initial power level */ f_wait_for_l1_power_level(pwr_var); /* update power param to enable power loop 48.058 The maximum power to be used is indicated in the BS and MS Power elements respectively. */ RSL.send(ts_RSL_MS_PWR_CTRL_with_pp(g_chan_nr, pwr_max_var)); /* By default, the MS power loop gets triggered every 4th SACCH block (1.92s). * We need 9 * 4 dB steps to get from 0 dBm to 33 dBm, so 9 * 1.92s total. * Add an extra offset to avoid race conditions: +1.92s. */ timer T1 := 9.0 * 1.92 + 1.92; T1.start; alt { [] as_l1_sacch_l1h(l1h) { f_send_meas_rep_l1h(ts_MeasurementResults, l1h); repeat; } [] L1CTL.receive { repeat; } [] T1.timeout { if (not isbound(l1h)) { setverdict(fail, "No SACCH blocks were received"); } else if (f_power_level_is_highest_dbm(l1h.ms_power_lvl)) { setverdict(pass, "Power level in L1 header reduced as expected"); } else { setverdict(fail, "Power level := ", l1h.ms_power_lvl, " did not ", "reach the expected value := ", pwr_max_var); } } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* see if we reach the band min power */ private function f_TC_rsl_ms_pwr_dyn_down(charstring id) runs on ConnHdlr { var SacchL1Header l1h; /* set a high value to ensure power decreases */ f_trxc_fake_rssi(rxlev2dbm(50)); f_l1_tune(L1CTL); RSL.clear; var uint5_t pwr_var := 5; var uint5_t pwr_target_val := 15; var template (value) RSL_IE_MS_Power ms_power := ts_RSL_IE_MS_Power(pwr_var); var template (value) RSL_IE pwr := t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ms_power}); /* establish with fixed power level */ f_est_dchan(more_ies :={valueof(pwr)}); /* check our initial power level */ f_wait_for_l1_power_level(pwr_var); /* update power param to enable power loop as per spec the supplied ms power IE should set the max allowed power...*/ RSL.send(ts_RSL_MS_PWR_CTRL_with_pp(g_chan_nr, pwr_target_val)); /* wait, then check that our power level was increased */ timer T1 := 10.0; T1.start; alt { [] as_l1_sacch_l1h(l1h) { repeat; } [] L1CTL.receive { repeat; } [] T1.timeout { if (not isbound(l1h)) { setverdict(fail, "No SACCH blocks were received"); } else if (f_power_level_is_lowest_dbm(l1h.ms_power_lvl)) { setverdict(pass, "Power level increased to lowest power value"); } else { setverdict(fail, "Power level NOT increased to lowest power value"); } } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* See if the power level remains constant when MS Power Parameters IE * is _absent_ in the CHANnel ACTIVation message. */ private function f_TC_rsl_ms_pwr_dyn_active(charstring id) runs on ConnHdlr { var SacchL1Header l1h; /* set a high value to ensure power decreases */ f_trxc_fake_rssi(rxlev2dbm(50)); f_l1_tune(L1CTL); RSL.clear; var uint5_t pwr_var := 5; var template (value) RSL_IE_MS_Power ms_power := ts_RSL_IE_MS_Power(pwr_var); var template (value) RSL_IE pwr := t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ms_power}); /* establish with fixed power level */ f_est_dchan(more_ies :={valueof(pwr)}); /* check our initial power level */ f_wait_for_l1_power_level(pwr_var); /* wait, then check that our power level did not change */ timer T1 := 10.0; T1.start; alt { [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.ms_power_lvl != pwr_var) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "BS power control should not be active unless we receive a power parameters IE!"); } repeat; } [] L1CTL.receive { repeat; } [] T1.timeout { setverdict(pass); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } /* See if the power level remains constant when MS Power Parameters IE * is _absent_ in the CHANnel ACTIVation and MS Power Control messages. */ private function f_TC_rsl_ms_pwr_dyn_active2(charstring id) runs on ConnHdlr { var SacchL1Header l1h; /* set a high value to ensure power decreases */ f_trxc_fake_rssi(rxlev2dbm(50)); f_l1_tune(L1CTL); RSL.clear; var uint5_t pwr_var := 5; var template (value) RSL_IE_MS_Power ms_power := ts_RSL_IE_MS_Power(pwr_var); var template (value) RSL_IE pwr := t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ms_power}); /* establish with fixed power level */ f_est_dchan(more_ies :={valueof(pwr)}); /* check our initial power level */ f_wait_for_l1_power_level(pwr_var); /* pwr control without power params IE, should NOT activate MS power control*/ RSL.send(ts_RSL_MS_PWR_CTRL(g_chan_nr, ms_power)); /* wait, then check that our power level did not change */ timer T1 := 10.0; T1.start; alt { [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.ms_power_lvl != pwr_var) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "BS power control should not be active unless we receive a power parameters IE!"); } repeat; } [] L1CTL.receive { repeat; } [] T1.timeout { setverdict(pass); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } private function f_wait_for_l1_power_level(integer level) runs on ConnHdlr { var SacchL1Header l1h; timer T0 := 10.0; T0.start; alt { [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.ms_power_lvl != level) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Power level in L1 header != signaled (RSL) power level."); } } [] L1CTL.receive { repeat; } [] T0.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for initial power level"); } } T0.stop; } private function f_power_level_is_lowest_dbm(integer level) runs on ConnHdlr return boolean { var IntegerRecord min_dbm_level; var IntegerRecord max_dbm_level; var IntegerRecord x := f_power_from_band(g_pars.bts0_band, min_dbm_level, max_dbm_level); for (var integer i := 0; i < sizeof(min_dbm_level); i := i+1) { if (min_dbm_level[i] == level) { return true; } } return false; } private function f_power_level_is_highest_dbm(integer level) runs on ConnHdlr return boolean { var IntegerRecord min_dbm_level; var IntegerRecord max_dbm_level; var IntegerRecord x := f_power_from_band(g_pars.bts0_band, min_dbm_level, max_dbm_level); for (var integer i := 0; i < sizeof(max_dbm_level); i := i+1) { if (max_dbm_level[i] == level) { return true; } } return false; } private function f_get_max_power_from_band() runs on ConnHdlr return integer { var IntegerRecord min_dbm_level; var IntegerRecord max_dbm_level; var IntegerRecord x := f_power_from_band(g_pars.bts0_band, min_dbm_level, max_dbm_level); return max_dbm_level[0]; } type charstring BtsBand ("GSM450","GSM480","GSM750","GSM810","GSM850","GSM900","DCS1800","PCS1900"); private template charstring BtsBand_allGSM := pattern "GSM???"; private function f_power_from_band(in BtsBand band, out IntegerRecord min_dbm_level, out IntegerRecord max_dbm_level) return IntegerRecord { // 45.005 4.1.1 var IntegerRecord gsm_power :={31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; var IntegerRecord dcs_power :={28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 31, 30, 29}; var IntegerRecord pcs_power :={15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 31, 30}; var IntegerRecord rv; select(band){ case (BtsBand_allGSM){ rv := gsm_power; min_dbm_level := {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19} ; max_dbm_level := {2, 1, 0}; } case("DCS1800"){ rv := dcs_power; min_dbm_level := {28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15}; max_dbm_level := {0, 29}; // let's cheat here, assume MS_TXPWR_MAX_CCH might be being broadcast, so maybe no 29,30,31 } case("PCS1900"){ rv := pcs_power; min_dbm_level := {15}; max_dbm_level := {30}; } case else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unsupported band: " & band); } } return rv; } private function f_vty_get_bts0_band() runs on test_CT return BtsBand { return f_vty_transceive_match_regex(BTSVTY, "show bts 0", "BTS 0 is of*type* in band (\w+),*", 0); } testcase TC_rsl_ms_pwr_dyn_ass_updown() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -100"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_ass_updown), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_ms_pwr_dyn_up() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -10"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN, t_guard := 30.0)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_up), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_ms_pwr_dyn_max() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -10"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_max), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_ms_pwr_dyn_down() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -100"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_down), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_ms_pwr_dyn_active() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -100"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_active), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_rsl_ms_pwr_dyn_active2() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "phy 0", "osmotrx ms-power-loop -100"); for (var integer tn := 1; tn <= 1; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); pars.bts0_band := f_vty_get_bts0_band(); vc_conn := f_start_handler(refers(f_TC_rsl_ms_pwr_dyn_active2), pars, trxc_comp := true); vc_conn.done; } f_vty_config(BTSVTY, "phy 0", "no osmotrx ms-power-loop"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } function f_TC_meas_res_speech_tchf(boolean facch_enabled) runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); ch_mode := ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1); for (var integer tn := 1; tn <= 1; tn := tn + 1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ch_mode)); pars.l1_pars.facch_enabled := facch_enabled; vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_speech_tchf() runs on test_CT { f_TC_meas_res_speech_tchf(false); } testcase TC_meas_res_speech_tchf_facch() runs on test_CT { f_TC_meas_res_speech_tchf(true); } function f_TC_meas_res_speech_tchh(boolean facch_enabled) runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); ch_mode := ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1); for (var integer ss := 0; ss <= 1; ss := ss + 1) { pars := valueof(t_Pars(t_RslChanNr_Lm(5, ss), ch_mode)); pars.l1_pars.facch_enabled := facch_enabled; vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_speech_tchh() runs on test_CT { f_TC_meas_res_speech_tchh(false) } testcase TC_meas_res_speech_tchh_facch() runs on test_CT { f_TC_meas_res_speech_tchh(true) } testcase TC_meas_res_speech_tchh_toa256() runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "supp-meas-info toa256"); ch_mode := ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1); for (var integer ss := 0; ss <= 1; ss := ss + 1) { pars := valueof(t_Pars(t_RslChanNr_Lm(5, ss), ch_mode)); pars.l1_pars.toa256_enabled := true; vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_sign_tchf() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "no supp-meas-info toa256"); for (var integer tn := 1; tn <= 4; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN(RSL_CHRT_TCH_F))); vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_sign_tchh() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "no supp-meas-info toa256"); for (var integer ss := 0; ss <= 1; ss := ss+1) { pars := valueof(t_Pars(t_RslChanNr_Lm(5, ss), ts_RSL_ChanMode_SIGN(RSL_CHRT_TCH_H))); vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_sign_sdcch4() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "no supp-meas-info toa256"); for (var integer ss := 0; ss <= 3; ss := ss+1) { pars := valueof(t_Pars(t_RslChanNr_SDCCH4(0, ss), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_sign_sdcch8() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "no supp-meas-info toa256"); for (var integer ss := 0; ss <= 7; ss := ss+1) { pars := valueof(t_Pars(t_RslChanNr_SDCCH8(6, ss), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_sign_tchh_toa256() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); f_vty_config(BTSVTY, "bts 0", "supp-meas-info toa256"); for (var integer ss := 0; ss <= 1; ss := ss+1) { pars := valueof(t_Pars(t_RslChanNr_Lm(5, ss), ts_RSL_ChanMode_SIGN(RSL_CHRT_TCH_H))); pars.l1_pars.toa256_enabled := true; vc_conn := f_start_handler(refers(f_TC_meas_res_periodic), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Make sure that we always get RSL MEASurement RESult messages regardless * of what is sent on SACCH: (RR) Measurement Report or SAPI=3 data (SMS). */ private function f_TC_meas_res_sapi3(charstring id) runs on ConnHdlr { timer Texec := 8.0; timer Timpf := 2.0; timer Tmr; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); L1CTL.clear; /* Establish the main SAPI=0 link on DCCH first */ f_tx_lapdm(ts_LAPDm_SABM(0, cr_MO_CMD, true, ''O), ts_RslLinkID_DCCH(0)); /* Give more time for the first RSL MEASurement RESult */ Tmr.start(0.480 * 2.0); Texec.start; /* EXECution timer */ Timpf.start; /* IMPFung timer */ alt { /* We expect RSL MEASurement RESult messages every ~480ms (plus some guard) */ [] RSL.receive(tr_RSL_MEAS_RES(g_pars.chan_nr)) { /* Reschedule the MEAS RES timer */ Tmr.start(0.480 + 0.120); repeat; } [] RSL.receive { repeat; } [] Tmr.timeout { setverdict(fail, "Timeout waiting for RSL MEAS RES"); } /* Inject some SAPI=3 traffic on SACCH every 2 seconds */ [] Timpf.timeout { f_tx_lapdm(ts_LAPDm_SABM(3, cr_MO_CMD, true, ''O), ts_RslLinkID_SACCH(3)); log("Injected SAPI=3 traffic on SACCH"); Timpf.start; repeat; } /* We're good if survived so far */ [] Texec.timeout { setverdict(pass); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_meas_res_speech_tchf_sapi3() runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var template ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); ch_mode := ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1); pars := t_Pars(t_RslChanNr_Bm(1), ch_mode); vc_conn := f_start_handler(refers(f_TC_meas_res_sapi3), valueof(pars)); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_meas_res_speech_tchh_sapi3() runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var template ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); ch_mode := ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1); pars := t_Pars(t_RslChanNr_Lm(5, 0), ch_mode); vc_conn := f_start_handler(refers(f_TC_meas_res_sapi3), valueof(pars)); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* establish DChan, and send MS POWER CONTROL messages via RSL, verify that * the BTS is forwarding those values to the MS via the SACCH L1 header. */ private function f_tc_rsl_ms_pwr_ctrl(charstring id) runs on ConnHdlr { var SacchL1Header l1h; var RSL_IE_MS_Power ms_power; var RSL_Message rsl; var uint5_t power_level := 0; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); ms_power.reserved := 0; ms_power.fpc_epc := false; /* Send the first power control command. This will disable any BTS/TRX * internal power control and switch the MS (which is not in scope of * this test) to a constant power level. We start with a power level * of 0 */ ms_power.power_level := power_level; rsl := valueof(ts_RSL_MS_PWR_CTRL(g_chan_nr, ms_power)); RSL.send(rsl); alt { [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.ms_power_lvl != power_level) { setverdict(fail, "Power level := ", l1h.ms_power_lvl, "does not ", "match the signaled (RSL) power level := ", power_level); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Signal a new power level via RSL for the next turn. */ if (power_level < 31) { power_level := power_level + 1; ms_power.power_level := power_level; rsl := valueof(ts_RSL_MS_PWR_CTRL(g_chan_nr, ms_power)); RSL.send(rsl); repeat; } } /* Ignore all other blocks */ [] L1CTL.receive { repeat; } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); setverdict(pass); } testcase TC_rsl_ms_pwr_ctrl() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer tn := 1; tn <= 4; tn := tn+1) { pars := valueof(t_Pars(t_RslChanNr_Bm(tn), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_tc_rsl_ms_pwr_ctrl), pars); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* establish DChan, verify that the BTS sets the TA in the first SACCH L1 header. TA for the IMM ASS messages is still controlled by g_pars.l1_pars.ms_actual_ta! */ private function f_tc_rsl_chan_initial_ta(charstring id) runs on ConnHdlr { var uint5_t ta_to_test := 16; var SacchL1Header l1h; f_l1_tune(L1CTL); RSL.clear; /* tell fake_trx to use a given timing offset for all bursts */ f_trxc_fake_toffs256(ta_to_test*256); f_est_dchan(more_ies :={valueof(t_RSL_IE(RSL_IE_TIMING_ADVANCE, RSL_IE_Body:{timing_adv := ta_to_test}))} ); alt { [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.actual_ta != ta_to_test) { setverdict(fail, "TA in L1 header does not match the signaled (RSL) TA."); } } /* Ignore all other blocks */ [] L1CTL.receive { repeat; } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); setverdict(pass); } testcase TC_rsl_chan_initial_ta() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_tc_rsl_chan_initial_ta), pars, pcu_comp := false, trxc_comp := true); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* establish DChan, verify that the BTS sets MS power in the first SACCH L1 header. */ private function f_tc_rsl_chan_initial_ms_pwr(charstring id) runs on ConnHdlr { var uint5_t ms_power_level := 7; var SacchL1Header l1h; var RSL_IE_MS_Power ms_power; ms_power.reserved := 0; ms_power.fpc_epc := false; ms_power.power_level := ms_power_level; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(more_ies :={valueof(t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ms_power := ms_power}))} ); timer T := 1.0; T.start; alt { /* Pick all SACCH blocks for checking */ [] as_l1_sacch_l1h(l1h, do_apply := false) { if (l1h.ms_power_lvl != ms_power_level) { setverdict(fail, "Power level := ", l1h.ms_power_lvl, "does not ", "match the signaled (RSL) power level := ", ms_power_level); } } /* Ignore all other blocks */ [] L1CTL.receive { repeat; } [] T.timeout { setverdict(fail, "Power Level in L1 header does not match the signaled (RSL) MS Power Level."); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); setverdict(pass); } testcase TC_rsl_chan_initial_ms_pwr() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_testmatrix_each_chan(pars, refers(f_tc_rsl_chan_initial_ms_pwr)); } /* Test if a channel without valid uplink bursts generates RSL CONN FAIL IND (TS 48.058 4.10) */ private function f_TC_conn_fail_crit(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); f_sleep(2.0); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_L1CTL_RESET(L1CTL); f_l1_tune(L1CTL); /* tune back to BCCH */ timer T := 40.0; T.start; alt { [] RSL.receive(tr_RSL_CONN_FAIL_IND(g_chan_nr, ?)) { setverdict(pass) } [] RSL.receive { repeat }; [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "No CONN FAIL IND received"); } } f_rsl_chan_deact(); } testcase TC_conn_fail_crit() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); pars := valueof(t_Pars(t_RslChanNr_SDCCH8(6, 3), ts_RSL_ChanMode_SIGN)); pars.t_guard := 60.0; vc_conn := f_start_handler(refers(f_TC_conn_fail_crit), pars); vc_conn.done; } /*********************************************************************** * Paging ***********************************************************************/ private function tmsi_is_dummy(TMSIP_TMSI_V tmsi) return boolean { if (tmsi == 'FFFFFFFF'O) { return true; } else { return false; } } private type record allowedFn { integer frame_nr } private template allowedFn bs_ag_blks_res_0 := { frame_nr := (6, 12, 16, 22, 26, 32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_1 := { frame_nr := (12, 16, 22, 26, 32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_2 := { frame_nr := (16, 22, 26, 32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_3 := { frame_nr := (22, 26, 32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_4 := { frame_nr := (26, 32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_5 := { frame_nr := (32, 36, 42, 46) } private template allowedFn bs_ag_blks_res_6 := { frame_nr := (36, 42, 46) } private template allowedFn bs_ag_blks_res_7 := { frame_nr := (42, 46) } private function check_pch_fn(integer frame_nr, integer bs_ag_blks_res) runs on test_CT { var integer frame_nr_51; frame_nr_51 := frame_nr mod 51 var allowedFn fn_check; fn_check.frame_nr := frame_nr_51; if (bs_ag_blks_res < 0 or bs_ag_blks_res > 7) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "bs_ag_blks_res out of valid range (0..7)"); return; } if (bs_ag_blks_res == 0 and match(fn_check, bs_ag_blks_res_0)) { return; } if (bs_ag_blks_res == 1 and match(fn_check, bs_ag_blks_res_1)) { return; } if (bs_ag_blks_res == 2 and match(fn_check, bs_ag_blks_res_2)) { return; } if (bs_ag_blks_res == 3 and match(fn_check, bs_ag_blks_res_3)) { return; } if (bs_ag_blks_res == 4 and match(fn_check, bs_ag_blks_res_4)) { return; } if (bs_ag_blks_res == 5 and match(fn_check, bs_ag_blks_res_5)) { return; } if (bs_ag_blks_res == 6 and match(fn_check, bs_ag_blks_res_6)) { return; } if (bs_ag_blks_res == 7 and match(fn_check, bs_ag_blks_res_7)) { return; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "received paging on AGCH"); return; } private altstep as_l1_count_paging(inout integer num_paging_rcv_msgs, inout integer num_paging_rcv_ids, PagingTestCfg cfg) runs on test_CT { var L1ctlMessage dl; [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?, c_DummyUI)) { repeat; } [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { var octetstring without_plen := substr(dl.payload.data_ind.payload, 1, lengthof(dl.payload.data_ind.payload)-1); var PDU_ML3_NW_MS rr := dec_PDU_ML3_NW_MS(without_plen); check_pch_fn(dl.dl_info.frame_nr, cfg.bs_ag_blks_res); if (match(rr, tr_PAGING_REQ1(tr_MI_LV(t_MI_NoIdentity(?))))) { /* Ignore empty RR Paging Request (PCH filling) messages. * TODO: does it make sense to count them? */ } else if (match(rr, tr_PAGING_REQ1)) { num_paging_rcv_msgs := num_paging_rcv_msgs + 1; num_paging_rcv_ids := num_paging_rcv_ids + 1; if (ispresent(rr.msgs.rrm.pagingReq_Type1.mobileIdentity2)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } } else if (match(rr, tr_PAGING_REQ2)) { num_paging_rcv_msgs := num_paging_rcv_msgs + 1; if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type2.mobileIdentity1)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type2.mobileIdentity2)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } if (ispresent(rr.msgs.rrm.pagingReq_Type2.mobileIdentity3)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } } else if (match(rr, tr_PAGING_REQ3)) { num_paging_rcv_msgs := num_paging_rcv_msgs + 1; if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type3.mobileIdentity1)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type3.mobileIdentity2)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type3.mobileIdentity3)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } if (not tmsi_is_dummy(rr.msgs.rrm.pagingReq_Type3.mobileIdentity4)) { num_paging_rcv_ids := num_paging_rcv_ids + 1; } } repeat; } } private type record PagingTestCfg { boolean combined_ccch, integer bs_ag_blks_res, float load_factor, /* Mix in a paging request through the PCU socket for every Nth (ps_load_modulus) * paging command that is issued via RSL. */ integer ps_load_modulus, /* Wait until the paging queue inside the BTS is congested before starting to add * pagings via the PCU socket (0 = disabled) */ boolean ps_wait_cong, /* Maximum time to wait until the paging queue is drained at the end of the test. * This usually takes about 15s (size: 200, ~ 13 per s -> 15s), it is recommended * to set this value to 18s, however the draining process may take significantly * longer when the queue contains paging requests for PS as those are implemented * as immediate assignments and require an entire MAC block alone. */ float queue_drain_timeout, boolean exp_load_ind, boolean exp_overload, boolean use_tmsi } private type record PagingTestState { integer num_paging_sent, integer num_paging_rcv_msgs, integer num_paging_rcv_ids, integer num_overload, /* When free space inside the paging queue reaches more than 2 thirds * of its capacity cong_detected is set to true. */ boolean cong_detected } /* Helper function for paging related testing */ private function f_TC_paging(PagingTestCfg cfg) runs on test_CT return PagingTestState { /* Using the PCU socket affects the timing of the paging processing. We only * activate the PCU socket if we do paging load tests that include additional * paging load that is introduced through the PCU socket. */ if (cfg.ps_load_modulus > 0) { f_init_with_pcuif(); } else { f_init(); } f_init_l1ctl(); f_l1_tune(L1CTL); var octetstring imm_ass := f_rnd_octstring(23); var ASP_RSL_Unitdata load_ind; var PagingTestState st := { num_paging_sent := 0, num_paging_rcv_msgs := 0, num_paging_rcv_ids := 0, num_overload := 0, cong_detected := false }; var float max_pch_blocks_per_sec := f_pch_block_rate_est(cfg.combined_ccch, cfg.bs_ag_blks_res); var float max_pch_imsi_per_sec; if (cfg.use_tmsi) { max_pch_imsi_per_sec := max_pch_blocks_per_sec * 4.0; /* Type 3 */ } else { max_pch_imsi_per_sec := max_pch_blocks_per_sec * 2.0; /* Type 1 */ } var float pch_blocks_per_sec := max_pch_imsi_per_sec * cfg.load_factor; var float interval := 1.0 / pch_blocks_per_sec; var float time_total := 20.0; var integer pkt_total := float2int(time_total * pch_blocks_per_sec); log("pch_blocks_total=", pkt_total," pch_blocks_per_sec=", pch_blocks_per_sec, " interval=", interval); timer T_total := 300.0; /* big value (far bigger than time_total), used to count elapsed time */ T_total.start; timer T_itv := 0.0; T_itv.start; while (st.num_paging_sent < pkt_total) { alt { /* check for presence of CCCH LOAD IND (paging load) */ [cfg.exp_overload] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_LOAD_IND(0))) { st.num_overload := st.num_overload + 1; repeat; } [not cfg.exp_overload] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_LOAD_IND(0))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected PCH Overload"); } [cfg.exp_load_ind] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_LOAD_IND)) -> value load_ind { log("Rx LOAD_IND"); /* Detect paging congestion: The logic inside the BTS will diagnose the * paging queue as congested when the fill state is more than 66% (which * is two thirds fill state, or approx. 66 of 200 paging slots. When a * paging congestion is detected pagings that are issued through the PCU * socket (PS) are dropped in order to prefer the CS related pagings. */ if (load_ind.rsl.ies[1].body.paging_load < 66) { st.cong_detected := true; } /* FIXME: analyze/verify interval + contents */ repeat; } /* ignore other RSL messages like RF RESource INDication */ [] RSL_CCHAN.receive { repeat; } /* check if paging requests arrive on Um side */ [] as_l1_count_paging(st.num_paging_rcv_msgs, st.num_paging_rcv_ids, cfg); [] L1CTL.receive { repeat; } /* Only relevant when testing including with PCU sock connected */ [] PCU.receive { repeat; } [] T_itv.timeout { /* Send paging cmds based on elapsed time */ var integer new_sent := f_min(pkt_total, float2int(T_total.read * pch_blocks_per_sec) + 1); while (st.num_paging_sent < new_sent) { var MobileIdentityV mi; /* build mobile Identity */ if (cfg.use_tmsi) { mi := valueof(t_MI_TMSI(f_rnd_octstring(4))); } else { mi := valueof(ts_MI_IMSI(f_gen_imsi(st.num_paging_sent))); } /* Send RSL PAGING COMMAND */ RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_PAGING_CMD(mi, st.num_paging_sent mod 4))); /* Add additional pagings through the PCU socket interface. */ if (cfg.ps_load_modulus > 0 and st.num_paging_sent mod cfg.ps_load_modulus == 0) { if (st.cong_detected == true or cfg.ps_wait_cong == false) { f_PCUIF_tx_imm_ass_pch(PCU, g_pcu_conn_id, imm_ass, '123459987'H, wait_for_cnf := false); } } st.num_paging_sent := st.num_paging_sent + 1; } if (st.num_paging_sent < pkt_total) { /* Wait for interval to next PAGING COMMAND */ var float time_now := T_total.read; var float next_sched := int2float(st.num_paging_sent)*interval; if (next_sched > time_now) { T_itv.start(next_sched - time_now); } else { T_itv.start(0.0); } } else { /* We are done, no need to keep counting */ T_total.stop; } } [] T_total.timeout { } } } timer T_wait := cfg.queue_drain_timeout T_wait.start; alt { [] as_l1_count_paging(st.num_paging_rcv_msgs, st.num_paging_rcv_ids, cfg); [] L1CTL.receive { repeat; } /* 65535 == empty paging queue, we can terminate*/ [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_LOAD_IND(65535))) { } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_PAGING_LOAD_IND)) -> value load_ind { repeat; } /* ignore other RSL messages like RF RESource INDication */ [] RSL_CCHAN.receive { repeat; } /* Only relevant when testing including with PCU sock connected */ [] PCU.receive { repeat; } [] T_wait.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Waiting for paging queue, last detected fill state: ", load_ind.rsl.ies[1].body.paging_load)); } } log("num_paging_sent=", st.num_paging_sent, " rcvd_msgs=", st.num_paging_rcv_msgs, " rcvd_ids=", st.num_paging_rcv_ids); return st; } /* Create ~ 80% paging load (IMSI only) sustained for about 20s, verifying that * - the number of Mobile Identities on Um PCH match the number of pages on RSL * - that CCCH LOAD IND (PCH) are being generated * - that CCCH LOAD IND (PCH) [no load] is received after paging flood is over */ testcase TC_paging_imsi_80percent() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); var PagingTestCfg cfg := { combined_ccch := true, bs_ag_blks_res := si3.payload.si3.ctrl_chan_desc.bs_ag_blks_res, load_factor := 0.8, ps_load_modulus := 0, ps_wait_cong := false, queue_drain_timeout := 18.0, exp_load_ind := true, exp_overload := false, use_tmsi := false }; var PagingTestState st := f_TC_paging(cfg); if (st.num_paging_sent != st.num_paging_rcv_ids) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Expected ", st.num_paging_sent, " pagings but have ", st.num_paging_rcv_ids)); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Create ~ 80% paging load (TMSI only) sustained for about 20s, verifying that * - the number of Mobile Identities on Um PCH match the number of pages on RSL * - that CCCH LOAD IND (PCH) are being generated * - that CCCH LOAD IND (PCH) [no load] is received after paging flood is over */ testcase TC_paging_tmsi_80percent() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); var PagingTestCfg cfg := { combined_ccch := true, bs_ag_blks_res := si3.payload.si3.ctrl_chan_desc.bs_ag_blks_res, load_factor := 0.8, ps_load_modulus := 0, ps_wait_cong := false, queue_drain_timeout := 18.0, exp_load_ind := true, exp_overload := false, use_tmsi := true }; var PagingTestState st := f_TC_paging(cfg); if (st.num_paging_sent != st.num_paging_rcv_ids) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Expected ", st.num_paging_sent, " pagings but have ", st.num_paging_rcv_ids)); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Create ~ 200% paging load (IMSI only) sustained for about 20s, verifying that * - the number of Mobile Identities on Um PCH are ~ 82% of the number of pages on RSL * - that CCCH LOAD IND (PCH) are being generated and reach 0 at some point * - that CCCH LOAD IND (PCH) [no load] is received after paging flood is over */ testcase TC_paging_imsi_200percent() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); var PagingTestCfg cfg := { combined_ccch := true, bs_ag_blks_res := si3.payload.si3.ctrl_chan_desc.bs_ag_blks_res, load_factor := 2.0, ps_load_modulus := 0, ps_wait_cong := false, queue_drain_timeout := 18.0, exp_load_ind := true, exp_overload := true, use_tmsi := false }; var PagingTestState st := f_TC_paging(cfg); /* We expect about 80-85% to pass, given that we can fill the paging buffer of 200 * slots and will fully drain that buffer before returning */ var template integer tpl := (st.num_paging_sent*78/100 .. st.num_paging_sent *85/100); if (not match(st.num_paging_rcv_ids, tpl)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Expected ", tpl, " pagings but have ", st.num_paging_rcv_ids)); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Create ~ 200% paging load (TMSI only) sustained for about 20s, verifying that * - the number of Mobile Identities on Um PCH are ~ 82% of the number of pages on RSL * - that CCCH LOAD IND (PCH) are being generated and reach 0 at some point * - that CCCH LOAD IND (PCH) [no load] is received after paging flood is over */ testcase TC_paging_tmsi_200percent() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); var PagingTestCfg cfg := { combined_ccch := true, bs_ag_blks_res := si3.payload.si3.ctrl_chan_desc.bs_ag_blks_res, load_factor := 2.0, ps_load_modulus := 0, ps_wait_cong := false, queue_drain_timeout := 18.0, exp_load_ind := true, exp_overload := true, use_tmsi := true }; var PagingTestState st := f_TC_paging(cfg); /* We expect about 70% to pass, given that we can fill the paging buffer of 200 * slots and will fully drain that buffer before returning */ var template integer tpl := (st.num_paging_sent*64/100 .. st.num_paging_sent *72/100); if (not match(st.num_paging_rcv_ids, tpl)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Expected ", tpl, " pagings but have ", st.num_paging_rcv_ids)); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Same as above, but with additional paging load created by PS pagings that * are issued through the PCU socket. The additional load is introduced when * the paging queue is already two thirds full. Since the BTS implements a * prioritization logic that drops the pagings from the PCU under in those * congestive sitautions the behaviour on the PS side is expected to be * unaffected and match the behavior of TC_paging_imsi_200percent() */ testcase TC_paging_imsi_200percent_with_ps() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); var PagingTestCfg cfg := { combined_ccch := true, bs_ag_blks_res := si3.payload.si3.ctrl_chan_desc.bs_ag_blks_res, load_factor := 2.0, ps_load_modulus := 16, ps_wait_cong := true, queue_drain_timeout := 18.0, exp_load_ind := true, exp_overload := true, use_tmsi := false }; var PagingTestState st := f_TC_paging(cfg); /* We expect about 80-85% to pass, given that we can fill the paging buffer of 200 * slots and will fully drain that buffer before returning */ var template integer tpl := (st.num_paging_sent*78/100 .. st.num_paging_sent *85/100); if (not match(st.num_paging_rcv_ids, tpl)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Expected ", tpl, " pagings but have ", st.num_paging_rcv_ids)); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * Immediate Assignment / AGCH ***********************************************************************/ private const MobileAllocationLV c_MA_null := { len := 0, ma := ''B } private function f_fmt_ia_stats(integer num_tx, integer num_rx, integer num_del) return charstring { return int2str(num_tx) & " sent, " & int2str(num_rx) & " received, " & int2str(num_del) & " deleted"; } private function f_TC_imm_ass(integer num_total, float sleep_s, float exp_pass) runs on test_CT { var L1ctlMessage l1_dl; timer T := 10.0; var integer num_tx := 0; var integer num_rx := 0; var integer num_del := 0; var charstring res_str; var float rx_ratio; f_init(); f_init_l1ctl(); f_l1_tune(L1CTL); for (var integer i := 0; i < num_total; i := i+1) { var ChannelDescription ch_desc := valueof(ts_ChanDescH0(ts_RslChanNr_SDCCH4(0, 0), mp_trx_pars[0].arfcn, mp_tsc_def)); var GsmRrMessage ia := valueof(ts_IMM_ASS(42, i, 5, ch_desc, c_MA_null)); var octetstring ia_enc := enc_GsmRrMessage(ia); RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_IMM_ASSIGN(ia_enc, 0))); num_tx := num_tx+1; f_sleep(sleep_s); } /* FIXME: check if imm.ass arrive on Um side */ T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_DELETE_IND(?, 0))) { num_del := num_del+1; repeat; } [] RSL_CCHAN.receive { repeat; } [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?)) -> value l1_dl { var GsmRrMessage rr := dec_GsmRrMessage(l1_dl.payload.data_ind.payload); if (not match(rr, tr_IMM_ASS(42, ?, 5, ?, ?))) { /* FIXME: Why are we seeing paging requests on PCH/AGCH? */ //Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected IMM-ASS values on AGCH: ", rr)); } else { num_rx := num_rx+1; } repeat; } [] L1CTL.receive { repeat; } [] T.timeout { } } res_str := f_fmt_ia_stats(num_tx, num_rx, num_del); log("AGCH test: " & res_str); if (num_rx + num_del != num_tx) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "RX + DEL != TX ?!?: " & res_str); } rx_ratio := int2float(num_rx) / int2float(num_tx); if (rx_ratio < exp_pass*0.8 or rx_ratio > exp_pass*1.2) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "RX ratio ("&float2str(rx_ratio)&") far from expected ("&float2str(exp_pass)&") " & res_str); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* send a long burst of 1000 IMM.ASS with 20ms spacing (50 per s); expect 75% of them to be deleted */ testcase TC_imm_ass_1000_20ms() runs on test_CT { f_TC_imm_ass(1000, 0.02, 0.25); } /* send a short burst of 200 IMM.ASS without any spacing; expect 95% of them to be deleted */ testcase TC_imm_ass_200_0ms() runs on test_CT { f_TC_imm_ass(200, 0.0, 0.05); } /* send 150 IMM.ASS at rate of 13/s; expect none of them to be deleted */ testcase TC_imm_ass_200_76ms() runs on test_CT { f_TC_imm_ass(150, 0.076, 1.00); } /*********************************************************************** * BCCH ***********************************************************************/ /* tuple of Frame Number + decoded SI */ private type record SystemInformationFn { GsmFrameNumber frame_number, SystemInformation si } /* an arbitrary-length vector of decoded SI + gsmtap header */ private type record of SystemInformationFn SystemInformationVector; /* an array of SI-vectors indexed by TC value */ private type SystemInformationVector SystemInformationVectorPerTc[8]; /* determine if a given SI vector contains given SI type at least once */ private function f_si_vecslot_contains(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false) return boolean { for (var integer i:= 0; i< sizeof(arr); i := i + 1) { var integer fn_mod51 := arr[i].frame_number mod 51; if (not bcch_ext and fn_mod51 == 2 or bcch_ext and fn_mod51 == 6) { if (arr[i].si.header.message_type == key) { return true; } } } return false; } /* ensure a given TC slot of the SI vector contains given SI type at least once at TC */ private function f_ensure_si_vec_contains(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false) { if (not f_si_vecslot_contains(arr[tc], key, ext_bcch)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No ", key, " in TC=", tc, "!")); } } /* check if a given SI vector contains given SI type at least once on any TC */ private function f_si_vec_contains(SystemInformationVectorPerTc arr, RrMessageType key) return boolean { for (var integer tc:= 0; tc < sizeof(arr); tc := tc + 1) { if (f_si_vecslot_contains(arr[tc], key) or f_si_vecslot_contains(arr[tc], key, true)) { return true; } } return false; } /* determine if a given SI vector contains given SI type at least N of M times */ private function f_si_vecslot_contains_n_of_m(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false, integer n := 1, integer m := 4) return boolean { var integer count := 0; if (sizeof(arr) < m) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Error: Insufficient SI in array: " & int2str(sizeof(arr)) & " < " & int2str(m)); } for (var integer i:= 0; i < m; i := i + 1) { var integer fn_mod51 := arr[i].frame_number mod 51; if (not bcch_ext and fn_mod51 == 2 or bcch_ext and fn_mod51 == 6) { if (arr[i].si.header.message_type == key) { count := count + 1; } } } if (count >= n) { return true; } else { return false; } } /* ensure a given TC slot of the SI vector contains given SI type at least N out of M times at TC */ private function f_ensure_si_vec_contains_n_of_m(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false, integer n, integer m) { if (not f_si_vecslot_contains_n_of_m(arr[tc], key, ext_bcch, n, m)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Not ", n, "/", m, " of ", key, " in TC=", tc, "!")); } } /* determine if a given SI vector contains given SI type at least once */ private function f_si_vecslot_contains_only(SystemInformationVector arr, RrMessageType key, boolean bcch_ext := false) return boolean { for (var integer i:= 0; i< sizeof(arr); i := i + 1) { var integer fn_mod51 := arr[i].frame_number mod 51; if (not bcch_ext and fn_mod51 == 2 or bcch_ext and fn_mod51 == 6) { if (arr[i].si.header.message_type != key) { return false; } } } return true; } /* ensure a given TC slot of the SI vector contains only given SI type */ private function f_ensure_si_vec_contains_only(SystemInformationVectorPerTc arr, integer tc, RrMessageType key, boolean ext_bcch := false) { if (not f_si_vecslot_contains_only(arr[tc], key, ext_bcch)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Not all ", key, " in TC=", tc, "!")); } } /* SI configuration of cell, against which we validate actual SI messages */ private type set SystemInformationConfig { boolean bcch_extended, boolean si1_present, boolean si2bis_present, boolean si2ter_present, boolean si2quater_present, boolean si7_present, boolean si8_present, boolean si9_present, boolean si13_present, boolean si13alt_present, boolean si15_present, boolean si16_present, boolean si17_present, boolean si2n_present, boolean si21_present, boolean si22_present } /* validate the SI scheduling according to TS 45.002 version 14.1.0 Release 14, Section 6.3.1.3 */ private function f_validate_si_scheduling(SystemInformationConfig cfg, SystemInformationVectorPerTc si_per_tc) { var integer i; for (i := 0; i < sizeof(si_per_tc); i := i + 1) { if (sizeof(si_per_tc[i]) == 0) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("No SI messages for TC=", i)); } } if (cfg.si1_present) { /* ii) System Information Type 1 needs to be sent if frequency hopping is in use or * when the NCH is present in a cell. If the MS finds another message on BCCH Norm * when TC = 0, it can assume that System Information Type 1 is not in use. */ f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_1); /* make sure *ALL* contain SI1 */ f_ensure_si_vec_contains_only(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_1); } f_ensure_si_vec_contains(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_2); /* iii) A SI 2 message will be sent at least every time TC = 1 */ f_ensure_si_vec_contains(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_3); f_ensure_si_vec_contains(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_3); f_ensure_si_vec_contains(si_per_tc, 3, SYSTEM_INFORMATION_TYPE_4); f_ensure_si_vec_contains(si_per_tc, 7, SYSTEM_INFORMATION_TYPE_4); /* iii) System information type 2 bis or 2 ter messages are sent if needed, as determined by the * system operator. If only one of them is needed, it is sent when TC = 5. If both are * needed, 2bis is sent when TC = 5 and 2ter is sent at least once within any of 4 * consecutive occurrences of TC = 4. */ if (cfg.si2bis_present and not cfg.si2ter_present) { f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2bis); } else if (cfg.si2ter_present and not cfg.si2bis_present) { f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2ter); } else if (cfg.si2ter_present and cfg.si2bis_present) { f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2bis); f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2ter, false, 1, 4); } if (cfg.si7_present or cfg.si8_present) { /* vi) Use of System Information type 7 and 8 is not always necessary. It is necessary * if System Information type 4 does not contain all information needed for cell * selection and reselection. */ if (not cfg.bcch_extended) { testcase.stop("Error: SI7/SI8 require BCCH Extd."); } if (cfg.si7_present) { f_ensure_si_vec_contains(si_per_tc, 7, SYSTEM_INFORMATION_TYPE_7, true); } if (cfg.si8_present) { f_ensure_si_vec_contains(si_per_tc, 3, SYSTEM_INFORMATION_TYPE_8, true); } } if (cfg.si2quater_present) { /* iii) System information type 2 quater is sent if needed, as determined by the system * operator. If sent on BCCH Norm, it shall be sent when TC = 5 if neither of 2bis * and 2ter are used, otherwise it shall be sent at least once within any of 4 * consecutive occurrences of TC = 4. If sent on BCCH Ext, it is sent at least once * within any of 4 consecutive occurrences of TC = 5. */ if (not (cfg.bcch_extended)) { if (not (cfg.si2bis_present or cfg.si2ter_present)) { f_ensure_si_vec_contains(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater); } else { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2quater, false, 1, 4); } } else { f_ensure_si_vec_contains_n_of_m(si_per_tc, 5, SYSTEM_INFORMATION_TYPE_2quater, true, 1, 4); } } if (cfg.si9_present) { /* vi) System Information type 9 is sent in those blocks with TC = 4 which are specified * in system information type 3 as defined in 3GPP TS 44.018. */ f_ensure_si_vec_contains(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_9); // FIXME SI3 } if (cfg.si13_present) { /* vii) System Information type 13 is only related to the GPRS service. System Information * Type 13 need only be sent if GPRS support is indicated in one or more of System * Information Type 3 or 4 or 7 or 8 messages. These messages also indicate if the * message is sent on the BCCH Norm or if the message is transmitted on the BCCH Ext. * In the case that the message is sent on the BCCH Norm, it is sent at least once * within any of 4 consecutive occurrences of TC=4. */ if (not cfg.bcch_extended) { log("not-bccch-extended"); f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13, false, 1, 4); } else { log("bccch-extended"); f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_13, true); } if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_13alt)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Cannot have SI13alt and SI13"); } } if (cfg.si16_present or cfg.si17_present) { /* viii) System Information type 16 and 17 are only related to the SoLSA service. They * should not be sent in a cell where network sharing is used (see rule xv). */ if (cfg.si22_present) { testcase.stop("Error: Cannot have SI16/SI17 and SI22!"); } if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_22)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Cannot have SI16/SI17 and SI22!"); } if (not cfg.bcch_extended) { testcase.stop("Error: SI16/SI17 requires BCCH Extd!"); } if (cfg.si16_present) { f_ensure_si_vec_contains(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_16, true); } if (cfg.si17_present) { f_ensure_si_vec_contains(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_17, true); } } /* ix) System Information type 18 and 20 are sent in order to transmit non-GSM * broadcast information. The frequency with which they are sent is determined by the * system operator. System Information type 9 identifies the scheduling of System * Information type 18 and 20 messages. */ /* x) System Information Type 19 is sent if COMPACT neighbours exist. If System * Information Type 19 is present, then its scheduling shall be indicated in System * Information Type 9. */ if (cfg.si15_present) { /* xi) System Information Type 15 is broadcast if dynamic ARFCN mapping is used in the * PLMN. If sent on BCCH Norm, it is sent at least once within any of 4 consecutive * occurrences of TC = 4. If sent on BCCH Ext, it is sent at least once within any of * 4 consecutive occurrences of TC = 1. */ if (not cfg.bcch_extended) { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_15, false, 1, 4); } else { f_ensure_si_vec_contains_n_of_m(si_per_tc, 1, SYSTEM_INFORMATION_TYPE_15, true, 1, 4); } } if (cfg.si13alt_present) { /* xii) System Information type 13 alt is only related to the GERAN Iu mode. System * Information Type 13 alt need only be sent if GERAN Iu mode support is indicated in * one or more of System Information Type 3 or 4 or 7 or 8 messages and SI 13 is not * broadcast. These messages also indicate if the message is sent on the BCCH Norm or * if the message is transmitted on the BCCH Ext. In the case that the message is sent * on the BCCH Norm, it is sent at least once within any of 4 consecutive occurrences * of TC = 4. */ if (cfg.si13_present) { testcase.stop("Error: Cannot have SI13alt and SI13"); } if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_13)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Cannot have SI13alt and SI13"); } if (not cfg.bcch_extended) { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_13alt, false, 1, 4); } else { f_ensure_si_vec_contains(si_per_tc, 0, SYSTEM_INFORMATION_TYPE_13alt, true); } } if (cfg.si2n_present) { /* xiii) System Information Type 2n is optionally sent on BCCH Norm or BCCH Ext if needed, * as determined by the system operator. In the case that the message is sent on the * BCCH Norm, it is sent at least once within any of 4 consecutive occurrences of TC = * 4. If the message is sent on BCCH Ext, it is sent at least once within any of 2 * consecutive occurrences of TC = 4. */ if (not cfg.bcch_extended) { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, false, 1, 4); } else { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_2n, true, 2, 4); } } if (cfg.si21_present) { /* xiv) System Information Type 21 is optionally sent on BCCH Norm or BCCH Ext, as * determined by the system operator. If Extended Access Barring is in use in the cell * then this message is sent at least once within any of 4 consecutive occurrences of * TC = 4 regardless if it is sent on BCCH Norm or BCCH Ext. If BCCH Ext is used in a * cell then this message shall only be sent on BCCH Ext. */ if (not cfg.bcch_extended) { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21, false, 1, 4); } else { f_ensure_si_vec_contains_n_of_m(si_per_tc, 4, SYSTEM_INFORMATION_TYPE_21, true, 1, 4); if (f_si_vecslot_contains(si_per_tc[4], SYSTEM_INFORMATION_TYPE_21)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Cannot have SI21 on BCCH Norm if BCCH Extd enabled!"); } } } if (cfg.si22_present) { /* xv) System Information Type 22 is sent if network sharing is in use in the cell. It * should not be sent in a cell where SoLSA is used (see rule viii). System * Information Type 22 instances shall be sent on BCCH Ext within any occurrence of TC * =2 and TC=6. */ if (cfg.si16_present or cfg.si17_present) { testcase.stop("Error: Cannot have SI16/SI17 and SI22!"); } if (f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_16) or f_si_vec_contains(si_per_tc, SYSTEM_INFORMATION_TYPE_17)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Cannot have SI16/SI17 and SI22!"); } if (not cfg.bcch_extended) { testcase.stop("Error: SI22 requires BCCH Extd!"); } else { f_ensure_si_vec_contains_only(si_per_tc, 2, SYSTEM_INFORMATION_TYPE_22, true); f_ensure_si_vec_contains_only(si_per_tc, 6, SYSTEM_INFORMATION_TYPE_22, true); } } } /* sample Systme Information for specified duration via L1CTL */ private function f_l1_sample_si(L1CTL_PT pt, float duration := 8.0) return SystemInformationVectorPerTc { timer T := duration; var SystemInformationVectorPerTc si_per_tc; var L1ctlMessage l1_dl; /* initialize all per-TC vectors empty */ for (var integer i:= 0; i < sizeof(si_per_tc); i := i+1) { si_per_tc[i] := {}; } /* flush all previous L1 queued msgs */ pt.clear; T.start; alt { [] pt.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { var SystemInformationFn sig := { frame_number := l1_dl.dl_info.frame_nr }; if (dec_SystemInformationSafe(l1_dl.payload.data_ind.payload, sig.si) != 0) { log("Ignoring non-RR or invalid SI ", l1_dl); repeat; } var integer tc := f_gsm_compute_tc(sig.frame_number); log("SI received at TC=", tc, ": ", sig.si); /* append to the per-TC bucket */ si_per_tc[tc] := si_per_tc[tc] & { sig }; repeat; } [] pt.receive { repeat; } [] T.timeout { } } for (var integer i:= 0; i < sizeof(si_per_tc); i := i+1) { log(testcasename(), ": TC=", i, " has #of SI=", sizeof(si_per_tc[i])); } log("si_per_tc=", si_per_tc); return si_per_tc; } /* helper function: Set given SI via RSL + validate scheduling. * CALLER MUST MAKE SURE TO CHANGE GLOBAL si_cfg! */ private function f_TC_si_sched(float duration := 8.0) runs on test_CT { var SystemInformationVectorPerTc si_per_tc; f_init_l1ctl(); f_l1_tune(L1CTL); /* Sample + Validate Scheduling */ si_per_tc := f_l1_sample_si(L1CTL, duration); f_validate_si_scheduling(si_cfg, si_per_tc); setverdict(pass); } testcase TC_si_sched_default() runs on test_CT { f_init(); /* 2+3+4 are mandatory and set in f_init() */ f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_1() runs on test_CT { f_init(); si_cfg.si1_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_1, '5506198fb38000000000000000000000000000e504002b'O); f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_2bis() runs on test_CT { f_init(); si_cfg.si2bis_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2bis, '550602bfe809b3ff00000000000000000000007900002b'O); f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_2ter() runs on test_CT { f_init(); si_cfg.si2ter_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2ter, '010603bf66b0aa0a00000002000000000000002b2b2b2b'O); f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_2ter_2bis() runs on test_CT { f_init(); si_cfg.si2bis_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2bis, '550602bfe809b3ff00000000000000000000007900002b'O); si_cfg.si2ter_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2ter, '010603bf66b0aa0a00000002000000000000002b2b2b2b'O); f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_2quater() runs on test_CT { f_init(); si_cfg.si2quater_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2quater, '050607b10004864982eddb8d555867ee3c95540b2b2b2b'O); f_TC_si_sched(16.0); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_13() runs on test_CT { /* NOTE: PCUIF connection is not used in this test case, but * without it the IUT would not broadcast SI13 (see OS#3075). */ f_init_with_pcuif(); si_cfg.si13_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_13, '0106009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b'O); f_TC_si_sched(); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_si_sched_13_2bis_2ter_2quater() runs on test_CT { /* NOTE: PCUIF connection is not used in this test case, but * without it the IUT would not broadcast SI13 (see OS#3075). */ f_init_with_pcuif(); si_cfg.si2bis_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2bis, '550602bfe809b3ff00000000000000000000007900002b'O); si_cfg.si2ter_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2ter, '010603bf66b0aa0a00000002000000000000002b2b2b2b'O); si_cfg.si2quater_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_2quater, '050607b10004864982eddb8d555867ee3c95540b2b2b2b'O); si_cfg.si13_present := true; f_rsl_bcch_fill_raw(RSL_SYSTEM_INFO_13, '0106009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b'O); f_TC_si_sched(16.0); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_bcch_info() runs on test_CT { f_init(); /* FIXME: enable / disable individual BCCH info */ //ts_RSL_BCCH_INFO(si_type, info); /* expect no ERROR REPORT after either of them * /* negative test: ensure ERROR REPORT on unsupported types */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * Low-Level Protocol Errors / ERROR REPORT ***********************************************************************/ private function f_exp_err_rep(template RSL_Cause cause) runs on test_CT { timer T := 5.0; T.start; alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_ERROR_REPORT(cause))) { setverdict(pass); } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_ERROR_REPORT(?))) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Wrong cause in RSL ERR REP"); } [] RSL_CCHAN.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RSL ERR REP"); } } } /* Provoke a protocol error (message too short) and match on ERROR REPORT */ testcase TC_rsl_protocol_error() runs on test_CT { f_init(); var RSL_Message rsl := valueof(ts_RSL_BCCH_INFO(RSL_SYSTEM_INFO_1, ''O)); rsl.ies := omit; RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); f_exp_err_rep(RSL_ERR_PROTO); } /* Provoke a mandatory IE error and match on ERROR REPORT */ testcase TC_rsl_mand_ie_error() runs on test_CT { f_init(); var RSL_Message rsl := valueof(ts_RSL_BCCH_INFO(RSL_SYSTEM_INFO_1, ''O)); rsl.ies := { rsl.ies[0] }; RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); f_exp_err_rep(RSL_ERR_MAND_IE_ERROR); } /* Provoke an IE content error and match on ERROR REPORT */ testcase TC_rsl_ie_content_error() runs on test_CT { f_init(); var RSL_Message rsl := valueof(ts_RSL_BCCH_INFO(RSL_SYSTEM_INFO_1, ''O)); rsl.ies[1].body.sysinfo_type := RSL_SYSTEM_INFO_5; RSL_CCHAN.send(ts_ASP_RSL_UD(rsl)); f_exp_err_rep(RSL_ERR_IE_CONTENT); } /* attempt to activate channel with wrong RSL Message Discriminator IE */ function f_TC_chan_act_wrong_mdisc(charstring id) runs on ConnHdlr { var template RSL_Message rsl := ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode); rsl.msg_disc := ts_RSL_MsgDisc(RSL_MDISC_RESERVED, false); RSL.send(rsl); f_rslem_unregister(0, g_chan_nr); } testcase TC_err_rep_wrong_mdisc() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars := valueof(t_Pars(ts_RslChanNr_SDCCH4(0,0), ts_RSL_ChanMode_SIGN)); f_init(); vc_conn := f_start_handler(refers(f_TC_chan_act_wrong_mdisc), pars); vc_conn.done; f_exp_err_rep(RSL_ERR_MSG_DISCR); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Send messages with wrong message type */ private function f_TC_wrong_msg_type_dchan(charstring id) runs on ConnHdlr { var template (value) RSL_Message rsl_tx; rsl_tx := ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode); rsl_tx.msg_type := RSL_MT_NOT_CMD; RSL.send(rsl_tx); f_rslem_unregister(0, g_chan_nr); } private function f_TC_wrong_msg_type_rll(charstring id) runs on ConnHdlr { var template (value) RSL_Message rsl_tx; /* we first have to activate the dedicated channel */ f_rsl_chan_act(g_pars.chan_mode); /* ... to then send an invalid RLL message */ rsl_tx := ts_RSL_UNITDATA_REQ(g_chan_nr, ts_RslLinkID_DCCH(0), '0102'O); rsl_tx.msg_type := RSL_MT_CBCH_LOAD_IND; RSL.send(rsl_tx); f_rslem_unregister(0, g_chan_nr); } testcase TC_err_rep_wrong_msg_type() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars := valueof(t_Pars(ts_RslChanNr_SDCCH4(0,0), ts_RSL_ChanMode_SIGN)); var template (value) RSL_Message rsl_tx; f_init(); /* Common Channel with wrong message type */ RSL_CCHAN.clear; rsl_tx := valueof(ts_RSL_BCCH_INFO(RSL_SYSTEM_INFO_1, ''O)); rsl_tx.msg_type := RSL_MT_LOCATION_INFO; RSL_CCHAN.send(ts_ASP_RSL_UD(rsl_tx)); f_exp_err_rep(RSL_ERR_MSG_TYPE); /* TRX Management */ RSL_CCHAN.clear; rsl_tx := ts_RSL_SACCH_FILL(RSL_SYSTEM_INFO_5, ''O); rsl_tx.msg_type := RSL_MT_UNIT_DATA_IND; RSL_CCHAN.send(ts_ASP_RSL_UD(rsl_tx)); f_exp_err_rep(RSL_ERR_MSG_TYPE); /* Dedicated Channel */ RSL_CCHAN.clear; vc_conn := f_start_handler(refers(f_TC_wrong_msg_type_dchan), pars); vc_conn.done; f_exp_err_rep(RSL_ERR_MSG_TYPE); /* RLL */ RSL_CCHAN.clear; vc_conn := f_start_handler(refers(f_TC_wrong_msg_type_rll), pars); vc_conn.done; f_exp_err_rep(RSL_ERR_MSG_TYPE); } /* Send messages in wrong sequence (RLL to an inactive lchan) */ private function f_TC_err_rep_wrong_sequence(charstring id) runs on ConnHdlr { RSL.send(ts_RSL_UNITDATA_REQ(g_chan_nr, ts_RslLinkID_DCCH(0), '0102'O)); f_rslem_unregister(0, g_chan_nr); } testcase TC_err_rep_wrong_sequence() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars := valueof(t_Pars(ts_RslChanNr_SDCCH4(0,0), ts_RSL_ChanMode_SIGN)); f_init(); RSL_CCHAN.clear; vc_conn := f_start_handler(refers(f_TC_err_rep_wrong_sequence), pars); vc_conn.done; f_exp_err_rep(RSL_ERR_MSG_SEQ); } /*********************************************************************** * IPA CRCX/MDCX/DLCS media stream handling ***********************************************************************/ /* Send IPA DLCX to inactive lchan */ private function f_TC_ipa_dlcx_not_active(charstring id) runs on ConnHdlr { f_rsl_transceive(ts_RSL_IPA_DLCX(g_chan_nr, 0), tr_RSL_IPA_DLCX_ACK(g_chan_nr, ?, ?), "IPA DLCX ACK"); } testcase TC_ipa_dlcx_not_active() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); var ConnHdlr vc_conn := f_start_handler(refers(f_TC_ipa_dlcx_not_active), pars); vc_conn.done; } /* Send IPA CRCX twice to inactive lchan */ private function f_TC_ipa_crcx_twice_not_active(charstring id) runs on ConnHdlr { f_rsl_transceive(ts_RSL_IPA_CRCX(g_chan_nr), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?), "IPA CRCX ACK"); f_rsl_transceive(ts_RSL_IPA_CRCX(g_chan_nr), tr_RSL_IPA_CRCX_NACK(g_chan_nr, RSL_ERR_RES_UNAVAIL), "IPA CRCX NACK"); } testcase TC_ipa_crcx_twice_not_active() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); var ConnHdlr vc_conn := f_start_handler(refers(f_TC_ipa_crcx_twice_not_active), pars); vc_conn.done; } /* Regular sequence of CRCX/MDCX/DLCX */ private function f_TC_ipa_crcx_mdcx_dlcx_not_active(charstring id) runs on ConnHdlr { f_rsl_transceive(ts_RSL_IPA_CRCX(g_chan_nr), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?), "IPA CRCX ACK"); var OCT4 remote_ip := f_rnd_octstring(4); var uint16_t remote_port := f_rnd_int(c_UINT16_MAX); var uint7_t rtp_pt2 := f_rnd_int(127); var uint16_t fake_conn_id := 23; /* we're too lazy to read it out from the CRCX ACK above */ f_rsl_transceive(ts_RSL_IPA_MDCX(g_chan_nr, fake_conn_id, remote_ip, remote_port, rtp_pt2), tr_RSL_IPA_MDCX_ACK(g_chan_nr, ?, ?, ?, rtp_pt2), "IPA MDCX ACK"); f_rsl_transceive(ts_RSL_IPA_DLCX(g_chan_nr, fake_conn_id), tr_RSL_IPA_DLCX_ACK(g_chan_nr, ?, ?), "IPA DLCX ACK"); } testcase TC_ipa_crcx_mdcx_dlcx_not_active() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); var ConnHdlr vc_conn := f_start_handler(refers(f_TC_ipa_crcx_mdcx_dlcx_not_active), pars); vc_conn.done; } /* Sequence of CRCX, 2x MDCX, DLCX */ private function f_TC_ipa_crcx_mdcx_mdcx_dlcx_not_active(charstring id) runs on ConnHdlr { f_rsl_transceive(ts_RSL_IPA_CRCX(g_chan_nr), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?), "IPA CRCX ACK"); var OCT4 remote_ip := f_rnd_octstring(4); var uint16_t remote_port := f_rnd_int(c_UINT16_MAX); var uint7_t rtp_pt2 := f_rnd_int(127); var uint16_t fake_conn_id := 23; /* we're too lazy to read it out from the CRCX ACK above */ f_rsl_transceive(ts_RSL_IPA_MDCX(g_chan_nr, fake_conn_id, remote_ip, remote_port, rtp_pt2), tr_RSL_IPA_MDCX_ACK(g_chan_nr, ?, ?, ?, rtp_pt2), "IPA MDCX ACK"); /* Second MDCX */ remote_ip := f_rnd_octstring(4); remote_port := f_rnd_int(c_UINT16_MAX); f_rsl_transceive(ts_RSL_IPA_MDCX(g_chan_nr, fake_conn_id, remote_ip, remote_port, rtp_pt2), tr_RSL_IPA_MDCX_ACK(g_chan_nr, ?, ?, ?, rtp_pt2), "IPA MDCX ACK"); f_rsl_transceive(ts_RSL_IPA_DLCX(g_chan_nr, fake_conn_id), tr_RSL_IPA_DLCX_ACK(g_chan_nr, ?, ?), "IPA DLCX ACK"); } testcase TC_ipa_crcx_mdcx_mdcx_dlcx_not_active() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); f_init(); var ConnHdlr vc_conn := f_start_handler(refers(f_TC_ipa_crcx_mdcx_mdcx_dlcx_not_active), pars); vc_conn.done; } /* IPA CRCX on SDCCH/4 and SDCCH/8 (doesn't make sense) */ private function f_TC_ipa_crcx_sdcch_not_active(charstring id) runs on ConnHdlr { f_rsl_transceive(ts_RSL_IPA_CRCX(g_chan_nr), tr_RSL_IPA_CRCX_NACK(g_chan_nr, ?), "IPA CRCX NACK"); } testcase TC_ipa_crcx_sdcch_not_active() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_SDCCH4(0,1), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_ipa_crcx_sdcch_not_active), pars); vc_conn.done; pars := valueof(t_Pars(t_RslChanNr_SDCCH8(6,5), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_ipa_crcx_sdcch_not_active), pars); vc_conn.done; } /* Make sure that CRCX ACK contains a valid listen address (see OS#5242) */ private function f_TC_ipa_crcx_ack_addr(charstring id) runs on ConnHdlr { var OCT4 bind_ip := f_inet_addr("192.168.1.1"); var PortNumber bind_port := 16811; var RSL_Message crcx_ack; crcx_ack := f_rsl_transceive_ret(ts_RSL_IPA_CRCX(g_chan_nr, bind_ip, bind_port), tr_RSL_IPA_CRCX_ACK(g_chan_nr, ?, ?, ?), "IPA CRCX ACK"); var HostName bts_bind_ip := f_inet_ntoa(crcx_ack.ies[2].body.ipa_local_ip); if (match(bts_bind_ip, "0.0.0.0")) { setverdict(fail, "CRCX ACK indicates nonsense addr ", bts_bind_ip); } var uint16_t conn_id := crcx_ack.ies[1].body.ipa_conn_id; f_rsl_transceive(ts_RSL_IPA_DLCX(g_chan_nr, conn_id), tr_RSL_IPA_DLCX_ACK(g_chan_nr, ?, ?), "IPA DLCX ACK"); } testcase TC_ipa_crcx_ack_addr() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_ipa_crcx_ack_addr), pars); vc_conn.done; pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_ipa_crcx_ack_addr), pars); vc_conn.done; } /*********************************************************************** * PCU Socket related tests ***********************************************************************/ /* Catch a PCUIF message (without having to use t_SD_PCUIF) */ private altstep as_pcuif_msg(out PCUIF_Message msg, template (present) PCUIF_Message tr_msg := ?) runs on test_CT { var PCUIF_send_data sd; [] PCU.receive(t_SD_PCUIF(g_pcu_conn_id, tr_msg)) -> value sd { msg := sd.data; } } /* Catch a L1CTL DL BLOCK.ind with the given parameters */ private altstep as_l1ctl_dl_block_ind(out L1ctlGprsDlBlockInd block_ind, template (present) GsmFrameNumber fn := ?, template (present) uint3_t tn := ?, template (present) uint3_t usf := ?, template (present) octetstring data := ?) runs on test_CT { var L1ctlMessage l1_dl; [] L1CTL.receive(tr_L1CTL_GPRS_DL_BLOCK_IND(fn, tn, usf, data)) -> value l1_dl { block_ind := l1_dl.payload.dl_block_ind; } } /* tuple of TDMA frame number and timeslot number */ private type record TdmaFnTn { GsmFrameNumber fn, uint3_t tn } /* Wait for a L1CTL DL BLOCK.ind and send an UL BLOCK.req */ private function f_TC_pcu_tx_ul_block_req(octetstring data, template (present) uint3_t tn := ?, template (present) uint3_t usf := ?) runs on test_CT return TdmaFnTn { var L1ctlGprsDlBlockInd block_ind; timer T; T.start(1.0); alt { [] as_l1ctl_dl_block_ind(block_ind, tn := tn, usf := usf) { var integer fn := block_ind.hdr.fn; /* Ignore PTCCH/D blocks, their Fn is too far away */ if (fn mod 104 == 12) { repeat; } /* Every fn % 13 == 12 we have either a PTCCH or an IDLE slot, thus * every fn % 13 == 8 we add 5 frames, or 4 frames othrwise. The * resulting value is first fn of the next block. */ if (fn mod 13 == 8) { fn := (fn + 5) mod (2048 * 51 * 26); } else { fn := (fn + 4) mod (2048 * 51 * 26); } L1CTL.send(ts_L1CTL_GPRS_UL_BLOCK_REQ(fn, block_ind.hdr.tn, data)); return {fn, block_ind.hdr.tn}; } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for DL BLOCK.ind (RTS)"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } return {0, 0}; /* make TITAN happy */ } /* Tune the L1 to PDCH on the given timeslot number */ private function f_TC_pcu_l1ctl_est_pdch(uint3_t tn) runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(ts_RslChanNr_PDCH(tn), ts_RSL_ChanMode_SIGN)); if (mp_freq_hop_enabled and mp_transceiver_num > 1) { f_resolve_fh_params(pars.fhp, tn); } f_l1ctl_est_dchan(L1CTL, pars); } /* Verify no RTS before ACT_REQ; verify RTS after ACT_REQ */ friend function f_TC_pcu_act_req(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, boolean exp_success) runs on test_CT { var PCUIF_Message msg; timer T := 3.0; /* we don't expect any RTS.req before PDCH are active */ T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(bts_nr)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "PCU RTS.req before PDCH active?"); } [] PCU.receive { repeat; } [] T.timeout { } } /* Send PDCH activate request for known PDCH timeslot */ PCU.send(t_SD_PCUIF(g_pcu_conn_id, ts_PCUIF_ACT_REQ(bts_nr, trx_nr, ts_nr))); /* we now expect RTS.req for this timeslot (only) */ T.start; alt { [exp_success] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(bts_nr, trx_nr, ts_nr)) { setverdict(pass); } [not exp_success] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(bts_nr, trx_nr, ts_nr)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected RTS.req for supposedly failing activation"); } [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "RTS.req for wrong TRX/TS"); } [] PCU.receive { repeat; } [exp_success] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU RTS.req"); } [not exp_success] T.timeout { setverdict(pass); } } } /* verify no more RTS after DEACT_REQ */ friend function f_TC_pcu_deact_req(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr) runs on test_CT { var PCUIF_Message msg; timer T := 3.0; /* Send PDCH activate request for known PDCH timeslot */ PCU.send(t_SD_PCUIF(g_pcu_conn_id, ts_PCUIF_DEACT_REQ(bts_nr, trx_nr, ts_nr))); /* wait for some time as there is no PCUIF_DEACT_RESP or the like, so we don't know * when it will actually have been executed in the BTS */ f_sleep(1.0); PCU.clear; /* we now expect no RTS.req for this timeslot */ T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(bts_nr, trx_nr, ts_nr)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Received unexpected PCU RTS.req"); } [] PCU.receive { repeat; } [] T.timeout { setverdict(pass); } } } friend function f_init_with_pcuif() runs on test_CT { f_init(); map(self:PCU, system:PCU); f_init_pcu(PCU, testcasename(), g_pcu_conn_id, g_pcu_last_info); PCU.send(t_SD_PCUIF(g_pcu_conn_id, ts_PCUIF_TXT_IND(0, PCU_VERSION, testcasename()))); } /* PDCH activation via PCU socket; check for presence of RTS.req */ testcase TC_pcu_act_req() runs on test_CT { f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 7, true); } /* PDCH activation via PCU socket on non-PDCU timeslot */ testcase TC_pcu_act_req_wrong_ts() runs on test_CT { f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 1, false); } /* PDCH activation via PCU socket on wrong BTS */ testcase TC_pcu_act_req_wrong_bts() runs on test_CT { f_init_with_pcuif(); f_TC_pcu_act_req(23, 0, 7, false); } /* PDCH activation via PCU socket on wrong TRX */ testcase TC_pcu_act_req_wrong_trx() runs on test_CT { f_init_with_pcuif(); f_TC_pcu_act_req(0, 23, 7, false); } /* PDCH deactivation via PCU socket; check for absence of RTS.req */ testcase TC_pcu_deact_req() runs on test_CT { f_init_with_pcuif(); /* Activate PDCH */ f_TC_pcu_act_req(0, 0, 7, true); f_sleep(1.0); /* and De-Activate again */ f_TC_pcu_deact_req(0, 0, 7); } /* Attempt to deactivate a PDCH on a non-PDCH timeslot */ testcase TC_pcu_deact_req_wrong_ts() runs on test_CT { f_init_with_pcuif(); f_TC_pcu_deact_req(0, 0, 1); } /* Test the PCU->BTS Version and BTS->PCU SI13 handshake */ function f_TC_pcu_ver_siXX(octetstring si, RSL_IE_SysinfoType rsl_si_type) runs on test_CT { var PCUIF_Message msg; timer T := 3.0; f_init_with_pcuif(); /* Set SI13 via RSL */ f_rsl_bcch_fill_raw(rsl_si_type, si); T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_DATA_IND(0, 0, 0, sapi := PCU_IF_SAPI_BCCH)) { if (substr(msg.u.data_ind.data, 0, lengthof(si)) == si) { setverdict(pass); } else { repeat; } } [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for SI"); } } } /* Test the PCU->BTS Version and BTS->PCU SI1 handshake */ testcase TC_pcu_ver_si1() runs on test_CT { const octetstring si1 := '550111132A252B27CC29AA11BB33CC'O; f_TC_pcu_ver_siXX(si1, RSL_SYSTEM_INFO_1); } /* Test the PCU->BTS Version and BTS->PCU SI3 handshake */ testcase TC_pcu_ver_si3() runs on test_CT { const octetstring si3 := '49012223242526272929AABBCC'O; f_TC_pcu_ver_siXX(si3, RSL_SYSTEM_INFO_3); } /* Test the PCU->BTS Version and BTS->PCU SI13 handshake */ testcase TC_pcu_ver_si13() runs on test_CT { const octetstring si13 := '01010203040506070909'O; f_TC_pcu_ver_siXX(si13, RSL_SYSTEM_INFO_13); } private const octetstring c_PCU_DATA := '000102030405060708090a0b0c0d0e0f10111213141516'O; /* helper function to send a PCU DATA.req */ friend function f_pcu_data_req(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, uint8_t block_nr, uint32_t fn, PCUIF_Sapi sapi, octetstring data) runs on test_CT { PCU.send(t_SD_PCUIF(g_pcu_conn_id, ts_PCUIF_DATA_REQ(bts_nr, trx_nr, ts_nr, block_nr, fn, sapi, data))); } /* helper function to wait for RTS.ind for given SAPI on given BTS/TRX/TS and then send */ friend function f_pcu_wait_rts_and_data_req(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, PCUIF_Sapi sapi, octetstring data) runs on test_CT { var PCUIF_Message msg; timer T := 3.0; T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(bts_nr, trx_nr, ts_nr, sapi)) { f_pcu_data_req(bts_nr, trx_nr, ts_nr, msg.u.rts_req.block_nr, msg.u.rts_req.fn, sapi, data); } [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RTS.ind"); } } } /* Send DATA.req on invalid BTS */ testcase TC_pcu_data_req_wrong_bts() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); f_TC_pcu_act_req(0, 0, 7, true); f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(23, 0, 7, PCU_IF_SAPI_PDTCH, data, false, false); } /* Send DATA.req on invalid TRX */ testcase TC_pcu_data_req_wrong_trx() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); f_TC_pcu_act_req(0, 0, 7, true); f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(0, 100, 7, PCU_IF_SAPI_PDTCH, data, false, false); } /* Send DATA.req on invalid timeslot */ testcase TC_pcu_data_req_wrong_ts() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); f_TC_pcu_act_req(0, 0, 7, true); f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(0, 0, 70, PCU_IF_SAPI_PDTCH, data, false, false); } /* Send DATA.req on timeslot that hasn't been activated */ testcase TC_pcu_data_req_ts_inactive() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); /* intentionally not calling f_TC_pcu_act_req() */ f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(0, 0, 7, PCU_IF_SAPI_PDTCH, data, false, false); } private function f_pcu_to_l1(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, PCUIF_Sapi sapi, octetstring data, boolean expect_data := true, boolean wait_rts := true) runs on test_CT { timer T := 5.0; var L1ctlMessage rx_dl; L1CTL.clear; PCU.clear; if (wait_rts) { f_pcu_wait_rts_and_data_req(bts_nr, trx_nr, ts_nr, sapi, data); } else { f_pcu_data_req(bts_nr, trx_nr, ts_nr, 0, 0, sapi, data); } T.start; alt { [expect_data] L1CTL.receive(tr_L1CTL_GPRS_DL_BLOCK_IND(tn := ts_nr, data := data)) { /* FIXME: why is fn of DL BLOCK.ind different to fn of RTS / DATA_REQ above? */ setverdict(pass); } [not expect_data] L1CTL.receive(tr_L1CTL_GPRS_DL_BLOCK_IND(tn := ts_nr, data := data)) -> value rx_dl { setverdict(fail, "Received unexpected ", rx_dl); } [] L1CTL.receive { repeat; } [expect_data] T.timeout { setverdict(fail, "Timeout waiting for ", data); } [not expect_data] T.timeout { setverdict(pass); } } } private function f_TC_pcu_init() runs on test_CT { f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); } testcase TC_pcu_data_req_pdtch() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); f_TC_pcu_act_req(0, 0, 7, true); f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(0, 0, 7, PCU_IF_SAPI_PDTCH, data); //c_PCU_DATA); } testcase TC_pcu_data_req_ptcch() runs on test_CT { var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_init(); f_TC_pcu_act_req(0, 0, 7, true); f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); f_pcu_to_l1(0, 0, 7, PCU_IF_SAPI_PTCCH, data); } private function f_TC_pcu_ptcch_ul(uint16_t ra) runs on test_CT { var template PCUIF_Message pcu_rach_ind; var PCUIF_Message msg; var GsmFrameNumber fn; timer T; /* Send an Access Burst on PTCCH/U over the Um-interface */ fn := f_L1CTL_RACH(L1CTL, ra := ra, combined := 0, offset := 0, chan_nr := ts_RslChanNr_PDCH(7), link_id := ts_RslLinkID_OSMO_PTCCH(0)); pcu_rach_ind := tr_PCUIF_RACH_IND(bts_nr := 0, trx_nr := 0, ts_nr := 7, ra := ra, fn := fn, sapi := PCU_IF_SAPI_PTCCH); /* Expect a RACH.ind on the PCU interface (timeout is one multi-frame) */ T.start(52.0 * 4.615 / 1000.0); alt { [] as_pcuif_msg(msg, pcu_rach_ind) { log("Rx an Access Burst on the PCU interface: ", msg); setverdict(pass); T.stop; } [] PCU.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for RACH.ind on the PCU interface"); /* Keep going, that's not the end of the world */ } } } testcase TC_pcu_ptcch() runs on test_CT { var L1ctlMessage dl; var octetstring data; timer T; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); /* Activate PDCH channel on TS7 */ f_TC_pcu_act_req(0, 0, 7, true); /* Tune the L1 to that PDCH channel */ f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_DL_TBF_CFG_REQ(0, '00000001'B)); /* Verify PTCCH/U: send several access bursts, make sure they're received */ for (var integer i := 0; i < 16; i := i + 1) { log("Sending an Access Burst towards the L1CTL interface"); f_TC_pcu_ptcch_ul(oct2int(f_rnd_ra_ps())); } /* Generate a random payload for PTCCH/D (23 octets, CS-1) */ data := f_rnd_octstring(23); /* Verify PTCCH/D: send a random data block, make sure it's received */ log("Sending a PTCCH/D block towards the PCU interface: ", data); f_pcu_wait_rts_and_data_req(0, 0, 7, PCU_IF_SAPI_PTCCH, data); /* PTCCH/D period is 2 multi-frames (2 * 52 * 4.615 ms), but * let's give it more time in case if we miss the beginning. */ T.start(2.0 * 2.0 * 52.0 * 4.615 / 1000.0); alt { [] L1CTL.receive(tr_L1CTL_GPRS_DL_BLOCK_IND(fn := ?, tn := 7, data := data)) { setverdict(pass); T.stop; } [] L1CTL.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting DL BLOCK.ind with PTCCH/D"); } } } /* Send AGCH from PCU; check it appears on Um side */ testcase TC_pcu_data_req_agch() runs on test_CT { timer T := 3.0; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); f_TC_pcu_act_req(0, 0, 7, true); f_PCUIF_tx_mac_block_agch(PCU, g_pcu_conn_id, c_PCU_DATA, confirm := false, wait_for_cnf := false, msg_id := 'ffffffff'O); T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?, c_PCU_DATA)) { setverdict(pass); } [] L1CTL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU-originated AGCH block on Um"); } } } /* Send PCH from PCU; check it appears on Um side */ testcase TC_pcu_data_req_pch() runs on test_CT { timer T := 3.0; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); f_TC_pcu_act_req(0, 0, 7, true); var PCUIF_pch pch := { msg_id := '01020304'O, imsi := "00101000000000123", data := c_PCU_DATA, confirm := false }; f_pcu_data_req(0, 0, 0, 0, 0, PCU_IF_SAPI_PCH_2, enc_PCUIF_pch(pch)); T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?, c_PCU_DATA)) { setverdict(pass); } [] L1CTL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU-originated PCH block on Um"); } } } /* Send IMM.ASS from PCU for PCH; check it appears on Um side */ testcase TC_pcu_data_req_imm_ass_pch() runs on test_CT { var octetstring req_ref := f_rnd_octstring(3); var octetstring tlli := f_rnd_octstring(4); var octetstring imm_ass := '2d063f300fc364'O & req_ref & '0000dc'O & tlli & '00232b2b2b2b'O; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); /* The BTS will use the last three IMSI digits to compute paging group. */ f_PCUIF_tx_imm_ass_pch(PCU, g_pcu_conn_id, imm_ass, '123459987'H); timer T := 0.5; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?, imm_ass)) { /* TODO: verify paging group */ setverdict(pass); } [] L1CTL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU-originated PCH block on Um"); } } } /* Send IMM.ASS from PCU for AGCH; check it appears on Um side */ testcase TC_pcu_data_req_imm_ass_agch() runs on test_CT { var octetstring req_ref := f_rnd_octstring(3); var octetstring tlli := f_rnd_octstring(4); var octetstring imm_ass := '2d063f300fc364'O & req_ref & '0000dc'O & tlli & '00232b2b2b2b'O; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); f_PCUIF_tx_mac_block_agch(PCU, g_pcu_conn_id, imm_ass); timer T := 0.5; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0), ?, imm_ass)) { setverdict(pass); } [] L1CTL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCU-originated AGCH block on Um"); } } } /* Send RACH from Um side, expect it to show up on PCU socket */ testcase TC_pcu_rach_content() runs on test_CT { f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); var GsmFrameNumber fn_last := 0; for (var integer i := 0; i < 1000; i := i+1) { var PCUIF_Message msg; var OCT1 ra := f_rnd_ra_ps(); var GsmFrameNumber fn := f_L1CTL_RACH(L1CTL, oct2int(ra)); if (fn == fn_last) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Two RACH in same FN?!?"); } fn_last := fn; timer T := 2.0; T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_RACH_IND(0, 0, 0, oct2int(ra), 0, ?, fn)) { T.stop; } [] as_pcuif_msg(msg, tr_PCUIF_RACH_IND) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected RACH IND"); } [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RACH IND"); } } } setverdict(pass); } /* Send extended (11-bit, TS1 & TS2) RACH bursts from the Um side, * expect them to show up on PCU socket (with proper BURST_TYPE_*). */ testcase TC_pcu_ext_rach_content() runs on test_CT { var template PCUIF_Message pcu_rach_ind; var GsmFrameNumber fn_last := 0; var L1ctlRachSynchSeq synch_seq; var PCUIF_BurstType pcu_bt; var GsmFrameNumber fn var BIT11 ra11; f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); for (var integer i := 0; i < 1000; i := i+1) { /* We need to test both TS1 & TS2 */ if (i rem 2 == 0) { synch_seq := RACH_SYNCH_SEQ_TS1; pcu_bt := BURST_TYPE_1; } else { synch_seq := RACH_SYNCH_SEQ_TS2; pcu_bt := BURST_TYPE_2; } ra11 := f_rnd_ra11_ps(); fn := f_L1CTL_EXT_RACH(L1CTL, bit2int(ra11), synch_seq); if (fn == fn_last) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Two RACH in same FN?!?"); } fn_last := fn; /* Compose the expected message */ pcu_rach_ind := tr_PCUIF_RACH_IND( bts_nr := 0, trx_nr := 0, ts_nr := 0, ra := bit2int(ra11), is_11bit := 1, burst_type := pcu_bt, fn := fn); var PCUIF_Message msg; timer T := 2.0; T.start; alt { [] as_pcuif_msg(msg, pcu_rach_ind) { T.stop; } [] as_pcuif_msg(msg, tr_PCUIF_RACH_IND) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected RACH IND"); } [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RACH IND"); } } } setverdict(pass); } private function f_TC_pcu_data_ind_lqual_cb(int16_t lqual_cb_exp) runs on test_CT { var PCUIF_Message msg; timer T := 1.0; L1CTL.clear; PCU.clear; /* Send a random Uplink block over the Um */ var octetstring data := '0000'O & f_rnd_octstring(21); f_TC_pcu_tx_ul_block_req(data, tn := 7); T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_DATA_IND(data := data)) { var integer lqual_cb := msg.u.data_ind.lqual_cb; log("Rx PCUIF_DATA.ind (lqual_cb=", lqual_cb, ")"); /* Make sure the actual link quality matches the expected value */ if (not match(lqual_cb, lqual_cb_exp)) { setverdict(fail, log2str("Link quality ", lqual_cb, " does not match ", "expected value ", lqual_cb_exp)); } else { setverdict(pass); } } /* Ignore PCUIF_RTS.req and PCUIF_TIME.ind */ [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PCUIF_DATA.ind"); } } } /* Verify C/I (Carrier-to-Interference ratio) processing of PDTCH frames */ testcase TC_pcu_data_ind_lqual_cb() runs on test_CT { f_init_with_pcuif(); PCU.clear; f_init_l1ctl(); f_l1_tune(L1CTL); /* Activate a PDCH channel on TS7 */ f_TC_pcu_act_req(0, 0, 7, true); /* Tune trxcon to that PDCH channel on TS7 */ f_TC_pcu_l1ctl_est_pdch(7); L1CTL.send(ts_L1CTL_GPRS_UL_TBF_CFG_REQ(0, '00000001'B)); /* C/I in centiBels, test range: -256 .. +1280, step 128 */ for (var int16_t i := -256; i <= 1280; i := i + 128) { var TrxcMessage ret; ret := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_CI(i)); log("Testing C/I=", i, " cB"); f_TC_pcu_data_ind_lqual_cb(i); } } /* Send PAGING via RSL, expect it to shw up on PCU socket */ testcase TC_pcu_paging_from_rsl() runs on test_CT { f_init_with_pcuif(); for (var integer i := 0; i < 100; i := i+1) { var MobileIdentityLV mi_lv; var octetstring mi_lv_enc; var MobileIdentityV mi; var PCUIF_Message msg; timer T := 3.0; if (i < 50) { mi := valueof(t_MI_TMSI(f_rnd_octstring(4))); } else { mi := valueof(ts_MI_IMSI(f_gen_imsi(i))); } /* Fancy encoding for PCUIF */ mi_lv := valueof(ts_MI_LV(mi)); mi_lv_enc := enc_MobileIdentityLV(mi_lv); mi_lv_enc := f_pad_oct(mi_lv_enc, 9, '00'O); /* Send RSL PAGING COMMAND */ RSL_CCHAN.send(ts_ASP_RSL_UD(ts_RSL_PAGING_CMD(mi, i mod 4))); T.start; alt { [] as_pcuif_msg(msg, tr_PCUIF_PAG_REQ(0, mi_lv_enc)) { } [] as_pcuif_msg(msg, tr_PCUIF_PAG_REQ) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected PAGING REQ"); } [] PCU.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for PAGING REQ"); } } } setverdict(pass); } /* test for periodic TIME_IND; check number of FN expired and number of TIME_IND within frames */ testcase TC_pcu_time_ind() runs on test_CT { var integer num_time_ind := 0; var integer first_fn, last_fn; var float test_duration := 5.0; var PCUIF_Message msg; timer T; f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 7, true); PCU.clear; T.start(test_duration); alt { [] as_pcuif_msg(msg, tr_PCUIF_TIME_IND(0, ?)) { num_time_ind := num_time_ind + 1; if (not isbound(first_fn)) { first_fn := msg.u.time_ind.fn; } last_fn := msg.u.time_ind.fn; repeat; } [] as_pcuif_msg(msg, tr_PCUIF_TIME_IND(?, ?)) { setverdict(fail, "Received unexpected PCUIF_TIME_IND: ", msg); repeat; } [] PCU.receive { repeat; } [] T.timeout {} } var integer fn_expired := last_fn - first_fn; log(fn_expired, " fn expired with ", num_time_ind, " PCU_TIME.ind"); /* verify the number of frames expired matches our expectation */ const float c_GSM_FN_DURATION_MS := 4.61538; var float fn_expected := test_duration * 1000.0 / c_GSM_FN_DURATION_MS; var template integer t_fn_expected := f_tolerance(float2int(fn_expected), 1, 100000, 20); if (not match(fn_expired, t_fn_expected)) { setverdict(fail, "Number of TDMA Frames (", fn_expired, ") not matching ", t_fn_expected); } /* There are three TIME.ind in every fn MOD 13 */ var float time_ind_expected := int2float(fn_expired) * 3.0 / 13.0; /* Add some tolerance */ var template integer t_time_ind_exp := f_tolerance(float2int(time_ind_expected), 1, 100000, 5); if (not match(num_time_ind, t_time_ind_exp)) { setverdict(fail, "Number of TIME.ind (", num_time_ind, ") not matching ", t_time_ind_exp); } setverdict(pass); } /* test for periodic RTS_REQ; check number of FN expired and number of RTS_IND per SAPI */ testcase TC_pcu_rts_req() runs on test_CT { var integer first_fn, last_fn; var integer num_rts_pdtch := 0; var integer num_rts_ptcch := 0; var float test_duration := 5.0; var PCUIF_Message msg; timer T; f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 7, true); PCU.clear; T.start(test_duration); alt { [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(0, 0, 7, PCU_IF_SAPI_PDTCH, ?, ?)) { num_rts_pdtch := num_rts_pdtch + 1; if (not isbound(first_fn)) { first_fn := msg.u.rts_req.fn; } last_fn := msg.u.rts_req.fn; repeat; } [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ(0, 0, 7, PCU_IF_SAPI_PTCCH, ?, ?)) { num_rts_ptcch := num_rts_ptcch + 1; if (not isbound(first_fn)) { first_fn := msg.u.rts_req.fn; } last_fn := msg.u.rts_req.fn; repeat; } [] as_pcuif_msg(msg, tr_PCUIF_RTS_REQ) { setverdict(fail, "Received unexpected PCUIF_RTS_REQ: ", msg); repeat; } [] PCU.receive { repeat; } [] T.timeout {} } var integer fn_expired := last_fn - first_fn; log(fn_expired, " fn expired with num_rts_pdtch=", num_rts_pdtch, ", num_rts_ptcch=", num_rts_ptcch); /* verify the number of frames expired matches our expectation */ const float c_GSM_FN_DURATION_MS := 4.61538; var float fn_expected := test_duration * 1000.0 / c_GSM_FN_DURATION_MS; var template integer t_fn_expected := f_tolerance(float2int(fn_expected), 1, 100000, 20); if (not match(fn_expired, t_fn_expected)) { setverdict(fail, "Number of TDMA Frames (", fn_expired, ") not matching ", t_fn_expected); } /* PTCCH is in pos. 12 + 38 of 52-multiframe, but four slots/bursts required per block */ var float ptcch_expected := int2float(fn_expired) / 52.0 * 0.5; var template integer t_ptcch_exp := f_tolerance(float2int(ptcch_expected), 1, 100000, 1); if (not match(num_rts_ptcch, t_ptcch_exp)) { setverdict(fail, "Number of RTS.ind for PTCCH (", num_rts_ptcch, ") not matching ", t_ptcch_exp); } /* We have 12 PDTCH blocks every 52-multiframe */ var float pdtch_expected := int2float(fn_expired) / 52.0 * 12.0; var template integer t_pdtch_exp := f_tolerance(float2int(pdtch_expected), 1, 100000, 2); if (not match(num_rts_pdtch, t_pdtch_exp)) { setverdict(fail, "Number of RTS.ind for PDTCH (", num_rts_pdtch, ") not matching ", t_pdtch_exp); } setverdict(pass); } /* test for generating Abis side OML ALERT from the PCU */ testcase TC_pcu_oml_alert() runs on test_CT { var PCUIF_send_data pcu_sd; var integer num_time_ind := 0; var integer first_fn, last_fn; var float test_duration := 5.0; timer T; f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 7, true); /* re-connect CTRL port from BTS to BSC */ f_ipa_ctrl_stop(); f_ipa_ctrl_start_client(mp_bsc_ctrl_ip, mp_bsc_ctrl_port); /* Send that OML Alert */ PCU.send(t_SD_PCUIF(g_pcu_conn_id, ts_PCUIF_TXT_IND(0, PCU_OML_ALERT, testcasename()))); /* This requires https://gerrit.osmocom.org/#/c/osmo-bsc/+/14177 to be merged */ f_ctrl_exp_trap(IPA_CTRL, "bts.0.oml_failure_report", ?); setverdict(pass); } /* test for forwarding of RR SUSPEND from CS lchan to PCU via PCU socket */ private function f_TC_rr_suspend_req(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer pcu_conn_id := -1; var RslLinkId link_id := valueof(ts_RslLinkID_DCCH(0)); var RoutingAreaIdentificationV rai := f_RAI('262'H, '42F'H, '2342'O, '55'O); var OCT4 tlli := '01020304'O; var OCT1 cause := '23'O; timer T := 5.0; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); L1CTL.clear; f_est_rll_mo(link_id.sapi, link_id, '23420815'O); var PDU_ML3_MS_NW susp_req := valueof(ts_RRM_GprsSuspReq(tlli, rai, cause)); var octetstring l3 := enc_PDU_ML3_MS_NW(susp_req); f_tx_lapdm(ts_LAPDm_I(link_id.sapi, cr_MO_CMD, true, 1, 0, l3), link_id); /* ConnHdlr has terminated after sending the RR SUSP REQ over a dedicaed channel */ T.start; alt { [] PCU.receive(t_SD_PCUIF(pcu_conn_id, tr_PCUIF_SUSP_REQ(0, tlli, ?, oct2int(cause)))) { setverdict(pass); } [] PCU.receive(t_SD_PCUIF(pcu_conn_id, tr_PCUIF_SUSP_REQ(?, ?, ?, ?))) { setverdict(fail, "Received unexpected PCUIF SUSPE REQ: "); } [] PCU.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for SUSP REQ on PCUIF"); } } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_pcu_rr_suspend() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init_with_pcuif(); pars := valueof(t_Pars(t_RslChanNr_SDCCH4(0,3), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_rr_suspend_req), pars, true); vc_conn.done; } /* Ensure that PCUIF socket can accept only a single connection */ testcase TC_pcu_socket_connect_multi() runs on test_CT { var boolean connected := false; var UD_connect_result cr; var integer cid; timer T := 5.0; var template UD_Result tr_ok := { result_code := SUCCESS, err := omit }; var template UD_Result tr_err := { result_code := ERROR, err := ? }; /* this (among other things) establishes the first connection to the PCUIF socket */ f_init_with_pcuif(); /* try to establish a second connection */ PCU.send(UD_connect:{mp_pcu_socket, -1}); T.start; alt { /* the IUT will first accept() the new connection, and close() it immediately */ [not connected] PCU.receive(UD_connect_result:{ id := ?, result := tr_ok }) -> value cr { log("BTS has accept()ed connection"); connected := true; cid := cr.id; repeat; } [connected] PCU.receive(UD_connect_result:{ id := cid, result := tr_err }) { log("BTS has close()d connection"); setverdict(pass); } /* ignore other messages related to the first connection */ [] PCU.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for connection result"); } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Ensure that PCUIF socket can disconnect + reconnect */ testcase TC_pcu_socket_reconnect() runs on test_CT { /* this (among other things) establishes the first connection to the PCUIF socket */ f_init_with_pcuif(); f_sleep(1.0); f_pcuif_close(PCU, g_pcu_conn_id); g_pcu_conn_id := -1; f_sleep(1.0); /* re-connect */ PCU.clear; f_init_pcu(PCU, testcasename(), g_pcu_conn_id, g_pcu_last_info); setverdict(pass); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Ensure that GPRS capability is not advertised before PCU socket conncet */ private function f_get_si(L1CTL_PT pt, RrMessageType si_type) runs on test_CT return SystemInformation { var L1ctlMessage l1_dl; var SystemInformation si; var integer rc; timer T := 5.0; T.start; alt { [] pt.receive(tr_L1CTL_DATA_IND(t_RslChanNr_BCCH(0), ?)) -> value l1_dl { rc := dec_SystemInformationSafe(l1_dl.payload.data_ind.payload, si); if (rc != 0 or si.header.message_type != si_type) { repeat; } } [] pt.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for ", si_type)); } } return si; } /* Check if GPRS Indicator is present in RR System Information of a given type */ private function f_si_has_gprs_indicator(RrMessageType si_type) runs on test_CT return boolean { var SystemInformation si := f_get_si(L1CTL, si_type); if (si_type == SYSTEM_INFORMATION_TYPE_3) { return si.payload.si3.rest_octets.gprs_ind.presence == '1'B; } else if (si_type == SYSTEM_INFORMATION_TYPE_4) { return si.payload.si4.rest_octets.gprs_ind.presence == '1'B; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unhandled SI type"); return false; } /* Make sure that GPRS Indicator is absent when the PCU is not connected */ private function f_TC_pcu_socket_noconnect(RrMessageType si_type) runs on test_CT { f_init(); f_init_l1ctl(); f_l1_tune(L1CTL); f_sleep(2.0); L1CTL.clear; if (f_si_has_gprs_indicator(si_type)) { setverdict(fail, si_type, " indicates GPRS even before PCU socket connected"); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_pcu_socket_noconnect_nosi3gprs() runs on test_CT { f_TC_pcu_socket_noconnect(SYSTEM_INFORMATION_TYPE_3); } testcase TC_pcu_socket_noconnect_nosi4gprs() runs on test_CT { f_TC_pcu_socket_noconnect(SYSTEM_INFORMATION_TYPE_4); } /* Ensure that GPRS capability is advertised after PCU socket connect */ private function f_TC_pcu_socket_connect(RrMessageType si_type) runs on test_CT { /* this (among other things) establishes the first connection to the PCUIF socket */ f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); f_sleep(2.0); L1CTL.clear; if (not f_si_has_gprs_indicator(si_type)) { setverdict(fail, si_type, " indicates no GPRS despite PCU socket connected"); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_pcu_socket_connect_si3gprs() runs on test_CT { f_TC_pcu_socket_connect(SYSTEM_INFORMATION_TYPE_3); } testcase TC_pcu_socket_connect_si4gprs() runs on test_CT { f_TC_pcu_socket_connect(SYSTEM_INFORMATION_TYPE_4); } /* Ensure that GPRS capability is no longer advertised after PCU socket disconnect */ private function f_TC_pcu_socket_disconnect(RrMessageType si_type) runs on test_CT { /* this (among other things) establishes the first connection to the PCUIF socket */ f_init_with_pcuif(); f_init_l1ctl(); f_l1_tune(L1CTL); f_pcuif_close(PCU, g_pcu_conn_id); g_pcu_conn_id := -1; f_sleep(1.0); /* re-connect */ PCU.clear; f_init_pcu(PCU, testcasename(), g_pcu_conn_id, g_pcu_last_info); f_sleep(2.0); L1CTL.clear; if (f_si_has_gprs_indicator(si_type)) { setverdict(fail, si_type, " indicates GPRS after PCU socket disconnected"); } else { setverdict(pass); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_pcu_socket_disconnect_nosi3gprs() runs on test_CT { f_TC_pcu_socket_disconnect(SYSTEM_INFORMATION_TYPE_3); } testcase TC_pcu_socket_disconnect_nosi4gprs() runs on test_CT { f_TC_pcu_socket_disconnect(SYSTEM_INFORMATION_TYPE_4); } /* Verify that the cell_id of SI3 (TS 04.08 9.1.35) and other values are passed properly to the PCU socket (OS#3854) */ testcase TC_pcu_socket_verify_info_ind() runs on test_CT { var SystemInformation si3 := valueof(ts_SI3_default); f_init_with_pcuif(); /* actually give the BTS some time to fully come up and to send a PCU INFO IND with the correct * information */ timer T := 2.0; T.start; alt { [] as_pcu_info_ind(PCU, g_pcu_conn_id, g_pcu_last_info) { repeat; } [] T.timeout {} } /* Verify cell_id */ var uint16_t cell_id_si3 := si3.payload.si3.cell_id; var uint16_t cell_id_pcu := g_pcu_last_info.u.info_ind.cell_id; if (cell_id_si3 != cell_id_pcu) { setverdict(fail, "Expected cell_id '", cell_id_si3, "' and got '", cell_id_pcu, "'. This either means,", " that the BTS is sending the wrong cell_id, or that the BTS sent it too early", " (OS#4179)"); } /* Verify LAC */ var uint16_t lac_si3 := si3.payload.si3.lai.lac; var uint16_t lac_pcu := g_pcu_last_info.u.info_ind.lac; if (lac_si3 != lac_pcu) { setverdict(fail, "Expected LAC ", lac_si3, " got: ", lac_pcu); } setverdict(pass); } /* Verify hopping parameters in the INFO.ind message (version >= 10) */ testcase TC_pcu_info_ind_fh_params() runs on test_CT { var PCUIF_info_ind info_ind; var FreqHopPars fhp; f_init_with_pcuif(); info_ind := g_pcu_last_info.u.info_ind; for (var integer i := 0; i < mp_transceiver_num; i := i + 1) { for (var integer tn := 0; tn < 8; tn := tn + 1) { if (info_ind.trx[i].pdch_mask[tn] != '1'B) { /* Skip inactive timeslots */ continue; } if (mp_freq_hop_enabled and mp_transceiver_num > 1) { f_resolve_fh_params(fhp, tn, trx_nr := i); } else { fhp.enabled := false; } var template PCUIF_InfoTrxTs tr_ts; if (fhp.enabled) { tr_ts := tr_PCUIF_InfoTrxTsH1( hsn := fhp.maio_hsn.hsn, maio := fhp.maio_hsn.maio, ma := f_pad_bit(fhp.ma_map.ma, 64, '0'B), ma_bit_len := mp_transceiver_num); } else { tr_ts := tr_PCUIF_InfoTrxTsH0; } var PCUIF_InfoTrxTs ts := info_ind.trx[i].ts[tn]; log("Checking timeslot #", tn, " of trx#", i, ": ", ts); if (not match(ts, tr_ts)) { setverdict(fail, "Hopping parameters do not match: ", "received ", ts, " vs expected ", tr_ts); } else { setverdict(pass); } } } } /* Verify IPv4 NSVC address in the INFO.ind message */ testcase TC_pcu_socket_nsvc_ipv4() runs on test_CT { f_init_vty_bsc(); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 remote ip 127.127.127.127"); f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_init_with_pcuif(); var PCUIF_RemoteAddr remote_addr := g_pcu_last_info.u.info_ind.remote_addr; var template PCUIF_RemoteAddr tr_remote_addr := { addr_type := { PCUIF_ADDR_TYPE_IPV4, ? }, addr := { f_pad_oct(f_inet_addr("127.127.127.127"), 16, '00'O), ? } }; if (not match(remote_addr, tr_remote_addr)) { setverdict(fail, "NSVC address ", remote_addr, " does not match ", tr_remote_addr); } setverdict(pass); } /* Verify IPv4 NSVC address in the INFO.ind message */ testcase TC_pcu_socket_nsvc_ipv6() runs on test_CT { f_init_vty_bsc(); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 remote ip fd00::ca:ff:ee"); f_init_with_pcuif(); var PCUIF_RemoteAddr remote_addr := g_pcu_last_info.u.info_ind.remote_addr; var template PCUIF_RemoteAddr tr_remote_addr := { addr_type := { PCUIF_ADDR_TYPE_IPV6, ? }, addr := { f_inet6_addr("fd00::ca:ff:ee"), ? } }; if (not match(remote_addr, tr_remote_addr)) { setverdict(fail, "NSVC address ", remote_addr, " does not match ", tr_remote_addr); } setverdict(pass); } /* Verify coding of two NSVCs in the INFO.ind message */ testcase TC_pcu_socket_two_nsvc() runs on test_CT { f_init_vty_bsc(); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 nsvci 1234"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 local udp port 1234"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 remote ip 127.127.127.127"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 0 remote udp port 1234"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 1 nsvci 5678"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 1 local udp port 5678"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 1 remote ip fd00::ca:ff:ee"); f_vty_config2(BSCVTY, {"network", "bts 0"} , "gprs nsvc 1 remote udp port 5678"); f_init_with_pcuif(); var PCUIF_info_ind info_ind := g_pcu_last_info.u.info_ind; var PCUIF_RemoteAddr remote_addr := info_ind.remote_addr; var template PCUIF_RemoteAddr tr_remote_addr := { addr_type := { PCUIF_ADDR_TYPE_IPV4, PCUIF_ADDR_TYPE_IPV6 }, addr := { f_pad_oct(f_inet_addr("127.127.127.127"), 16, '00'O), f_inet6_addr("fd00::ca:ff:ee") } }; if (not match(info_ind.nsvci, { 1234, 5678 })) { setverdict(fail, "NSVCI ", info_ind.nsvci, " does not match { 1234, 5678 }"); } if (not match(remote_addr, tr_remote_addr)) { setverdict(fail, "NSVC address ", remote_addr, " does not match ", tr_remote_addr); } if (not match(info_ind.local_port, { 1234, 5678 })) { setverdict(fail, "NSVC lport ", info_ind.local_port, " does not match { 1234, 5678 }"); } if (not match(info_ind.remote_port, { 1234, 5678 })) { setverdict(fail, "NSVC rport ", info_ind.remote_port, " does not match { 1234, 5678 }"); } setverdict(pass); } /* Verify periodic interference reports on PDCH */ testcase TC_pcu_interf_ind() runs on test_CT { var template PCUIF_Message tr_interf_ind; var integer interf_ind_num := 0; var boolean first := true; var PCUIF_Message msg; timer T; /* Set the averaging/reporting period to 480ms */ f_init_vty_bsc(); f_vty_cfg_bts(BSCVTY, 0, { "interference-meas avg-period 1" }); f_vty_transceive(BSCVTY, "drop bts connection 0 oml"); f_init_with_pcuif(); f_TC_pcu_act_req(0, 0, 7, true); /* We need trxcon for NOPE indications */ f_init_l1ctl(); f_l1_tune(L1CTL); /* Expect -120 .. -90 dBm on TS7 of BTS0/TRX0 */ tr_interf_ind := tr_PCUIF_INTERF_IND( bts_nr := 0, trx_nr := 0, fn := ?, interf := { 0, 0, 0, 0, 0, 0, 0, (90..120) } ); T.start(0.480 * 1.5); alt { /* The first interference report may contain unreliable values, so we ignore it */ [first] as_pcuif_msg(msg, tr_PCUIF_INTERF_IND(0, 0)) { /* 4 SACCH periods => 4 reports (plus some guard time) */ T.start(0.480 * 4.0 + 0.480 / 2.0); first := false; repeat; } /* Subsequent interference reports shall match our expectations */ [not first] as_pcuif_msg(msg, tr_interf_ind) { /* Check TDMA frame number period */ if (msg.u.interf_ind.fn mod 104 != 0) { setverdict(fail, "Odd TDMA frame number := ", msg.u.interf_ind.fn); } interf_ind_num := interf_ind_num + 1; if (interf_ind_num < 4) { repeat; } } [not first] as_pcuif_msg(msg, tr_PCUIF_INTERF_IND(0, 0)) { setverdict(fail, "Received unexpected interference report: ", msg); } [] PCU.receive { repeat; } [] T.timeout { setverdict(fail, "Timeout waiting for interference reports"); } } /* Reset the averaging/reporting period back to default */ f_vty_cfg_bts(BSCVTY, 0, { "interference-meas avg-period 6" }); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /*********************************************************************** * Osmocom Style Dynamic Timeslot Support ***********************************************************************/ private altstep as_pcuif_check_pdch_mask(integer pcu_conn_id, BIT1 exp, integer bts_nr, integer trx_nr) runs on ConnHdlr { var PCUIF_send_data sd; [] PCU.receive(t_SD_PCUIF(pcu_conn_id, tr_PCUIF_INFO_IND(bts_nr, ?))) -> value sd { var BIT8 pdch_mask := sd.data.u.info_ind.trx[trx_nr].pdch_mask; if (substr(pdch_mask, g_chan_nr.tn, 1) != exp) { repeat; } } } private function f_dyn_osmo_pdch_act(integer pcu_conn_id, integer bts_nr, integer trx_nr) runs on ConnHdlr { /* Expect BTS to immediately acknowledge activation as PDCH */ PCU.clear; f_rsl_chan_act(g_pars.chan_mode); /* expect INFO_IND on PCU interface listing TS as PDCH */ timer T_wait := 2.0; T_wait.start; alt { [] as_pcuif_check_pdch_mask(pcu_conn_id, '1'B, bts_nr, trx_nr); [] PCU.receive { repeat; } [] T_wait.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for PCUIF_INFO_IND PDCH_MASK to be '1' on TS", g_chan_nr.tn)); } } /* try to activate this PDCH from the PCU point of view */ PCU.send(t_SD_PCUIF(pcu_conn_id, ts_PCUIF_ACT_REQ(bts_nr, trx_nr, g_chan_nr.tn))); /* FIXME: is there a response? */ } private function f_dyn_osmo_pdch_deact(integer pcu_conn_id, integer bts_nr, integer trx_nr) runs on ConnHdlr { /* Send RSL CHAN REL (deactivate) */ PCU.clear; RSL.send(ts_RSL_RF_CHAN_REL(g_chan_nr)); /* expect BTS to ask PCU to deactivate the channel */ timer T_wait := 2.0; T_wait.start; alt { [] as_pcuif_check_pdch_mask(pcu_conn_id, '0'B, bts_nr, trx_nr); [] PCU.receive { repeat; } [] T_wait.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for PCUIF_INFO_IND PDCH_MASK to be '0' on TS", g_chan_nr.tn)); } } /* Emulate PCU asking BTS to deactivate PDCH */ PCU.send(t_SD_PCUIF(pcu_conn_id, ts_PCUIF_DEACT_REQ(bts_nr, trx_nr, g_chan_nr.tn))); alt { [] RSL.receive(tr_RSL_RF_CHAN_REL_ACK(g_chan_nr)) { setverdict(pass); } [] RSL.receive { repeat; } } } /* Activate Osmocom-style dynamic PDCH from BSC side */ private function f_TC_dyn_osmo_pdch_act_deact(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_dyn_osmo_pdch_act(pcu_conn_id, bts_nr, trx_nr); f_sleep(3.0); f_dyn_osmo_pdch_deact(pcu_conn_id, bts_nr, trx_nr); setverdict(pass); } testcase TC_dyn_osmo_pdch_act_deact() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_act_deact), pars, true); vc_conn.done; } /* send a RF CHAN REL for PDCH on an osmocom dynamci PDCH that's already inactive */ private function f_TC_dyn_osmo_pdch_unsol_deact(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); RSL.send(ts_RSL_RF_CHAN_REL(g_chan_nr)); /* since the lchan is already released, we don't expect any PCU changes, just a rel ack. */ RSL.receive(tr_RSL_RF_CHAN_REL_ACK(g_chan_nr)); setverdict(pass); } testcase TC_dyn_osmo_pdch_unsol_deact() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_unsol_deact), pars, true); vc_conn.done; } /* try to RSL CHAN ACT a PDCH on an osmocom-style PDCH that's already active */ private function f_TC_dyn_osmo_pdch_double_act(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_dyn_osmo_pdch_act(pcu_conn_id, bts_nr, trx_nr); /* Send a second Chan Activ and expect it to be NACKed */ f_rsl_transceive(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode), tr_RSL_CHAN_ACT_NACK(g_chan_nr), "RSL CHAN ACT NACK"); setverdict(pass); } testcase TC_dyn_osmo_pdch_double_act() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_double_act), pars, true); vc_conn.done; } /* try to RSL CHAN ACT a TCH/F on an osmocom-style PDCH */ private function f_TC_dyn_osmo_pdch_tchf_act(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; var RslChannelNr chan_nr := valueof(t_RslChanNr_Bm(g_chan_nr.tn)); /* register for the TCH/F channel number */ f_rslem_register(0, chan_nr); f_init_pcu(PCU, id, pcu_conn_id, first_info); f_rsl_transceive(ts_RSL_CHAN_ACT(chan_nr, g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(chan_nr), "RSL CHAN ACT"); setverdict(pass); } testcase TC_dyn_osmo_pdch_tchf_act() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_tchf_act), pars, true); vc_conn.done; } /* try to RSL CHAN ACT the TCH/H on an osmocom-style PDCH */ private function f_TC_dyn_osmo_pdch_tchh_act(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; var RslChannelNr chan_nr[2] := { valueof(t_RslChanNr_Lm(g_chan_nr.tn, 0)), valueof(t_RslChanNr_Lm(g_chan_nr.tn, 1)) }; /* register for the TCH/H channel numbers */ f_rslem_register(0, chan_nr[0]); f_rslem_register(0, chan_nr[1]); f_init_pcu(PCU, id, pcu_conn_id, first_info); f_rsl_transceive(ts_RSL_CHAN_ACT(chan_nr[1], g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(chan_nr[1]), "RSL CHAN ACT [1]"); f_rsl_transceive(ts_RSL_CHAN_ACT(chan_nr[0], g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(chan_nr[0]), "RSL CHAN ACT [0]"); setverdict(pass); } testcase TC_dyn_osmo_pdch_tchh_act() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_tchh_act), pars, true); vc_conn.done; } /* try to RSL CHAN ACT the SDCCH8 on an osmocom-style PDCH */ private function f_TC_dyn_osmo_pdch_sdcch8_act(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; var RslChannelNr chan_nr[8] := { valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 0)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 1)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 2)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 3)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 4)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 5)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 6)), valueof(t_RslChanNr_SDCCH8(g_chan_nr.tn, 7)) }; /* register for the TCH/H channel numbers */ for (var integer i := 0; i < lengthof(chan_nr); i := i + 1) { f_rslem_register(0, chan_nr[i]); } f_init_pcu(PCU, id, pcu_conn_id, first_info); for (var integer i := 0; i < lengthof(chan_nr); i := i + 1) { f_rsl_transceive(ts_RSL_CHAN_ACT(chan_nr[i], g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(chan_nr[i]), "RSL CHAN ACT [" & int2str(i) & "]"); } setverdict(pass); } testcase TC_dyn_osmo_pdch_sdcch8_act() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_PDCH(4), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_osmo_pdch_sdcch8_act), pars, true); vc_conn.done; } /* Reproduce a race condition described in OS#5245 */ private function f_rsl_chan_act_deact(charstring id) runs on ConnHdlr { f_rsl_chan_act(g_pars.chan_mode); f_sleep(1.0); f_rsl_chan_deact(); setverdict(pass); } testcase TC_dyn_osmo_pdch_tchh_race_act() runs on test_CT { var ConnHdlrPars pars[2]; var ConnHdlr vc_conn[2]; var TrxcMessage rsp; f_init(); /* Configure an artificial delay of 200 ms for TRXC RSP messages */ rsp := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TRXC_DELAY(200)); /* Activate all sub-channels of TCH/H on TS4 immediately in hope that the second * CHANnel ACTIVation message will be handled before the PHY responds to 'SETSLOT' */ for (var integer i := 0; i < sizeof(pars); i := i + 1) { /* TS4 is an Osmocom style dynamic timeslot, we want it to be TCH/H */ pars[i] := valueof(t_Pars(t_RslChanNr_Lm(4, i), ts_RSL_ChanMode_SIGN)); vc_conn[i] := f_start_handler(refers(f_rsl_chan_act_deact), pars[i], l1ctl := false); } /* Wait for all components to finish */ for (var integer i := 0; i < sizeof(pars); i := i + 1) { vc_conn[i].done; } /* Disable the artificial delay for TRXC RSP messages */ rsp := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TRXC_DELAY(0)); } testcase TC_dyn_osmo_pdch_sdcch8_race_act() runs on test_CT { var ConnHdlrPars pars[8]; var ConnHdlr vc_conn[8]; var TrxcMessage rsp; f_init(); /* Configure an artificial delay of 200 ms for TRXC RSP messages */ rsp := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TRXC_DELAY(200)); /* Activate all sub-channels of SDCCH/8 on TS4 immediately in hope that subsequent * CHANnel ACTIVation messages will be handled before the PHY responds to 'SETSLOT' */ for (var integer i := 0; i < sizeof(pars); i := i + 1) { /* TS4 is an Osmocom style dynamic timeslot, we want it to be SDCCH/8 */ pars[i] := valueof(t_Pars(t_RslChanNr_SDCCH8(4, i), ts_RSL_ChanMode_SIGN)); vc_conn[i] := f_start_handler(refers(f_rsl_chan_act_deact), pars[i], l1ctl := false); } /* Wait for all components to finish */ for (var integer i := 0; i < sizeof(pars); i := i + 1) { vc_conn[i].done; } /* Disable the artificial delay for TRXC RSP messages */ rsp := f_TRXC_transceive(BTS_TRXC, g_bts_trxc_conn_id, ts_TRXC_FAKE_TRXC_DELAY(0)); } /*********************************************************************** * IPA Style Dynamic Timeslot Support ***********************************************************************/ private function f_dyn_ipa_pdch_act(integer pcu_conn_id, integer bts_nr, integer trx_nr) runs on ConnHdlr { /* Expect BTS to immediately acknowledge activation as PDCH */ PCU.clear; RSL.send(ts_RSL_IPA_PDCH_ACT(g_chan_nr)); /* expect INFO_IND on PCU interface listing TS as PDCH */ timer T_wait := 2.0; T_wait.start; alt { [] as_pcuif_check_pdch_mask(pcu_conn_id, '1'B, bts_nr, trx_nr); [] PCU.receive { repeat; } [] T_wait.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for PCUIF_INFO_IND PDCH_MASK to be '1' on TS", g_chan_nr.tn)); } } /* try to activate this PDCH from the PCU point of view */ PCU.send(t_SD_PCUIF(pcu_conn_id, ts_PCUIF_ACT_REQ(bts_nr, trx_nr, g_chan_nr.tn))); /* FIXME: is there a response? */ RSL.receive(tr_RSL_IPA_PDCH_ACT_ACK(g_chan_nr, ?)); } private function f_dyn_ipa_pdch_deact(integer pcu_conn_id, integer bts_nr, integer trx_nr) runs on ConnHdlr { /* Send RSL CHAN REL (deactivate) */ RSL.send(ts_RSL_IPA_PDCH_DEACT(g_chan_nr)); PCU.clear; /* expect BTS to ask PCU to deactivate the channel */ timer T_wait := 2.0; T_wait.start; alt { [] as_pcuif_check_pdch_mask(pcu_conn_id, '0'B, bts_nr, trx_nr); [] PCU.receive { repeat; } [] T_wait.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for PCUIF_INFO_IND PDCH_MASK to be '0' on TS", g_chan_nr.tn)); } } /* Emulate PCU asking BTS to deactivate PDCH */ PCU.send(t_SD_PCUIF(pcu_conn_id, ts_PCUIF_DEACT_REQ(bts_nr, trx_nr, g_chan_nr.tn))); alt { [] RSL.receive(tr_RSL_IPA_PDCH_DEACT_ACK(g_chan_nr)) { setverdict(pass); } [] RSL.receive { repeat; } } } /* Activate and de-activate an IPA-style dynamic TCH/F + PDCH */ private function f_TC_dyn_ipa_pdch_act_deact(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_dyn_ipa_pdch_act(pcu_conn_id, bts_nr, trx_nr); f_sleep(3.0); f_dyn_ipa_pdch_deact(pcu_conn_id, bts_nr, trx_nr); setverdict(pass); } testcase TC_dyn_ipa_pdch_act_deact() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(3), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_ipa_pdch_act_deact), pars, true); vc_conn.done; } /* try to RSL CHAN ACT a TCH/F on an IPA-style PDCH */ private function f_TC_dyn_ipa_pdch_tchf_act(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_rsl_transceive(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(g_chan_nr), "RSL CHAN ACT"); f_rsl_transceive(ts_RSL_RF_CHAN_REL(g_chan_nr), tr_RSL_RF_CHAN_REL_ACK(g_chan_nr), "RF CHAN REL", true); setverdict(pass); } testcase TC_dyn_ipa_pdch_tchf_act() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(3), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_ipa_pdch_tchf_act), pars, true); vc_conn.done; } /* Activate IPA style dyn PDCH as TCH/F and then illegally try to activate it as PDCH, too */ private function f_TC_dyn_ipa_pdch_tchf_act_pdch_act_nack(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; f_init_pcu(PCU, id, pcu_conn_id, first_info); f_rsl_transceive(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode), tr_RSL_CHAN_ACT_ACK(g_chan_nr), "RSL CHAN ACT"); RSL.send(ts_RSL_IPA_PDCH_ACT(g_chan_nr)); alt { [] RSL.receive(tr_RSL_IPA_PDCH_ACT_NACK(g_chan_nr, ?)); [] RSL.receive(tr_RSL_IPA_PDCH_ACT_ACK(g_chan_nr, ?)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected PDCH ACT ACK"); } [] RSL.receive { repeat; } } f_rsl_transceive(ts_RSL_RF_CHAN_REL(g_chan_nr), tr_RSL_RF_CHAN_REL_ACK(g_chan_nr), "RF CHAN REL", true); setverdict(pass); } testcase TC_dyn_ipa_pdch_tchf_act_pdch_act_nack() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(3), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_ipa_pdch_tchf_act_pdch_act_nack), pars, true); vc_conn.done; } /* try to RSL CHAN ACT a TCH/F on an IPA-style PDCH that's already in PDCH mode; expect NACK */ private function f_TC_dyn_ipa_pdch_act_tchf_act_nack(charstring id) runs on ConnHdlr { var PCUIF_Message first_info; var integer ts_nr := g_chan_nr.tn; var integer trx_nr := 0; var integer bts_nr := 0; var integer pcu_conn_id := -1; /* register for the TCH/F channel number */ f_rslem_register(0, g_chan_nr); f_init_pcu(PCU, id, pcu_conn_id, first_info); f_dyn_ipa_pdch_act(pcu_conn_id, bts_nr, trx_nr); f_rsl_transceive(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode), tr_RSL_CHAN_ACT_NACK(g_chan_nr), "RSL CHAN ACT"); f_dyn_ipa_pdch_deact(pcu_conn_id, bts_nr, trx_nr); setverdict(pass); } testcase TC_dyn_ipa_pdch_act_tchf_act_nack() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); pars := valueof(t_Pars(t_RslChanNr_Bm(3), ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_dyn_ipa_pdch_act_tchf_act_nack), pars, true); vc_conn.done; } /*********************************************************************** * LAPDm / RLL related ***********************************************************************/ private function f_tx_lapdm(template (value) LapdmFrame l, template (value) RslLinkId link_id) runs on ConnHdlr { var octetstring l2 := enc_LapdmFrame(valueof(l)); var template (value) SacchL1Header l1h; /* TODO: we can use an extension of TTCN-3 for padding, i.e. PADDING('2B'O) */ if (valueof(link_id.c) == SACCH) { /* Compose dummy L1 header */ l1h := ts_SacchL1Header(g_pars.l1_pars.ms_power_level, g_pars.l1_pars.ms_actual_ta); L1CTL.send(ts_L1CTL_DATA_REQ_SACCH(g_chan_nr, link_id, l1h, f_pad_oct(l2, 21, '2B'O))); } else { /* If required, pad L2 frame with constant 0x2b filling */ L1CTL.send(ts_L1CTL_DATA_REQ(g_chan_nr, link_id, f_pad_oct(l2, 23, '2B'O))); } } friend type record RllTestCase { uint3_t sapi, RslLinkId link_id, octetstring l3, boolean exp, RSL_IE_ActivationType act_type } friend type record of RllTestCase RllTestCases; friend template RllTestCase t_EITC(template (present) uint3_t sapi, template (present) RslLinkId id, octetstring l3, boolean exp, RSL_IE_ActivationType act_type := t_RSL_IE_ActType_IA) := { sapi := sapi, link_id := id, l3 := l3, exp := exp, act_type := act_type } /* execute the same callback function with a set of different parameters (tcs) on a * variety of logical channels */ private function f_rll_testmatrix(RllTestCases tcs, void_fn fn) runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* test on each of the channels we have */ for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i+1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); /* test each of the test cases on the current channel */ for (var integer j := 0; j < sizeof(tcs); j := j+1) { pars.spec.rll := tcs[j]; log(testcasename(), ": XXX Starting ", tcs[j] , " on ", g_AllChanTypes[i]); vc_conn := f_start_handler(fn, pars); vc_conn.done; } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* test if SABM on Um triggers EST IND (TS 48.058 3.1) */ private function f_TC_rll_est_ind(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; timer T := 3.0; f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(act_type := tc.act_type); L1CTL.clear; f_tx_lapdm(ts_LAPDm_SABM(tc.sapi, cr_MO_CMD, true, tc.l3), tc.link_id); T.start; alt { [tc.l3 != ''O] RSL.receive(tr_RSL_EST_IND(g_chan_nr, tc.link_id, tc.l3)) { if (tc.exp) { setverdict(pass); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected EST IND with L3 in ", tc)); } } [tc.l3 == ''O] RSL.receive(tr_RSL_EST_IND_NOL3(g_chan_nr, tc.link_id)) { if (tc.exp) { setverdict(pass); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Unexpected EST IND without L3 in ", tc)); } } /* We also expect to receive the measurements */ [] as_meas_res(verify_meas := false); [tc.exp] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for EST IND"); } [not tc.exp] T.timeout { setverdict(pass); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_est_ind() runs on test_CT { var RllTestCases tcs := { /* SAPI0 establishment (contention resolution) */ valueof(t_EITC(0, ts_RslLinkID_DCCH(0), '01020304'O, true)), /* normal SAPI0 establishment (immediate assignment) */ valueof(t_EITC(0, ts_RslLinkID_DCCH(0), ''O, false)), /* normal SAPI0 establishment (normal assignment) */ valueof(t_EITC(0, ts_RslLinkID_DCCH(0), ''O, true, t_RSL_IE_ActType_ASS)), /* SAPI 3 doesn't support contention resolution */ valueof(t_EITC(3, ts_RslLinkID_DCCH(3), '01020304'O, false)), valueof(t_EITC(3, ts_RslLinkID_SACCH(3), '01020304'O, false)), /* normal SAPI3 establishment on main DCCH */ valueof(t_EITC(3, ts_RslLinkID_DCCH(3), ''O, true)), /* normal SAPI3 establishment on SACCH */ valueof(t_EITC(3, ts_RslLinkID_SACCH(3), ''O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_est_ind)); } /* test if RLL EST REQ trigeres SABM on Um; UA on Um triggers EST CONF (TS 48.058 3.2) */ private function f_TC_rll_est_req(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; var L1ctlMessage dl; timer T := 3.0; f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* Send a RSL EST REQ for SAPI3 on main DCCH */ RSL.send(ts_RSL_EST_REQ(g_chan_nr, tc.link_id)); T.start; alt { [] as_l1_exp_lapdm(tr_LAPDm_SABM(tc.sapi, cr_MT_CMD, true, ''O)); [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for SABM"); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_est_req_DCCH_3() runs on test_CT { var RllTestCases tcs := { /* normal SAPI3 establishment on main DCCH */ valueof(t_EITC(3, ts_RslLinkID_DCCH(3), ''O, true))//, }; f_rll_testmatrix(tcs, refers(f_TC_rll_est_req)); } testcase TC_rll_est_req_ACCH_3() runs on test_CT { var RllTestCases tcs := { /* normal SAPI3 establishment on SACCH */ valueof(t_EITC(3, ts_RslLinkID_SACCH(3), ''O, true)) } f_rll_testmatrix(tcs, refers(f_TC_rll_est_req)); } /* altstep to receive a LAPDm frame matching the given template */ friend altstep as_l1_exp_lapdm(template LapdmFrame exp) runs on ConnHdlr { var L1ctlMessage dl; [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, ?)) -> value dl { var LapdmFrame lapdm; var octetstring l2 := dl.payload.data_ind.payload; if (dl.dl_info.link_id.c == SACCH) { /* remove L1 header */ l2 := substr(l2, 2, lengthof(l2)-2); } if (ischosen(exp.ab)) { lapdm.ab := dec_LapdmFrameAB(l2); } else if (ischosen(exp.b4)) { lapdm.b4 := dec_LapdmFrameB4(l2); } else if (ischosen(exp.bbis)) { lapdm.bbis := dec_LapdmFrameBbis(l2); } else if (ischosen(exp.bter)) { lapdm.bter := dec_LapdmFrameBter(l2); } log("Rx LAPDm ", lapdm); if (match(lapdm, exp)) { setverdict(pass); } else { repeat; } } [] L1CTL.receive { repeat; } } friend function f_l1_exp_lapdm(template LapdmFrame exp, float t := 3.0) runs on ConnHdlr { timer T := t; T.start; alt { [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Timeout waiting for LAPDm ", exp)); } [] as_l1_exp_lapdm(exp); } } /* establish one Radio Link Layer via SABM -> UA. Use l3 for contention resolution */ friend function f_est_rll_mo(uint3_t sapi, RslLinkId link_id, octetstring l3) runs on ConnHdlr { timer T := 2.0; /* send SABM from MS -> BTS */ f_tx_lapdm(ts_LAPDm_SABM(sapi, cr_MO_CMD, true, l3), link_id); /* expect RLL EST IND on Abis */ T.start; alt { [l3 != ''O] RSL.receive(tr_RSL_EST_IND(g_chan_nr, link_id, l3)); [l3 == ''O] RSL.receive(tr_RSL_EST_IND_NOL3(g_chan_nr, link_id)); [] RSL.receive(tr_RSL_ERROR_IND(g_chan_nr, link_id, ?)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Failing due to RSL_ERROR_IND"); } [] RSL.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RLL EST IND"); } } /* expect UA from BTS -> MS */ f_l1_exp_lapdm(tr_LAPDm_UA(sapi, cr_MT_RSP, true, l3)); } /* test if DISC on Um triggers RLL REL IND (TS 48.058 3.3) */ private function f_TC_rll_rel_ind(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* first establish the link-layer */ f_est_rll_mo(tc.sapi, tc.link_id, tc.l3); /* then send the DISC */ f_tx_lapdm(ts_LAPDm_DISC(tc.sapi, cr_MO_CMD, true), tc.link_id); /* ... and expect the REL IND on the RSL side */ alt { [] RSL.receive(tr_RSL_REL_IND(g_chan_nr, tc.link_id)) { setverdict(pass); } /* We also expect to receive the measurements */ [] as_meas_res(verify_meas := false); } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_rel_ind_DCCH_0() runs on test_CT { var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_DCCH(0), '01020304'O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_rel_ind)); } testcase TC_rll_rel_ind_ACCH_0() runs on test_CT { var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_SACCH(0), ''O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_rel_ind)); } testcase TC_rll_rel_ind_DCCH_3() runs on test_CT { var RllTestCases tcs := { valueof(t_EITC(3, ts_RslLinkID_DCCH(3), ''O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_rel_ind)); } testcase TC_rll_rel_ind_ACCH_3() runs on test_CT { var RllTestCases tcs := { valueof(t_EITC(3, ts_RslLinkID_SACCH(3), ''O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_rel_ind)); } /* test if RLL REL REQ triggers DISC on Um; UA/DM triggers RLL REL CONF (TS 48.058 3.4) */ private function f_TC_rll_rel_req(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; f_l1_tune(L1CTL); RSL.clear; /* activate the logical channel */ f_est_dchan(); L1CTL.clear; /* first establish the link-layer */ f_est_rll_mo(tc.sapi, tc.link_id, tc.l3); /* then send the REL REQ via RSL */ RSL.send(ts_RSL_REL_REQ(g_chan_nr, tc.link_id, RSL_REL_MODE_NORMAL)); /* ... and expect the DISC on the Um side */ alt { [] as_l1_exp_lapdm(tr_LAPDm_DISC(tc.sapi, cr_MT_CMD, true)) { /* FIXME: send a UA in response to the DISC */ } } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_rel_req() runs on test_CT { var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_DCCH(0), '01020304'O, true)), valueof(t_EITC(0, ts_RslLinkID_SACCH(0), ''O, true)), valueof(t_EITC(3, ts_RslLinkID_DCCH(3), ''O, true)), valueof(t_EITC(3, ts_RslLinkID_SACCH(3), ''O, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_rel_req)); } /* TODO: test if RLL DATA REQ triggers I-frames on Um (TS 48.058 3.5) */ testcase TC_rll_data_req() runs on test_CT { } /* TODO: test if I-frames on Um trigger RLL DATA IND (TS 48.058 3.6) */ testcase TC_rll_data_ind() runs on test_CT { } /* test if RLL UNIT DATA REQ triggers UI-frame on Um (TS 48.058 3.7) */ private function f_TC_rll_ud_req(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); L1CTL.clear; /* Send UNITDATA REQ on RSL side */ RSL.send(ts_RSL_UNITDATA_REQ(g_chan_nr, tc.link_id, tc.l3)); /* Expect it to arrive on the other side */ if (tc.link_id.c == SACCH) { f_l1_exp_lapdm(tr_LAPDm_B4_UI(tc.sapi, cr_MT_CMD, tc.l3)); } else { f_l1_exp_lapdm(tr_LAPDm_UI(tc.sapi, cr_MT_CMD, tc.l3)); } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_unit_data_req_DCCH() runs on test_CT { var octetstring l3 := f_rnd_octstring(15); var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_DCCH(0), l3, true)), valueof(t_EITC(3, ts_RslLinkID_DCCH(3), l3, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_ud_req)); } testcase TC_rll_unit_data_req_ACCH() runs on test_CT { var octetstring l3 := f_rnd_octstring(19); var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_SACCH(0), l3, true)), valueof(t_EITC(3, ts_RslLinkID_SACCH(3), l3, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_ud_req)); } /* test if UI-frames on Um trigger RLL UNIT DATA IND (TS 48.058 3.8) */ private function f_TC_rll_ud_ind(charstring id) runs on ConnHdlr { var RllTestCase tc := g_pars.spec.rll; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); L1CTL.clear; /* Send LAPDm UI frame. There is no B4 format in uplink! */ f_tx_lapdm(ts_LAPDm_UI(tc.sapi, cr_MO_CMD, tc.l3), tc.link_id); /* Expdct RLL UNITDATA IND on RSL side */ alt { [] RSL.receive(tr_RSL_UNITDATA_IND(g_chan_nr, tc.link_id, tc.l3)) { setverdict(pass); } [] RSL.receive { repeat; } } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rll_unit_data_ind_DCCH() runs on test_CT { var octetstring l3 := f_rnd_octstring(20); var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_DCCH(0), l3, true)), valueof(t_EITC(3, ts_RslLinkID_DCCH(3), l3, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_ud_ind)); } testcase TC_rll_unit_data_ind_ACCH() runs on test_CT { var octetstring l3 := f_rnd_octstring(18); var RllTestCases tcs := { valueof(t_EITC(0, ts_RslLinkID_SACCH(0), l3, true)), valueof(t_EITC(3, ts_RslLinkID_SACCH(3), l3, true)) }; f_rll_testmatrix(tcs, refers(f_TC_rll_ud_ind)); } /*********************************************************************** * Encryption Related ***********************************************************************/ /* send UNITDATA_REQ from BTS to MS and expect it to arrive */ private function f_unitdata_mt(RslLinkId link_id, octetstring l3) runs on ConnHdlr { RSL.send(ts_RSL_UNITDATA_REQ(g_chan_nr, link_id, l3)); if (link_id.c == SACCH) { f_l1_exp_lapdm(tr_LAPDm_B4_UI(link_id.sapi, cr_MT_CMD, l3)); } else { f_l1_exp_lapdm(tr_LAPDm_UI(link_id.sapi, cr_MT_CMD, l3)); } } /* Expect (or not expect) other kinds of messages */ private altstep as_rsl_any_ind(boolean exp_any) runs on ConnHdlr { [exp_any] RSL.receive { repeat; } [not exp_any] RSL.receive { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Unexpected RSL message!"); } } /* Send UI frame from MS and expect it to arrive as RLL UNITDATA IND on Abis */ private function f_unitdata_mo( RslLinkId link_id, octetstring l3, boolean exp_sacch := true, /* Should tolerate SACCH messages? */ boolean exp_any := false /* Should tolerate any other RSL messages? */ ) runs on ConnHdlr { timer T := 3.0; f_tx_lapdm(ts_LAPDm_UI(link_id.sapi, cr_MO_CMD, l3), link_id); T.start; /* Expect RLL UNITDATA IND on RSL side */ alt { [] RSL.receive(tr_RSL_UNITDATA_IND(g_chan_nr, link_id, l3)) { setverdict(pass); } [exp_sacch] as_meas_res(verify_meas := false); [] as_rsl_any_ind(exp_any); [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for UNIT_DATA_IND"); } } } /* Send I-frame from MS and expect it to arrive as RLL DATA IND on Abis */ private function f_data_mo( RslLinkId link_id, boolean p, uint3_t nr, uint3_t ns, octetstring l3, boolean exp_sacch := true, /* Should tolerate SACCH messages? */ boolean exp_any := false /* Should tolerate any other RSL messages? */ ) runs on ConnHdlr { timer T := 3.0; f_tx_lapdm(ts_LAPDm_I(link_id.sapi, cr_MO_CMD, p, nr, ns, l3), link_id); T.start; /* Expect RLL DATA IND on RSL side */ alt { [] RSL.receive(tr_RSL_DATA_IND(g_chan_nr, link_id, l3)) { setverdict(pass); } [exp_sacch] as_meas_res(verify_meas := false); [] as_rsl_any_ind(exp_any); [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for DATA_IND"); } } } /* Test channel activation with A5/n right from the beginning (like in assignment + hand-over) */ private function f_TC_chan_act_encr(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); f_est_dchan(true); /* now we actually need to transmit some data both ways to check if the encryption works */ var L1ctlMessage dl; var octetstring l3 := f_rnd_octstring(20); var RslLinkId link_id := valueof(ts_RslLinkID_DCCH(0)); /* send UNITDATA_REQ from BTS to MS and expect it to arrive */ f_unitdata_mt(link_id, l3); /* Send UI frame from MS and expect it to arrive as RLL UNITDATA IND on Abis */ f_unitdata_mo(link_id, l3); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_chan_act_a51() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_1, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_chan_act_encr)); } testcase TC_chan_act_a52() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_2, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_chan_act_encr)); } testcase TC_chan_act_a53() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_3, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_chan_act_encr)); } testcase TC_chan_act_a54() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_4, f_rnd_octstring(16))); f_testmatrix_each_chan(pars, refers(f_TC_chan_act_encr)); } /* Test channel activation with A5/n right from the beginning and RSL MODE MODIFY which should break the en/decryption on purpose by supplying a new key that is unknown to the MS*/ private function f_TC_rsl_modify_encr(charstring id) runs on ConnHdlr { f_l1_tune(L1CTL); f_est_dchan(true); /* now we actually need to transmit some data both ways to check if the encryption works */ var L1ctlMessage dl; var octetstring l3 := f_rnd_octstring(20); var RslLinkId link_id := valueof(ts_RslLinkID_DCCH(0)); /* send UNITDATA_REQ from BTS to MS and expect it to arrive */ f_unitdata_mt(link_id, l3); /* Send UI frame from MS and expect it to arrive as RLL UNITDATA IND on Abis */ f_unitdata_mo(link_id, l3); var RSL_Message rsl; rsl := valueof(ts_RSL_MODE_MODIFY_REQ(g_chan_nr, ts_RSL_ChanMode_SIGN)); /* modify key to break proper encryption */ g_pars.encr.key := f_rnd_octstring(8); var RSL_IE ei := valueof(t_RSL_IE(RSL_IE_ENCR_INFO, RSL_IE_Body:{encr_info := g_pars.encr})); rsl.ies := rsl.ies & { ei }; RSL.send(rsl); timer T0 := 1.0; T0.start; /* Expect RSL MODIFY ACK */ alt { [] RSL.receive(tr_RSL_MODE_MODIFY_ACK(g_chan_nr)) {} [] RSL.receive(tr_RSL_MODE_MODIFY_NACK(g_chan_nr, ?)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,"MODE MODIFY NACK"); } [] T0.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for MODE MODIFY (N)ACK"); } } var octetstring l3msg := f_rnd_octstring(15); timer T1 := 3.0; /* Send UI frame from MS, do not expect it to arrive as RLL UNITDATA IND on Abis due to broken encryption */ f_tx_lapdm(ts_LAPDm_UI(link_id.sapi, cr_MO_CMD, l3msg), link_id); T1.start; alt { [] RSL.receive(tr_RSL_UNITDATA_IND(g_chan_nr, link_id, l3msg)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "BTS shouldn't be able to decrypt after key change") } [] T1.timeout { setverdict(pass); } } /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_rsl_modify_encr() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_1, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_rsl_modify_encr)); } /* Verify RF RESource INDication messages (periodically sent over the RSL) */ private function f_TC_rsl_rf_resource_ind(template (present) RSL_ResourceInfo info) runs on test_CT { const IpaStreamId sid := IPAC_PROTO_RSL_TRX0; var ASP_RSL_Unitdata ud; timer T; /* Intave is 6 SACCH periods by default */ var float Tval := int2float(6 * 480) / 1000.0 + 0.5; T.start(Tval); alt { [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RF_RES_IND(info), sid)) { setverdict(pass); } [] RSL_CCHAN.receive(tr_ASP_RSL_UD(tr_RSL_RF_RES_IND(?), sid)) -> value ud { setverdict(fail, "RF RESource INDication mismatch: ", ud.rsl); } [] RSL_CCHAN.receive { repeat; } [] T.timeout { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for RF RESource INDication"); } } } testcase TC_rsl_rf_resource_ind() runs on test_CT { var TrxParsItem trx_pars := mp_trx_pars[0]; var RSL_ResourceInfo info := { }; var ChannelNrs chans := { }; f_init(trx_nr := 0); f_init_l1ctl(); f_l1_tune(L1CTL); /* Generate a list of logical channels from module parameters */ for (var integer tn := 0; tn < lengthof(trx_pars.ts); tn := tn + 1) { var PchanConfig config := trx_pars.ts[tn].config; select (config) { case (GSM_PCHAN_TCHH_TCHF_PDCH) { /* dyn/osmocom: set to NONE by default */ } case (GSM_PCHAN_TCHF_PDCH) { /* dyn/ipaccess: set to TCH/F by default */ chans := chans & { valueof(ts_RslChanNr_Bm(tn)) }; } case (GSM_PCHAN_PDCH) { chans := chans & { valueof(ts_RslChanNr_PDCH(tn)) }; } case else { chans := chans & f_gen_chans_for_ts(tn, config); } } } /* Generate a list of RSL ResourceInfo items */ for (var integer i := 0; i < lengthof(chans); i := i + 1) { info := info & { valueof(ts_RSL_ResourceInfoItem(chans[i], mp_interf_band)) }; } /* Align to the first interference report */ f_TC_rsl_rf_resource_ind(?); /* Test 4 consecutive messages */ for (var integer i := 0; i < 4; i := i + 1) { f_TC_rsl_rf_resource_ind(info); } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test unencrypted channel activation followed by explicit ENCR CMD later */ private function f_TC_encr_cmd(charstring id) runs on ConnHdlr { /* L3 payload doesn't matter, as it is passed transparently */ var BIT3 l3_alg_id := f_alg_id_to_l3(g_pars.encr.alg_id); var octetstring l3 := enc_PDU_ML3_NW_MS(valueof(ts_RRM_CiphModeCmd(l3_alg_id))); var RslLinkId link_id := valueof(ts_RslLinkID_DCCH(0)); f_l1_tune(L1CTL); /* first establish a dedicated channel in the clear */ f_est_dchan(false); /* Establish ABM */ f_est_rll_mo(link_id.sapi, link_id, '23420815'O); /* then send the RSL ENCR CMD with an actual RR CIPH MOD CMD inside */ RSL.send(ts_RSL_ENCR_CMD(g_chan_nr, link_id, g_pars.encr.alg_id, g_pars.encr.key, l3)); /* expect the L3 to arrive still unencrypted on the MS side */ f_l1_exp_lapdm(tr_LAPDm_I(link_id.sapi, cr_MT_CMD, ?, ?, ?, l3)); /* configure L1 to apply ciphering */ var uint8_t alg_id := f_alg_id_to_l1ctl(g_pars.encr.alg_id); f_L1CTL_CRYPTO_REQ(L1CTL, g_pars.chan_nr, alg_id, g_pars.encr.key); /* send first ciphered I-frame in response and expect it on RSL */ f_data_mo(link_id, true, 1, 0, '0a0b0c0d'O, exp_sacch := true); /* now the BTS code should have detected the first properly encrypted uplink I-frame, * and hence enable encryption also on the downlink */ /* expect bi-directional communication work in encrypted mode */ f_unitdata_mo(link_id, f_rnd_octstring(15)); f_unitdata_mt(link_id, f_rnd_octstring(15)); /* release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(0, g_chan_nr); } testcase TC_encr_cmd_a51() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_1, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_encr_cmd)); } testcase TC_encr_cmd_a52() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_2, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_encr_cmd)); } testcase TC_encr_cmd_a53() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_3, f_rnd_octstring(8))); f_testmatrix_each_chan(pars, refers(f_TC_encr_cmd)); } testcase TC_encr_cmd_a54() runs on test_CT { var ConnHdlrPars pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN)); pars.encr := valueof(ts_RSL_IE_EncrInfo(RSL_ALG_ID_A5_4, f_rnd_octstring(16))); f_testmatrix_each_chan(pars, refers(f_TC_encr_cmd)); } private function f_assert_lapdm(octetstring enc, template LapdmFrame exp_match, charstring name := "") { var LapdmFrame lf; var octetstring reenc; /* decode the LAPDm frame */ if (ischosen(exp_match.ab)) { lf.ab := dec_LapdmFrameAB(enc); } else { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "unsupported frame type"); } /* check if decoder result matches expectation */ if (not match(lf, exp_match)) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(name, ": decoded LAPDm doesn't match")); } else { log(name, ": matched"); setverdict(pass); } /* test if re-encoded frame equals original input */ reenc := enc_LapdmFrame(lf); if (enc != reenc) { Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str(name, ": re-encoded LAPDm frame doesn't match")); } else { setverdict(pass); } } testcase TC_lapdm_selftest() runs on test_CT { f_assert_lapdm('030301'O, tr_LAPDm_UI(0, true, ''O), "ui_s0_empty"); f_assert_lapdm('0F0301'O, tr_LAPDm_UI(3, true, ''O), "ui_s3_empty"); f_assert_lapdm('013F01'O, tr_LAPDm_SABM(0, false, true, ''O), "sabm_s0_empty"); f_assert_lapdm('013F1123420815'O, tr_LAPDm_SABM(0, false, true, '23420815'O), "sabm_s0_l3"); f_assert_lapdm('03E101'O, tr_LAPDm_RR(0, true, false, 7), "rr_s0_7"); f_assert_lapdm('03000d063505'O, tr_LAPDm_I(0, true, false, 0, 0, '063505'O), "I/0/0"); f_assert_lapdm('03e00d063505'O, tr_LAPDm_I(0, true, false, 7, 0, '063505'O), "I/7/0"); } /*********************************************************************** * DTX Related (see GSM 05.08, section 8.3) ***********************************************************************/ private function f_TC_tch_sign_l2_fill_frame(charstring id) runs on ConnHdlr { var L1ctlMessage dl; var GsmFrameNumber first_fn; var boolean is_first_frame := true; var integer nfill_frames := 0; var integer expected_fill_frames := 10000; /* initial value causes test failure if not overridden */ /* Frames numbers (mod 104) for which a fill frame is expected on TCHF if DTX is enabled. */ var Integers required_tdma_frames_dtx_tchf := { 52, 53, 54, 55, 56, 57, 58, 59 }; const integer frame_dtx_tchf_mod := 104; /* Frame numbers (mod 104) for which a fill frame is expected at the L1SAP level, * which operates in terms of blocks rather than frames. */ var Integers required_tdma_blocks_dtx_tchf := { 52, 56 }; const integer block_dtx_tchf_mod := 26; timer T := 5.0; f_l1_tune(L1CTL); RSL.clear; L1CTL.clear; /* activate TCHF signalling channel */ f_est_dchan(false); /* A template for matching dummy LAPDm func=UA frames */ var template L1ctlMessage tr_fill_frame := tr_L1CTL_DATA_IND( chan_nr := g_chan_nr, link_id := tr_RslLinkID_DCCH(?), l2_data := f_pad_oct('030301'O, 23, '2B'O)); T.start; alt { [] L1CTL.receive(tr_fill_frame) -> value dl { var GsmFrameNumber fn := dl.dl_info.frame_nr; if (is_first_frame) { is_first_frame := false; first_fn := dl.dl_info.frame_nr; } if (g_pars.chan_mode.dtx_d) { if (fn > first_fn + frame_dtx_tchf_mod) { T.stop; f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); /* With DTX enabled we can expect at least 3 fill frames for every 104 frames. */ expected_fill_frames := 2; if (nfill_frames < expected_fill_frames) { setverdict(fail, "Not enough fill frames received: ", nfill_frames, " out of ", expected_fill_frames); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } else { setverdict(pass); } } else { /* On DTX TCH/F channels, fill frames occur only for specific frame numbers mod 104. * Furthermore, the L1SAP layer gives us frame numbers for the start of a block so * we should only see the subset of frames numbers which correspond to a block boundary. * TCH/F blocks are defined to start at 0,4,8,13,17,21 (modulo 26) */ for (var integer i := 0; i < lengthof(required_tdma_blocks_dtx_tchf); i := i + 1) { if (fn mod frame_dtx_tchf_mod == required_tdma_blocks_dtx_tchf[i]) { nfill_frames := nfill_frames + 1; repeat; } } setverdict(fail, "Received DTX TCH fill frame with bad frame number: ", fn, " (mod ", frame_dtx_tchf_mod, ": ", fn mod frame_dtx_tchf_mod, ")", " (mod ", block_dtx_tchf_mod, ": ", fn mod block_dtx_tchf_mod, ")"); f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } } else { nfill_frames := nfill_frames + 1; if (fn > first_fn + frame_dtx_tchf_mod) { T.stop; select (g_chan_nr) { /* TCH/F: we expect 24 fill frames for every 104 frames. */ case (t_RslChanNr_Bm(?)) { expected_fill_frames := 24; } /* TCH/H: we expect 2 fill frames for every 104 frames. */ case (t_RslChanNr_Lm(?, ?)) { expected_fill_frames := 2; } /* SDCCH: we expect 5 fill frames for every 104 frames. */ case (t_RslChanNr_SDCCH4(?, ?)) { expected_fill_frames := 4; } case (t_RslChanNr_SDCCH8(?, ?)) { expected_fill_frames := 4; } case else { /* This shall not happen, just to be sure */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); if (nfill_frames >= expected_fill_frames) { setverdict(pass); } else { setverdict(fail, "Not enough fill frames received: ", nfill_frames, " out of ", expected_fill_frames); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } else { repeat; } } } [] L1CTL.receive { repeat; } [] T.timeout { f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Timeout waiting for L2 fill frames on Um"); } } } private function f_tch_sign_l2_fill_frame(boolean dtxd) runs on test_CT { var template RSL_IE_ChannelMode ch_mode; var ConnHdlr vc_conn; var ConnHdlrPars pars; pars.t_guard := 60.0; f_init(); ch_mode := ts_RSL_ChanMode_SIGN(dtxd := dtxd); for (var integer i := 0; i < sizeof(g_AllChannels); i := i + 1) { pars := valueof(t_Pars(g_AllChannels[i], ch_mode)); if (dtxd) { if (i >= 4) { /* DTX is only allowed on TCH/F */ break; } } vc_conn := f_start_handler(refers(f_TC_tch_sign_l2_fill_frame), pars); vc_conn.done; } } /* Verify that L2 fill frames are sent on TCH in signaling mode if * there is nothing to transmit while DTX is disabled on downlink. */ testcase TC_tch_sign_l2_fill_frame() runs on test_CT { f_tch_sign_l2_fill_frame(false); } /* Verify that particular L2 fill frames are sent on TCH in signaling mode if * there is nothing to transmit while DTX is enabled on downlink. */ testcase TC_tch_sign_l2_fill_frame_dtxd() runs on test_CT { f_tch_sign_l2_fill_frame(true); } testcase TC_chopped_ipa_ping() runs on test_CT { IPA_Testing.f_run_TC_chopped_ipa_ping(mp_rsl_ip, mp_rsl_port, LISTEN_FOR_CLIENT); } testcase TC_chopped_ipa_payload() runs on test_CT { IPA_Testing.f_run_TC_chopped_ipa_payload(mp_rsl_ip, mp_rsl_port, LISTEN_FOR_CLIENT); } /* Callback function to be called by as_TC_ms_pwr_ctrl(). * Return value: Measurement Report to be sent (encoded octetstring). */ type function f_TC_ms_pwr_ctrl_cb(inout SacchL1Header l1h, integer num_blocks) runs on ConnHdlr return octetstring; private altstep as_TC_ms_pwr_ctrl(f_TC_ms_pwr_ctrl_cb cb, inout integer num_blocks) runs on ConnHdlr { var L1ctlMessage l1_dl; var SacchL1Header l1h; var octetstring l2; [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr, tr_RslLinkID_SACCH(?))) -> value l1_dl { /* Parse the L1 SACCH header (MS Power Level & Timing Advance) */ l1h := dec_SacchL1Header(substr(l1_dl.payload.data_ind.payload, 0, 2)); log("as_TC_ms_pwr_ctrl(): Rx SACCH L1 header: ", l1h); /* Pass it to the user specified call-back function */ l2 := cb.apply(l1h, num_blocks); /* Send a Measurement Report generated by the call-back */ L1CTL.send(ts_L1CTL_DATA_REQ_SACCH(l1_dl.dl_info.chan_nr, l1_dl.dl_info.link_id, l1h, l2)); /* Shall we keep going? */ num_blocks := num_blocks - 1; log("as_TC_ms_pwr_ctrl(): ", num_blocks, " SACCH blocks remaining"); if (num_blocks > 0) { repeat; } } [] L1CTL.receive { repeat; } } private function f_TC_ms_pwr_ctrl_cb_def(inout SacchL1Header l1h, integer num_blocks) runs on ConnHdlr return octetstring { /* Command the L1 to apply received parameters */ f_L1CTL_PARAM(L1CTL, l1h.actual_ta, l1h.ms_power_lvl); /* Dummy measurement report (the results are invalid) */ return f_pad_oct('010349'O & '0615004001C0'O, 21, '00'O); } private function f_TC_ms_pwr_ctrl_cb_const(inout SacchL1Header l1h, integer num_blocks) runs on ConnHdlr return octetstring { if (l1h.ms_power_lvl != g_pars.l1_pars.ms_power_level) { setverdict(fail, "Unexpected MS Power level change: ", g_pars.l1_pars.ms_power_level, " -> ", l1h.ms_power_lvl); } return f_TC_ms_pwr_ctrl_cb_def(l1h, num_blocks); } private function f_TC_ms_pwr_ctrl_cb_rssi_pwm(inout SacchL1Header l1h, integer num_blocks) runs on ConnHdlr return octetstring { /* UL RSSI oscillation driven by SACCH block number */ if (num_blocks rem 2 == 0) { f_trxc_fake_rssi(-100); } else { f_trxc_fake_rssi(-50); } /* Make sure that MS power level remains constant */ return f_TC_ms_pwr_ctrl_cb_const(l1h, num_blocks); } /* Make sure that MS power level remains constant when 'rx-current' equals 'rx-target' */ private function f_TC_ms_pwr_ctrl_constant(charstring id) runs on ConnHdlr { var integer num_blocks := 8; timer T := int2float(num_blocks); f_l1_tune(L1CTL); RSL.clear; /* These IEs are needed for autonomous MS power control */ var template (value) RSL_IE_List ies := { t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ ms_power := ts_RSL_IE_MS_Power(g_pars.l1_pars.ms_power_level) }), t_RSL_IE(RSL_IE_MS_POWER_PARAM, RSL_IE_Body:{ ms_power_params := ts_RSL_IE_MS_Power_Parameters(''O) }) }; /* Ensure that 'rx-current' equals 'rx-target' */ f_trxc_fake_rssi(mp_uplink_power_target); /* Populate SACCH cache with a Measurement Report */ f_send_meas_rep(ts_MeasurementResults); /* Establish a dedicated channel */ f_est_dchan(more_ies := valueof(ies)); L1CTL.clear; T.start; alt { [] as_TC_ms_pwr_ctrl(refers(f_TC_ms_pwr_ctrl_cb_const), num_blocks); [] T.timeout { setverdict(fail, "Not all SACCH blocks were processed in time, ", num_blocks, " were not handled"); } } /* Release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_ms_pwr_ctrl_constant() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* Explicitly configure the Uplink power target (in dBm) */ f_vty_config(BTSVTY, "bts 0", "uplink-power-target " & int2str(mp_uplink_power_target)); for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i + 1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": starting on ", pars.chan_nr); vc_conn := f_start_handler(refers(f_TC_ms_pwr_ctrl_constant), pars, trxc_comp := true); vc_conn.done; } /* No need to reset Uplink power parameters - the IUT restarts anyway */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Test Exponentially Weighted Moving Average (EWMA) power filtering */ private function f_TC_ms_pwr_ctrl_pf_ewma(charstring id) runs on ConnHdlr { var integer num_blocks := 16; timer T := int2float(num_blocks); f_l1_tune(L1CTL); RSL.clear; /* These IEs are needed for autonomous MS power control */ var template (value) RSL_IE_List ies := { t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ ms_power := ts_RSL_IE_MS_Power(g_pars.l1_pars.ms_power_level) }), t_RSL_IE(RSL_IE_MS_POWER_PARAM, RSL_IE_Body:{ ms_power_params := ts_RSL_IE_MS_Power_Parameters(''O) }) }; /* Ensure that 'rx-current' equals 'rx-target' */ f_trxc_fake_rssi(mp_uplink_power_target); /* Populate SACCH cache with a Measurement Report */ f_send_meas_rep(ts_MeasurementResults); /* Establish a dedicated channel */ f_est_dchan(more_ies := valueof(ies)); L1CTL.clear; T.start; alt { [] as_TC_ms_pwr_ctrl(refers(f_TC_ms_pwr_ctrl_cb_rssi_pwm), num_blocks); [] T.timeout { setverdict(fail, "Not all SACCH blocks were processed in time, ", num_blocks, " were not handled"); } } /* Release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_ms_pwr_ctrl_pf_ewma() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* Explicitly configure EWMA filtering with 80% smoothing (alpha = 0.2) */ f_vty_config(BTSVTY, "bts 0", "uplink-power-filtering algo ewma beta 80"); /* Explicitly configure the Uplink power range (target and delte) */ f_vty_config(BTSVTY, "bts 0", "uplink-power-target " & int2str(mp_uplink_power_target) & " hysteresis " & int2str(mp_uplink_power_hysteresis)); for (var integer i := 0; i < sizeof(g_AllChanTypes); i := i + 1) { pars := valueof(t_Pars(g_AllChanTypes[i], ts_RSL_ChanMode_SIGN)); log(testcasename(), ": starting on ", pars.chan_nr); vc_conn := f_start_handler(refers(f_TC_ms_pwr_ctrl_pf_ewma), pars, trxc_comp := true); vc_conn.done; } /* No need to reset Uplink power parameters - the IUT restarts anyway */ Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_TC_speech_no_rtp(charstring id) runs on ConnHdlr { var template L1ctlMessage tr_dummy_frame; var integer dummy_frame_num := 0; var L1ctlMessage l1_dl; timer T := 8.0; f_l1_tune(L1CTL); RSL.clear; f_est_dchan(); /* There may be a few errors right after the channel activation */ f_sleep(2.0); /* ... so let's give the L1 some time to stabilize */ L1CTL.clear; /* Expect empty TRAFFIC.ind with no bit errors and bad CRC. In the absence * of RTP, osmo-bts is transmitting dummy speech frames with inverted CRC3. * This is a beautiful hack inducing a BFI condition in the MS receiver. * See https://osmocom.org/issues/4823#note-13 for more details. */ tr_dummy_frame := tr_L1CTL_TRAFFIC_IND(g_chan_nr, tr_RslLinkID_DCCH(0)); tr_dummy_frame.dl_info.fire_crc := (1..255); /* != 0 */ tr_dummy_frame.dl_info.num_biterr := 0; tr_dummy_frame.payload := omit; T.start; alt { [] L1CTL.receive(tr_dummy_frame) -> value l1_dl { dummy_frame_num := dummy_frame_num + 1; log("Rx dummy TRAFFIC.ind (num ", dummy_frame_num, "): ", l1_dl); /* break the loop if we got 5 dummy frames */ if (dummy_frame_num < 5) { repeat; } } [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr)) -> value l1_dl { setverdict(fail, "Rx unexpected TRAFFIC.ind: ", l1_dl); } [] as_l1_sacch_loop(); [] T.timeout { setverdict(fail, "Timeout waiting for TRAFFIC.ind"); } } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rsl_chan_deact(); f_rslem_unregister(0, g_chan_nr); } testcase TC_speech_no_rtp_tchf() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F, V1 (FR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_speech_no_rtp), pars); vc_conn.done; /* TS1, TCH/F, V2 (EFR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM2))); vc_conn := f_start_handler(refers(f_TC_speech_no_rtp), pars); vc_conn.done; /* TS1, TCH/F, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_no_rtp), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_speech_no_rtp_tchh() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS5, TCH/H, V1 (HR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_speech_no_rtp), pars); vc_conn.done; /* TS5, TCH/H, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_no_rtp), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Verify handling of Downlink and Uplink speech frames */ private function f_TC_speech_rtp(charstring id) runs on ConnHdlr { var octetstring payload; var L1ctlMessage l1_dl; var PDU_RTP rtp_pdu; timer Td, Tu; log("Testing channel mode ", g_pars.chan_mode); f_l1_tune(L1CTL); f_est_dchan(); select (g_pars.chan_mode) { case (tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1)) /* TCH/FS */ { payload := f_pad_oct('D0'O & f_rnd_octstring(6), 33, 'FF'O); } case (tr_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1)) /* TCH/HS */ { payload := f_pad_oct('00'O & f_rnd_octstring(6), 15, '00'O); } case (tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM2)) /* TCH/EFS */ { payload := f_pad_oct('C0'O & f_rnd_octstring(6), 31, '00'O); } case (tr_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3)) /* TCH/AFS */ { var integer amr_ft := get_start_amr_ft(); var integer payload_len := f_amrft_payload_len(amr_ft) + 2; payload := enc_RTP_AMR_Hdr(valueof(ts_RTP_AMR_Hdr(amr_ft, amr_ft, '1'B))); payload := f_pad_oct(payload & f_rnd_octstring(6), payload_len, '00'O); } case (tr_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3)) /* TCH/AHS */ { var integer amr_ft := get_start_amr_ft(); var integer payload_len := f_amrft_payload_len(amr_ft) + 2; payload := enc_RTP_AMR_Hdr(valueof(ts_RTP_AMR_Hdr(amr_ft, amr_ft, '1'B))); payload := f_pad_oct(payload & f_rnd_octstring(6), payload_len, '00'O); } case else { setverdict(fail, "Unhandled RSL channel mode := ", g_pars.chan_mode); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* Activate the RTP emulation */ var RtpemConfig cfg := c_RtpemDefaultCfg; cfg.tx_payloads[0].fixed_payload := payload; f_rtpem_activate(cfg, RTPEM_MODE_BIDIR); /* Give the scheduler some time to fill up the buffers */ f_sleep(2.0); L1CTL.clear; RSL.clear; /* Make sure that Downlink frames are received at the UE */ Td.start(2.0); alt { [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := payload)) -> value l1_dl { var octetstring data := l1_dl.payload.traffic_ind.data; log("TCH received (len=", lengthof(data), "): ", data); L1CTL.send(ts_L1CTL_TRAFFIC_REQ(g_chan_nr, l1_dl.dl_info.link_id, l1_dl.payload.traffic_ind.data)); setverdict(pass); } [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := ?)) -> value l1_dl { setverdict(fail, "Rx unexpected Downlink speech frame ", "(", l1_dl.payload.traffic_ind.data, ") ", "expected (", payload, ")"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } [] as_l1_sacch_loop(); [] L1CTL.receive { repeat; } [] Td.timeout { setverdict(fail, "Timeout waiting for Downlink speech frames"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* Make sure that Uplink frames are received at the BTS */ RTPEM_DATA.clear; Tu.start(2.0); alt { [] as_l1_tch_loop(); [] as_l1_sacch_loop(); [] RTPEM_DATA.receive(PDU_RTP:?) -> value rtp_pdu { if (rtp_pdu.data != payload) { repeat; } } [] RTPEM_DATA.receive { repeat; } [] L1CTL.receive { repeat; } [] Tu.timeout { setverdict(fail, "Timeout waiting for Uplink speech frames"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } f_rtpem_mode(RTPEM_CTRL, RTPEM_MODE_NONE); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rsl_chan_deact(); f_rslem_unregister(0, g_chan_nr); } testcase TC_speech_rtp_tchf() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F, V1 (FR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_speech_rtp), pars); vc_conn.done; /* TS1, TCH/F, V2 (EFR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM2))); vc_conn := f_start_handler(refers(f_TC_speech_rtp), pars); vc_conn.done; /* TS1, TCH/F, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_rtp), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_speech_rtp_tchh() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS5, TCH/H0, V1 (HR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM1))); vc_conn := f_start_handler(refers(f_TC_speech_rtp), pars); vc_conn.done; /* TS1, TCH/H0, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_rtp), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Verify handling of Downlink and Uplink Osmux speech frames */ private function f_TC_speech_osmux(charstring id) runs on ConnHdlr { var L1ctlMessage l1_dl; var OSMUX_PDU osmux_pdu; var octetstring pl; var octetstring exp_rtp_pl; timer Td, Tu; f_l1_tune(L1CTL); f_est_dchan(); /* Activate the RTP emulation */ pl := f_rnd_octstring(6); f_osmuxem_activate(pl); /* Give the scheduler some time to fill up the buffers */ f_sleep(2.0); L1CTL.clear; RSL.clear; /* we transmit using AMR_FT_2 (5.90), see t_TxHandleAMR590 in f_osmuxem_activate() */ var integer amr_ft := get_start_amr_ft(); var integer amr_pl_len := f_amrft_payload_len(amr_ft); var octetstring hdr := enc_RTP_AMR_Hdr(valueof(ts_RTP_AMR_Hdr(amr_ft, amr_ft, '1'B))); pl := f_osmux_gen_expected_rx_rtp_payload(amr_ft, pl); exp_rtp_pl := hdr & pl; /* Make sure that Downlink frames are received at the UE */ Td.start(2.0); alt { [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := exp_rtp_pl)) -> value l1_dl { var octetstring data := l1_dl.payload.traffic_ind.data; log("TCH received (len=", lengthof(data), "): ", data); L1CTL.send(ts_L1CTL_TRAFFIC_REQ(g_chan_nr, l1_dl.dl_info.link_id, data)); setverdict(pass); } [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := ?)) -> value l1_dl { setverdict(fail, "Rx unexpected Downlink speech frame ", "(", l1_dl.payload.traffic_ind.data, ") ", "expected (", exp_rtp_pl, ")"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } [] as_l1_sacch_loop(); [] L1CTL.receive { repeat; } [] Td.timeout { setverdict(fail, "Timeout waiting for Downlink speech frames"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* Make sure that Uplink frames are received at the BTS */ OsmuxEM_DATA.clear; var template (present) OSMUX_PDU osmux_pdu_exp := tr_PDU_Osmux_AMR(cid := g_pars.loc_osmux_cid, amr_ft := amr_ft, amr_cmr := amr_ft); Tu.start(2.0); alt { [] as_l1_tch_loop(); [] as_l1_sacch_loop(); [] OsmuxEM_DATA.receive(osmux_pdu_exp) -> value osmux_pdu { var boolean matched := false; for (var integer i := 0; i < osmux_pdu.osmux_amr.header.ctr + 1; i := i + 1) { var octetstring rx_pl; rx_pl := f_osmux_amr_get_nth_amr_payload(osmux_pdu.osmux_amr, i); log("got ", rx_pl, " vs exp ", pl); if (rx_pl == pl) { matched := true; break; } } if (not matched) { repeat; } } [] OsmuxEM_DATA.receive { repeat; } [] Tu.timeout { setverdict(fail, "Timeout waiting for Uplink speech frames"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } f_osmuxem_mode(OsmuxEM_CTRL, OSMUXEM_MODE_NONE); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rsl_chan_deact(); f_rslem_unregister(0, g_chan_nr); } testcase TC_speech_osmux_tchf() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS5, TCH/H0, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode(RSL_CHRT_TCH_F, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_osmux), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_speech_osmux_tchh() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS5, TCH/H0, V3 (AMR codec) */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode(RSL_CHRT_TCH_H, RSL_CMOD_SP_GSM3))); pars.mr_conf := valueof(ts_RSL_MultirateCfg); vc_conn := f_start_handler(refers(f_TC_speech_osmux), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* CSD channel tests */ private function f_TC_data_rtp_loopback(charstring id) runs on ConnHdlr { var octetstring udata := ''O; var integer payload_len; var L1ctlMessage l1_dl; timer Td; log("Testing channel mode ", g_pars.chan_mode); select (g_pars.chan_mode) { case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, (RSL_CMOD_CSD_T_14k4, RSL_CMOD_CSD_NT_14k5))) { payload_len := 290; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, (RSL_CMOD_CSD_T_9k6, RSL_CMOD_CSD_NT_12k0))) { payload_len := 4 * 60; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, (RSL_CMOD_CSD_T_4k8, RSL_CMOD_CSD_NT_6k0))) { payload_len := 2 * 60; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, (RSL_CMOD_CSD_T_4k8, RSL_CMOD_CSD_NT_6k0))) { payload_len := 4 * 60; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, (RSL_CMOD_CSD_T_2k4, RSL_CMOD_CSD_T_1k2, RSL_CMOD_CSD_T_600, RSL_CMOD_CSD_T_1200_75))) { payload_len := 2 * 36; } case (tr_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, (RSL_CMOD_CSD_T_2k4, RSL_CMOD_CSD_T_1k2, RSL_CMOD_CSD_T_600, RSL_CMOD_CSD_T_1200_75))) { payload_len := 4 * 36; } case else { setverdict(fail, "Unhandled RSL channel mode := ", g_pars.chan_mode); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* Generate a pseudo-random payload */ for (var integer i := 0; i < payload_len; i := i + 1) { udata := udata & int2oct(i mod 2, 1); } /* Activate the RTP emulation */ f_rtpem_activate(mode := RTPEM_MODE_LOOPBACK, rtp_pt := 120); /* Tune to the dedicated channel */ f_l1_tune(L1CTL); f_est_dchan(); /* Send a pseudo-random data frame on the Uplink */ log("Sending Uplink TCH (len=", lengthof(udata), "): ", udata); L1CTL.send(ts_L1CTL_TRAFFIC_REQ(g_chan_nr, ts_RslLinkID_DCCH(0), udata)); /* Expect this frame to show up on the Downlink */ Td.start(4.0); alt { [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr, frame := udata)) { log("TCH received (len=", lengthof(udata), "): ", udata); setverdict(pass); Td.stop; } [] L1CTL.receive(tr_L1CTL_TRAFFIC_IND(g_chan_nr)) -> value l1_dl { var octetstring ddata := l1_dl.payload.traffic_ind.data; log("TCH received (len=", lengthof(ddata), "): ", ddata); repeat; } [] L1CTL.receive(tr_L1CTL_DATA_IND(g_chan_nr)) -> value l1_dl { var octetstring ddata := l1_dl.payload.data_ind.payload; log("FACCH received: ", ddata); repeat; } [] as_l1_sacch_loop(); [] L1CTL.receive { repeat; } [] Td.timeout { setverdict(fail, "Timeout waiting for matching Downlink data frame"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } /* TODO: send and expect FACCH */ f_rtpem_mode(RTPEM_CTRL, RTPEM_MODE_NONE); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rsl_chan_deact(); f_rslem_unregister(0, g_chan_nr); } testcase TC_data_rtp_tchf144() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F14.4 (14k5 radio interface rate), transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_T_14k4))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; /* TS1, TCH/F14.4 (14k5 radio interface rate), non-transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_NT_14k5))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_data_rtp_tchf96() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F9.6 (12k radio interface rate), transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_T_9k6))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; /* TS1, TCH/F9.6 (12k radio interface rate), non-transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_NT_12k0))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_data_rtp_tchf48() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F4.8 (6k radio interface rate), transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_T_4k8))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; /* TS1, TCH/F4.8 (6k radio interface rate), non-transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_NT_6k0))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_data_rtp_tchh48() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/H4.8 (6k radio interface rate), transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, RSL_CMOD_CSD_T_4k8))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; /* TS1, TCH/H4.8 (6k radio interface rate), non-transparent service */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, RSL_CMOD_CSD_NT_6k0))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_data_rtp_tchf24() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/F2.4 (3k6 radio interface rate), transparent services only */ pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_T_2k4))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; pars := valueof(t_Pars(ts_RslChanNr_Bm(1), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_F, RSL_CMOD_CSD_T_1k2))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_data_rtp_tchh24() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* TS1, TCH/H2.4 (3k6 radio interface rate), transparent services only */ pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, RSL_CMOD_CSD_T_2k4))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; pars := valueof(t_Pars(ts_RslChanNr_Lm(5, 0), ts_RSL_ChanMode_DATA(RSL_CHRT_TCH_H, RSL_CMOD_CSD_T_1k2))); vc_conn := f_start_handler(refers(f_TC_data_rtp_loopback), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_TC_early_immediate_assignment(charstring id) runs on ConnHdlr { var GsmFrameNumber fn; var ChannelDescription ch_desc; var integer ra := 23; f_l1_tune(L1CTL); RSL.clear; /* Send RACH request and wait for ChanReq */ fn := f_rach_req_wait_chan_rqd(ra); /* The BSC already sends the Immediate Assignment, before the channel is active. * (Also before the Channel Activation, even. I tried to write this test so that we first send the Chan Act and then the IMM * ASS, but osmo-bts-trx responds so fast that the Chan Activ ACK comes back even before titan dispatches the * IMM ASS. So move the IMM ASS even before the Chan Activ.) */ if (g_pars.fhp.enabled) { ch_desc := valueof(ts_ChanDescH1(g_pars.chan_nr, g_pars.fhp.maio_hsn, g_pars.tsc)); } else { var GsmArfcn arfcn := mp_trx_pars[g_pars.trx_nr].arfcn; ch_desc := valueof(ts_ChanDescH0(g_pars.chan_nr, arfcn, g_pars.tsc)); } var GsmRrMessage rr_msg := valueof(ts_IMM_ASS(ra, fn, 0, ch_desc, g_pars.fhp.ma_map)); RSL.send(ts_ASP_RSL_UD(ts_RSL_IMM_ASSIGN(enc_GsmRrMessage(rr_msg)))); /* force sending to TRX0 */ /* Do not expect the Immediate Assignment to show up on MS side yet. Even give it one second before the BSC * requests Chan Activ, to make sure the RR IMM ASS is held back. */ var L1ctlMessage dl; var GsmRrMessage rr; var template GsmRrMessage rr_imm_ass := tr_IMM_ASS(ra, 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 := *; timer T := 1.0; T.start; alt { [] L1CTL.receive(tr_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { rr := dec_GsmRrMessage(dl.payload.data_ind.payload); if (match(rr, rr_imm_ass)) { setverdict(fail, "Expected IMM ASS to be delayed until Chan Act ACK, but it was passed to the MS immediately"); mtc.stop; } else { repeat; } } [] L1CTL.receive { repeat; } [] T.timeout; } RSL.send(ts_RSL_CHAN_ACT(g_chan_nr, g_pars.chan_mode, t_RSL_IE_ActType_IA)); RSL.receive(tr_RSL_CHAN_ACT_ACK(g_chan_nr)); /* Now expect the IMM ASS on Um */ f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, fn); setverdict(pass); /* Release the channel */ f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); } testcase TC_early_immediate_assignment() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); /* verify early Immediate Assignment on C0 (the BCCH carrier): TCH/F on TS1 */ pars := valueof(t_Pars(t_RslChanNr_Bm(1), ts_RSL_ChanMode_SIGN, trx_nr := 0)); vc_conn := f_start_handler(refers(f_TC_early_immediate_assignment), pars); vc_conn.done; /* verify early Immediate Assignment on C1: TCH/F on TS0 */ pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_early_immediate_assignment), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private function f_TC_est_dchan(charstring id) runs on ConnHdlr { var integer ra := oct2int(f_rnd_ra_cs()); var ChannelDescription ch_desc; /* Tune the MS to BCCH */ f_l1_tune(L1CTL); /* Send RACH request and wait for ChanReq */ var GsmFrameNumber fn := f_rach_req_wait_chan_rqd(ra); /* Activate channel on the BTS side */ f_rsl_chan_act(g_pars.chan_mode); /* Craft channel description (with or without frequency hopping parameters) */ if (g_pars.fhp.enabled) { ch_desc := valueof(ts_ChanDescH1(g_pars.chan_nr, g_pars.fhp.maio_hsn, g_pars.tsc)); } else { ch_desc := valueof(ts_ChanDescH0(g_pars.chan_nr, mp_trx_pars[0].arfcn, g_pars.tsc)); } /* Send IMM.ASS via CCHAN */ var GsmRrMessage rr_msg := valueof(ts_IMM_ASS(ra, fn, 0, ch_desc, g_pars.fhp.ma_map)); RSL.send(ts_RSL_IMM_ASSIGN(enc_GsmRrMessage(rr_msg))); /* Receive IMM.ASS on the MS side */ var ImmediateAssignment imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, fn); /* Match the Channel Description IE in received IMM.ASS */ if (not match(imm_ass.chan_desc, ch_desc)) { setverdict(fail, "Channel Description IE does not match"); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* Tune the MS to a dedicated channel indicated in the IMM.ASS */ f_L1CTL_DM_EST_REQ_IA(L1CTL, imm_ass, g_pars.fhp.ma); /* Expect SACCH frames on Downlink */ L1CTL.clear; f_exp_sacch(true); /* We're done, deactivate and release */ f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rsl_chan_deact(); } testcase TC_est_dchan() runs on test_CT { var ConnHdlr vc_conn; var ConnHdlrPars pars; f_init(); for (var integer i := 0; i < sizeof(g_AllChannels); i := i + 1) { pars := valueof(t_Pars(g_AllChannels[i], ts_RSL_ChanMode_SIGN)); vc_conn := f_start_handler(refers(f_TC_est_dchan), pars); vc_conn.done; } Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } private type record TopTestCase { RSL_IE_BS_Power bs_power, RSL_IE_OSMO_TopAcchCap top_cap, record of TopTestStep steps }; private type record TopTestStep { MeasurementResults meas_res optional, GsmRxLev overpower_sacch, GsmRxLev overpower_facch }; private function f_rxlev_match(template (present) RslLinkId link_id, template (present) GsmRxLev rxlev) runs on ConnHdlr { var L1ctlMessage dl := f_L1CTL_rx_data(L1CTL, g_chan_nr, link_id); if (not match(dl.dl_info.rx_level, rxlev)) { setverdict(fail, "RxLev(", link_id, ") := ", dl.dl_info.rx_level, " does not match expected RxLev := ", rxlev); Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } } private function f_TC_acch_overpower(charstring id) runs on ConnHdlr { var TopTestCase tc := g_pars.spec.top; var GsmRxLev rxlev_dcch; var L1ctlMessage dl; /* Wait for Pau ramping to complete */ f_sleep(6.0); f_l1_tune(L1CTL); L1CTL.clear; RSL.clear; /* Measure RxLev(BCCH), calculate RxLev(DCCH) */ dl := f_L1CTL_rx_data(L1CTL, t_RslChanNr_BCCH(0)); rxlev_dcch := dl.dl_info.rx_level - (tc.bs_power.power_level * 2); log("RxLev(BCCH) := ", dl.dl_info.rx_level); log("Expected RxLev(DCCH) := ", rxlev_dcch); /* Additional IEs for the CHANnel ACTIVation message */ var template (value) RSL_IE_List ies := { /* Employ BS power control in static mode */ t_RSL_IE(RSL_IE_MS_POWER, RSL_IE_Body:{ bs_power := tc.bs_power }), /* Indicate the given Temporary Overpower capability */ t_RSL_IE(RSL_IE_OSMO_TOP_ACCH_CAP, RSL_IE_Body:{ top_acch_cap := tc.top_cap }) }; /* Establish a dedicated channel */ f_est_dchan(more_ies := valueof(ies)); /* Give it some time to stabilize */ f_sleep(0.480 * 2.0); L1CTL.clear; RSL.clear; for (var integer i := 0; i < lengthof(tc.steps); i := i + 1) { var TopTestStep step := tc.steps[i]; var GsmRxLev rxlev_facch := rxlev_dcch + step.overpower_facch; var GsmRxLev rxlev_sacch := rxlev_dcch + step.overpower_sacch; log("Executing step[", i, "] := ", step); /* Send RR Measurement Report (if present) */ if (ispresent(step.meas_res)) { f_transceive_meas_rep(step.meas_res); f_sleep(0.480 * 2.0); L1CTL.clear; } /* Check RxLev on both FACCH and SACCH */ f_rxlev_match(tr_RslLinkID_DCCH(?), rxlev_facch); f_rxlev_match(tr_RslLinkID_SACCH(?), rxlev_sacch); setverdict(pass); } f_rsl_chan_deact(); f_L1CTL_DM_REL_REQ(L1CTL, g_chan_nr); f_rslem_unregister(g_pars.trx_nr, g_chan_nr); } testcase TC_acch_overpower_rxqual_thresh() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* Verify lower and upper RxQual thresholds */ var template (value) TopTestCase top := { bs_power := ts_RSL_IE_BS_Power(4), /* 4 x 2dB = 8dB */ top_cap := ts_RSL_IE_OSMO_TopAcchCap(4), /* 4dB */ steps := { /* Channel established, no overpower */ { meas_res := omit, overpower_sacch := 0, overpower_facch := 0 }, /* Worst possible RxQual value, overpower of 4dB */ { meas_res := ts_MeasurementResults(rxq_f := 7), overpower_sacch := 4, overpower_facch := 4 }, /* Worst possible RxQual value, disabling overpower */ { meas_res := ts_MeasurementResults(rxq_f := 0), overpower_sacch := 0, overpower_facch := 0 }, /* Lower threshold not reached, no overpower */ { meas_res := ts_MeasurementResults(rxq_f := 1), overpower_sacch := 0, overpower_facch := 0 }, /* Lower threshold not reached, no overpower */ { meas_res := ts_MeasurementResults(rxq_f := 2), overpower_sacch := 0, overpower_facch := 0 }, /* Lower threshold reached, overpower of 4dB */ { meas_res := ts_MeasurementResults(rxq_f := 4), overpower_sacch := 4, overpower_facch := 4 }, /* Upper threshold not reached, keeping overpower */ { meas_res := ts_MeasurementResults(rxq_f := 3), overpower_sacch := 4, overpower_facch := 4 }, /* Upper threshold reached, disabling overpower */ { meas_res := ts_MeasurementResults(rxq_f := 2), overpower_sacch := 0, overpower_facch := 0 } } }; pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, spec := { top := top }, trx_nr := 1, t_guard := 30.0)); vc_conn := f_start_handler(refers(f_TC_acch_overpower), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_acch_overpower_rxqual_thresh_dtx() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* Verify handling of FULL and SUB values */ var template (value) TopTestCase top := { bs_power := ts_RSL_IE_BS_Power(4), /* 4 x 2dB = 8dB */ top_cap := ts_RSL_IE_OSMO_TopAcchCap(4), /* 4dB */ steps := { /* Channel established, no overpower */ { meas_res := omit, overpower_sacch := 0, overpower_facch := 0 }, /* Invalid measurement results, no overpower */ { meas_res := ts_MeasurementResults(rxq_f := 7, rxq_s := 7, valid := false), overpower_sacch := 0, overpower_facch := 0 }, /* DTXu was in use, no overpower */ { meas_res := ts_MeasurementResults(rxq_f := 7, rxq_s := 0, dtx_used := true), overpower_sacch := 0, overpower_facch := 0 }, /* DTXu was in use, overpower of 4 dB */ { meas_res := ts_MeasurementResults(rxq_f := 0, rxq_s := 7, dtx_used := true), overpower_sacch := 4, overpower_facch := 4 } } }; pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, spec := { top := top }, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_acch_overpower), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_acch_overpower_always_on_facch() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* Overpower is always on, SACCH disabled */ var template (value) TopTestCase top := { bs_power := ts_RSL_IE_BS_Power(4), /* 4 x 2dB = 8dB */ top_cap := ts_RSL_IE_OSMO_TopAcchCap(overpower := 4, /* 4dB */ rxqual := 0, /* always on */ sacch_enable := false), steps := { /* Channel established, FACCH overpower */ { meas_res := omit, overpower_sacch := 0, overpower_facch := 4 }, /* MS indicates good RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 0), overpower_sacch := 0, overpower_facch := 4 }, /* MS indicates bad RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 7), overpower_sacch := 0, overpower_facch := 4 } } }; pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, spec := { top := top }, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_acch_overpower), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_acch_overpower_always_on_sacch() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* Overpower is always on, FACCH disabled */ var template (value) TopTestCase top := { bs_power := ts_RSL_IE_BS_Power(4), /* 4 x 2dB = 8dB */ top_cap := ts_RSL_IE_OSMO_TopAcchCap(overpower := 4, /* 4dB */ rxqual := 0, /* always on */ facch_enable := false), steps := { /* Channel established, SACCH overpower */ { meas_res := omit, overpower_sacch := 4, overpower_facch := 0 }, /* MS indicates good RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 0), overpower_sacch := 4, overpower_facch := 0 }, /* MS indicates bad RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 7), overpower_sacch := 4, overpower_facch := 0 } } }; pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, spec := { top := top }, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_acch_overpower), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } testcase TC_acch_overpower_limit() runs on test_CT { var ConnHdlrPars pars; var ConnHdlr vc_conn; f_init(); /* Overpower higher than current power reduction level (2dB) */ var template (value) TopTestCase top := { bs_power := ts_RSL_IE_BS_Power(1), /* 1 x 2dB = 2dB */ top_cap := ts_RSL_IE_OSMO_TopAcchCap(overpower := 4, /* 4dB */ rxqual := 0 /* always on */), steps := { /* Channel established, ACCH overpower of 2dB */ { meas_res := omit, overpower_sacch := 2, overpower_facch := 2 }, /* MS indicates good RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 0), overpower_sacch := 2, overpower_facch := 2 }, /* MS indicates bad RxQual, no difference */ { meas_res := ts_MeasurementResults(rxq_f := 7), overpower_sacch := 2, overpower_facch := 2 } } }; pars := valueof(t_Pars(t_RslChanNr_Bm(0), ts_RSL_ChanMode_SIGN, spec := { top := top }, trx_nr := 1)); vc_conn := f_start_handler(refers(f_TC_acch_overpower), pars); vc_conn.done; Misc_Helpers.f_shutdown(__BFILE__, __LINE__); } /* test generation of RLL ERR IND based on Um errors (TS 48.058 3.9) */ /* protocol error as per 44.006 */ /* link layer failure (repetition of I-frame N200 times without ACK */ /* repetition of SABM or DISC N200 times without ACK */ /* receptiom of SABM in multi-frame established state */ /* TODO Areas: * channel activation ** with BS_Power / MS_Power, bypassing power control loop ** on primary vs. secondary TRX ** with timing advance from initial activation on * mode modify ** encryption ** multirate * check DEACTIVATE SACCH ** unsupported algorithm * BS Power Control * Physical Context * error handling ** IE duplicated? * PCU interface ** DATA_IND from BTS->PCU ** verification of PCU-originated DATA_REQ arrival on Um/MS side */ control { execute( TC_est_dchan() ); execute( TC_chan_act_stress() ); execute( TC_chan_act_react() ); execute( TC_chan_deact_not_active() ); execute( TC_chan_act_wrong_nr() ); execute( TC_deact_sacch() ); execute( TC_sacch_filling() ); execute( TC_sacch_info_mod() ); execute( TC_sacch_multi() ); execute( TC_sacch_multi_chg() ); execute( TC_sacch_chan_act() ); execute( TC_sacch_chan_act_ho_async() ); execute( TC_sacch_chan_act_ho_sync() ); execute( TC_rach_content() ); execute( TC_rach_content_emerg() ); execute( TC_rach_count() ); execute( TC_rach_max_ta() ); execute( TC_ho_rach() ); execute( TC_ho_physical_info() ); execute( TC_rach_load_idle_thresh0() ); execute( TC_rach_load_idle_below_thresh() ); execute( TC_rach_load_count() ); execute( TC_meas_res_speech_tchf() ); execute( TC_meas_res_speech_tchf_facch() ); execute( TC_meas_res_speech_tchh() ); execute( TC_meas_res_speech_tchh_facch() ); execute( TC_meas_res_speech_tchh_toa256() ); execute( TC_meas_res_sign_tchf() ); execute( TC_meas_res_sign_tchh() ); execute( TC_meas_res_sign_sdcch4() ); execute( TC_meas_res_sign_sdcch8() ); execute( TC_meas_res_sign_tchh_toa256() ); execute( TC_meas_res_speech_tchf_sapi3() ); execute( TC_meas_res_speech_tchh_sapi3() ); execute( TC_tx_power_start_ramp_up_bcch() ); execute( TC_tx_power_down_bcch() ); execute( TC_tx_power_ramp_adm_state_change() ); execute( TC_rsl_bs_pwr_static_ass() ); execute( TC_rsl_bs_pwr_static_power_control() ); execute( TC_rsl_ms_pwr_ctrl() ); execute( TC_rsl_ms_pwr_dyn_active() ); execute( TC_rsl_ms_pwr_dyn_active2() ); execute( TC_rsl_ms_pwr_dyn_up() ); execute( TC_rsl_ms_pwr_dyn_down() ); execute( TC_rsl_ms_pwr_dyn_ass_updown() ); execute( TC_rsl_ms_pwr_dyn_max() ); execute( TC_rsl_chan_initial_ms_pwr() ); execute( TC_rsl_chan_initial_ta() ); execute( TC_rsl_modify_encr() ); execute( TC_rsl_rf_resource_ind() ); execute( TC_conn_fail_crit() ); execute( TC_paging_imsi_80percent() ); execute( TC_paging_tmsi_80percent() ); execute( TC_paging_imsi_200percent() ); execute( TC_paging_tmsi_200percent() ); execute( TC_rsl_protocol_error() ); execute( TC_rsl_mand_ie_error() ); execute( TC_rsl_ie_content_error() ); execute( TC_si_sched_default() ); execute( TC_si_sched_1() ); execute( TC_si_sched_2bis() ); execute( TC_si_sched_2ter() ); execute( TC_si_sched_2ter_2bis() ); execute( TC_si_sched_2quater() ); execute( TC_si_sched_13() ); execute( TC_si_sched_13_2bis_2ter_2quater() ); execute( TC_ipa_dlcx_not_active() ); execute( TC_ipa_crcx_twice_not_active() ); execute( TC_ipa_crcx_mdcx_dlcx_not_active() ); execute( TC_ipa_crcx_mdcx_mdcx_dlcx_not_active() ); execute( TC_ipa_crcx_sdcch_not_active() ); execute( TC_ipa_crcx_ack_addr() ); if (mp_pcu_socket != "") { execute( TC_paging_imsi_200percent_with_ps() ); execute( TC_pcu_act_req() ); execute( TC_pcu_act_req_wrong_ts() ); execute( TC_pcu_act_req_wrong_bts() ); execute( TC_pcu_act_req_wrong_trx() ); execute( TC_pcu_deact_req() ); execute( TC_pcu_deact_req_wrong_ts() ); execute( TC_pcu_ver_si1() ); execute( TC_pcu_ver_si3() ); execute( TC_pcu_ver_si13() ); if (mp_l1_supports_gprs) { execute( TC_pcu_data_req_pdtch() ); execute( TC_pcu_data_req_ptcch() ); execute( TC_pcu_data_req_wrong_bts() ); execute( TC_pcu_data_req_wrong_trx() ); execute( TC_pcu_data_req_wrong_ts() ); execute( TC_pcu_data_req_ts_inactive() ); } execute( TC_pcu_ptcch() ); execute( TC_pcu_data_req_agch() ); execute( TC_pcu_data_req_pch() ); execute( TC_pcu_data_req_imm_ass_pch() ); execute( TC_pcu_data_req_imm_ass_agch() ); execute( TC_pcu_rach_content() ); execute( TC_pcu_ext_rach_content() ); execute( TC_pcu_data_ind_lqual_cb() ); execute( TC_pcu_paging_from_rsl() ); execute( TC_pcu_time_ind() ); execute( TC_pcu_rts_req() ); execute( TC_pcu_oml_alert() ); execute( TC_pcu_rr_suspend() ); execute( TC_pcu_socket_connect_multi() ); execute( TC_pcu_socket_reconnect() ); execute( TC_pcu_socket_noconnect_nosi3gprs() ); execute( TC_pcu_socket_noconnect_nosi4gprs() ); execute( TC_pcu_socket_connect_si3gprs() ); execute( TC_pcu_socket_connect_si4gprs() ); execute( TC_pcu_socket_disconnect_nosi3gprs() ); execute( TC_pcu_socket_disconnect_nosi4gprs() ); execute( TC_pcu_socket_verify_info_ind() ); execute( TC_dyn_osmo_pdch_act_deact() ); execute( TC_dyn_osmo_pdch_double_act() ); execute( TC_dyn_ipa_pdch_act_deact() ); execute( TC_dyn_ipa_pdch_act_tchf_act_nack() ); execute( TC_pcu_info_ind_fh_params() ); execute( TC_pcu_socket_nsvc_ipv4() ); execute( TC_pcu_socket_nsvc_ipv6() ); execute( TC_pcu_socket_two_nsvc() ); execute( TC_pcu_interf_ind() ); } else { log("PCU socket path not available, skipping PCU tests"); } execute( TC_dyn_osmo_pdch_unsol_deact() ); execute( TC_dyn_osmo_pdch_tchf_act() ); execute( TC_dyn_osmo_pdch_tchh_act() ); execute( TC_dyn_osmo_pdch_sdcch8_act() ); execute( TC_dyn_osmo_pdch_tchh_race_act() ); execute( TC_dyn_osmo_pdch_sdcch8_race_act() ); execute( TC_dyn_ipa_pdch_tchf_act() ); execute( TC_dyn_ipa_pdch_tchf_act_pdch_act_nack() ); execute( TC_rll_est_ind() ); execute( TC_rll_est_req_DCCH_3() ); execute( TC_rll_est_req_ACCH_3() ); execute( TC_rll_rel_ind_DCCH_0() ); execute( TC_rll_rel_ind_DCCH_3() ); execute( TC_rll_rel_ind_ACCH_0() ); execute( TC_rll_rel_ind_ACCH_3() ); execute( TC_rll_rel_req() ); execute( TC_rll_unit_data_req_DCCH() ); execute( TC_rll_unit_data_req_ACCH() ); execute( TC_rll_unit_data_ind_DCCH() ); execute( TC_rll_unit_data_ind_ACCH() ); execute( TC_chan_act_a51() ); execute( TC_chan_act_a52() ); execute( TC_chan_act_a53() ); execute( TC_chan_act_a54() ); execute( TC_encr_cmd_a51() ); execute( TC_encr_cmd_a52() ); execute( TC_encr_cmd_a53() ); execute( TC_encr_cmd_a54() ); execute( TC_err_rep_wrong_mdisc() ); execute( TC_err_rep_wrong_msg_type() ); execute( TC_err_rep_wrong_sequence() ); execute( TC_lapdm_selftest() ); execute( TC_tch_sign_l2_fill_frame() ); execute( TC_tch_sign_l2_fill_frame_dtxd() ); execute( TC_chopped_ipa_ping() ); execute( TC_chopped_ipa_payload() ); execute( TC_ms_pwr_ctrl_constant() ); execute( TC_ms_pwr_ctrl_pf_ewma() ); execute( TC_speech_no_rtp_tchf() ); execute( TC_speech_no_rtp_tchh() ); execute( TC_speech_rtp_tchf() ); execute( TC_speech_rtp_tchh() ); execute( TC_speech_osmux_tchf() ); execute( TC_speech_osmux_tchh() ); execute( TC_data_rtp_tchf144() ); execute( TC_data_rtp_tchf96() ); execute( TC_data_rtp_tchf48() ); execute( TC_data_rtp_tchh48() ); execute( TC_data_rtp_tchf24() ); execute( TC_data_rtp_tchh24() ); execute( TC_early_immediate_assignment() ); execute( TC_acch_overpower_rxqual_thresh() ); execute( TC_acch_overpower_rxqual_thresh_dtx() ); execute( TC_acch_overpower_always_on_facch() ); execute( TC_acch_overpower_always_on_sacch() ); execute( TC_acch_overpower_limit() ); } }