/* Implementation of some PDU parsing of the TETRA upper MAC */ /* (C) 2011 by Harald Welte * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include "tetra_common.h" #include "tetra_mac_pdu.h" static void decode_d_mle_sysinfo(struct tetra_mle_si_decoded *msid, const uint8_t *bits) { const uint8_t *cur = bits; msid->la = bits_to_uint(cur, 14); cur += 14; msid->subscr_class = bits_to_uint(cur, 16); cur += 16; msid->bs_service_details = bits_to_uint(cur, 12); cur += 12; } /* see 21.4.4.1 */ void macpdu_decode_sysinfo(struct tetra_si_decoded *sid, const uint8_t *si_bits) { const uint8_t *cur = si_bits; cur += 2; // skip Broadcast PDU header cur += 2; // skip Sysinfo PDU header sid->main_carrier = bits_to_uint(cur, 12); cur += 12; sid->freq_band = bits_to_uint(cur, 4); cur += 4; sid->freq_offset = bits_to_uint(cur, 2); cur += 2; sid->duplex_spacing = bits_to_uint(cur, 3); cur += 3; sid->reverse_operation = *cur++; sid->num_of_csch = bits_to_uint(cur, 2); cur += 2; sid->ms_txpwr_max_cell = bits_to_uint(cur, 3); cur += 3; sid->rxlev_access_min = bits_to_uint(cur, 4); cur += 4; sid->access_parameter = bits_to_uint(cur, 4); cur += 4; sid->radio_dl_timeout = bits_to_uint(cur, 4); cur += 4; sid->cck_valid_no_hf = *cur++; if (sid->cck_valid_no_hf) sid->cck_id = bits_to_uint(cur, 16); else sid->hyperframe_number = bits_to_uint(cur, 16); sid->option_field = bits_to_uint(cur, 2); cur += 2; switch (sid->option_field) { case TETRA_MAC_OPT_FIELD_EVEN_MULTIFRAME: // Even multiframe definition for TS mode case TETRA_MAC_OPT_FIELD_ODD_MULTIFRAME: // Odd multiframe definition for TS mode sid->frame_bitmap = bits_to_uint(cur, 20); cur += 20; break; case TETRA_MAC_OPT_FIELD_ACCESS_CODE: // Default definition for access code A sid->access_code = bits_to_uint(cur, 20); cur += 20; break; case TETRA_MAC_OPT_FIELD_EXT_SERVICES: // Extended services broadcast sid->ext_service = bits_to_uint(cur, 20); cur += 20; break; } decode_d_mle_sysinfo(&sid->mle_si, si_bits + 124-42); // could be also cur due to previous fixes } static const uint8_t addr_len_by_type[] = { [ADDR_TYPE_SSI] = 24, [ADDR_TYPE_EVENT_LABEL] = 10, [ADDR_TYPE_USSI] = 24, [ADDR_TYPE_SMI] = 24, [ADDR_TYPE_SSI_EVENT] = 34, [ADDR_TYPE_SSI_USAGE] = 30, [ADDR_TYPE_SMI_EVENT] = 34, }; /* 21.5.2 */ int macpdu_decode_chan_alloc(struct tetra_chan_alloc_decoded *cad, const uint8_t *bits) { const uint8_t *cur = bits; cad->type = bits_to_uint(cur, 2); cur += 2; cad->timeslot = bits_to_uint(cur, 4); cur += 4; cad->ul_dl = bits_to_uint(cur, 2); cur += 2; cad->clch_perm = *cur++; cad->cell_chg_f = *cur++; cad->carrier_nr = bits_to_uint(cur, 12); cur += 12; cad->ext_carr_pres = *cur++; if (cad->ext_carr_pres) { cad->ext_carr.freq_band = bits_to_uint(cur, 4); cur += 4; cad->ext_carr.freq_offset = bits_to_uint(cur, 2); cur += 2; cad->ext_carr.duplex_spc = bits_to_uint(cur, 3); cur += 3; cad->ext_carr.reverse_oper = bits_to_uint(cur, 1); cur += 1; } cad->monit_pattern = bits_to_uint(cur, 2); cur += 2; if (cad->monit_pattern == 0) { cad->monit_patt_f18 = bits_to_uint(cur, 2); cur += 2; } if (cad->ul_dl == 0) { cad->aug.ul_dl_ass = bits_to_uint(cur, 2); cur += 2; cad->aug.bandwidth = bits_to_uint(cur, 3); cur += 3; cad->aug.modulation = bits_to_uint(cur, 3); cur += 3; cad->aug.max_ul_qam = bits_to_uint(cur, 3); cur += 3; cur += 3; /* reserved */ cad->aug.conf_chan_stat = bits_to_uint(cur, 3); cur += 3; cad->aug.bs_imbalance = bits_to_uint(cur, 4); cur += 4; cad->aug.bs_tx_rel = bits_to_uint(cur, 5); cur += 5; cad->aug.napping_sts = bits_to_uint(cur, 2); cur += 2; if (cad->aug.napping_sts == 1) cur += 11; /* napping info 21.5.2c */ cur += 4; /* reserved */ if (*cur++) cur += 16; if (*cur++) cur += 16; cur++; } return cur - bits; } /* According to table 21.90 */ static int decode_nr_slots(uint8_t in) { const uint8_t dec_tbl[] = { [0x0] = 0, /* first sub-slot */ [0x1] = 1, [0x2] = 2, [0x3] = 3, [0x4] = 4, [0x5] = 5, [0x6] = 6, [0x7] = 8, [0x8] = 10, [0x9] = 13, [0xa] = 17, [0xb] = 24, [0xc] = 34, [0xd] = 51, [0xe] = 68, [0xf] = 0xff /* second sub-slot */ }; return dec_tbl[in & 0xf]; } static int decode_length(unsigned int length_ind) { /* FIXME: Y2/Z2 for non-pi4 DQPSK */ unsigned int y2 = 1, z2 = 1; if (length_ind == 0 || length_ind == 0x3b || length_ind == 0x3c) return -EINVAL; else if (length_ind <= 0x12) return y2 * length_ind; else if (length_ind <= 0x3a) return (18 * y2 + (length_ind - 18) * z2); else if (length_ind == 0x3e) return MACPDU_LEN_2ND_STOLEN; else if (length_ind == 0x3f) return MACPDU_LEN_START_FRAG; else return -EINVAL; } /* Section 21.4.3.1 MAC-RESOURCE */ int macpdu_decode_resource(struct tetra_resrc_decoded *rsd, const uint8_t *bits, uint8_t is_decrypted) { const uint8_t *cur = bits + 2; rsd->fill_bits = bits_to_uint(cur, 1); cur += 1; rsd->grant_position = bits_to_uint(cur, 1); cur += 1; rsd->encryption_mode = bits_to_uint(cur, 2); cur += 2; rsd->is_encrypted = rsd->encryption_mode > 0 && !is_decrypted; rsd->rand_acc_flag = *cur++; rsd->macpdu_length = decode_length(bits_to_uint(cur, 6)); cur += 6; rsd->addr.type = bits_to_uint(cur, 3); cur += 3; switch (rsd->addr.type) { case ADDR_TYPE_NULL: return 0; break; case ADDR_TYPE_SSI: case ADDR_TYPE_USSI: case ADDR_TYPE_SMI: rsd->addr.ssi = bits_to_uint(cur, 24); break; case ADDR_TYPE_EVENT_LABEL: rsd->addr.event_label = bits_to_uint(cur, 10); break; case ADDR_TYPE_SSI_EVENT: case ADDR_TYPE_SMI_EVENT: rsd->addr.ssi = bits_to_uint(cur, 24); rsd->addr.event_label = bits_to_uint(cur+24, 10); break; case ADDR_TYPE_SSI_USAGE: rsd->addr.ssi = bits_to_uint(cur, 24); rsd->addr.usage_marker = bits_to_uint(cur+24, 6); break; default: return -EINVAL; break; } cur += addr_len_by_type[rsd->addr.type]; /* no intermediate napping in pi/4 */ rsd->power_control_pres = *cur++; if (rsd->power_control_pres) cur += 4; rsd->slot_granting.pres = *cur++; if (rsd->slot_granting.pres) { #if 0 /* check for multiple slot granting flag (can only exist in QAM) */ if (*cur++) { cur += 0; //FIXME; } else { #endif rsd->slot_granting.nr_slots = decode_nr_slots(bits_to_uint(cur, 4)); cur += 4; rsd->slot_granting.delay = bits_to_uint(cur, 4); cur += 4; #if 0 } #endif } rsd->chan_alloc_pres = *cur++; if (rsd->chan_alloc_pres && !rsd->is_encrypted) // We can only determine length if the frame is unencrypted cur += macpdu_decode_chan_alloc(&rsd->cad, cur); return cur - bits; } static void decode_access_field(struct tetra_access_field *taf, uint8_t field) { field &= 0x3f; taf->access_code = field >> 4; taf->base_frame_len = field & 0xf; } /* Section 21.4.7.2 ACCESS-ASSIGN PDU */ void macpdu_decode_access_assign(struct tetra_acc_ass_decoded *aad, const uint8_t *bits, int f18) { uint8_t field1, field2; aad->hdr = bits_to_uint(bits, 2); field1 = bits_to_uint(bits+2, 6); field2 = bits_to_uint(bits+8, 6); if (f18 == 0) { switch (aad->hdr) { case TETRA_ACC_ASS_DLCC_ULCO: /* Field 1 and Field2 are Access fields */ decode_access_field(&aad->access[0], field1); decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS1; aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_DLF1_ULCA: /* Field1: DL usage marker */ aad->dl_usage = field1; aad->pres |= TETRA_ACC_ASS_PRES_DL_USAGE; /* Field2: Access field */ decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_DLF1_ULAO: /* Field1: DL usage marker */ aad->dl_usage = field1; aad->pres |= TETRA_ACC_ASS_PRES_DL_USAGE; /* Field2: Access field */ decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_DLF1_ULF1: /* Field1: DL usage marker */ aad->dl_usage = field1; aad->pres |= TETRA_ACC_ASS_PRES_DL_USAGE; /* Field2: UL usage marker */ aad->ul_usage = field2; aad->pres |= TETRA_ACC_ASS_PRES_UL_USAGE; break; } } else { switch (aad->hdr) { case TETRA_ACC_ASS_ULCO: /* Field1 and Field2: Access field */ decode_access_field(&aad->access[0], field1); decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS1; aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_ULCA: /* Field1 and Field2: Access field */ decode_access_field(&aad->access[0], field1); decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS1; aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_ULAO: /* Field1 and Field2: Access field */ decode_access_field(&aad->access[0], field1); decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS1; aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; case TETRA_ACC_ASS_ULCA2: /* Field1: Traffic usage marker (UMt) */ /* FIXME */ /* Field2: Access field */ decode_access_field(&aad->access[1], field2); aad->pres |= TETRA_ACC_ASS_PRES_ACCESS2; break; } } } static const struct value_string tetra_macpdu_t_names[5] = { { TETRA_PDU_T_MAC_RESOURCE, "RESOURCE" }, { TETRA_PDU_T_MAC_FRAG_END, "FRAG/END" }, { TETRA_PDU_T_BROADCAST, "BROADCAST" }, { TETRA_PDU_T_MAC_SUPPL, "SUPPLEMENTARY" }, { 0, NULL } }; const char *tetra_get_macpdu_name(uint8_t pdu_type) { return get_value_string(tetra_macpdu_t_names, pdu_type); } static const struct value_string serv_det_names[] = { { BS_SERVDET_REG_RQD, "Registration mandatory" }, { BS_SERVDET_DEREG_RQD, "De-registration mandatory" }, { BS_SERVDET_PRIO_CELL, "Priority cell" }, { BS_SERVDET_MIN_MODE, "Cell never uses minimum mode" }, { BS_SERVDET_MIGRATION, "Migration supported" }, { BS_SERVDET_SYS_W_SERV, "Normal mode" }, { BS_SERVDET_VOICE_SERV, "Voice service" }, { BS_SERVDET_CSD_SERV, "Circuit data" }, { BS_SERVDET_SNDCP_SERV, "SNDCP data" }, { BS_SERVDET_AIR_ENCR, "Air encryption" }, { BS_SERVDET_ADV_LINK, "Advanced link" }, { 0, NULL }, }; const char *tetra_get_bs_serv_det_name(uint32_t pdu_type) { return get_value_string(serv_det_names, pdu_type); } static const struct value_string dl_usage_names[] = { { TETRA_DL_US_UNALLOC, "Unallocated" }, { TETRA_DL_US_ASS_CTRL, "Assigned control" }, { TETRA_DL_US_COM_CTRL, "Common control" }, { TETRA_DL_US_RESERVED, "Reserved" }, { 0, NULL }, }; const char *tetra_get_dl_usage_name(uint8_t num) { if (num <= 3) return get_value_string(dl_usage_names, num); return "Traffic"; } const char *tetra_get_ul_usage_name(uint8_t num) { if (num == 0) return "Unallocated"; return "Traffic"; } static const struct value_string addr_type_names[] = { { ADDR_TYPE_NULL, "Null PDU" }, { ADDR_TYPE_SSI, "SSI" }, { ADDR_TYPE_EVENT_LABEL, "Event Label" }, { ADDR_TYPE_USSI, "USSI (migrading MS un-exchanged)" }, { ADDR_TYPE_SMI, "SMI (management)" }, { ADDR_TYPE_SSI_EVENT, "SSI + Event Label" }, { ADDR_TYPE_SSI_USAGE, "SSI + Usage Marker" }, { ADDR_TYPE_SMI_EVENT, "SMI + Event Label" }, { 0, NULL } }; const char *tetra_get_addr_t_name(uint8_t addrt) { return get_value_string(addr_type_names, addrt); } static const struct value_string alloc_type_names[] = { { TMAC_ALLOC_T_REPLACE, "Replace" }, { TMAC_ALLOC_T_ADDITIONAL, "Additional" }, { TMAC_ALLOC_T_QUIT_GO, "Quit and go" }, { TMAC_ALLOC_T_REPL_SLOT1, "Replace + Slot1" }, { 0, NULL } }; const char *tetra_get_alloc_t_name(uint8_t alloct) { return get_value_string(alloc_type_names, alloct); } const char *tetra_addr_dump(const struct tetra_addr *addr) { static char buf[64]; char *cur = buf; memset(buf, 0, sizeof(buf)); cur += sprintf(cur, "%s(", tetra_get_addr_t_name(addr->type)); switch (addr->type) { case ADDR_TYPE_NULL: break; case ADDR_TYPE_SSI: case ADDR_TYPE_USSI: case ADDR_TYPE_SMI: cur += sprintf(cur, "%u", addr->ssi); break; case ADDR_TYPE_EVENT_LABEL: case ADDR_TYPE_SSI_EVENT: case ADDR_TYPE_SMI_EVENT: cur += sprintf(cur, "%u/E%u", addr->ssi, addr->event_label); break; case ADDR_TYPE_SSI_USAGE: cur += sprintf(cur, "%u/U%u", addr->ssi, addr->usage_marker); break; } cur += sprintf(cur, ")"); return buf; } static const struct value_string ul_dl_names[] = { { 0, "Augmented" }, { 1, "Downlink only" }, { 2, "Uplink only" }, { 3, "Uplink + Downlink" }, { 0, NULL } }; const char *tetra_get_ul_dl_name(uint8_t ul_dl) { return get_value_string(ul_dl_names, ul_dl); }