diff options
-rw-r--r-- | src/common/measurement.c | 266 | ||||
-rw-r--r-- | tests/meas/meas_test.c | 22 | ||||
-rw-r--r-- | tests/meas/meas_test.ok | 148 | ||||
-rw-r--r-- | tests/meas/meas_testcases.h | 519 |
4 files changed, 859 insertions, 96 deletions
diff --git a/src/common/measurement.c b/src/common/measurement.c index d7580e6e..59f5d834 100644 --- a/src/common/measurement.c +++ b/src/common/measurement.c @@ -16,6 +16,21 @@ static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }; static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }; static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; +/* In cases where we less measurements than we expect we must assume that we + * just did not receive the block because it was lost due to bad channel + * conditions. We set up a dummy measurement result here that reflects the + * worst possible result. In our* calculation we will use this dummy to replace + * the missing measurements */ +#define MEASUREMENT_DUMMY_BER 10000 /* 100% BER */ +#define MEASUREMENT_DUMMY_IRSSI 109 /* noise floor in -dBm */ +static const struct bts_ul_meas measurement_dummy = (struct bts_ul_meas) { + .ber10k = MEASUREMENT_DUMMY_BER, + .ta_offs_256bits = 0, + .c_i = 0, + .is_sub = 0, + .inv_rssi = MEASUREMENT_DUMMY_IRSSI +}; + /* find out if an array contains a given key as element */ #define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val) static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) { @@ -468,6 +483,64 @@ static uint8_t ber10k_to_rxqual(uint32_t ber10k) return 7; } +/* Get the number of measurements that we expect for a specific lchan. + * (This is a static number that is defined by the specific slot layout of + * the channel) */ +static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + /* 24 for TCH + 1 for SACCH */ + return 25; + case GSM_PCHAN_TCH_H: + /* 24 half-blocks for TCH + 1 for SACCH */ + return 25; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* 2 for SDCCH + 1 for SACCH */ + return 3; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* 2 for SDCCH + 1 for SACCH */ + return 3; + default: + return lchan->meas.num_ul_meas; + } +} + +/* In DTX a subset of blocks must always be transmitted + * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) */ +static unsigned int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + /* AMR is using a more elaborated model with a dymanic amount of + * DTX blocks so this function is not applicable to determine the + * amount of expected SUB Measurements when AMR is used */ + OSMO_ASSERT(lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) + + switch (pchan) { + case GSM_PCHAN_TCH_F: + /* 1 block SDCCH, 2 blocks TCH */ + return 3; + case GSM_PCHAN_TCH_H: + /* 1 block SDCCH, 4 half-blocks TCH */ + return 5; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* no DTX here, all blocks must be present! */ + return 3; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* no DTX here, all blocks must be present! */ + return 3; + default: + return 0; + } +} + /* if we clip the TOA value to 12 bits, i.e. toa256=3200, * -> the maximum deviation can be 2*3200 = 6400 * -> the maximum squared deviation can be 6400^2 = 40960000 @@ -482,6 +555,10 @@ static uint8_t ber10k_to_rxqual(uint32_t ber10k) /* compute Osmocom extended measurements for the given lchan */ static void lchan_meas_compute_extended(struct gsm_lchan *lchan) { + unsigned int num_ul_meas; + unsigned int num_ul_meas_excess = 0; + unsigned int num_ul_meas_expect; + /* we assume that lchan_meas_check_compute() has already computed the mean value * and we can compute the min/max/variance/stddev from this */ int i; @@ -489,19 +566,47 @@ static void lchan_meas_compute_extended(struct gsm_lchan *lchan) /* each measurement is an int32_t, so the squared difference value must fit in 32bits */ /* the sum of the squared values (each up to 32bit) can very easily exceed 32 bits */ u_int64_t sq_diff_sum = 0; + + /* In case we do not have any measurement values collected there is no + * computation possible. We just skip the whole computation here, the + * lchan->meas.flags will not get the LC_UL_M_F_OSMO_EXT_VALID flag set + * so no extended measurement results will be reported back via RSL. + * this is ok, since we have nothing to report anyway and apart of that + * we also just lost the signal (otherwise we would have at least some + * measurements). */ + if (!lchan->meas.num_ul_meas) + return; + /* initialize min/max values with their counterpart */ lchan->meas.ext.toa256_min = INT16_MAX; lchan->meas.ext.toa256_max = INT16_MIN; - OSMO_ASSERT(lchan->meas.num_ul_meas); + /* Determine the number of measurement values we need to take into the + * computation. In this case we only compute over the measurements we + * have indeed received. Since this computation is about timing + * information it does not make sense to approach missing measurement + * samples the TOA with 0. This would bend the average towards 0. What + * counts is the average TOA of the properly received blocks so that + * the TA logic can make a proper decision. */ + num_ul_meas_expect = lchan_meas_num_expected(lchan); + if (lchan->meas.num_ul_meas > num_ul_meas_expect) { + num_ul_meas = num_ul_meas_expect; + num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; + } + else + num_ul_meas = lchan->meas.num_ul_meas; /* all computations are done on the relative arrival time of the burst, relative to the * beginning of its slot. This is of course excluding the TA value that the MS has already * compensated/pre-empted its transmission */ /* step 1: compute the sum of the squared difference of each value to mean */ - for (i = 0; i < lchan->meas.num_ul_meas; i++) { - struct bts_ul_meas *m = &lchan->meas.uplink[i]; + for (i = 0; i < num_ul_meas; i++) { + const struct bts_ul_meas *m; + + OSMO_ASSERT(i < lchan->meas.num_ul_meas); + m = &lchan->meas.uplink[i+num_ul_meas_excess]; + int32_t diff = (int32_t)m->ta_offs_256bits - (int32_t)lchan->meas.ms_toa256; /* diff can now be any value of +65535 to -65535, so we can safely square it, * but only in unsigned math. As squaring looses the sign, we can simply drop @@ -517,7 +622,7 @@ static void lchan_meas_compute_extended(struct gsm_lchan *lchan) lchan->meas.ext.toa256_min = m->ta_offs_256bits; } /* step 2: compute the variance (mean of sum of squared differences) */ - sq_diff_sum = sq_diff_sum / lchan->meas.num_ul_meas; + sq_diff_sum = sq_diff_sum / num_ul_meas; /* as the individual summed values can each not exceed 2^32, and we're * dividing by the number of summands, the resulting value can also not exceed 2^32 */ OSMO_ASSERT(sq_diff_sum <= UINT32_MAX); @@ -535,54 +640,137 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) uint32_t irssi_sub_sum = 0; int32_t ta256b_sum = 0; unsigned int num_meas_sub = 0; + unsigned int num_meas_sub_actual = 0; + unsigned int num_meas_sub_subst = 0; + unsigned int num_meas_sub_expect; + unsigned int num_ul_meas; + unsigned int num_ul_meas_actual = 0; + unsigned int num_ul_meas_subst = 0; + unsigned int num_ul_meas_expect; + unsigned int num_ul_meas_excess = 0; int i; /* if measurement period is not complete, abort */ if (!is_meas_complete(lchan, fn)) return 0; - /* if there are no measurements, skip computation */ - if (lchan->meas.num_ul_meas == 0) - return 0; + LOGP(DMEAS, LOGL_DEBUG, "%s Calculating measurement results for physical channel:%s\n", + gsm_lchan_name(lchan), gsm_pchan_name(ts_pchan(lchan->ts))); + + /* Note: Some phys will send no measurement indication at all + * when a block is lost. Also in DTX mode blocks are left out + * intentionally to save energy. It is not necessarly an error + * when we get less measurements as we expect. */ + num_ul_meas_expect = lchan_meas_num_expected(lchan); + + if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) + num_meas_sub_expect = lchan_meas_sub_num_expected(lchan); + else { + /* FIXME: the amount of SUB Measurements is a dynamic parameter + * in AMR and can not be determined by using a lookup table. + * See also: OS#2978 */ + num_meas_sub_expect = 0; + } - /* compute the actual measurements */ + if (lchan->meas.num_ul_meas > num_ul_meas_expect) + num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; + num_ul_meas = num_ul_meas_expect; + + LOGP(DMEAS, LOGL_DEBUG, "%s received %u UL measurements, expected %u\n", gsm_lchan_name(lchan), + lchan->meas.num_ul_meas, num_ul_meas_expect); + if (num_ul_meas_excess) + LOGP(DMEAS, LOGL_DEBUG, "%s received %u excess UL measurements\n", gsm_lchan_name(lchan), + num_ul_meas_excess); + + /* Measurement computation step 1: add up */ + for (i = 0; i < num_ul_meas; i++) { + const struct bts_ul_meas *m; + bool is_sub = false; + + /* Note: We will always compute over a full measurement, + * interval even when not enough measurement samples are in + * the buffer. As soon as we run out of measurement values + * we continue the calculation using dummy values. This works + * well for the BER, since there we can safely assume 100% + * since a missing measurement means that the data (block) + * is lost as well (some phys do not give us measurement + * reports for lost blocks or blocks that are spaced out for + * DTX). However, for RSSI and TA this does not work since + * there we would distort the calculation if we would replace + * them with a made up number. This means for those values we + * only compute over the data we have actually received. */ + + if (i < lchan->meas.num_ul_meas) { + m = &lchan->meas.uplink[i + num_ul_meas_excess]; + if (m->is_sub) { + irssi_sub_sum += m->inv_rssi; + num_meas_sub_actual++; + is_sub = true; + } + irssi_full_sum += m->inv_rssi; + ta256b_sum += m->ta_offs_256bits; + + num_ul_meas_actual++; + } else { + m = &measurement_dummy; + if (num_ul_meas_expect - i <= num_meas_sub_expect - num_meas_sub) { + num_meas_sub_subst++; + is_sub = true; + } - /* step 1: add up */ - for (i = 0; i < lchan->meas.num_ul_meas; i++) { - struct bts_ul_meas *m = &lchan->meas.uplink[i]; + num_ul_meas_subst++; + } ber_full_sum += m->ber10k; - irssi_full_sum += m->inv_rssi; - ta256b_sum += m->ta_offs_256bits; - - if (m->is_sub) { + if (is_sub) { num_meas_sub++; ber_sub_sum += m->ber10k; - irssi_sub_sum += m->inv_rssi; } } - /* step 2: divide */ - ber_full_sum = ber_full_sum / lchan->meas.num_ul_meas; - irssi_full_sum = irssi_full_sum / lchan->meas.num_ul_meas; - ta256b_sum = ta256b_sum / lchan->meas.num_ul_meas; + LOGP(DMEAS, LOGL_DEBUG, "%s received UL measurements contain %u SUB measurements, expected %u\n", + gsm_lchan_name(lchan), num_meas_sub_actual, num_meas_sub_expect); + LOGP(DMEAS, LOGL_DEBUG, "%s replaced %u measurements with dummy values, from which %u were SUB measurements\n", + gsm_lchan_name(lchan), num_ul_meas_subst, num_meas_sub_subst); + + if (num_meas_sub != num_meas_sub_expect) { + LOGP(DMEAS, LOGL_ERROR, "%s Incorrect number of SUB measurements detected!\n", gsm_lchan_name(lchan)); + /* Normally the logic above should make sure that there is + * always the exact amount of SUB measurements taken into + * account. If not then the logic that decides tags the received + * measurements as is_sub works incorrectly. Since the logic + * above only adds missing measurements during the calculation + * it can not remove excess SUB measurements or add missing SUB + * measurements when there is no more room in the interval. */ + } + + /* Measurement computation step 2: divide */ + ber_full_sum = ber_full_sum / num_ul_meas; + + if (!irssi_full_sum) + ber_full_sum = MEASUREMENT_DUMMY_IRSSI; + else + irssi_full_sum = irssi_full_sum / num_ul_meas_actual; + + if (!num_ul_meas_actual) + ta256b_sum = lchan->meas.ms_toa256; + else + ta256b_sum = ta256b_sum / num_ul_meas_actual; - if (num_meas_sub) { + if (!num_meas_sub) + ber_sub_sum = MEASUREMENT_DUMMY_BER; + else ber_sub_sum = ber_sub_sum / num_meas_sub; - irssi_sub_sum = irssi_sub_sum / num_meas_sub; - } else { - LOGP(DMEAS, LOGL_ERROR, "%s No measurements for SUB!!!\n", gsm_lchan_name(lchan)); - /* The only situation in which this can occur is if the related uplink burst/block was - * missing, so let's set BER to 100% and level to lowest possible. */ - ber_sub_sum = 10000; /* 100% */ - irssi_sub_sum = 120; /* -120 dBm */ - } + + if (!num_meas_sub_actual) + irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI; + else + irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual; LOGP(DMEAS, LOGL_INFO, "%s Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), " - "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), - ta256b_sum, ber_full_sum/100, - ber_full_sum%100, irssi_full_sum, ber_sub_sum/100, ber_sub_sum%100, - irssi_sub_sum); + "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), + ta256b_sum, ber_full_sum / 100, + ber_full_sum % 100, irssi_full_sum, ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum); /* store results */ mru = &lchan->meas.ul_res; @@ -593,20 +781,18 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) lchan->meas.ms_toa256 = ta256b_sum; LOGP(DMEAS, LOGL_INFO, "%s UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u)," - "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n", - gsm_lchan_name(lchan), - mru->full.rx_lev, - mru->sub.rx_lev, - mru->full.rx_qual, - mru->sub.rx_qual, num_meas_sub, lchan->meas.num_ul_meas); + "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n", + gsm_lchan_name(lchan), + mru->full.rx_lev, mru->sub.rx_lev, mru->full.rx_qual, mru->sub.rx_qual, num_meas_sub, num_ul_meas_expect); lchan->meas.flags |= LC_UL_M_F_RES_VALID; lchan_meas_compute_extended(lchan); lchan->meas.num_ul_meas = 0; - /* send a signal indicating computation is complete */ + /* return 1 to indicte that the computation has been done and the next + * interval begins. */ return 1; } diff --git a/tests/meas/meas_test.c b/tests/meas/meas_test.c index b90227a7..c397ddd1 100644 --- a/tests/meas/meas_test.c +++ b/tests/meas/meas_test.c @@ -24,6 +24,7 @@ struct fn_sample { #include "sysmobts_fr_samples.h" #include "meas_testcases.h" + void test_fn_sample(struct fn_sample *s, unsigned int len, uint8_t pchan, uint8_t tsmap) { int rc; @@ -73,13 +74,16 @@ static void reset_lchan_meas(struct gsm_lchan *lchan) static void test_meas_compute(const struct meas_testcase *mtc) { - struct gsm_lchan *lchan = &trx->ts[1].lchan[0]; + struct gsm_lchan *lchan; unsigned int i; unsigned int fn = 0; - printf("\nMeasurement Compute Test %s\n", mtc->name); + printf("\n\n"); + printf("===========================================================\n"); + printf("Measurement Compute Test: %s\n", mtc->name); - lchan->ts->pchan = GSM_PCHAN_TCH_F; + lchan = &trx->ts[mtc->ts].lchan[0]; + lchan->ts->pchan = mtc->pchan; reset_lchan_meas(lchan); /* feed uplink measurements into the code */ @@ -94,6 +98,8 @@ static void test_meas_compute(const struct meas_testcase *mtc) OSMO_ASSERT(!(lchan->meas.flags & LC_UL_M_F_RES_VALID)); } else { OSMO_ASSERT(lchan->meas.flags & (LC_UL_M_F_RES_VALID|LC_UL_M_F_OSMO_EXT_VALID)); + printf("number of measurements: %u\n", mtc->ulm_count); + printf("parameter | actual | expected\n"); printf("meas.ext.toa256_min | %6d | %6d\n", lchan->meas.ext.toa256_min, mtc->res.toa256_min); printf("meas.ext.toa256_max | %6d | %6d\n", @@ -113,6 +119,7 @@ static void test_meas_compute(const struct meas_testcase *mtc) (lchan->meas.ext.toa256_std_dev != mtc->res.toa256_std_dev) || (lchan->meas.ul_res.full.rx_lev != mtc->res.rx_lev_full)) { fprintf(stderr, "%s: Unexpected measurement result!\n", mtc->name); + OSMO_ASSERT(false); } } @@ -1121,6 +1128,15 @@ int main(int argc, char **argv) test_meas_compute(&mtc3); test_meas_compute(&mtc4); test_meas_compute(&mtc5); + test_meas_compute(&mtc_tch_f_complete); + test_meas_compute(&mtc_tch_f_dtx_with_lost_subs); + test_meas_compute(&mtc_tch_f_dtx); + test_meas_compute(&mtc_tch_h_complete); + test_meas_compute(&mtc_tch_h_dtx_with_lost_subs); + test_meas_compute(&mtc_tch_h_dtx); + test_meas_compute(&mtc_overrun); + test_meas_compute(&mtc_sdcch4_complete); + test_meas_compute(&mtc_sdcch8_complete); printf("\n"); printf("***************************************************\n"); diff --git a/tests/meas/meas_test.ok b/tests/meas/meas_test.ok index 58e527af..86d8d871 100644 --- a/tests/meas/meas_test.ok +++ b/tests/meas/meas_test.ok @@ -540,33 +540,167 @@ Testing: ts[7]->lchan[1], fn=15079=>015079/11/25/34/23, fn%104=103, rc=1, delta= Testing: ts[7]->lchan[0], fn=15170=>015170/11/12/23/14, fn%104=90, rc=1, delta=91 Testing: ts[7]->lchan[1], fn=15183=>015183/11/25/36/27, fn%104=103, rc=1, delta=13 -Measurement Compute Test TOA256 Min-Max negative/positive + +=========================================================== +Measurement Compute Test: TOA256 Min-Max negative/positive +number of measurements: 3 +parameter | actual | expected meas.ext.toa256_min | -256 | -256 meas.ext.toa256_max | 256 | 256 meas.ms_toa256 | 0 | 0 meas.ext.toa256_std_dev | 209 | 209 meas.ul_res.full.rx_lev | 20 | 20 -meas.ul_res.full.rx_qual | 0 | 0 +meas.ul_res.full.rx_qual | 7 | 0 -Measurement Compute Test TOA256 small jitter around 256 + +=========================================================== +Measurement Compute Test: TOA256 small jitter around 256 +number of measurements: 25 +parameter | actual | expected meas.ext.toa256_min | 254 | 254 meas.ext.toa256_max | 258 | 258 meas.ms_toa256 | 256 | 256 meas.ext.toa256_std_dev | 1 | 1 meas.ul_res.full.rx_lev | 20 | 20 -meas.ul_res.full.rx_qual | 0 | 0 +meas.ul_res.full.rx_qual | 0 | 7 -Measurement Compute Test RxLEv averaging + +=========================================================== +Measurement Compute Test: RxLEv averaging +number of measurements: 5 +parameter | actual | expected meas.ext.toa256_min | 0 | 0 meas.ext.toa256_max | 0 | 0 meas.ms_toa256 | 0 | 0 meas.ext.toa256_std_dev | 0 | 0 meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 0 + + +=========================================================== +Measurement Compute Test: Empty measurements +number of measurements: 0 +parameter | actual | expected +meas.ext.toa256_min | 0 | 0 +meas.ext.toa256_max | 0 | 0 +meas.ms_toa256 | 0 | 0 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 63 | 63 +meas.ul_res.full.rx_qual | 3 | 3 + + +=========================================================== +Measurement Compute Test: TOA256 26 blocks with max TOA256 +number of measurements: 26 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Complete TCH/F measurement period (26 measurements, 3 sub-frames) +number of measurements: 25 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Incomplete TCH/F measurement period (16 measurements, 1 sub-frame) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Complete TCH/H measurement period (26 measurements, 5 sub-frames) +number of measurements: 25 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Incomplete TCH/H measurement period (14 measurements, 3 sub-frames) +number of measurements: 14 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames) +number of measurements: 16 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 7 | 7 + + +=========================================================== +Measurement Compute Test: TCH/F measurement period with too much measurement values (overrun) +number of measurements: 59 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 meas.ul_res.full.rx_qual | 0 | 0 -Measurement Compute Test Empty measurements -Measurement Compute Test TOA256 26 blocks with max TOA256 +=========================================================== +Measurement Compute Test: Complete SDCCH4 measurement period (3 measurements) +number of measurements: 3 +parameter | actual | expected +meas.ext.toa256_min | 16384 | 16384 +meas.ext.toa256_max | 16384 | 16384 +meas.ms_toa256 | 16384 | 16384 +meas.ext.toa256_std_dev | 0 | 0 +meas.ul_res.full.rx_lev | 20 | 20 +meas.ul_res.full.rx_qual | 0 | 0 + + +=========================================================== +Measurement Compute Test: Complete SDCCH8 measurement period (3 measurements) +number of measurements: 3 +parameter | actual | expected meas.ext.toa256_min | 16384 | 16384 meas.ext.toa256_max | 16384 | 16384 meas.ms_toa256 | 16384 | 16384 diff --git a/tests/meas/meas_testcases.h b/tests/meas/meas_testcases.h index ff74a207..fefa34f7 100644 --- a/tests/meas/meas_testcases.h +++ b/tests/meas/meas_testcases.h @@ -1,5 +1,5 @@ -#define ULM(ber, ta, neg_rssi) \ - { .ber10k = (ber), .ta_offs_256bits = (ta), .c_i = 1.0, .is_sub = 0, .inv_rssi = (neg_rssi) } +#define ULM(ber, ta, sub, neg_rssi) \ + { .ber10k = (ber), .ta_offs_256bits = (ta), .c_i = 1.0, .is_sub = sub, .inv_rssi = (neg_rssi) } struct meas_testcase { const char *name; @@ -7,6 +7,8 @@ struct meas_testcase { const struct bts_ul_meas *ulm; unsigned int ulm_count; uint32_t final_fn; + uint8_t ts; + enum gsm_phys_chan_config pchan; /* results */ struct { int success; @@ -20,15 +22,21 @@ struct meas_testcase { }; static struct bts_ul_meas ulm1[] = { - ULM(0, 0, 90), - ULM(0, 256, 90), - ULM(0, -256, 90), + /* Note: The assumptions about the frame number and the subset + * allegiance is random since for the calculation only the amount + * is of relevance. This is true for all following testcases */ + ULM(0, 0, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, -256, 0, 90), }; + static const struct meas_testcase mtc1 = { .name = "TOA256 Min-Max negative/positive", .ulm = ulm1, .ulm_count = ARRAY_SIZE(ulm1), .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, .res = { .success = 1, .rx_lev_full = 110-90, @@ -41,22 +49,44 @@ static const struct meas_testcase mtc1 = { }; static struct bts_ul_meas ulm2[] = { - ULM(0, 256, 90), - ULM(0, 258, 90), - ULM(0, 254, 90), - ULM(0, 258, 90), - ULM(0, 254, 90), - ULM(0, 256, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 1, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 1, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 1, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 258, 0, 90), + ULM(0, 254, 0, 90), + ULM(0, 256, 0, 90), + ULM(0, 256, 0, 90), }; + static const struct meas_testcase mtc2 = { .name = "TOA256 small jitter around 256", .ulm = ulm2, .ulm_count = ARRAY_SIZE(ulm2), .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, .res = { .success = 1, .rx_lev_full = 110-90, - .rx_qual_full = 0, + .rx_qual_full = 7, .toa256_mean = 256, .toa256_max = 258, .toa256_min = 254, @@ -65,17 +95,20 @@ static const struct meas_testcase mtc2 = { }; static struct bts_ul_meas ulm3[] = { - ULM(0, 0, 90), - ULM(0, 0, 80), - ULM(0, 0, 80), - ULM(0, 0, 100), - ULM(0, 0, 100), + ULM(0, 0, 0, 90), + ULM(0, 0, 0, 80), + ULM(0, 0, 0, 80), + ULM(0, 0, 0, 100), + ULM(0, 0, 0, 100), }; + static const struct meas_testcase mtc3 = { .name = "RxLEv averaging", .ulm = ulm3, .ulm_count = ARRAY_SIZE(ulm3), .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, .res = { .success = 1, .rx_lev_full = 110-90, @@ -88,15 +121,18 @@ static const struct meas_testcase mtc3 = { }; static struct bts_ul_meas ulm4[] = {}; + static const struct meas_testcase mtc4 = { .name = "Empty measurements", .ulm = ulm4, .ulm_count = ARRAY_SIZE(ulm4), .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, .res = { - .success = 0, - .rx_lev_full = 0, - .rx_qual_full = 0, + .success = 1, + .rx_lev_full = 63, + .rx_qual_full = 3, .toa256_mean = 0, .toa256_max = 0, .toa256_min = 0, @@ -107,38 +143,41 @@ static const struct meas_testcase mtc4 = { static struct bts_ul_meas ulm5[] = { /* one 104 multiframe can at max contain 26 blocks (TCH/F), * each of which can at maximum be 64 bits in advance (TA range) */ - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), - ULM(0, 64*256, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), }; + static const struct meas_testcase mtc5 = { .name = "TOA256 26 blocks with max TOA256", .ulm = ulm5, .ulm_count = ARRAY_SIZE(ulm5), .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, .res = { .success = 1, .rx_lev_full = 110-90, @@ -149,3 +188,391 @@ static const struct meas_testcase mtc5 = { .toa256_std_dev = 0, }, }; + +/* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ +static struct bts_ul_meas ulm_tch_f_complete[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_f_complete = { + .name = "Complete TCH/F measurement period (26 measurements, 3 sub-frames)", + .ulm = ulm_tch_f_complete, + .ulm_count = ARRAY_SIZE(ulm_tch_f_complete), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models an error case where two of 3 expected sub measurements + * are lost. The calculation logic must detect this and replace those + * measurements. Note that this example also lacks some blocks due to DTX, + * which is normal. */ +static struct bts_ul_meas ulm_tch_f_dtx_with_lost_subs[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_f_dtx_with_lost_subs = { + /* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ + .name = "Incomplete TCH/F measurement period (16 measurements, 1 sub-frame)", + .ulm = ulm_tch_f_dtx_with_lost_subs, + .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx_with_lost_subs), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good-case with DTX. Some measurements are missing + * because no block was transmitted, all sub measurements are there. */ +static struct bts_ul_meas ulm_tch_f_dtx[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_f_dtx = { + .name = "Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames)", + .ulm = ulm_tch_f_dtx, + .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good case as we can see it when all TCH + * and SACCH blocks are received */ +static struct bts_ul_meas ulm_tch_h_complete[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_tch_h_complete = { + .name = "Complete TCH/H measurement period (26 measurements, 5 sub-frames)", + .ulm = ulm_tch_h_complete, + .ulm_count = ARRAY_SIZE(ulm_tch_h_complete), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 110 - 90, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +static struct bts_ul_meas ulm_tch_h_dtx_with_lost_subs[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_h_dtx_with_lost_subs = { + .name = "Incomplete TCH/H measurement period (14 measurements, 3 sub-frames)", + .ulm = ulm_tch_h_dtx_with_lost_subs, + .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx_with_lost_subs), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase models a good-case with DTX. Some measurements are missing + * because no block was transmitted, all sub measurements are there. */ +static struct bts_ul_meas ulm_tch_h_dtx[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_tch_h_dtx = { + .name = "Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames)", + .ulm = ulm_tch_h_dtx, + .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx), + .final_fn = 38, + .ts = 2, + .pchan = GSM_PCHAN_TCH_H, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 7, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* This testcase assumes that too many measurements were collected. This can + * happen when the measurement calculation for a previous cycle were not + * executed. In this case the older part of the excess data must be discarded. + * the calculation algorithm must make sure that the calculation only takes + * place on the last measurement interval */ +static struct bts_ul_meas ulm_overrun[] = { + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + /* All measurements above must be discarded */ + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), + ULM(0, 64*256, 0, 90), +}; + +static const struct meas_testcase mtc_overrun = { + .name = "TCH/F measurement period with too much measurement values (overrun)", + .ulm = ulm_overrun, + .ulm_count = ARRAY_SIZE(ulm_overrun), + .final_fn = 25, + .ts = 1, + .pchan = GSM_PCHAN_TCH_F, + .res = { + .success = 1, + .rx_lev_full = 110 - 90, + .rx_qual_full = 0, + .toa256_mean = 64*256, + .toa256_max = 64*256, + .toa256_min = 64*256, + .toa256_std_dev = 0, + }, +}; + +/* Test SDCCH4 with all frames received */ +static struct bts_ul_meas ulm_sdcch4_complete[] = { + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_sdcch4_complete = { + .name = "Complete SDCCH4 measurement period (3 measurements)", + .ulm = ulm_sdcch4_complete, + .ulm_count = ARRAY_SIZE(ulm_sdcch4_complete), + .final_fn = 88, + .ts = 0, + .pchan = GSM_PCHAN_CCCH_SDCCH4, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; + +/* Test SDCCH8 with all frames received */ +static struct bts_ul_meas ulm_sdcch8_complete[] = { + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), + ULM(0, 64*256, 1, 90), +}; + +static const struct meas_testcase mtc_sdcch8_complete = { + .name = "Complete SDCCH8 measurement period (3 measurements)", + .ulm = ulm_sdcch8_complete, + .ulm_count = ARRAY_SIZE(ulm_sdcch8_complete), + .final_fn = 66, + .ts = 0, + .pchan = GSM_PCHAN_SDCCH8_SACCH8C, + .res = { + .success = 1, + .rx_lev_full = 20, + .rx_qual_full = 0, + .toa256_mean = 16384, + .toa256_max = 16384, + .toa256_min = 16384, + .toa256_std_dev = 0, + }, +}; |