From 7ee08faee0846e91ffa170a75947e821eb280931 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Wed, 19 Jan 2011 10:39:59 +0100 Subject: [PATCH] initial import of Osmocom TETRA phy and lower MAC code --- src/.gitignore | 8 + src/Makefile | 25 +++ src/burst_test.c | 74 ++++++ src/conv_enc_test.c | 351 +++++++++++++++++++++++++++++ src/crc_test.c | 73 ++++++ src/float_to_bits.c | 117 ++++++++++ src/lower_mac/crc_simple.c | 106 +++++++++ src/lower_mac/crc_simple.h | 48 ++++ src/lower_mac/tetra_conv_enc.c | 301 +++++++++++++++++++++++++ src/lower_mac/tetra_conv_enc.h | 33 +++ src/lower_mac/tetra_interleave.c | 59 +++++ src/lower_mac/tetra_interleave.h | 12 + src/lower_mac/tetra_lower_mac.c | 257 +++++++++++++++++++++ src/lower_mac/tetra_rm3014.c | 96 ++++++++ src/lower_mac/tetra_rm3014.h | 16 ++ src/lower_mac/tetra_scramb.c | 99 ++++++++ src/lower_mac/tetra_scramb.h | 23 ++ src/lower_mac/viterbi.c | 25 +++ src/lower_mac/viterbi.h | 6 + src/lower_mac/viterbi_cch.c | 163 ++++++++++++++ src/lower_mac/viterbi_cch.h | 7 + src/lower_mac/viterbi_tch.c | 159 +++++++++++++ src/lower_mac/viterbi_tch.h | 7 + src/osmo_prim.h | 19 ++ src/phy/tetra_burst.c | 345 ++++++++++++++++++++++++++++ src/phy/tetra_burst.h | 35 +++ src/phy/tetra_burst_sync.c | 141 ++++++++++++ src/phy/tetra_burst_sync.h | 26 +++ src/testpdu.c | 99 ++++++++ src/testpdu.h | 10 + src/tetra_common.c | 157 +++++++++++++ src/tetra_common.h | 52 +++++ src/tetra_gsmtap.c | 134 +++++++++++ src/tetra_gsmtap.h | 13 ++ src/tetra_mac_pdu.c | 375 +++++++++++++++++++++++++++++++ src/tetra_mac_pdu.h | 221 ++++++++++++++++++ src/tetra_mle_pdu.c | 38 ++++ src/tetra_mle_pdu.h | 17 ++ src/tetra_mm_pdu.c | 46 ++++ src/tetra_mm_pdu.h | 26 +++ src/tetra_prim.h | 48 ++++ src/tetra_tdma.c | 100 +++++++++ src/tetra_tdma.h | 20 ++ src/tetra_upper_mac.c | 222 ++++++++++++++++++ src/tetra_upper_mac.h | 8 + 45 files changed, 4217 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/Makefile create mode 100644 src/burst_test.c create mode 100644 src/conv_enc_test.c create mode 100644 src/crc_test.c create mode 100644 src/float_to_bits.c create mode 100644 src/lower_mac/crc_simple.c create mode 100644 src/lower_mac/crc_simple.h create mode 100644 src/lower_mac/tetra_conv_enc.c create mode 100644 src/lower_mac/tetra_conv_enc.h create mode 100644 src/lower_mac/tetra_interleave.c create mode 100644 src/lower_mac/tetra_interleave.h create mode 100644 src/lower_mac/tetra_lower_mac.c create mode 100644 src/lower_mac/tetra_rm3014.c create mode 100644 src/lower_mac/tetra_rm3014.h create mode 100644 src/lower_mac/tetra_scramb.c create mode 100644 src/lower_mac/tetra_scramb.h create mode 100644 src/lower_mac/viterbi.c create mode 100644 src/lower_mac/viterbi.h create mode 100644 src/lower_mac/viterbi_cch.c create mode 100644 src/lower_mac/viterbi_cch.h create mode 100644 src/lower_mac/viterbi_tch.c create mode 100644 src/lower_mac/viterbi_tch.h create mode 100644 src/osmo_prim.h create mode 100644 src/phy/tetra_burst.c create mode 100644 src/phy/tetra_burst.h create mode 100644 src/phy/tetra_burst_sync.c create mode 100644 src/phy/tetra_burst_sync.h create mode 100644 src/testpdu.c create mode 100644 src/testpdu.h create mode 100644 src/tetra_common.c create mode 100644 src/tetra_common.h create mode 100644 src/tetra_gsmtap.c create mode 100644 src/tetra_gsmtap.h create mode 100644 src/tetra_mac_pdu.c create mode 100644 src/tetra_mac_pdu.h create mode 100644 src/tetra_mle_pdu.c create mode 100644 src/tetra_mle_pdu.h create mode 100644 src/tetra_mm_pdu.c create mode 100644 src/tetra_mm_pdu.h create mode 100644 src/tetra_prim.h create mode 100644 src/tetra_tdma.c create mode 100644 src/tetra_tdma.h create mode 100644 src/tetra_upper_mac.c create mode 100644 src/tetra_upper_mac.h diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..f3ab7b6 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,8 @@ +*.o +*.a +*.pyc +.*.swp +conv_enc_test +burst_test +float_to_bits +crc_test diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..8769bf0 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,25 @@ +CFLAGS=-g -O0 -Wall `pkg-config --cflags libosmocore 2> /dev/null` -I. +LDFLAGS=`pkg-config --libs libosmocore 2> /dev/null` -losmocore + +all: conv_enc_test crc_test burst_test float_to_bits + +%.o: %.c + $(CC) $(CFLAGS) -c $^ -o $@ + +libosmo-tetra-phy.a: phy/tetra_burst_sync.o phy/tetra_burst.o + $(AR) r $@ $^ + +libosmo-tetra-mac.a: lower_mac/tetra_conv_enc.o tetra_tdma.o lower_mac/tetra_scramb.o lower_mac/tetra_rm3014.o lower_mac/tetra_interleave.o lower_mac/crc_simple.o tetra_common.o lower_mac/viterbi.o lower_mac/viterbi_cch.o lower_mac/viterbi_tch.o lower_mac/tetra_lower_mac.o tetra_upper_mac.o tetra_mac_pdu.o tetra_mle_pdu.o tetra_mm_pdu.o tetra_gsmtap.o + $(AR) r $@ $^ + +float_to_bits: float_to_bits.o + +crc_test: crc_test.o tetra_common.o libosmo-tetra-mac.a + +burst_test: burst_test.o libosmo-tetra-phy.a libosmo-tetra-mac.a + +conv_enc_test: conv_enc_test.o testpdu.o libosmo-tetra-phy.a libosmo-tetra-mac.a + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +clean: + @rm -f conv_enc_test crc_test burst_test float_to_bits *.o phy/*.o lower_mac/*.o *.a diff --git a/src/burst_test.c b/src/burst_test.c new file mode 100644 index 0000000..a5cb53e --- /dev/null +++ b/src/burst_test.c @@ -0,0 +1,74 @@ +/* Test program for tetra burst synchronizer */ + +/* (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 + +#include "tetra_common.h" +#include +#include +#include "tetra_gsmtap.h" + +int main(int argc, char **argv) +{ + int fd; + struct tetra_rx_state *trs; + + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open"); + exit(2); + } + + tetra_gsmtap_init("localhost", 0); + + trs = calloc(1, sizeof(*trs)); + + while (1) { + uint8_t buf[64]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + perror("read"); + exit(1); + } else if (len == 0) { + printf("EOF"); + break; + } + tetra_burst_sync_in(trs, buf, len); + } + + free(trs); + + exit(0); +} diff --git a/src/conv_enc_test.c b/src/conv_enc_test.c new file mode 100644 index 0000000..1d0e2f0 --- /dev/null +++ b/src/conv_enc_test.c @@ -0,0 +1,351 @@ +/* (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 +#include + + +#include "tetra_common.h" +#include +#include +#include +#include +#include +#include +#include +#include "testpdu.h" + + +#define swap16(x) ((x)<<8)|((x)>>8) + +static unsigned int num_crc_err; + +/* incoming TP-SAP UNITDATA.ind from PHY into lower MAC */ +void tp_sap_udata_ind(enum tp_sap_data_type type, const uint8_t *bits, unsigned int len, void *priv) +{ +} + +static void decode_schf(const uint8_t *bits) +{ + uint8_t type4[1024]; + uint8_t type3dp[1024*4]; + uint8_t type3[1024]; + uint8_t type2[1024]; + + printf("SCH/f type5: %s\n", bitdump(bits, 432)); + memcpy(type4, bits, 432); + tetra_scramb_bits(SCRAMB_INIT, type4, 432); + printf("SCH/F type4: %s\n", bitdump(type4, 432)); + /* Run (120,11) block deinterleaving: type-3 bits */ + block_deinterleave(432, 103, type4, type3); + printf("SCH/F type3: %s\n", bitdump(type3, 432)); + /* De-puncture */ + memset(type3dp, 0xff, sizeof(type3dp)); + tetra_rcpc_depunct(TETRA_RCPC_PUNCT_2_3, type3, 432, type3dp); + printf("SCH/F type3dp: %s\n", bitdump(type3dp, 288*4)); + viterbi_dec_sb1_wrapper(type3dp, type2, 288); + printf("SCH/F type2: %s\n", bitdump(type2, 288)); + + { + uint16_t crc; + crc = crc16_ccitt_bits(type2, 288-4); + printf("CRC COMP: 0x%04x ", crc); + if (crc == 0x1d0f) + printf("OK\n"); + else { + printf("WRONG\n"); + num_crc_err++; + } + } + printf("SCH/F type1: %s\n", bitdump(type2, 268)); +} + +/* Build a full 'downlink continuous SYNC burst' from SYSINFO-PDU and SYNC-PDU */ +int build_ndb_schf() +{ + /* input: 268 type-1 bits */ + uint8_t type2[284]; + uint8_t master[284*4]; + uint8_t type3[432]; + uint8_t type4[432]; + uint8_t type5[432]; + uint8_t bb_type5[30]; + uint8_t burst[255*2]; + uint16_t crc; + uint8_t *cur; + uint32_t bb_rm3014, bb_rm3014_be; + + memset(type2, 0, sizeof(type2)); + cur = type2; + + /* Use pdu_sync from testpdu.c */ + cur += osmo_pbit2ubit(type2, pdu_schf, 268); + + crc = ~crc16_ccitt_bits(type2, 268); + crc = swap16(crc); + cur += osmo_pbit2ubit(cur, (uint8_t *) &crc, 16); + + /* Append 4 tail bits: type-2 bits */ + cur += 4; + + printf("SCH/F type2: %s\n", bitdump(type2, 288)); + + /* Run rate 2/3 RCPC code: type-3 bits*/ + { + struct conv_enc_state *ces = calloc(1, sizeof(*ces)); + conv_enc_init(ces); + conv_enc_input(ces, type2, 288, master); + get_punctured_rate(TETRA_RCPC_PUNCT_2_3, master, 432, type3); + free(ces); + } + printf("SCH/F type3: %s\n", bitdump(type3, 432)); + + /* Run (432,103) block interleaving: type-4 bits */ + block_interleave(432, 103, type3, type4); + printf("SCH/F type4: %s\n", bitdump(type4, 432)); + + /* Run scrambling (all-zero): type-5 bits */ + memcpy(type5, type4, 432); + tetra_scramb_bits(SCRAMB_INIT, type5, 432); + printf("SCH/F type5: %s\n", bitdump(type5, 432)); + + decode_schf(type5); + + /* Use pdu_acc_ass from testpdu.c */ + /* Run it through (30,14) RM code: type-2=3=4 bits */ + printf("AACH type-1: %s\n", bitdump(pdu_acc_ass, 2)); + bb_rm3014 = tetra_rm3014_compute(*(uint16_t *)pdu_acc_ass); + printf("AACH RM3014: 0x0%x\n", bb_rm3014); + /* convert to big endian */ + bb_rm3014_be = htonl(bb_rm3014); + /* shift two bits left as it is only a 30 bit value */ + bb_rm3014_be <<= 2; + osmo_pbit2ubit(bb_type5, (uint8_t *) &bb_rm3014_be, 30); + /* Run scrambling (all-zero): type-5 bits */ + printf("AACH type-5: %s\n", bitdump(bb_type5, 30)); + + /* Finally, hand it into the physical layer */ + build_norm_c_d_burst(burst, type5, bb_type5, type5+216, 0); + printf("cont norm DL burst: %s\n", bitdump(burst, 255*2)); + + return 0; +} + + +static void decode_sb1(const uint8_t *bits) +{ + uint8_t type4[1024]; + uint8_t type3dp[1024*4]; + uint8_t type3[1024]; + uint8_t type2[1024]; + + printf("SB1 type5: %s\n", bitdump(bits, 120)); + memcpy(type4, bits, 120); + tetra_scramb_bits(SCRAMB_INIT, type4, 120); + printf("SB1 type4: %s\n", bitdump(type4, 120)); + /* Run (120,11) block deinterleaving: type-3 bits */ + block_deinterleave(120, 11, type4, type3); + printf("SB1 type3: %s\n", bitdump(type3, 120)); + /* De-puncture */ + memset(type3dp, 0xff, sizeof(type3dp)); + tetra_rcpc_depunct(TETRA_RCPC_PUNCT_2_3, type3, 120, type3dp); + printf("SB1 type3dp: %s\n", bitdump(type3dp, 80*4)); + viterbi_dec_sb1_wrapper(type3dp, type2, 80); + printf("SB1 type2: %s\n", bitdump(type2, 80)); + + { + uint16_t crc; + crc = crc16_ccitt_bits(type2, 76); + printf("CRC COMP: 0x%04x ", crc); + if (crc == 0x1d0f) + printf("OK\n"); + else { + printf("WRONG\n"); + num_crc_err++; + } + } + + printf("TN %s ", bitdump(type2+10, 2)); + printf("MCC %s ", bitdump(type2+31, 10)); + printf("MNC %s\n", bitdump(type2+41, 14)); +} + +/* Build a full 'downlink continuous SYNC burst' from SYSINFO-PDU and SYNC-PDU */ +int build_sb() +{ + uint8_t sb_type2[80]; + uint8_t sb_master[80*4]; + uint8_t sb_type3[120]; + uint8_t sb_type4[120]; + uint8_t sb_type5[120]; + + uint8_t si_type2[140]; + uint8_t si_master[140*4]; + uint8_t si_type3[216]; + uint8_t si_type4[216]; + uint8_t si_type5[216]; + + uint8_t bb_type5[30]; + uint8_t burst[255*2]; + uint16_t crc; + uint8_t *cur; + uint32_t bb_rm3014, bb_rm3014_be; + + memset(sb_type2, 0, sizeof(sb_type2)); + cur = sb_type2; + + /* Use pdu_sync from testpdu.c */ + cur += osmo_pbit2ubit(sb_type2, pdu_sync, 60); + + crc = ~crc16_ccitt_bits(sb_type2, 60); + crc = swap16(crc); + cur += osmo_pbit2ubit(cur, (uint8_t *) &crc, 16); + + /* Append 4 tail bits: type-2 bits */ + cur += 4; + + printf("SYNC type2: %s\n", bitdump(sb_type2, 80)); + + /* Run rate 2/3 RCPC code: type-3 bits*/ + { + struct conv_enc_state *ces = calloc(1, sizeof(*ces)); + conv_enc_init(ces); + conv_enc_input(ces, sb_type2, 80, sb_master); + get_punctured_rate(TETRA_RCPC_PUNCT_2_3, sb_master, 120, sb_type3); + free(ces); + } + printf("SYNC type3: %s\n", bitdump(sb_type3, 120)); + + /* Run (120,11) block interleaving: type-4 bits */ + block_interleave(120, 11, sb_type3, sb_type4); + printf("SYNC type4: %s\n", bitdump(sb_type4, 120)); + + /* Run scrambling (all-zero): type-5 bits */ + memcpy(sb_type5, sb_type4, 120); + tetra_scramb_bits(SCRAMB_INIT, sb_type5, 120); + printf("SYNC type5: %s\n", bitdump(sb_type5, 120)); + + decode_sb1(sb_type5); + + /* Use pdu_sysinfo from testpdu.c */ + memset(si_type2, 0, sizeof(si_type2)); + cur = si_type2; + cur += osmo_pbit2ubit(si_type2, pdu_sysinfo, 124); + + /* Run it through CRC16-CCITT */ + crc = ~crc16_ccitt_bits(si_type2, 124); + crc = swap16(crc); + cur += osmo_pbit2ubit(cur, (uint8_t *) &crc, 16); + + /* Append 4 tail bits: type-2 bits */ + cur += 4; + + printf("SI type2: %s\n", bitdump(si_type2, 140)); + + /* Run rate 2/3 RCPC code: type-3 bits */ + { + struct conv_enc_state *ces = calloc(1, sizeof(*ces)); + conv_enc_init(ces); + conv_enc_input(ces, sb_type2, 144, si_master); + get_punctured_rate(TETRA_RCPC_PUNCT_2_3, si_master, 216, si_type3); + free(ces); + } + printf("SI type3: %s\n", bitdump(si_type3, 216)); + + /* Run (216,101) block interleaving: type-4 bits */ + block_interleave(216, 101, si_type3, si_type4); + printf("SI type4: %s\n", bitdump(si_type4, 216)); + + /* Run scrambling (all-zero): type-5 bits */ + memcpy(si_type5, si_type4, 216); + + + /* Use pdu_acc_ass from testpdu.c */ + /* Run it through (30,14) RM code: type-2=3=4 bits */ + printf("AACH type-1: %s\n", bitdump(pdu_acc_ass, 2)); + bb_rm3014 = tetra_rm3014_compute(*(uint16_t *)pdu_acc_ass); + printf("AACH RM3014: 0x0%x\n", bb_rm3014); + /* convert to big endian */ + bb_rm3014_be = htonl(bb_rm3014); + /* shift two bits left as it is only a 30 bit value */ + bb_rm3014_be <<= 2; + osmo_pbit2ubit(bb_type5, (uint8_t *) &bb_rm3014_be, 30); + /* Run scrambling (all-zero): type-5 bits */ + printf("AACH type-5: %s\n", bitdump(bb_type5, 30)); + + /* Finally, hand it into the physical layer */ + build_sync_c_d_burst(burst, sb_type5, bb_type5, si_type5); + printf("cont sync DL burst: %s\n", bitdump(burst, 255*2)); + + return 0; +} + +int main(int argc, char **argv) +{ + int err, i; + uint16_t out; + uint32_t ret; + + /* first: run some subsystem tests */ + ret = tetra_punct_test(); + if (ret < 0) + exit(1); + + tetra_rm3014_init(); +#if 0 + ret = tetra_rm3014_compute(0x1001); + printf("RM3014: 0x%08x\n", ret); + + err = tetra_rm3014_decode(ret, &out); + printf("RM3014: 0x%x error: %d\n", out, err); +#endif + + /* finally, build some test PDUs and encocde them */ + testpdu_init(); +#if 0 + build_sb(); + + build_ndb_schf(); +#else + /* iterate over various random PDUs and throw them throguh the viterbi */ + srand(time(NULL)); + for (i = 0; i < 100; i++) { + uint32_t r = rand(); + osmo_pbit2ubit(pdu_sync, (uint8_t *) &r, 32); + osmo_pbit2ubit(pdu_sync+32, (uint8_t *)&r, 60-32); + //build_sb(); + + osmo_pbit2ubit(pdu_schf, (uint8_t *) &r, 32); + osmo_pbit2ubit(pdu_schf+32, (uint8_t *)&r, 60-32); + build_ndb_schf(); + } +#endif + + printf("total number of CRC Errors: %u\n", num_crc_err); + + exit(0); +} diff --git a/src/crc_test.c b/src/crc_test.c new file mode 100644 index 0000000..3bc6868 --- /dev/null +++ b/src/crc_test.c @@ -0,0 +1,73 @@ +/* (C) 2011 by Holger Hans Peter Freyther + * 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 "tetra_common.h" +#include + +int main(int argc, char **argv) +{ + uint8_t input1[] = { 0x01 }; + uint16_t crc; + + crc = crc16_itut_bytes(0x0, input1, 8); + printf("The CRC is now: %u/0x%x\n", crc, crc); + + crc = crc16_itut_bits(0x0, input1, 1); + printf("The CRC is now: %d/0x%x\n", crc, crc); + + crc = crc16_itut_bytes(0xFFFF, input1, 8); + printf("The CRC is now: %u/0x%x\n", crc, crc); + + crc = crc16_itut_bits(0xFFFF, input1, 1); + printf("The CRC is now: %d/0x%x\n", crc, crc); + + /* actual CRC-CCIT usage */ + uint8_t input2[] = { + 0, 0, 0, 1, 0, 0, 0, 0, + 1, 0, 1, 1, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 0, 1, 0, 0, 1, 1, + 0, 0, 1, 1, + + /* checksum */ + 1, 1, 0, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, 0, 0, 1, + /* tail bits */ + 0, 0, 0, 0 }; + + + crc = ~crc16_itut_bits(0xffff, input2, 60); + printf("The CRC is now: %d/0x%x\n", crc, crc); + + /* swap the bytes */ + crc = (crc << 8) | (crc >> 8); + osmo_pbit2ubit(&input2[60], (uint8_t *)&crc, 16); + + crc = crc16_itut_bits(0xFFFF, input2, 60 + 16); + printf("The CRC is now: %d/0x%x\n", crc, crc); + if (crc == 0x1d0f) + printf("Decoded successfully.\n"); + else + printf("Failed to decode.\n"); + + return 0; +} diff --git a/src/float_to_bits.c b/src/float_to_bits.c new file mode 100644 index 0000000..88cb2f0 --- /dev/null +++ b/src/float_to_bits.c @@ -0,0 +1,117 @@ +/* Convert floating point symbols in the range +3/-3 to hard bits, + * in the 1-bit-per-byte format */ + +/* (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 +#include + +static int process_sym_fl(float offset, float fl) +{ + int ret; + fl += offset; + + /* very simplistic scheme */ + if (fl > 2) + ret = 3; + else if (fl > 0) + ret = 1; + else if (fl < -2) + ret = -3; + else + ret = -1; + + return ret; +} + +static void sym_int2bits(int sym, uint8_t *ret) +{ + switch (sym) { + case -3: + ret[0] = 1; + ret[1] = 1; + break; + case 1: + ret[0] = 0; + ret[1] = 0; + break; + //case -1: + case 3: + ret[0] = 0; + ret[1] = 1; + break; + //case 3: + case -1: + ret[0] = 1; + ret[1] = 0; + break; + } +} + +int main(int argc, char **argv) +{ + int fd, fd_out; + + if (argc < 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(2); + } + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open infile"); + exit(1); + } + fd_out = creat(argv[2], 0660); + if (fd_out < 0) { + perror("open outfile"); + exit(1); + } + while (1) { + int rc; + float fl; + uint8_t bits[2]; + rc = read(fd, &fl, sizeof(fl)); + if (rc < 0) { + perror("read"); + exit(1); + } else if (rc == 0) + break; + rc = process_sym_fl(0.3f, fl); + sym_int2bits(rc, bits); + //printf("%2d %1u %1u %f\n", rc, bits[0], bits[1], fl); + //printf("%1u%1u", bits[0], bits[1]); + + rc = write(fd_out, bits, 2); + if (rc < 0) { + perror("write"); + exit(1); + } else if (rc == 0) + break; + } + exit(0); +} diff --git a/src/lower_mac/crc_simple.c b/src/lower_mac/crc_simple.c new file mode 100644 index 0000000..db7b82f --- /dev/null +++ b/src/lower_mac/crc_simple.c @@ -0,0 +1,106 @@ +/* CRC16 (most likely the ITU variant) working on plain bits as a trial + * to find out if simple padding to the right is going to be enough + */ +/* (C) 2011 by Holger Hans Peter Freyther + * 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 + +/** + * X.25 rec 2.2.7.4 Frame Check Sequence. This should be + * CRC ITU-T from the kernel or such. + */ +#define GEN_POLY 0x1021 + +uint16_t get_nth_bit(const uint8_t *input, int _bit) +{ + uint16_t val; + int byte = _bit / 8; + int bit = 7 - _bit % 8; + + val = (input[byte] & (1 << bit)) >> bit; + return val; +} + +/** + * This is mostly from http://en.wikipedia.org/wiki/Computation_of_CRC + * Code fragment 2. Due some stupidity it took longer to implement than + * it should have taken. + */ +uint16_t crc16_itut_bytes(uint16_t crc, const uint8_t *input, int number_bits) +{ + int i; + + for (i = 0; i < number_bits; ++i) { + uint16_t bit = get_nth_bit(input, i); + + crc ^= bit << 15; + if ((crc & 0x8000)) { + crc <<= 1; + crc ^= GEN_POLY; + } else { + crc <<= 1; + } + } + + return crc; +} + +uint16_t crc16_itut_bits(uint16_t crc, const uint8_t *input, int number_bits) +{ + int i; + + for (i = 0; i < number_bits; ++i) { + uint16_t bit = input[i] & 0x1; + + crc ^= bit << 15; + if ((crc & 0x8000)) { + crc <<= 1; + crc ^= GEN_POLY; + } else { + crc <<= 1; + } + } + + return crc; +} + +uint16_t crc16_itut_poly(uint16_t crc, uint32_t poly, const uint8_t *input, int number_bits) +{ + int i; + + for (i = 0; i < number_bits; ++i) { + uint16_t bit = input[i] & 0x1; + + crc ^= bit << 15; + if ((crc & 0x8000)) { + crc <<= 1; + crc ^= poly; + } else { + crc <<= 1; + } + } + + return crc; +} + +uint16_t crc16_ccitt_bits(uint8_t *bits, unsigned int len) +{ + return crc16_itut_bits(0xffff, bits, len); +} diff --git a/src/lower_mac/crc_simple.h b/src/lower_mac/crc_simple.h new file mode 100644 index 0000000..f4de41c --- /dev/null +++ b/src/lower_mac/crc_simple.h @@ -0,0 +1,48 @@ +/* (C) 2011 by Holger Hans Peter Freyther + * 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 . + * + */ + +#ifndef CRC_16 +#define CRC_16 + +#include + +/** + * Code to generate a CRC16-ITU-T as of the X.25 specification and + * compatible with Linux's implementations. At least the polynom is + * coming from the X.25 spec. + */ + +/** + * This is working of bits packed together. The high bits will be + * accessed first. If you only pass one bit the array needs to contain + * a 0xF0 instead of a 0x01. This interface is compatible with the + * normal CRC interface (besides the length). + */ +uint16_t crc16_itut_bytes(uint16_t crc, + const uint8_t *input, const int number_bits); + +/** + * Each byte contains one bit. Calculate the CRC16-ITU-T for it. + */ +uint16_t crc16_itut_bits(uint16_t crc, + const uint8_t *input, const int number_bits); + + +uint16_t crc16_ccitt_bits(uint8_t *bits, unsigned int len); + +#endif diff --git a/src/lower_mac/tetra_conv_enc.c b/src/lower_mac/tetra_conv_enc.c new file mode 100644 index 0000000..046d42a --- /dev/null +++ b/src/lower_mac/tetra_conv_enc.c @@ -0,0 +1,301 @@ +/* Tetra convolutional encoder, according to ETSI EN 300 392-2 V3.2.1 (2007-09) */ + +/* This converts from type type-2 bits into type-3 bits */ + +/* (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 +#include + +static char *dump_state(struct conv_enc_state *ces) +{ + static char pbuf[1024]; + snprintf(pbuf, sizeof(pbuf), "%u-%u-%u-%u", ces->delayed[0], + ces->delayed[1], ces->delayed[2], ces->delayed[3]); + return pbuf; +} + +/* Mother code according to Section 8.2.3.1.1 */ +static uint8_t conv_enc_in_bit(struct conv_enc_state *ces, uint8_t bit, uint8_t *out) +{ + uint8_t g1, g2, g3, g4; + uint8_t *delayed = ces->delayed; + + DEBUGP("State in: %s, Input bit: %u: ", dump_state(ces), bit); + + /* G1 = 1 + D + D4 */ + g1 = (bit + delayed[0] + delayed[3]) % 2; + /* G2 = 1 + D2 + D3 + D4 */ + g2 = (bit + delayed[1] + delayed[2] + delayed[3]) % 2; + /* G3 = 1 + D + D2 + D4 */ + g3 = (bit + delayed[0] + delayed[1] + delayed[3]) % 2; + /* G4 = 1 + D + D3 + D4 */ + g4 = (bit + delayed[0] + delayed[2] + delayed[3]) % 2; + + /* shift the state and input our new bit */ + ces->delayed[3] = ces->delayed[2]; + ces->delayed[2] = ces->delayed[1]; + ces->delayed[1] = ces->delayed[0]; + ces->delayed[0] = bit; + + DEBUGP("Output bits: %u-%u-%u-%u, State out: %s\n", g1, g2, g3, g4, + dump_state(ces)); + + *out++ = g1; + *out++ = g2; + *out++ = g3; + *out++ = g4; + + return (g1 | (g2 << 1) | (g3 << 2) | (g4 << 3)); +} + +/* in: bit-per-byte (len), out: bit-per-byte (4*len) */ +int conv_enc_input(struct conv_enc_state *ces, uint8_t *in, int len, uint8_t *out) +{ + int i; + + for (i = 0; i < len; i++) { + conv_enc_in_bit(ces, in[i], out); + out += 4; + } + return 0; +} + +int conv_enc_init(struct conv_enc_state *ces) +{ + memset(ces->delayed, 0, sizeof(ces->delayed)); + return 0; +} + +/* Puncturing */ + +const uint8_t P_rate2_3[] = { 0, 1, 2, 5 }; +const uint8_t P_rate1_3[] = { 0, 1, 2, 3, 5, 6, 7 }; + +struct puncturer { + enum tetra_rcpc_puncturer type; + const uint8_t *P; + uint8_t t; + uint32_t (*i_func)(uint32_t j); +}; + +static uint32_t i_func_equals(uint32_t j) +{ + return j; +} + +static uint32_t i_func_292(uint32_t j) +{ + return (j + ((j-1)/65)); +} + +static uint32_t i_func_148(uint32_t j) +{ + return (j + ((j-1)/35)); +} + +/* Section 8.2.3.1.3 */ +static const struct puncturer punct_2_3 = { + .type = TETRA_RCPC_PUNCT_2_3, + .P = P_rate2_3, + .t = 3, + .i_func = &i_func_equals, +}; + +/* Section 8.2.3.1.4 */ +static const struct puncturer punct_1_3 = { + .type = TETRA_RCPC_PUNCT_1_3, + .P = P_rate1_3, + .t = 6, + .i_func = &i_func_equals, +}; + +/* Section 8.2.3.1.5 */ +static const struct puncturer punct_292_432 = { + .type = TETRA_RCPC_PUNCT_292_432, + .P = P_rate2_3, + .t = 3, + .i_func = &i_func_292, +}; + +/* Section 8.2.3.1.6 */ +static const struct puncturer punct_148_432 = { + .type = TETRA_RCPC_PUNCT_148_432, + .P = P_rate1_3, + .t = 6, + .i_func = &i_func_148, +}; + +static const struct puncturer *tetra_puncts[] = { + [TETRA_RCPC_PUNCT_2_3] = &punct_2_3, + [TETRA_RCPC_PUNCT_1_3] = &punct_1_3, + [TETRA_RCPC_PUNCT_292_432] = &punct_292_432, + [TETRA_RCPC_PUNCT_148_432] = &punct_148_432, +}; + +/* Puncture the mother code (in) and write 'len' symbols to out */ +int get_punctured_rate(enum tetra_rcpc_puncturer pu, uint8_t *in, int len, uint8_t *out) +{ + const struct puncturer *punct; + uint32_t i, j, k; + uint8_t t; + const uint8_t *P; + + if (pu >= ARRAY_SIZE(tetra_puncts)) + return -EINVAL; + + punct = tetra_puncts[pu]; + t = punct->t; + P = punct->P; + + /* Section 8.2.3.1.2 */ + for (j = 1; j <= len; j++) { + i = punct->i_func(j); + k = 8 * ((i-1)/t) + P[i - t*((i-1)/t)]; + DEBUGP("j = %u, i = %u, k = %u\n", j, i, k); + out[j-1] = in[k-1]; + } + return 0; +} + +/* De-Puncture the 'len' type-3 bits (in) and write mother code to out */ +int tetra_rcpc_depunct(enum tetra_rcpc_puncturer pu, const uint8_t *in, int len, uint8_t *out) +{ + const struct puncturer *punct; + uint32_t i, j, k; + uint8_t t; + const uint8_t *P; + + if (pu >= ARRAY_SIZE(tetra_puncts)) + return -EINVAL; + + punct = tetra_puncts[pu]; + t = punct->t; + P = punct->P; + + /* Section 8.2.3.1.2 */ + for (j = 1; j <= len; j++) { + i = punct->i_func(j); + k = 8 * ((i-1)/t) + P[i - t*((i-1)/t)]; + DEBUGP("j = %u, i = %u, k = %u\n", j, i, k); + out[k-1] = in[j-1]; + } + return 0; +} + +struct punct_test_param { + uint16_t type2_len; + uint16_t type3_len; + enum tetra_rcpc_puncturer punct; +}; + +static const struct punct_test_param punct_test_params[] = { + { 80, 120, TETRA_RCPC_PUNCT_2_3 }, /* BSCH */ + { 292, 432, TETRA_RCPC_PUNCT_292_432 }, /* TCH/4.8 */ + { 148, 432, TETRA_RCPC_PUNCT_148_432 }, /* TCH/2.4 */ + { 144, 216, TETRA_RCPC_PUNCT_2_3 }, /* SCH/HD, BNCH, STCH */ + { 112, 168, TETRA_RCPC_PUNCT_2_3 }, /* SCH/HU */ + { 288, 432, TETRA_RCPC_PUNCT_2_3 }, /* SCH/F */ +}; + +static int mother_memcmp(const uint8_t *mother, const uint8_t *depunct, int len) +{ + unsigned int i, equal = 0; + + for (i = 0; i < len; i++) { + /* ignore any 0xff-initialized part */ + if (depunct[i] == 0xff) + continue; + if (depunct[i] != mother[i]) + return -1; + equal++; + } + + return equal; +} + +static int test_one_punct(const struct punct_test_param *ptp) +{ + uint8_t *mother_buf; + uint8_t *depunct_buf; + uint8_t *type3_buf; + int i, mother_len; + + printf("==> Testing Puncture/Depuncture mode %u (%u/%u)\n", + ptp->punct, ptp->type2_len, ptp->type3_len); + + mother_len = ptp->type2_len*4; + mother_buf = malloc(mother_len); + depunct_buf = malloc(ptp->type2_len*4); + type3_buf = malloc(ptp->type3_len); + + /* initialize mother buffer with sequence of bytes starting at 0 */ + for (i = 0; i < mother_len; i++) + mother_buf[i] = i & 0xff; + + /* puncture the mother_buf to type3_buf using rate 2/3 on 60 bits */ + get_punctured_rate(ptp->punct, mother_buf, ptp->type3_len, type3_buf); + + /* initialize the de-punctured buffer */ + memset(depunct_buf, 0xff, mother_len); + + /* de-puncture into the depunct_buf (i.e. what happens at the receiver) */ + tetra_rcpc_depunct(ptp->punct, type3_buf, ptp->type3_len, depunct_buf); + + DEBUGP("MOTH: %s\n", hexdump(mother_buf, mother_len)); + DEBUGP("PUNC: %s\n", hexdump(type3_buf, ptp->type3_len)); + DEBUGP("DEPU: %s\n", hexdump(depunct_buf, mother_len)); + + i = mother_memcmp(mother_buf, depunct_buf, mother_len); + if (i < 0) { + fprintf(stderr, "Mother buf != Depunct buf\n"); + return i; + } else if (i != ptp->type3_len) { + fprintf(stderr, "Depunct buf only has %u equal symbols, we need %u\n", + i, ptp->type3_len); + return -EINVAL; + } + + free(type3_buf); + free(depunct_buf); + free(mother_buf); + + return 0; +} + +int tetra_punct_test(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(punct_test_params); i++) { + rc = test_one_punct(&punct_test_params[i]); + if (rc < 0) + return rc; + } + + return 0; +} diff --git a/src/lower_mac/tetra_conv_enc.h b/src/lower_mac/tetra_conv_enc.h new file mode 100644 index 0000000..28c73be --- /dev/null +++ b/src/lower_mac/tetra_conv_enc.h @@ -0,0 +1,33 @@ +#ifndef TETRA_CONV_ENC_H +#define TETRA_CONV_ENC_H + +#include + +struct conv_enc_state { + uint8_t delayed[4]; +}; + + +/* in: one-bit-per-byte out: 4bit-per-byte, both 'len' long */ +int conv_enc_input(struct conv_enc_state *ces, uint8_t *in, int len, uint8_t *out); + +int conv_enc_init(struct conv_enc_state *ces); + +enum tetra_rcpc_puncturer { + TETRA_RCPC_PUNCT_2_3, + TETRA_RCPC_PUNCT_1_3, + TETRA_RCPC_PUNCT_292_432, + TETRA_RCPC_PUNCT_148_432, +}; + +/* Puncture the mother code (in) and write 'len' symbols to out */ +int get_punctured_rate(enum tetra_rcpc_puncturer pu, uint8_t *in, int len, uint8_t *out); + + +/* De-Puncture the 'len' type-3 bits (in) and write mother code to out */ +int tetra_rcpc_depunct(enum tetra_rcpc_puncturer pu, const uint8_t *in, int len, uint8_t *out); + +/* Self-test the puncturing/de-puncturing */ +int tetra_punct_test(void); + +#endif /* TETRA_CONV_ENC_H */ diff --git a/src/lower_mac/tetra_interleave.c b/src/lower_mac/tetra_interleave.c new file mode 100644 index 0000000..b4e55c1 --- /dev/null +++ b/src/lower_mac/tetra_interleave.c @@ -0,0 +1,59 @@ +/* Tetra block interleaver, according to ETSI EN 300 392-2 V3.2.1 (2007-09) */ + +/* This converts from type type-3 bits into type-4 bits (and vice versa) */ + +/* (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 + +/* Section 8.2.4.1 Block interleaving for phase modulation */ +static uint32_t block_interl_func(uint32_t K, uint32_t a, uint32_t i) +{ + return (1 + ( (a * i) % K)); +} + +void block_interleave(uint32_t K, uint32_t a, const uint8_t *in, uint8_t *out) +{ + int i; + for (i = 1; i <= K; i++) { + uint32_t k = block_interl_func(K, a, i); + DEBUGP("interl: i=%u, k=%u\n", i, k); + out[k-1] = in[i-1]; + } +} + +void block_deinterleave(uint32_t K, uint32_t a, const uint8_t *in, uint8_t *out) +{ + int i; + for (i = 1; i <= K; i++) { + uint32_t k = block_interl_func(K, a, i); + DEBUGP("deinterl: i=%u, k=%u\n", i, k); + out[i-1] = in[k-1]; + } +} diff --git a/src/lower_mac/tetra_interleave.h b/src/lower_mac/tetra_interleave.h new file mode 100644 index 0000000..e08b80a --- /dev/null +++ b/src/lower_mac/tetra_interleave.h @@ -0,0 +1,12 @@ +#ifndef TETRA_INTERLEAVE_H +#define TETRA_INTERLEAVE_H +/* Tetra block interleaver, according to ETSI EN 300 392-2 V3.2.1 (2007-09) */ + +/* This converts from type type-3 bits into type-4 bits (and vice versa) */ + +#include + +void block_interleave(uint32_t K, uint32_t a, const uint8_t *in, uint8_t *out); +void block_deinterleave(uint32_t K, uint32_t a, const uint8_t *in, uint8_t *out); + +#endif /* TETRA_INTERLEAVE_H */ diff --git a/src/lower_mac/tetra_lower_mac.c b/src/lower_mac/tetra_lower_mac.c new file mode 100644 index 0000000..ab00866 --- /dev/null +++ b/src/lower_mac/tetra_lower_mac.c @@ -0,0 +1,257 @@ +/* TETRA lower MAC layer main routine, between TP-SAP and TMV-SAP */ + +/* (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 +#include +#include +#include +#include +#include +#include +#include +#include "tetra_upper_mac.h" +#include + + +struct tetra_blk_param { + const char *name; + uint16_t type345_bits; + uint16_t type2_bits; + uint16_t type1_bits; + uint16_t interleave_a; + uint8_t have_crc16; +}; + +/* try to aggregate all of the magic numbers somewhere central */ +static const struct tetra_blk_param tetra_blk_param[] = { + [TPSAP_T_SB1] = { + .name = "SB1", + .type345_bits = 120, + .type2_bits = 80, + .type1_bits = 60, + .interleave_a = 11, + .have_crc16 = 1, + }, + [TPSAP_T_SB2] = { + .name = "SB2", + .type345_bits = 216, + .type2_bits = 144, + .type1_bits = 124, + .interleave_a = 101, + .have_crc16 = 1, + }, + [TPSAP_T_NDB] = { + .name = "NDB", + .type345_bits = 216, + .type2_bits = 144, + .type1_bits = 124, + .interleave_a = 101, + .have_crc16 = 1, + }, + [TPSAP_T_SCH_HU] = { + .name = "SCH/HU", + .type345_bits = 168, + .type2_bits = 112, + .type1_bits = 92, + .interleave_a = 13, + .have_crc16 = 1, + }, + [TPSAP_T_SCH_F] = { + .name = "SCH/F", + .type345_bits = 432, + .type2_bits = 288, + .type1_bits = 268, + .interleave_a = 103, + .have_crc16 = 1, + }, + [TPSAP_T_BBK] = { + .name = "BBK", + .type345_bits = 30, + .type2_bits = 30, + .type1_bits = 14, + }, +}; + +struct tetra_cell_data { + uint16_t mcc; + uint16_t mnc; + uint8_t colour_code; + struct tetra_tdma_time time; + + uint32_t scramb_init; +}; + +static struct tetra_cell_data _tcd, *tcd = &_tcd; + +int is_bsch(struct tetra_tdma_time *tm) +{ + if (tm->fn == 18 && tm->tn == 4 - ((tm->mn+1)%4)) + return 1; + return 0; +} + +int is_bnch(struct tetra_tdma_time *tm) +{ + if (tm->fn == 18 && tm->tn == 4 - ((tm->mn+3)%4)) + return 1; + return 0; +} + +struct tetra_tmvsap_prim *tmvsap_prim_alloc(uint16_t prim, uint8_t op) +{ + struct tetra_tmvsap_prim *ttp; + + //ttp = talloc_zero(NULL, struct tetra_tmvsap_prim); + ttp = calloc(1, sizeof(struct tetra_tmvsap_prim)); + ttp->oph.sap = TETRA_SAP_TMV; + ttp->oph.primitive = prim; + ttp->oph.operation = op; + + return ttp; +} + +/* incoming TP-SAP UNITDATA.ind from PHY into lower MAC */ +void tp_sap_udata_ind(enum tp_sap_data_type type, const uint8_t *bits, unsigned int len, void *priv) +{ + /* various intermediary buffers */ + uint8_t type4[512]; + uint8_t type3dp[512*4]; + uint8_t type3[512]; + uint8_t type2[512]; + + const struct tetra_blk_param *tbp = &tetra_blk_param[type]; + const char *time_str; + + /* TMV-SAP.UNITDATA.ind primitive which we will send to the upper MAC */ + struct tetra_tmvsap_prim *ttp; + struct tmv_unitdata_param *tup; + + ttp = tmvsap_prim_alloc(PRIM_TMV_UNITDATA, PRIM_OP_INDICATION); + tup = &ttp->u.unitdata; + + /* update the cell time */ + memcpy(&tcd->time, &t_phy_state.time, sizeof(tcd->time)); + time_str = tetra_tdma_time_dump(&tcd->time); + + if (type == TPSAP_T_SB2 && is_bnch(&tcd->time)) { + tup->lchan = TETRA_LC_BNCH; + printf("BNCH FOLLOWS\n"); + } + + printf("%s %s type5: %s\n", tbp->name, tetra_tdma_time_dump(&tcd->time), + bitdump(bits, tbp->type345_bits)); + + /* De-scramble, pay special attention to SB1 pre-defined scrambling */ + memcpy(type4, bits, tbp->type345_bits); + if (type == TPSAP_T_SB1) { + tetra_scramb_bits(SCRAMB_INIT, type4, tbp->type345_bits); + tup->scrambling_code = SCRAMB_INIT; + } else { + tetra_scramb_bits(tcd->scramb_init, type4, tbp->type345_bits); + tup->scrambling_code = tcd->scramb_init; + } + + printf("%s %s type4: %s\n", tbp->name, time_str, + bitdump(type4, tbp->type345_bits)); + + if (tbp->interleave_a) { + /* Run block deinterleaving: type-3 bits */ + block_deinterleave(tbp->type345_bits, tbp->interleave_a, type4, type3); + printf("%s %s type3: %s\n", tbp->name, time_str, + bitdump(type3, tbp->type345_bits)); + /* De-puncture */ + memset(type3dp, 0xff, sizeof(type3dp)); + tetra_rcpc_depunct(TETRA_RCPC_PUNCT_2_3, type3, tbp->type345_bits, type3dp); + printf("%s %s type3dp: %s\n", tbp->name, time_str, + bitdump(type3dp, tbp->type2_bits*4)); + viterbi_dec_sb1_wrapper(type3dp, type2, tbp->type2_bits); + printf("%s %s type2: %s\n", tbp->name, time_str, + bitdump(type2, tbp->type2_bits)); + } + + if (tbp->have_crc16) { + uint16_t crc = crc16_ccitt_bits(type2, tbp->type1_bits+16); + printf("CRC COMP: 0x%04x ", crc); + if (crc == TETRA_CRC_OK) { + printf("OK\n"); + tup->crc_ok = 1; + printf("%s %s type1: %s\n", tbp->name, time_str, + bitdump(type2, tbp->type1_bits)); + } else + printf("WRONG\n"); + } + + memcpy(tup->mac_block, type2, tbp->type1_bits); + tup->mac_block_len = tbp->type1_bits; + + switch (type) { + case TPSAP_T_SB1: + printf("TMB-SAP SYNC CC %s(0x%02x) ", bitdump(type2+4, 6), bits_to_uint(type2+4, 6)); + printf("TN %s(%u) ", bitdump(type2+10, 2), bits_to_uint(type2+10, 2)); + printf("FN %s(%2u) ", bitdump(type2+12, 5), bits_to_uint(type2+12, 5)); + printf("MN %s(%2u) ", bitdump(type2+17, 6), bits_to_uint(type2+17, 6)); + printf("MCC %s(%u) ", bitdump(type2+31, 10), bits_to_uint(type2+31, 10)); + printf("MNC %s(%u)\n", bitdump(type2+41, 14), bits_to_uint(type2+41, 14)); + /* obtain information from SYNC PDU */ + tcd->colour_code = bits_to_uint(type2+4, 6); + tcd->time.tn = bits_to_uint(type2+10, 2); + tcd->time.fn = bits_to_uint(type2+12, 5); + tcd->time.mn = bits_to_uint(type2+17, 6); + tcd->mcc = bits_to_uint(type2+31, 10); + tcd->mnc = bits_to_uint(type2+41, 14); + /* compute the scrambling code for the current cell */ + tcd->scramb_init = tetra_scramb_get_init(tcd->mcc, tcd->mnc, tcd->colour_code); + /* update the PHY layer time */ + memcpy(&t_phy_state.time, &tcd->time, sizeof(t_phy_state.time)); + tup->lchan = TETRA_LC_BSCH; + break; + case TPSAP_T_SB2: + case TPSAP_T_NDB: + /* FIXME: do something */ + break; + case TPSAP_T_BBK: + /* FIXME: RM3014-decode */ + tup->crc_ok = 1; + memcpy(tup->mac_block, type4, tbp->type1_bits); + printf("%s %s type1: %s\n", tbp->name, time_str, bitdump(tup->mac_block, tbp->type1_bits)); + tup->lchan = TETRA_LC_AACH; + break; + case TPSAP_T_SCH_F: + tup->lchan = TETRA_LC_SCH_F; + break; + default: + /* FIXME: do something */ + break; + } + /* send Rx time along with the TMV-UNITDATA.ind primitive */ + memcpy(&tup->tdma_time, &tcd->time, sizeof(tup->tdma_time)); + + upper_mac_prim_recv(&ttp->oph, NULL); +} + + diff --git a/src/lower_mac/tetra_rm3014.c b/src/lower_mac/tetra_rm3014.c new file mode 100644 index 0000000..a52de09 --- /dev/null +++ b/src/lower_mac/tetra_rm3014.c @@ -0,0 +1,96 @@ +/* Shortened (30,14) Reed-Muller (RM) code */ + +/* (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 + +/* Generator matrix from Section 8.2.3.2 */ + +static const uint8_t rm_30_14_gen[14][16] = { + { 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 }, + { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0 }, + { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1 }, + { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1 }, + { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1 }, + { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1 }, + { 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1 }, + { 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1 } +}; + +static uint32_t rm_30_14_rows[14]; + + +static uint32_t shift_bits_together(const uint8_t *bits, int len) +{ + uint32_t ret = 0; + int i; + + for (i = len-1; i >= 0; i--) + ret |= bits[i] << (len-1-i); + + return ret; +} + +void tetra_rm3014_init(void) +{ + int i; + uint32_t val; + + for (i = 0; i < 14; i++) { + /* upper 14 bits identity matrix */ + val = (1 << (16+13 - i)); + /* lower 16 bits from rm_30_14_gen */ + val |= shift_bits_together(rm_30_14_gen[i], 16); + rm_30_14_rows[i] = val; + printf("rm_30_14_rows[%u] = 0x%08x\n", i, val); + } +} + +uint32_t tetra_rm3014_compute(const uint16_t in) +{ + int i; + uint32_t val = 0; + + for (i = 0; i < 14; i++) { + uint32_t bit = (in >> (14-1-i)) & 1; + if (bit) + val ^= rm_30_14_rows[i]; + /* we can skip the 'else' as XOR with 0 has no effect */ + } + return val; +} + +/** + * This is a systematic code. We can remove the control bits + * and then check for an error. Maybe correct it in the future. + */ +int tetra_rm3014_decode(const uint32_t inp, uint16_t *out) +{ + *out = inp >> 16; + return 0; +} diff --git a/src/lower_mac/tetra_rm3014.h b/src/lower_mac/tetra_rm3014.h new file mode 100644 index 0000000..c6fc304 --- /dev/null +++ b/src/lower_mac/tetra_rm3014.h @@ -0,0 +1,16 @@ +#ifndef TETRA_RM3014_H +#define TETRA_RM3014_H + +#include + +void tetra_rm3014_init(void); +uint32_t tetra_rm3014_compute(const uint16_t in); + +/** + * Decode @param inp to @param out and return if there was + * an error in the input. In the future this should correct + * the error or such. + */ +int tetra_rm3013_decode(const uint32_t inp, uint16_t *out); + +#endif diff --git a/src/lower_mac/tetra_scramb.c b/src/lower_mac/tetra_scramb.c new file mode 100644 index 0000000..c83165a --- /dev/null +++ b/src/lower_mac/tetra_scramb.c @@ -0,0 +1,99 @@ +/* TETRA scrambling according to Section 8.2.5 of EN 300 392-2 V3.2.1 */ + +/* This converts from type-4 to type-5 bits (and vice versa) */ + +/* (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 + +/* Tap macro for the standard XOR / Fibonacci form */ +#define ST(x, y) ((x) >> (32-y)) + +/* Tap macro and constant for the Galois form */ +#define GL(x) (1<<(x-1)) +#define GALOIS_LFSR (GL(32)|GL(26)|GL(23)|GL(22)|GL(16)|GL(12)|GL(11)|GL(10)|GL(8)|GL(7)|GL(5)|GL(4)|GL(2)|GL(1)) + +#if 1 +static uint8_t next_lfsr_bit(uint32_t *lf) +{ + uint32_t lfsr = *lf; + uint32_t bit; + + /* taps: 32 26 23 22 16 12 11 10 8 7 5 4 2 1 */ + bit = (ST(lfsr, 32) ^ ST(lfsr, 26) ^ ST(lfsr, 23) ^ ST(lfsr, 22) ^ + ST(lfsr, 16) ^ ST(lfsr, 12) ^ ST(lfsr, 11) ^ ST(lfsr, 10) ^ + ST(lfsr, 8) ^ ST(lfsr, 7) ^ ST(lfsr, 5) ^ ST(lfsr, 4) ^ + ST(lfsr, 2) ^ ST(lfsr, 1)) & 1; + lfsr = (lfsr >> 1) | (bit << 31); + + /* update the caller's LFSR state */ + *lf = lfsr; + + return bit & 0xff; +} +#else +/* WARNING: this version somehow does not produce the desired result! */ +static uint8_t next_lfsr_bit(uint32_t *lf) +{ + uint32_t lfsr = *lf; + uint32_t bit = lfsr & 1; + + lfsr = (lfsr >> 1) ^ (uint32_t)(0 - ((lfsr & 1u) & GALOIS_LFSR)); + + *lf = lfsr; + + return bit; +} +#endif + +int tetra_scramb_get_bits(uint32_t lfsr_init, uint8_t *out, int len) +{ + int i; + + for (i = 0; i < len; i++) + out[i] = next_lfsr_bit(&lfsr_init); + + return 0; +} + +/* XOR the bitstring at 'out/len' using the TETRA scrambling LFSR */ +int tetra_scramb_bits(uint32_t lfsr_init, uint8_t *out, int len) +{ + int i; + + for (i = 0; i < len; i++) + out[i] ^= next_lfsr_bit(&lfsr_init); + + return 0; +} + +uint32_t tetra_scramb_get_init(uint16_t mcc, uint16_t mnc, uint8_t colour) +{ + uint32_t scramb_init; + + mcc &= 0x3ff; + mnc &= 0x3fff; + colour &= 0x3f; + + scramb_init = colour | (mnc << 6) | (mcc << 20); + scramb_init = (scramb_init << 2) | SCRAMB_INIT; + + return scramb_init; +} diff --git a/src/lower_mac/tetra_scramb.h b/src/lower_mac/tetra_scramb.h new file mode 100644 index 0000000..9729ac2 --- /dev/null +++ b/src/lower_mac/tetra_scramb.h @@ -0,0 +1,23 @@ +#ifndef TETRA_SCRAMB_H +#define TETRA_SCRAMB_H +/* TETRA scrambling according to Section 8.2.5 of EN 300 392-2 V3.2.1 */ + +/* This converts from type-4 to type-5 bits (and vice versa) */ + +#include + +/* Section 8.2.5.2: + * For scrambling of BSCH, all bits e(1) to e(30) shall equal to zero + * p(k) = e(1-k) for k = -29, ... 0 + * p(k) = 1 for k = -31, -30 + */ +#define SCRAMB_INIT 3 + +uint32_t tetra_scramb_get_init(uint16_t mcc, uint16_t mnc, uint8_t colour); + +int tetra_scramb_get_bits(uint32_t lfsr_init, uint8_t *out, int len); + +/* XOR the bitstring at 'out/len' using the TETRA scrambling LFSR */ +int tetra_scramb_bits(uint32_t lfsr_init, uint8_t *out, int len); + +#endif /* TETRA_SCRAMB_H */ diff --git a/src/lower_mac/viterbi.c b/src/lower_mac/viterbi.c new file mode 100644 index 0000000..86aff77 --- /dev/null +++ b/src/lower_mac/viterbi.c @@ -0,0 +1,25 @@ +#include +#include + +#include + +void viterbi_dec_sb1_wrapper(const uint8_t *in, uint8_t *out, unsigned int sym_count) +{ + int8_t vit_inp[864*4]; + int i; + + for (i = 0; i < sym_count*4; i++) { + switch (in[i]) { + case 0: + vit_inp[i] = 127; + break; + case 0xff: + vit_inp[i] = 0; + break; + default: + vit_inp[i] = -127; + break; + } + } + conv_cch_decode(vit_inp, out, sym_count); +} diff --git a/src/lower_mac/viterbi.h b/src/lower_mac/viterbi.h new file mode 100644 index 0000000..ebe519a --- /dev/null +++ b/src/lower_mac/viterbi.h @@ -0,0 +1,6 @@ +#ifndef VITERBI_H +#define VITERBI_H + +void viterbi_dec_sb1_wrapper(const uint8_t *in, uint8_t *out, unsigned int sym_count); + +#endif /* VITERBI_H */ diff --git a/src/lower_mac/viterbi_cch.c b/src/lower_mac/viterbi_cch.c new file mode 100644 index 0000000..8ef3618 --- /dev/null +++ b/src/lower_mac/viterbi_cch.c @@ -0,0 +1,163 @@ +/* (C) 2011 by Sylvain Munaut + * + * 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 + + +#define CONV_CCH_N 4 +#define CONV_CCH_K 5 +#define CONV_CCH_N_STATES (1<<(CONV_CCH_K-1)) + +#define MAX_AE 0x00ffffff + + +static const uint8_t conv_cch_next_output[CONV_CCH_N_STATES][2] = { + { 0, 15 }, { 11, 4 }, { 6, 9 }, { 13, 2 }, + { 5, 10 }, { 14, 1 }, { 3, 12 }, { 8, 7 }, + { 15, 0 }, { 4, 11 }, { 9, 6 }, { 2, 13 }, + { 10, 5 }, { 1, 14 }, { 12, 3 }, { 7, 8 }, +}; + +static const uint8_t conv_cch_next_state[CONV_CCH_N_STATES][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + + +int conv_cch_encode(uint8_t *input, uint8_t *output, int n) +{ + uint8_t state; + int i; + + state = 0; + + for (i=0; i> 3) & 1; + output[(i<<2)+1] = (out >> 2) & 1; + output[(i<<2)+2] = (out >> 1) & 1; + output[(i<<2)+3] = out & 1; + } + + return 0; +} + +int conv_cch_decode(int8_t *input, uint8_t *output, int n) +{ + int i, s, b; + unsigned int ae[CONV_CCH_N_STATES]; + unsigned int ae_next[CONV_CCH_N_STATES]; + int8_t in_sym[CONV_CCH_N]; + int8_t ev_sym[CONV_CCH_N]; + int state_history[CONV_CCH_N_STATES][n]; + int min_ae; + int min_state; + int cur_state; + + /* Initial error (only state 0 is valid) */ + ae[0] = 0; + for (i=1; i> 3) & 1 ? -127 : 127; + ev_sym[1] = (out >> 2) & 1 ? -127 : 127; + ev_sym[2] = (out >> 1) & 1 ? -127 : 127; + ev_sym[3] = out & 1 ? -127 : 127; + + /* New error for this path */ + #define DIFF(x,y) (((x-y)*(x-y)) >> 9) + nae = ae[s] + \ + DIFF(ev_sym[0], in_sym[0]) + \ + DIFF(ev_sym[1], in_sym[1]) + \ + DIFF(ev_sym[2], in_sym[2]) + \ + DIFF(ev_sym[3], in_sym[3]); + + /* Is it survivor */ + if (ae_next[state] > nae) { + ae_next[state] = nae; + state_history[state][i+1] = s; + } + } + } + + /* Copy accumulated error */ + memcpy(ae, ae_next, sizeof(int) * CONV_CCH_N_STATES); + } + + /* Find state with least error */ + min_ae = MAX_AE; + min_state = -1; + + for (s=0; s= 0; i--) + { + min_state = cur_state; + cur_state = state_history[cur_state][i+1]; + if (conv_cch_next_state[cur_state][0] == min_state) + output[i] = 0; + else + output[i] = 1; + } + + return 0; +} diff --git a/src/lower_mac/viterbi_cch.h b/src/lower_mac/viterbi_cch.h new file mode 100644 index 0000000..2045251 --- /dev/null +++ b/src/lower_mac/viterbi_cch.h @@ -0,0 +1,7 @@ +#ifndef VITERBI_CCH_H +#define VITERBI_CCH_H + +int conv_cch_encode(uint8_t *input, uint8_t *output, int n); +int conv_cch_decode(int8_t *input, uint8_t *output, int n); + +#endif /* VITERBI_CCH_H */ diff --git a/src/lower_mac/viterbi_tch.c b/src/lower_mac/viterbi_tch.c new file mode 100644 index 0000000..382330e --- /dev/null +++ b/src/lower_mac/viterbi_tch.c @@ -0,0 +1,159 @@ +/* (C) 2011 by Sylvain Munaut + * + * 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 + + +#define CONV_TCH_N 3 +#define CONV_TCH_K 5 +#define CONV_TCH_N_STATES (1<<(CONV_TCH_K-1)) + +#define MAX_AE 0x00ffffff + + +static const uint8_t conv_tch_next_output[CONV_TCH_N_STATES][2] = { + { 0, 7 }, { 6, 1 }, { 5, 2 }, { 3, 4 }, + { 6, 1 }, { 0, 7 }, { 3, 4 }, { 5, 2 }, + { 7, 0 }, { 1, 6 }, { 2, 5 }, { 4, 3 }, + { 1, 6 }, { 7, 0 }, { 4, 3 }, { 2, 5 }, +}; + +static const uint8_t conv_tch_next_state[CONV_TCH_N_STATES][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + + +int conv_tch_encode(uint8_t *input, uint8_t *output, int n) +{ + uint8_t state; + int i; + + state = 0; + + for (i=0; i> 2) & 1; + output[(i<<2)+1] = (out >> 1) & 1; + output[(i<<2)+2] = out & 1; + } + + return 0; +} + +int conv_tch_decode(int8_t *input, uint8_t *output, int n) +{ + int i, s, b; + unsigned int ae[CONV_TCH_N_STATES]; + unsigned int ae_next[CONV_TCH_N_STATES]; + int8_t in_sym[CONV_TCH_N]; + int8_t ev_sym[CONV_TCH_N]; + int state_history[CONV_TCH_N_STATES][n]; + int min_ae; + int min_state; + int cur_state; + + /* Initial error (only state 0 is valid) */ + ae[0] = 0; + for (i=1; i> 2) & 1 ? -127 : 127; + ev_sym[1] = (out >> 1) & 1 ? -127 : 127; + ev_sym[2] = out & 1 ? -127 : 127; + + /* New error for this path */ + #define DIFF(x,y) (((x-y)*(x-y)) >> 9) + nae = ae[s] + \ + DIFF(ev_sym[0], in_sym[0]) + \ + DIFF(ev_sym[1], in_sym[1]) + \ + DIFF(ev_sym[2], in_sym[2]); + + /* Is it survivor */ + if (ae_next[state] > nae) { + ae_next[state] = nae; + state_history[state][i+1] = s; + } + } + } + + /* Copy accumulated error */ + memcpy(ae, ae_next, sizeof(int) * CONV_TCH_N_STATES); + } + + /* Find state with least error */ + min_ae = MAX_AE; + min_state = -1; + + for (s=0; s= 0; i--) + { + min_state = cur_state; + cur_state = state_history[cur_state][i+1]; + if (conv_tch_next_state[cur_state][0] == min_state) + output[i] = 0; + else + output[i] = 1; + } + + return 0; +} diff --git a/src/lower_mac/viterbi_tch.h b/src/lower_mac/viterbi_tch.h new file mode 100644 index 0000000..59c5966 --- /dev/null +++ b/src/lower_mac/viterbi_tch.h @@ -0,0 +1,7 @@ +#ifndef VITERBI_TCH_H +#define VITERBI_TCH_H + +int conv_tch_encode(uint8_t *input, uint8_t *output, int n); +int conv_tch_decode(int8_t *input, uint8_t *output, int n); + +#endif /* VITERBI_TCH_H */ diff --git a/src/osmo_prim.h b/src/osmo_prim.h new file mode 100644 index 0000000..fc22714 --- /dev/null +++ b/src/osmo_prim.h @@ -0,0 +1,19 @@ +#ifndef OSMO_PRIMITIVE_H +#define OSMO_PRIMITIVE_H + +#include + +enum osmo_prim_operation { + PRIM_OP_REQUEST, + PRIM_OP_RESPONSE, + PRIM_OP_INDICATION, + PRIM_OP_CONFIRM, +}; + +struct osmo_prim_hdr { + uint16_t sap; + uint16_t primitive; + enum osmo_prim_operation operation; +}; + +#endif diff --git a/src/phy/tetra_burst.c b/src/phy/tetra_burst.c new file mode 100644 index 0000000..62efb72 --- /dev/null +++ b/src/phy/tetra_burst.c @@ -0,0 +1,345 @@ +/* Implementation of TETRA Physical Layer, i.e. what is _below_ + * CRC, FEC, Interleaving and Scrambling */ + +/* (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 + +#define DQPSK4_BITS_PER_SYM 2 + +#define SB_BLK1_OFFSET ((6+1+40)*DQPSK4_BITS_PER_SYM) +#define SB_BBK_OFFSET ((6+1+40+60+19)*DQPSK4_BITS_PER_SYM) +#define SB_BLK2_OFFSET ((6+1+40+60+19+15)*DQPSK4_BITS_PER_SYM) + +#define SB_BLK1_BITS (60*DQPSK4_BITS_PER_SYM) +#define SB_BBK_BITS (15*DQPSK4_BITS_PER_SYM) +#define SB_BLK2_BITS (108*DQPSK4_BITS_PER_SYM) + +#define NDB_BLK1_OFFSET ((5+1+1)*DQPSK4_BITS_PER_SYM) +#define NDB_BBK1_OFFSET ((5+1+1+108)*DQPSK4_BITS_PER_SYM) +#define NDB_BBK2_OFFSET ((5+1+1+108+7+11)*DQPSK4_BITS_PER_SYM) +#define NDB_BLK2_OFFSET ((5+1+1+108+7+11+8)*DQPSK4_BITS_PER_SYM) + +#define NDB_BBK1_BITS (7*DQPSK4_BITS_PER_SYM) +#define NDB_BBK2_BITS (8*DQPSK4_BITS_PER_SYM) +#define NDB_BLK_BITS (108*DQPSK4_BITS_PER_SYM) +#define NDB_BBK_BITS SB_BBK_BITS + + +/* 9.4.4.3.1 Frequency Correction Field */ +static const uint8_t f_bits[80] = { + /* f1 .. f8 = 1 */ + 1, 1, 1, 1, 1, 1, 1, 1, + /* f73..f80 = 1*/ + [72] = 1, [73] = 1, [74] = 1, [75] = 1, + [76] = 1, [77] = 1, [78] = 1, [79] = 1 }; + +/* 9.4.4.3.2 Normal Training Sequence */ +static const uint8_t n_bits[22] = { 1,1, 0,1, 0,0, 0,0, 1,1, 1,0, 1,0, 0,1, 1,1, 0,1, 0,0 }; +static const uint8_t p_bits[22] = { 0,1, 1,1, 1,0, 1,0, 0,1, 0,0, 0,0, 1,1, 0,1, 1,1, 1,0 }; +static const uint8_t q_bits[22] = { 1,0, 1,1, 0,1, 1,1, 0,0, 0,0, 0,1, 1,0, 1,0, 1,1, 0,1 }; +static const uint8_t N_bits[33] = { 1,1,1, 0,0,1, 1,0,1, 1,1,1, 0,0,0, 1,1,1, 1,0,0, 0,1,1, 1,1,0, 0,0,0, 0,0,0 }; +static const uint8_t P_bits[33] = { 1,0,1, 0,1,1, 1,1,1, 1,0,1, 0,1,0, 1,0,1, 1,1,0, 0,0,1, 1,0,0, 0,1,0, 0,1,0 }; + +/* 9.4.4.3.3 Extended training sequence */ +static const uint8_t x_bits[30] = { 1,0, 0,1, 1,1, 0,1, 0,0, 0,0, 1,1, 1,0, 1,0, 0,1, 1,1, 0,1, 0,0, 0,0, 1,1 }; +static const uint8_t X_bits[45] = { 0,1,1,1,0,0,1,1,0,1,0,0,0,0,1,0,0,0,1,1,1,0,1,1,0,1,0,1,0,1,1,1,1,1,0,1,0,0,0,0,0,1,1,1,0 }; + +/* 9.4.4.3.4 Synchronization training sequence */ +static const uint8_t y_bits[38] = { 1,1, 0,0, 0,0, 0,1, 1,0, 0,1, 1,1, 0,0, 1,1, 1,0, 1,0, 0,1, 1,1, 0,0, 0,0, 0,1, 1,0, 0,1, 1,1 }; + +/* 9.4.4.3.5 Tail bits */ +static const uint8_t t_bits[4] = { 1, 1, 0, 0 }; +static const uint8_t T_bits[6] = { 1, 1, 1, 0, 0, 0 }; + +/* 9.4.4.3.6 Phase adjustment bits */ +enum phase_adj_bits { HA, HB, HC, HD, HE, HF, HG, HH, HI, HJ }; +struct phase_adj_n { + uint16_t n1; + uint16_t n2; +}; + +/* Table 8.14 */ +static const struct phase_adj_n phase_adj_n[] = { + [HA] = { .n1 = 8, .n2 = 122 }, + [HB] = { .n1 = 123, .n2 = 249 }, + [HC] = { .n1 = 8, .n2 = 108 }, + [HD] = { .n1 = 109, .n2 = 249 }, + [HE] = { .n1 = 112, .n2 = 230 }, + [HF] = { .n1 = 1, .n2 = 111 }, + [HG] = { .n1 = 3, .n2 = 117 }, + [HH] = { .n1 = 118, .n2 = 224 }, + [HI] = { .n1 = 3, .n2 = 103 }, + [HJ] = { .n1 = 104, .n2 = 224 }, +}; + +static const int8_t bits2phase[] = { + [0] = 1, /* +pi/4 needs to become -pi/4 */ + [1] = -1, /* -pi/4 needs to become +pi/4 */ + [2] = +3, /* +3pi/4 needs to become -3pi/4 */ + [3] = -3, /* -3pi/4 needs to become +3pi/4 */ +}; + +/* offset everything by 3 in order to get positive array index */ +#define PHASE(x) ((x)+3) +struct phase2bits { + int8_t phase; + uint8_t bits[2]; +}; +static const struct phase2bits phase2bits[] = { + [PHASE(-3)] = { -3, {1, 1} }, + [PHASE(-1)] = { -1, {0, 1} }, + [PHASE( 1)] = { 1, {0, 0} }, + [PHASE( 3)] = { 3, {1, 0} }, +}; + +static int32_t calc_phase_adj(int32_t phase) +{ + int32_t adj_phase = -(phase % 8); + + /* 'wrap around' to get a value in the range between +3 / -3 */ + if (adj_phase > 3) + adj_phase -= 8; + else if (adj_phase < -3) + adj_phase += 8; + + return adj_phase; +} + +/* return the cumulative phase shift of all bits (in units of pi/4) */ +int32_t sum_up_phase(const uint8_t *bits, unsigned int sym_count) +{ + uint8_t sym_in; + int32_t sum_phase = 0; + unsigned int n; + + for (n = 0; n < sym_count; n++) { + /* offset '-1' due to array-index starting at 0 */ + uint32_t bn = 2*n; + sym_in = bits[bn]; + sym_in |= bits[bn+1] << 1; + + sum_phase += bits2phase[sym_in]; + } + + printf("phase sum over %u symbols: %dpi/4, mod 8 = %dpi/4, wrap = %dpi/4\n", + sym_count, sum_phase, sum_phase % 8, calc_phase_adj(sum_phase)); + return sum_phase; +} + +/* compute phase adjustment bits according to 'pa' and write them to {out, out+2} */ +void put_phase_adj_bits(const uint8_t *bits, enum phase_adj_bits pa, uint8_t *out) +{ + int32_t sum_phase, adj_phase; + const struct phase_adj_n *pan = &phase_adj_n[pa]; + const struct phase2bits *p2b; + + /* offset '-1' due to array-index starting at 0 */ + sum_phase = sum_up_phase(bits + 2*(pan->n1-1), 1 + pan->n2 - pan->n1); + adj_phase = calc_phase_adj(sum_phase); + + p2b = &phase2bits[adj_phase]; + + *out++ = p2b->bits[0]; + *out++ = p2b->bits[1]; +} + +/* 9.4.4.2.6 Synchronization continuous downlink burst */ +int build_sync_c_d_burst(uint8_t *buf, const uint8_t *sb, const uint8_t *bb, const uint8_t *bkn) +{ + uint8_t *cur = buf; + uint8_t *hc, *hd; + + /* Normal Training Sequence: q11 to q22 */ + memcpy(cur, q_bits+10, 12); + cur += 12; + + /* Phase adjustment bits: hc1 to hc2 */ + hc = cur; + cur += 2; + + /* Frequency correction: f1 to f80 */ + memcpy(cur, f_bits, 80); + cur += 80; + + /* Scrambled synchronization block 1 bits: sb(1) to sb(120) */ + memcpy(cur, sb, 120); + cur += 120; + + /* Synchronization training sequence: y1 to y38 */ + memcpy(cur, y_bits, 38); + cur+= 38; + + /* Scrambled broadcast bits: bb(1) to bb(30) */ + memcpy(cur, bb, 30); + cur += 30; + + /* Scrambled block2 bits: bkn2(1) to bkn2(216) */ + memcpy(cur, bkn, 216); + cur += 216; + + /* Phase adjustment bits: hd1 to hd2 */ + hd = cur; + cur += 2; + + /* Normal training sequence 3: q1 to q10 */ + memcpy(cur, q_bits, 10); + cur += 10; + + /* put in the phase adjustment bits */ + put_phase_adj_bits(buf, HC, hc); + put_phase_adj_bits(buf, HD, hd); + + return cur - buf; +} + +/* 9.4.4.2.5 Normal continuous downlink burst */ +int build_norm_c_d_burst(uint8_t *buf, const uint8_t *bkn1, const uint8_t *bb, const uint8_t *bkn2, int two_log_chan) +{ + uint8_t *cur = buf; + uint8_t *ha, *hb; + + /* Normal Training Sequence: q11 to q22 */ + memcpy(cur, q_bits+10, 12); + cur += 12; + + /* Phase adjustment bits: hc1 to hc2 */ + ha = cur; + cur += 2; + + /* Scrambled block 1 bits: bkn1(1) to bkn1(216) */ + memcpy(cur, bkn1, 216); + cur += 216; + + /* Scrambled broadcast bits: bb(1) to bb(14) */ + memcpy(cur, bb, 14); + cur += 14; + + /* Normal training sequence: n1 to n22 or p1 to p22 */ + if (two_log_chan) + memcpy(cur, p_bits, 22); + else + memcpy(cur, n_bits, 22); + cur += 22; + + /* Scrambled broadcast bits: bb(15) to bb(30) */ + memcpy(cur, bb+14, 16); + cur += 16; + + /* Scrambled block2 bits: bkn2(1) to bkn2(216) */ + memcpy(cur, bkn2, 216); + cur += 216; + + /* Phase adjustment bits: hd1 to hd2 */ + hb = cur; + cur += 2; + + /* Normal training sequence 3: q1 to q10 */ + memcpy(cur, q_bits, 10); + cur += 10; + + /* put in the phase adjustment bits */ + put_phase_adj_bits(buf, HA, ha); + put_phase_adj_bits(buf, HB, hb); + + return cur - buf; +} + +int tetra_find_train_seq(const uint8_t *in, unsigned int end_of_in, + uint32_t mask_of_train_seq, unsigned int *offset) +{ + const uint8_t *cur; + + for (cur = in; cur < in + end_of_in; cur++) { + int remain_len = (in + end_of_in) - cur; + + if (mask_of_train_seq & (1 << TETRA_TRAIN_SYNC) && + remain_len >= sizeof(y_bits) && + !memcmp(cur, y_bits, sizeof(y_bits))) { + *offset = (cur - in); + return TETRA_TRAIN_SYNC; + } + if (mask_of_train_seq & (1 << TETRA_TRAIN_NORM_1) && + remain_len >= sizeof(n_bits) && + !memcmp(cur, n_bits, sizeof(n_bits))) { + *offset = (cur - in); + return TETRA_TRAIN_NORM_1; + } + if (mask_of_train_seq & (1 << TETRA_TRAIN_NORM_2) && + remain_len >= sizeof(p_bits) && + !memcmp(cur, p_bits, sizeof(p_bits))) { + *offset = (cur - in); + return TETRA_TRAIN_NORM_2; + } + if (mask_of_train_seq & (1 << TETRA_TRAIN_NORM_3) && + remain_len >= sizeof(q_bits) && + !memcmp(cur, q_bits, sizeof(q_bits))) { + *offset = (cur - in); + return TETRA_TRAIN_NORM_3; + } + if (mask_of_train_seq & (1 << TETRA_TRAIN_EXT) && + remain_len >= sizeof(x_bits) && + !memcmp(cur, x_bits, sizeof(x_bits))) { + *offset = (cur - in); + return TETRA_TRAIN_EXT; + } + } + return -1; +} + +void tetra_burst_rx_cb(const uint8_t *burst, unsigned int len, enum tetra_train_seq type, void *priv) +{ + uint8_t bbk_buf[NDB_BBK_BITS]; + uint8_t ndbf_buf[2*NDB_BLK_BITS]; + + switch (type) { + case TETRA_TRAIN_SYNC: + /* Split SB1, SB2 and Broadcast Block */ + /* send three parts of the burst via TP-SAP into lower MAC */ + tp_sap_udata_ind(TPSAP_T_SB1, burst+SB_BLK1_OFFSET, SB_BLK1_BITS, priv); + tp_sap_udata_ind(TPSAP_T_BBK, burst+SB_BBK_OFFSET, SB_BBK_BITS, priv); + tp_sap_udata_ind(TPSAP_T_SB2, burst+SB_BLK2_OFFSET, SB_BLK2_BITS, priv); + break; + case TETRA_TRAIN_NORM_2: + /* re-combine the broadcast block */ + memcpy(bbk_buf, burst+NDB_BBK1_OFFSET, NDB_BBK1_BITS); + memcpy(bbk_buf+NDB_BBK1_BITS, burst+NDB_BBK2_OFFSET, NDB_BBK2_BITS); + /* send three parts of the burst via TP-SAP into lower MAC */ + tp_sap_udata_ind(TPSAP_T_BBK, bbk_buf, NDB_BBK_BITS, priv); + tp_sap_udata_ind(TPSAP_T_NDB, burst+NDB_BLK1_OFFSET, NDB_BLK_BITS, priv); + tp_sap_udata_ind(TPSAP_T_NDB, burst+NDB_BLK2_OFFSET, NDB_BLK_BITS, priv); + break; + case TETRA_TRAIN_NORM_1: + /* re-combine the broadcast block */ + memcpy(bbk_buf, burst+NDB_BBK1_OFFSET, NDB_BBK1_BITS); + memcpy(bbk_buf+NDB_BBK1_BITS, burst+NDB_BBK2_OFFSET, NDB_BBK2_BITS); + /* re-combine the two parts */ + memcpy(ndbf_buf, burst+NDB_BLK1_OFFSET, NDB_BLK_BITS); + memcpy(ndbf_buf+NDB_BLK_BITS, burst+NDB_BLK2_OFFSET, NDB_BLK_BITS); + /* send two parts of the burst via TP-SAP into lower MAC */ + tp_sap_udata_ind(TPSAP_T_BBK, bbk_buf, NDB_BBK_BITS, priv); + tp_sap_udata_ind(TPSAP_T_SCH_F, ndbf_buf, 2*NDB_BLK_BITS, priv); + break; + } +} diff --git a/src/phy/tetra_burst.h b/src/phy/tetra_burst.h new file mode 100644 index 0000000..8feb09c --- /dev/null +++ b/src/phy/tetra_burst.h @@ -0,0 +1,35 @@ +#ifndef TETRA_BURST_H +#define TETRA_BURST_H + +#include + +enum tp_sap_data_type { + TPSAP_T_SB1, + TPSAP_T_SB2, + TPSAP_T_NDB, + TPSAP_T_BBK, + TPSAP_T_SCH_HU, + TPSAP_T_SCH_F, +}; + +extern void tp_sap_udata_ind(enum tp_sap_data_type type, const uint8_t *bits, unsigned int len, void *priv); + +/* 9.4.4.2.6 Synchronization continuous downlink burst */ +int build_sync_c_d_burst(uint8_t *buf, const uint8_t *sb, const uint8_t *bb, const uint8_t *bkn); + +/* 9.4.4.2.5 Normal continuous downlink burst */ +int build_norm_c_d_burst(uint8_t *buf, const uint8_t *bkn1, const uint8_t *bb, const uint8_t *bkn2, int two_log_chan); + +enum tetra_train_seq { + TETRA_TRAIN_NORM_1, + TETRA_TRAIN_NORM_2, + TETRA_TRAIN_NORM_3, + TETRA_TRAIN_SYNC, + TETRA_TRAIN_EXT, +}; + +/* find a TETRA training sequence in the burst buffer indicated */ +int tetra_find_train_seq(const uint8_t *in, unsigned int end_of_in, + uint32_t mask_of_train_seq, unsigned int *offset); + +#endif /* TETRA_BURST_H */ diff --git a/src/phy/tetra_burst_sync.c b/src/phy/tetra_burst_sync.c new file mode 100644 index 0000000..0191329 --- /dev/null +++ b/src/phy/tetra_burst_sync.c @@ -0,0 +1,141 @@ +/* Implementation of TETRA burst synchronization */ + +/* (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 + +//#define DEBUG + +#include +#include +#include +#include + +struct tetra_phy_state t_phy_state; + +void tetra_burst_rx_cb(const uint8_t *burst, unsigned int len, enum tetra_train_seq type, void *priv); + +/* input a raw bitstream into the tetra burst synchronizaer */ +int tetra_burst_sync_in(struct tetra_rx_state *trs, uint8_t *bits, unsigned int len) +{ + int rc; + unsigned int train_seq_offs; + int cpy_len; + + DEBUGP("burst_sync_in: %u bits, state %u\n", len, trs->state); + + /* First: append the data to the bitbuf */ + if (sizeof(trs->bitbuf) - trs->bits_in_buf < len) + cpy_len = sizeof(trs->bitbuf) - trs->bits_in_buf; + else + cpy_len = len; + memcpy(trs->bitbuf + trs->bits_in_buf, bits, cpy_len); + trs->bits_in_buf += cpy_len; + + switch (trs->state) { + case RX_S_UNLOCKED: + if (trs->bits_in_buf < TETRA_BITS_PER_TS*2) { + /* wait for more bits to arrive */ + DEBUGP("-> waiting for more bits to arrive\n"); + return cpy_len; + } + DEBUGP("-> trying to find training sequence between bit %u and %u\n", + trs->bitbuf_start_bitnum, trs->bits_in_buf); + rc = tetra_find_train_seq(trs->bitbuf, trs->bits_in_buf, + (1 << TETRA_TRAIN_SYNC), &train_seq_offs); + if (rc < 0) + return rc; + printf("found SYNC training sequence in bit #%u\n", train_seq_offs); + trs->state = RX_S_KNOW_FSTART; + trs->next_frame_start_bitnum = trs->bitbuf_start_bitnum + train_seq_offs + 296; +#if 0 + if (train_seq_offs < 214) { + /* not enough leading bits for start of burst */ + /* we just drop everything that we received so far */ + trs->bitbuf_start_bitnum += trs->bits_in_buf; + trs->bits_in_buf = 0; + } +#endif + break; + case RX_S_KNOW_FSTART: + /* we are locked, i.e. already know when the next frame should start */ + if (trs->bitbuf_start_bitnum + trs->bits_in_buf < trs->next_frame_start_bitnum) + return 0; + else { + /* shift start of frame to start of bitbuf */ + int offset = trs->next_frame_start_bitnum - trs->bitbuf_start_bitnum; + int bits_remaining = trs->bits_in_buf - offset; + + memmove(trs->bitbuf, trs->bitbuf+offset, bits_remaining); + trs->bits_in_buf = bits_remaining; + trs->bitbuf_start_bitnum += offset; + + trs->next_frame_start_bitnum += TETRA_BITS_PER_TS; + trs->state = RX_S_LOCKED; + } + case RX_S_LOCKED: + if (trs->bits_in_buf < TETRA_BITS_PER_TS) { + /* not sufficient data for the full frame yet */ + return cpy_len; + } else { + /* we have successfully received (at least) one frame */ + tetra_tdma_time_add_tn(&t_phy_state.time, 1); + printf("\nBURST: %s\n", bitdump(trs->bitbuf, TETRA_BITS_PER_TS)); + rc = tetra_find_train_seq(trs->bitbuf, trs->bits_in_buf, + (1 << TETRA_TRAIN_NORM_1)| + (1 << TETRA_TRAIN_NORM_2)| + (1 << TETRA_TRAIN_SYNC), &train_seq_offs); + switch (rc) { + case TETRA_TRAIN_SYNC: + if (train_seq_offs == 214) + tetra_burst_rx_cb(trs->bitbuf, TETRA_BITS_PER_TS, rc, trs->burst_cb_priv); + else { + fprintf(stderr, "#### SYNC burst at offset %u?!?\n", train_seq_offs); + trs->state = RX_S_UNLOCKED; + } + break; + case TETRA_TRAIN_NORM_1: + case TETRA_TRAIN_NORM_2: + case TETRA_TRAIN_NORM_3: + if (train_seq_offs == 244) + tetra_burst_rx_cb(trs->bitbuf, TETRA_BITS_PER_TS, rc, trs->burst_cb_priv); + else + fprintf(stderr, "#### SYNC burst at offset %u?!?\n", train_seq_offs); + break; + default: + fprintf(stderr, "#### could not find successive burst training sequence\n"); + trs->state = RX_S_UNLOCKED; + break; + } + + /* move remainder to start of buffer */ + trs->bits_in_buf -= TETRA_BITS_PER_TS; + memmove(trs->bitbuf, trs->bitbuf+TETRA_BITS_PER_TS, trs->bits_in_buf); + trs->bitbuf_start_bitnum += TETRA_BITS_PER_TS; + trs->next_frame_start_bitnum += TETRA_BITS_PER_TS; + } + break; + + } + return cpy_len; +} diff --git a/src/phy/tetra_burst_sync.h b/src/phy/tetra_burst_sync.h new file mode 100644 index 0000000..7862461 --- /dev/null +++ b/src/phy/tetra_burst_sync.h @@ -0,0 +1,26 @@ +#ifndef TETRA_BURST_SYNC_H +#define TETRA_BURST_SYNC_H + +#include + +enum rx_state { + RX_S_UNLOCKED, /* we're completely unlocked */ + RX_S_KNOW_FSTART, /* we know the next frame start */ + RX_S_LOCKED, /* fully locked */ +}; + +struct tetra_rx_state { + enum rx_state state; + unsigned int bits_in_buf; /* how many bits are currently in bitbuf */ + uint8_t bitbuf[4096]; + unsigned int bitbuf_start_bitnum; /* bit number at first element in bitbuf */ + unsigned int next_frame_start_bitnum; /* frame start expected at this bitnum */ + + void *burst_cb_priv; +}; + + +/* input a raw bitstream into the tetra burst synchronizaer */ +int tetra_burst_sync_in(struct tetra_rx_state *trs, uint8_t *bits, unsigned int len); + +#endif /* TETRA_BURST_SYNC_H */ diff --git a/src/testpdu.c b/src/testpdu.c new file mode 100644 index 0000000..5a3ddba --- /dev/null +++ b/src/testpdu.c @@ -0,0 +1,99 @@ +/* (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 "testpdu.h" + +uint8_t pdu_sync[8]; /* 60 bits */ +uint8_t pdu_sysinfo[16]; /* 124 bits */ +uint8_t pdu_acc_ass[2]; +uint8_t pdu_schf[268]; + +void testpdu_init() +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = pdu_sync; + bv.data_len = sizeof(pdu_sync); + + //bitvec_set_uint(&bv, 0, 4); /* alignment */ + /* According to Table 21.73: SYNC PDU Contents */ + bitvec_set_uint(&bv, 0, 4); /* System Code: ETS 300 392-2 ed. 1 */ + bitvec_set_uint(&bv, 0, 6); /* Colour Code: Predefined Scrambling */ + bitvec_set_uint(&bv, 0, 2); /* Timeslot number: TN 1 */ + bitvec_set_uint(&bv, 1, 5); /* Frame number: FN 1 */ + bitvec_set_uint(&bv, 1, 6); /* Multiframe number: MN 1 */ + bitvec_set_uint(&bv, 0, 2); /* Sharing mode: continuous transmission */ + bitvec_set_uint(&bv, 0, 3); /* TS reserved frames: 1 frame per 2 mfrm */ + bitvec_set_bit(&bv, 0); /* No DTX */ + bitvec_set_bit(&bv, 0); /* No Frame 18 extension */ + bitvec_set_bit(&bv, 0); /* Reserved */ + /* As defined in Table 18.4.2.1: D-MLE-SYNC */ + bitvec_set_uint(&bv, 262, 10); /* MCC */ + bitvec_set_uint(&bv, 42, 14); /* MNC */ + bitvec_set_uint(&bv, 0, 2); /* Neighbor cell boradcast: not supported */ + bitvec_set_uint(&bv, 0, 2); /* Cell service level: unknown */ + bitvec_set_bit(&bv, 0); /* Late entry information */ + printf("SYNC PDU: %s\n", hexdump(pdu_sync, sizeof(pdu_sync))); + + memset(&bv, 0, sizeof(bv)); + bv.data = pdu_sysinfo; + bv.data_len = sizeof(pdu_sysinfo); + + //bitvec_set_uint(&bv, 0, 4); /* alignment */ + /* According to Table 21.4.4.1: SYSINFO PDU contents */ + bitvec_set_uint(&bv, 2, 2); /* MAC PDU type: Broadcast */ + bitvec_set_uint(&bv, 0, 2); /* SYSINVO PDU */ + bitvec_set_uint(&bv, ((392775-300000)/25), 12); /* Main carier */ + bitvec_set_uint(&bv, 3, 4); /* Frequency band: 390/400 */ + bitvec_set_uint(&bv, 0, 2); /* Offset: No offset */ + bitvec_set_uint(&bv, 0, 3); /* Duplex Spacing: */ + bitvec_set_bit(&bv, 0); /* Normal operation */ + bitvec_set_uint(&bv, 0, 2); /* Number of CSCH: none */ + bitvec_set_uint(&bv, 1, 3); /* MS_TXPWR_MAX_CELL: 15 dBm */ + bitvec_set_uint(&bv, 0, 4); /* RXLEV_ACCESS_MIN: -125dBm */ + bitvec_set_uint(&bv, 0, 4); /* ACCESS_PARAMETER: -53 dBm */ + bitvec_set_uint(&bv, 0, 4); /* RADIO_DOWNLINK_TIMEOUT: Disable */ + bitvec_set_bit(&bv, 0); /* Hyperframe number follows */ + bitvec_set_uint(&bv, 0, 16); /* Hyperframe number */ + bitvec_set_uint(&bv, 0, 2); /* Optional field: Even multiframe */ + bitvec_set_uint(&bv, 0, 20); /* TS_COMMON_FRAMES for even mframe */ + /* TM-SDU (42 bit), Section 18.4.2.2, Table 18.15 */ + bitvec_set_uint(&bv, 0, 14); /* Location Area (18.5.9) */ + bitvec_set_uint(&bv, 0xFFFF, 16); /* Subscriber Class (18.5.22) */ + bitvec_set_uint(&bv, 0, 12); /* BS service details (18.5.2) */ + printf("SYSINFO PDU: %s\n", hexdump(pdu_sysinfo, sizeof(pdu_sysinfo))); + + memset(&bv, 0, sizeof(bv)); + bv.data = pdu_acc_ass; + bv.data_len = sizeof(pdu_acc_ass); + + bitvec_set_uint(&bv, 0, 2); /* alignment */ + /* According to Table 21.27: ACCESS-ASSIGN PDU */ + bitvec_set_uint(&bv, 0, 2); /* DL/UL: common only */ + bitvec_set_uint(&bv, 0, 6); + bitvec_set_uint(&bv, 0, 6); + printf("ACCESS-ASSIGN PDU: %s\n", hexdump(pdu_acc_ass, sizeof(pdu_acc_ass))); +} diff --git a/src/testpdu.h b/src/testpdu.h new file mode 100644 index 0000000..ea5b33f --- /dev/null +++ b/src/testpdu.h @@ -0,0 +1,10 @@ + +#include + +extern uint8_t pdu_sync[8]; /* 60 bits */ +extern uint8_t pdu_sysinfo[16]; /* 124 bits */ +extern uint8_t pdu_acc_ass[2]; +extern uint8_t pdu_schf[268]; + +void testpdu_init(); + diff --git a/src/tetra_common.c b/src/tetra_common.c new file mode 100644 index 0000000..ab71ae5 --- /dev/null +++ b/src/tetra_common.c @@ -0,0 +1,157 @@ +/* Common routines for the TETRA PHY/MAC implementation */ + +/* (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 "tetra_common.h" +#include "tetra_prim.h" + +uint32_t bits_to_uint(const uint8_t *bits, unsigned int len) +{ + uint32_t ret = 0; + + while (len--) + ret = (ret << 1) | (*bits++ & 1); + + return ret; +} + +const char *bitdump(const uint8_t *bits, unsigned int len) +{ + int i; + static char hexd_buff[4096]; + + if (len > sizeof(hexd_buff)-1) + len = sizeof(hexd_buff)-1; + memset(hexd_buff, 0, sizeof(hexd_buff)); + + for (i = 0; i < len; i++) { + char outch; + switch (bits[i]) { + case 0: + outch = '0'; + break; + case 0xff: + outch = '?'; + break; + case 1: + outch = '1'; + break; + default: + outch = 'E'; + break; + } + hexd_buff[i] = outch; + } + hexd_buff[sizeof(hexd_buff)-1] = 0; + return hexd_buff; +} + +static inline uint32_t tetra_band_base_hz(uint8_t band) +{ + return (band * 100000000); +} + +static const int16_t tetra_carrier_offset[4] = { + [0] = 0, + [1] = 6250, + [2] = -6250, + [3] = +12500, +}; + +uint32_t tetra_dl_carrier_hz(uint8_t band, uint16_t carrier, uint8_t offset) +{ + uint32_t freq = tetra_band_base_hz(band); + freq += carrier * 25000; + freq += tetra_carrier_offset[offset&3]; + return freq; +} + +/* TS 100 392-15, Table 2 */ +static const int16_t tetra_duplex_spacing[8][16] = { /* values are in kHz */ + [0] = { -1, 1600, 10000, 10000, 10000, 10000, 10000, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + [1] = { -1, 4500, -1, 36000, 7000, -1, -1, -1, 45000, 45000, -1, -1, -1, -1, -1, -1 }, + [2] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + [3] = { -1, -1, -1, 8000, 8000, -1, -1, -1, 18000, 18000, -1, -1, -1, -1, -1, -1 }, + [4] = { -1, -1, -1, 18000, 5000, -1, 30000, 30000, -1, 39000, -1, -1, -1, -1, -1, -1 }, + [5] = { -1, -1, -1, -1, 9500, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + [6] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + [7] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, +}; + +uint32_t tetra_ul_carrier_hz(uint8_t band, uint16_t carrier, uint8_t offset, + uint8_t duplex, uint8_t reverse) +{ + uint32_t freq = tetra_dl_carrier_hz(band, carrier, offset); + + uint32_t duplex_spacing = tetra_duplex_spacing[duplex & 7][band & 15]; + + if (duplex_spacing < 0) /* reserved for future standardization */ + return 0; + + duplex_spacing *= 1000; // make Hz + + if (reverse) + freq += duplex_spacing; + else + freq -= duplex_spacing; + + return freq; +} + +static const struct value_string tetra_sap_names[] = { + { TETRA_SAP_TP, "TP-SAP" }, + { TETRA_SAP_TMV, "TMV-SAP" }, + { TETRA_SAP_TMA, "TMA-SAP" }, + { TETRA_SAP_TMB, "TMB-SAP" }, + { TETRA_SAP_TMD, "TMD-SAP" }, + { 0, NULL } +}; + +static const struct value_string tetra_lchan_names[] = { + { TETRA_LC_UNKNOWN, "UNKNOWN" }, + { TETRA_LC_SCH_F, "SCH/F" }, + { TETRA_LC_SCH_HD, "SCH/HD" }, + { TETRA_LC_SCH_HU, "SCH/HU" }, + { TETRA_LC_STCH, "STCH" }, + { TETRA_LC_SCH_P8_F, "SCH-P8/F" }, + { TETRA_LC_SCH_P8_HD, "SCH-P8/HD" }, + { TETRA_LC_SCH_P8_HU, "SCH-P8/HU" }, + { TETRA_LC_AACH, "AACH" }, + { TETRA_LC_TCH, "TCH" }, + { TETRA_LC_BSCH, "BSCH" }, + { TETRA_LC_BNCH, "BNCH" }, + { 0, NULL } +}; + +const char *tetra_get_lchan_name(enum tetra_log_chan lchan) +{ + return get_value_string(tetra_lchan_names, lchan); +} + +const char *tetra_get_sap_name(uint8_t sap) +{ + return get_value_string(tetra_sap_names, sap); +} diff --git a/src/tetra_common.h b/src/tetra_common.h new file mode 100644 index 0000000..a560444 --- /dev/null +++ b/src/tetra_common.h @@ -0,0 +1,52 @@ +#ifndef TETRA_COMMON_H +#define TETRA_COMMON_H + +#include + +#ifdef DEBUG +#define DEBUGP(x, args...) printf(x, ## args) +#else +#define DEBUGP(x, args...) do { } while(0) +#endif + +#define TETRA_SYM_PER_TS 255 +#define TETRA_BITS_PER_TS (TETRA_SYM_PER_TS*2) + +/* Chapter 22.2.x */ +enum tetra_log_chan { + TETRA_LC_UNKNOWN, + /* TMA SAP */ + TETRA_LC_SCH_F, + TETRA_LC_SCH_HD, + TETRA_LC_SCH_HU, + TETRA_LC_STCH, + TETRA_LC_SCH_P8_F, + TETRA_LC_SCH_P8_HD, + TETRA_LC_SCH_P8_HU, + + TETRA_LC_AACH, + TETRA_LC_TCH, + TETRA_LC_BSCH, + TETRA_LC_BNCH, + + /* FIXME: QAM */ +}; + +const char *bitdump(const uint8_t *bits, unsigned int len); +uint32_t bits_to_uint(const uint8_t *bits, unsigned int len); + +#include "tetra_tdma.h" +struct tetra_phy_state { + struct tetra_tdma_time time; +}; +extern struct tetra_phy_state t_phy_state; + +#define TETRA_CRC_OK 0x1d0f + +uint32_t tetra_dl_carrier_hz(uint8_t band, uint16_t carrier, uint8_t offset); +uint32_t tetra_ul_carrier_hz(uint8_t band, uint16_t carrier, uint8_t offset, + uint8_t duplex, uint8_t reverse); + +const char *tetra_get_lchan_name(enum tetra_log_chan lchan); +const char *tetra_get_sap_name(uint8_t sap); +#endif diff --git a/src/tetra_gsmtap.c b/src/tetra_gsmtap.c new file mode 100644 index 0000000..ed06c17 --- /dev/null +++ b/src/tetra_gsmtap.c @@ -0,0 +1,134 @@ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tetra_common.h" +#include "tetra_tdma.h" + +static int gsmtap_fd = -1; + +static const uint8_t lchan2gsmtap[] = { + [TETRA_LC_SCH_F] = GSMTAP_TETRA_SCH_F, + [TETRA_LC_SCH_HD] = GSMTAP_TETRA_SCH_HD, + [TETRA_LC_SCH_HU] = GSMTAP_TETRA_SCH_HU, + [TETRA_LC_STCH] = GSMTAP_TETRA_STCH, + [TETRA_LC_AACH] = GSMTAP_TETRA_AACH, + [TETRA_LC_TCH] = GSMTAP_TETRA_TCH_F, + [TETRA_LC_BSCH] = GSMTAP_TETRA_BSCH, + [TETRA_LC_BNCH] = GSMTAP_TETRA_BNCH, +}; + + +struct msgb *tetra_gsmtap_makemsg(struct tetra_tdma_time *tm, enum tetra_log_chan lchan, + uint8_t ts, uint8_t ss, int8_t signal_dbm, + uint8_t snr, const ubit_t *bitdata, unsigned int bitlen) +{ + struct msgb *msg; + struct gsmtap_hdr *gh; + uint32_t fn = tetra_tdma_time2fn(tm); + unsigned int packed_len = osmo_pbit_bytesize(bitlen); + uint8_t *dst; + + msg = msgb_alloc(sizeof(*gh) + packed_len, "tetra_gsmtap_tx"); + if (!msg) + return NULL; + + gh = (struct gsmtap_hdr *) msgb_put(msg, sizeof(*gh)); + gh->version = GSMTAP_VERSION; + gh->hdr_len = sizeof(*gh)/4; + gh->type = GSMTAP_TYPE_TETRA_I1; + gh->timeslot = ts; + gh->sub_slot = ss; + gh->snr_db = snr; + gh->signal_dbm = signal_dbm; + gh->frame_number = htonl(fn); + gh->sub_type = lchan2gsmtap[lchan]; + gh->antenna_nr = 0; + + /* convert from 1bit-per-byte to compressed bits!!! */ + dst = msgb_put(msg, packed_len); + osmo_ubit2pbit(dst, bitdata, bitlen); + + return msg; +} + +int tetra_gsmtap_sendmsg(struct msgb *msg) +{ + if (gsmtap_fd != -1) + return write(gsmtap_fd, msg->data, msg->len); + else + return 0; +} + +/* this block should move to libosmocore */ + +#include +#include +#include +#include + +int gsmtap_init(const char *host, uint16_t port) +{ + struct addrinfo hints, *result, *rp; + int sfd, rc; + char portbuf[16]; + + if (port == 0) + port = GSMTAP_UDP_PORT; + if (host == NULL) + host = "localhost"; + + sprintf(portbuf, "%u", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = IPPROTO_UDP; + + rc = getaddrinfo(host, portbuf, &hints, &result); + if (rc != 0) { + perror("getaddrinfo returned NULL"); + return -EINVAL; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + printf("creating socket %u, %u, %u\n", rp->ai_family, rp->ai_socktype, rp->ai_protocol); + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) + continue; + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; + close(sfd); + } + freeaddrinfo(result); + + if (rp == NULL) { + perror("unable to bind to socket"); + return -ENODEV; + } + + /* FIXME: if host == localhost, bind to another socket and discard data */ + + return sfd; +} +/* end block for libosmocore */ + +int tetra_gsmtap_init(const char *host, uint16_t port) +{ + int fd; + + fd = gsmtap_init(host, port); + if (fd < 0) + return fd; + + gsmtap_fd = fd; + + return 0; +} diff --git a/src/tetra_gsmtap.h b/src/tetra_gsmtap.h new file mode 100644 index 0000000..b62a4c5 --- /dev/null +++ b/src/tetra_gsmtap.h @@ -0,0 +1,13 @@ +#ifndef TETRA_GSMTAP_H +#define TETRA_GSMTAP_H +#include "tetra_common.h" + +struct msgb *tetra_gsmtap_makemsg(struct tetra_tdma_time *tm, enum tetra_log_chan lchan, + uint8_t ts, uint8_t ss, int8_t signal_dbm, + uint8_t snr, const uint8_t *data, unsigned int len); + +int tetra_gsmtap_sendmsg(struct msgb *msg); + +int tetra_gsmtap_init(const char *host, uint16_t port); + +#endif diff --git a/src/tetra_mac_pdu.c b/src/tetra_mac_pdu.c new file mode 100644 index 0000000..7f685a8 --- /dev/null +++ b/src/tetra_mac_pdu.c @@ -0,0 +1,375 @@ +/* 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 "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; +} + +void macpdu_decode_sysinfo(struct tetra_si_decoded *sid, const uint8_t *si_bits) +{ + const uint8_t *cur = si_bits + 4; + + 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); + cur += 16; + /* FIXME: more */ + decode_d_mle_sysinfo(&sid->mle_si, si_bits + 124-42); +} + +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 */ +static int 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; +} + +/* Section 21.4.3.1 MAC-RESOURCE */ +int macpdu_decode_resource(struct tetra_resrc_decoded *rsd, const uint8_t *bits) +{ + const uint8_t *cur = bits + 4; + + rsd->encryption_mode = bits_to_uint(cur, 2); cur += 2; + rsd->rand_acc_flag = *cur++; + /* FIXME: Y2/... octet calculation */ + rsd->length_ind = 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: + 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, 6); + break; + } + cur += addr_len_by_type[rsd->addr.type]; + /* no intermediate mapping 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 (*cur++) + cur += 0; //FIXME; + else + cur += 8; + } + rsd->chan_alloc_pres = *cur++; + /* FIXME: If encryption is enabled, Channel Allocation is encrypted !!! */ + if (rsd->chan_alloc_pres) + cur += decode_chan_alloc(&rsd->cad, cur); + /* FIXME: TM-SDU */ + + 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); +} diff --git a/src/tetra_mac_pdu.h b/src/tetra_mac_pdu.h new file mode 100644 index 0000000..20ad83d --- /dev/null +++ b/src/tetra_mac_pdu.h @@ -0,0 +1,221 @@ +#ifndef TETRA_MAC_PDU +#define TETRA_MAC_PDU + +enum tetra_mac_pdu_types { + TETRA_PDU_T_MAC_RESOURCE = 0, + TETRA_PDU_T_MAC_FRAG_END = 1, + TETRA_PDU_T_BROADCAST = 2, + TETRA_PDU_T_MAC_SUPPL = 3, +}; + +enum tetra_mac_frage_pdu_types { + TETRA_MAC_FRAGE_FRAG = 0, + TETRA_MAC_FRAGE_END = 1, +}; + +enum tetra_mac_bcast_pdu_types { + TETRA_MAC_BC_SYSINFO = 0, + TETRA_MAC_BC_ACCESS_DEFINE = 1, +}; + +enum tetra_mac_supp_pdu_types { + TETRA_MAC_SUPP_D_BLCK = 0, +}; + +enum tetra_bs_serv_details { + BS_SERVDET_REG_RQD = (1 << 11), + BS_SERVDET_DEREG_RQD = (1 << 10), + BS_SERVDET_PRIO_CELL = (1 << 9), + BS_SERVDET_MIN_MODE = (1 << 8), + BS_SERVDET_MIGRATION = (1 << 7), + BS_SERVDET_SYS_W_SERV = (1 << 6), + BS_SERVDET_VOICE_SERV = (1 << 5), + BS_SERVDET_CSD_SERV = (1 << 4), + BS_SERVDET_SNDCP_SERV = (1 << 2), + BS_SERVDET_AIR_ENCR = (1 << 1), + BS_SERVDET_ADV_LINK = (1 << 0), +}; + +const char *tetra_get_bs_serv_det_name(uint32_t pdu_type); + +struct tetra_mle_si_decoded { + uint16_t la; + uint16_t subscr_class; + uint16_t bs_service_details; +}; + +struct tetra_si_decoded { + uint16_t main_carrier; + uint8_t freq_band; + uint8_t freq_offset; + uint8_t duplex_spacing; + uint8_t reverse_operation; + uint8_t num_of_csch; + uint8_t ms_txpwr_max_cell; + uint8_t rxlev_access_min; + uint8_t access_parameter; + uint8_t radio_dl_timeout; + int cck_valid_no_hf; + union { + uint16_t cck_id; + uint16_t hyperframe_number; + }; + struct tetra_mle_si_decoded mle_si; +}; + +const char *tetra_get_macpdu_name(uint8_t pdu_type); + +void macpdu_decode_sysinfo(struct tetra_si_decoded *sid, const uint8_t *si_bits); + + +/* Section 21.4.7.2 ACCESS-ASSIGN PDU */ +enum tetra_acc_ass_hdr { + TETRA_ACC_ASS_DLCC_ULCO, + TETRA_ACC_ASS_DLF1_ULCA, + TETRA_ACC_ASS_DLF1_ULAO, + TETRA_ACC_ASS_DLF1_ULF1, +}; + +enum tetra_acc_ass_hdr_f18 { + TETRA_ACC_ASS_ULCO, + TETRA_ACC_ASS_ULCA, + TETRA_ACC_ASS_ULAO, + TETRA_ACC_ASS_ULCA2, +}; + +enum tetra_dl_usage { + TETRA_DL_US_UNALLOC = 0, + TETRA_DL_US_ASS_CTRL = 1, + TETRA_DL_US_COM_CTRL = 2, + TETRA_DL_US_RESERVED = 3, + TETRA_DL_US_TRAFFIC, +}; + +enum tetra_ul_usage { + TETRA_UL_US_UNALLOC = 0, + TETRA_UL_US_TRAFFIC, +}; + +/* Section 21.5.1 */ +enum tetra_access_field_bf_len { + TETRA_ACC_BFL_RES_SUBS = 0, + TETRA_ACC_BFL_CLCH_SUBS = 1, + TETRA_ACC_BFL_ONGOING = 2, + TETRA_ACC_BFL_1 = 3, + TETRA_ACC_BFL_2 = 4, + TETRA_ACC_BFL_3 = 5, + TETRA_ACC_BFL_4 = 7, + TETRA_ACC_BFL_5 = 6, + TETRA_ACC_BFL_6 = 8, + TETRA_ACC_BFL_8 = 9, + TETRA_ACC_BFL_10 = 0xa, + TETRA_ACC_BFL_12 = 0xb, + TETRA_ACC_BFL_16 = 0xc, + TETRA_ACC_BFL_20 = 0xd, + TETRA_ACC_BFL_24 = 0xe, + TETRA_ACC_BFL_32 = 0xf, +}; + +struct tetra_access_field { + uint8_t access_code; + enum tetra_access_field_bf_len base_frame_len; +}; + +enum tetra_acc_ass_pres { + TETRA_ACC_ASS_PRES_ACCESS1 = (1 << 0), + TETRA_ACC_ASS_PRES_ACCESS2 = (1 << 1), + TETRA_ACC_ASS_PRES_DL_USAGE = (1 << 2), + TETRA_ACC_ASS_PRES_UL_USAGE = (1 << 3), +}; + +struct tetra_acc_ass_decoded { + uint8_t hdr; + uint32_t pres; /* which of the fields below are present */ + + enum tetra_ul_usage ul_usage; + enum tetra_dl_usage dl_usage; + struct tetra_access_field access[2]; +}; + +void macpdu_decode_access_assign(struct tetra_acc_ass_decoded *aad, const uint8_t *bits, int f18); +const char *tetra_get_dl_usage_name(uint8_t num); +const char *tetra_get_ul_usage_name(uint8_t num); + +enum tetra_mac_res_addr_type { + ADDR_TYPE_NULL = 0, + ADDR_TYPE_SSI = 1, + ADDR_TYPE_EVENT_LABEL = 2, + ADDR_TYPE_USSI = 3, + ADDR_TYPE_SMI = 4, + ADDR_TYPE_SSI_EVENT = 5, + ADDR_TYPE_SSI_USAGE = 6, + ADDR_TYPE_SMI_EVENT = 7, +}; +const char *tetra_get_addr_t_name(uint8_t addrt); + +enum tetra_mac_alloc_type { + TMAC_ALLOC_T_REPLACE = 0, + TMAC_ALLOC_T_ADDITIONAL = 1, + TMAC_ALLOC_T_QUIT_GO = 2, + TMAC_ALLOC_T_REPL_SLOT1 = 3, +}; +const char *tetra_get_alloc_t_name(uint8_t alloct); + +struct tetra_chan_alloc_decoded { + uint8_t type; + uint8_t timeslot; + uint8_t ul_dl; + uint8_t clch_perm; + uint8_t cell_chg_f; + uint16_t carrier_nr; + uint8_t ext_carr_pres; + struct { + uint8_t freq_band; + uint8_t freq_offset; + uint8_t duplex_spc; + uint8_t reverse_oper; + } ext_carr; + uint8_t monit_pattern; + uint8_t monit_patt_f18; + struct { + uint8_t ul_dl_ass; + uint8_t bandwidth; + uint8_t modulation; + uint8_t max_ul_qam; + uint8_t conf_chan_stat; + uint8_t bs_imbalance; + uint8_t bs_tx_rel; + uint8_t napping_sts; + } aug; +}; + +struct tetra_addr { + uint8_t type; + uint16_t mcc; + uint16_t mnc; + uint32_t ssi; + + uint16_t event_label; + uint8_t usage_marker; +}; + +struct tetra_resrc_decoded { + uint8_t encryption_mode; + uint8_t rand_acc_flag; + uint8_t length_ind; + struct tetra_addr addr; + + uint8_t power_control_pres; + + uint8_t slot_granting_pres; + + uint8_t chan_alloc_pres; + struct tetra_chan_alloc_decoded cad; +}; +int macpdu_decode_resource(struct tetra_resrc_decoded *rsd, const uint8_t *bits); + +const char *tetra_addr_dump(const struct tetra_addr *addr); + +const char *tetra_get_ul_dl_name(uint8_t ul_dl); + +#endif diff --git a/src/tetra_mle_pdu.c b/src/tetra_mle_pdu.c new file mode 100644 index 0000000..bea2d90 --- /dev/null +++ b/src/tetra_mle_pdu.c @@ -0,0 +1,38 @@ +/* Implementation of TETRA MLE PDU parsing */ + +/* (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 "tetra_mle_pdu.h" + +static const struct value_string mle_pdisc_names[] = { + { TMLE_PDISC_MM, "MM" }, + { TMLE_PDISC_CMCE, "CMCE" }, + { TMLE_PDISC_SNDCP, "SNDCP" }, + { TMLE_PDUSC_MLE, "MLE" }, + { TMLE_PDISC_MGMT, "MGMT" }, + { TMLE_PDISC_TEST, "TEST" }, + { 0, NULL } +}; +const char *tetra_get_mle_pdisc_name(uint8_t pdisc) +{ + return get_value_string(mle_pdisc_names, pdisc); +} diff --git a/src/tetra_mle_pdu.h b/src/tetra_mle_pdu.h new file mode 100644 index 0000000..cd9231f --- /dev/null +++ b/src/tetra_mle_pdu.h @@ -0,0 +1,17 @@ +#ifndef TETRA_MLE_PDU_H +#define TETRA_MLE_PDU_H + +#include + +/* 18.5.21 */ +enum tetra_mle_pdisc { + TMLE_PDISC_MM = 1, + TMLE_PDISC_CMCE = 2, + TMLE_PDISC_SNDCP = 4, + TMLE_PDUSC_MLE = 5, + TMLE_PDISC_MGMT = 6, + TMLE_PDISC_TEST = 7, +}; +const char *tetra_get_mle_pdisc_name(uint8_t pdisc); + +#endif diff --git a/src/tetra_mm_pdu.c b/src/tetra_mm_pdu.c new file mode 100644 index 0000000..0fde1df --- /dev/null +++ b/src/tetra_mm_pdu.c @@ -0,0 +1,46 @@ +/* Implementation of TETRA MM PDU parsing */ + +/* (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 "tetra_mm_pdu.h" + +static const struct value_string mm_pdut_d_names[] = { + { TMM_PDU_T_D_OTAR, "D-OTAR" }, + { TMM_PDU_T_D_AUTH, "D-AUTHENTICATION" }, + { TMM_PDU_T_D_CK_CHG_DEM, "D-CK CHANGE DEMAND" }, + { TMM_PDU_T_D_DISABLE, "D-DISABLE" }, + { TMM_PDU_T_D_ENABLE, "D-ENABLE" }, + { TMM_PDU_T_D_LOC_UPD_ACC, "D-LOCATION UPDATE ACCEPT" }, + { TMM_PDU_T_D_LOC_UPD_CMD, "D-LOCATION UPDATE COMMAND" }, + { TMM_PDU_T_D_LOC_UPD_REJ, "D-LOCATION UPDATE REJECT" }, + { TMM_PDU_T_D_LOC_UPD_PROC, "D-LOCATION UPDATE PROCEEDING" }, + { TMM_PDU_T_D_ATT_DET_GRP, "D-ATTACH/DETACH GROUP ID" }, + { TMM_PDU_T_D_ATT_DET_GRP_ACK, "D-ATTACH/DETACH GROUP ID ACK" }, + { TMM_PDU_T_D_MM_STATUS, "D-MM STATUS" }, + { TMM_PDU_T_D_MM_PDU_NOTSUPP, "MM PDU/FUNCTION NOT SUPPORTED" }, + { 0, NULL } +}; +const char *tetra_get_mm_pdut_name(uint8_t pdut, int uplink) +{ + /* FIXME: uplink */ + return get_value_string(mm_pdut_d_names, pdut); +} diff --git a/src/tetra_mm_pdu.h b/src/tetra_mm_pdu.h new file mode 100644 index 0000000..5b8c0d5 --- /dev/null +++ b/src/tetra_mm_pdu.h @@ -0,0 +1,26 @@ +#ifndef TETRA_MM_PDU_H +#define TETRA_MM_PDU_H + +/* 16.10.39 PDU Type */ +enum tetra_mm_pdu_type_d { + TMM_PDU_T_D_OTAR = 0x0, + TMM_PDU_T_D_AUTH = 0x1, + TMM_PDU_T_D_CK_CHG_DEM = 0x2, + TMM_PDU_T_D_DISABLE = 0x3, + TMM_PDU_T_D_ENABLE = 0x4, + TMM_PDU_T_D_LOC_UPD_ACC = 0x5, + TMM_PDU_T_D_LOC_UPD_CMD = 0x6, + TMM_PDU_T_D_LOC_UPD_REJ = 0x7, + /* RES */ + TMM_PDU_T_D_LOC_UPD_PROC = 0x9, + TMM_PDU_T_D_ATT_DET_GRP = 0xa, + TMM_PDU_T_D_ATT_DET_GRP_ACK = 0xb, + TMM_PDU_T_D_MM_STATUS = 0xc, + /* RES */ + /* RES */ + TMM_PDU_T_D_MM_PDU_NOTSUPP = 0xf +}; + +const char *tetra_get_mm_pdut_name(uint8_t pdut, int uplink); + +#endif diff --git a/src/tetra_prim.h b/src/tetra_prim.h new file mode 100644 index 0000000..c0c1606 --- /dev/null +++ b/src/tetra_prim.h @@ -0,0 +1,48 @@ +#ifndef TETRA_PRIM_H +#define TETRA_PRIM_H + +#include + +#include "osmo_prim.h" + +#include "tetra_common.h" + +enum tetra_saps { + TETRA_SAP_TP, /* between PHY and lower MAC */ + TETRA_SAP_TMV, /* beetween lower and upper MAC */ + TETRA_SAP_TMA, + TETRA_SAP_TMB, + TETRA_SAP_TMD, +}; + +/* Table 23.1 */ +enum tmv_sap_prim { + PRIM_TMV_UNITDATA, + PRIM_TMV_CONFIGURE, +}; + +/* Table 23.2 */ +struct tmv_unitdata_param { + uint32_t mac_block_len; + enum tetra_log_chan lchan; + int crc_ok; + uint32_t scrambling_code; + struct tetra_tdma_time tdma_time; + uint8_t mac_block[412]; /* maximum length of bits in a non-QAM chan */ +}; + +/* Table 23.3 */ +struct tmv_configure_param { + /* FIXME */ + uint32_t scrambling_rx; +}; + +struct tetra_tmvsap_prim { + struct osmo_prim_hdr oph; + union { + struct tmv_unitdata_param unitdata; + struct tmv_configure_param configure; + } u; +}; + +#endif diff --git a/src/tetra_tdma.c b/src/tetra_tdma.c new file mode 100644 index 0000000..8fffa39 --- /dev/null +++ b/src/tetra_tdma.c @@ -0,0 +1,100 @@ +/* TETRA TDMA time functions, see Section 7.3 of EN 300 392-2 */ + +/* (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 "tetra_tdma.h" + +static void normalize_mn(struct tetra_tdma_time *tm) +{ + if (tm->mn > 60) + tm->mn = (tm->mn%60); +} + +static void normalize_fn(struct tetra_tdma_time *tm) +{ + uint32_t mn_delta; + + if (tm->fn > 18) { + mn_delta = tm->fn/60; + tm->fn = (tm->fn%18); + tm->mn += mn_delta; + } + normalize_mn(tm); +} + +static void normalize_tn(struct tetra_tdma_time *tm) +{ + uint32_t fn_delta; + + if (tm->tn > 4) { + fn_delta = tm->tn/4; + tm->tn = (tm->tn%4); + tm->fn += fn_delta; + } + normalize_fn(tm); +} + +static void normalize_sn(struct tetra_tdma_time *tm) +{ + uint32_t tn_delta; + + if (tm->sn > 255) { + tn_delta = (tm->sn/255); + tm->sn = (tm->sn % 255) + 1; + tm->tn += tn_delta; + } + normalize_tn(tm); +} + +void tetra_tdma_time_add_sym(struct tetra_tdma_time *tm, uint32_t sym_count) +{ + tm->sn += sym_count; + normalize_sn(tm); +} + +void tetra_tdma_time_add_tn(struct tetra_tdma_time *tm, uint32_t tn_count) +{ + tm->tn += tn_count; + normalize_tn(tm); +} + +void tetra_tdma_time_add_fn(struct tetra_tdma_time *tm, uint32_t fn_count) +{ + tm->fn += fn_count; + normalize_fn(tm); +} + +char *tetra_tdma_time_dump(const struct tetra_tdma_time *tm) +{ + static char buf[256]; + + snprintf(buf, sizeof(buf), "%02u/%02u/%u/%03u", tm->mn, tm->fn, tm->tn, tm->sn); + + return buf; +} + +uint32_t tetra_tdma_time2fn(struct tetra_tdma_time *tm) +{ + /* FIXME: add hyperframe number !! */ + return (tm->mn *18) + tm->fn; +} diff --git a/src/tetra_tdma.h b/src/tetra_tdma.h new file mode 100644 index 0000000..86c17af --- /dev/null +++ b/src/tetra_tdma.h @@ -0,0 +1,20 @@ +#ifndef TETRA_TDMA_H +#define TETRA_TDMA_H + +#include + +struct tetra_tdma_time { + uint32_t sn; /* symbol number (1 ... 255) */ + uint32_t tn; /* timeslot number (1 .. 4) */ + uint32_t fn; /* frame number (1 .. 18) */ + uint32_t mn; /* multiframe number (1 .. 60) */ +}; + +void tetra_tdma_time_add_sym(struct tetra_tdma_time *tm, uint32_t sym_count); +void tetra_tdma_time_add_tn(struct tetra_tdma_time *tm, uint32_t tn_count); +void tetra_tdma_time_add_fn(struct tetra_tdma_time *tm, uint32_t fn_count); +char *tetra_tdma_time_dump(const struct tetra_tdma_time *tm); + +uint32_t tetra_tdma_time2fn(struct tetra_tdma_time *tm); + +#endif diff --git a/src/tetra_upper_mac.c b/src/tetra_upper_mac.c new file mode 100644 index 0000000..d607ee2 --- /dev/null +++ b/src/tetra_upper_mac.c @@ -0,0 +1,222 @@ +/* TETRA upper MAC layer main routine, above TMV-SAP */ + +/* (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 "tetra_common.h" +#include "tetra_prim.h" +#include "tetra_upper_mac.h" +#include "tetra_mac_pdu.h" +#include "tetra_mle_pdu.h" +#include "tetra_mm_pdu.h" +#include "tetra_gsmtap.h" + +static void rx_bcast(struct tetra_tmvsap_prim *tmvp) +{ + struct tmv_unitdata_param *tup = &tmvp->u.unitdata; + struct tetra_si_decoded sid; + uint32_t dl_freq, ul_freq; + int i; + + memset(&sid, 0, sizeof(sid)); + macpdu_decode_sysinfo(&sid, tup->mac_block); + + dl_freq = tetra_dl_carrier_hz(sid.freq_band, + sid.main_carrier, + sid.freq_offset); + + ul_freq = tetra_ul_carrier_hz(sid.freq_band, + sid.main_carrier, + sid.freq_offset, + sid.duplex_spacing, + sid.reverse_operation); + + printf("BNCH SYSINFO (DL %u Hz, UL %u Hz), service_details 0x%04x ", + dl_freq, ul_freq, sid.mle_si.bs_service_details); + if (sid.cck_valid_no_hf) + printf("CCK ID %u", sid.cck_id); + else + printf("Hyperframe %u", sid.hyperframe_number); + printf("\n"); + for (i = 0; i < 12; i++) + printf("\t%s: %u\n", tetra_get_bs_serv_det_name(1 << i), + sid.mle_si.bs_service_details & (1 << i) ? 1 : 0); + +} + +const char *tetra_alloc_dump(const struct tetra_chan_alloc_decoded *cad) +{ + static char buf[64]; + char *cur = buf; + + cur += sprintf(cur, "%s (TN%u/%s/%uHz)", + tetra_get_alloc_t_name(cad->type), cad->timeslot, + tetra_get_ul_dl_name(cad->ul_dl), + tetra_dl_carrier_hz(cad->ext_carr.freq_band, cad->carrier_nr, + cad->ext_carr.freq_offset)); + + return buf; +} + +static int rx_tm_sdu(uint8_t *bits, unsigned int len) +{ + uint8_t mle_pdisc = bits_to_uint(bits, 3); + + printf("TM-SDU(%s): %s", tetra_get_mle_pdisc_name(mle_pdisc), + bitdump(bits, len)); + switch (mle_pdisc) { + case TMLE_PDISC_MM: + printf(" %s", tetra_get_mm_pdut_name(bits_to_uint(bits+3, 4), 0)); + break; + default: + break; + } + return len; +} + +static void rx_resrc(struct tetra_tmvsap_prim *tmvp) +{ + struct tmv_unitdata_param *tup = &tmvp->u.unitdata; + struct tetra_resrc_decoded rsd; + int tmpdu_offset; + + memset(&rsd, 0, sizeof(rsd)); + tmpdu_offset = macpdu_decode_resource(&rsd, tup->mac_block); + + printf("RESOURCE Encr=%u, Length_ind=%u Addr=%s ", + rsd.encryption_mode, rsd.length_ind, + tetra_addr_dump(&rsd.addr)); + + if (rsd.chan_alloc_pres) + printf("ChanAlloc=%s ", tetra_alloc_dump(&rsd.cad)); + + if (rsd.length_ind && rsd.encryption_mode == 0) { + int len_bits = rsd.length_ind*8; + if (tup->mac_block + tmpdu_offset + len_bits > + tup->mac_block + tup->mac_block_len) + len_bits = tup->mac_block_len - tmpdu_offset; + rx_tm_sdu(tup->mac_block + tmpdu_offset, len_bits); + } + + printf("\n"); +} + +static void dump_access(struct tetra_access_field *acc, unsigned int num) +{ + printf("ACCESS%u: %c/%u ", num, 'A'+acc->access_code, acc->base_frame_len); +} + +static void rx_aach(struct tetra_tmvsap_prim *tmvp) +{ + struct tmv_unitdata_param *tup = &tmvp->u.unitdata; + struct tetra_acc_ass_decoded aad; + + printf("ACCESS-ASSIGN PDU: "); + + memset(&aad, 0, sizeof(aad)); + macpdu_decode_access_assign(&aad, tup->mac_block, + tup->tdma_time.fn == 18 ? 1 : 0); + + if (aad.pres & TETRA_ACC_ASS_PRES_ACCESS1) + dump_access(&aad.access[0], 1); + if (aad.pres & TETRA_ACC_ASS_PRES_ACCESS2) + dump_access(&aad.access[1], 2); + if (aad.pres & TETRA_ACC_ASS_PRES_DL_USAGE) + printf("DL_USAGE: %s ", tetra_get_dl_usage_name(aad.dl_usage)); + if (aad.pres & TETRA_ACC_ASS_PRES_UL_USAGE) + printf("UL_USAGE: %s ", tetra_get_ul_usage_name(aad.ul_usage)); + + printf("\n"); +} + +static int rx_tmv_unitdata_ind(struct tetra_tmvsap_prim *tmvp) +{ + struct tmv_unitdata_param *tup = &tmvp->u.unitdata; + uint8_t pdu_type = bits_to_uint(tup->mac_block, 2); + const char *pdu_name; + struct msgb *gsmtap_msg; + + if (tup->lchan == TETRA_LC_BSCH) + pdu_name = "SYNC"; + else if (tup->lchan == TETRA_LC_AACH) + pdu_name = "ACCESS-ASSIGN"; + else { + pdu_type = bits_to_uint(tup->mac_block, 2); + pdu_name = tetra_get_macpdu_name(pdu_type); + } + + printf("TMV-UNITDATA.ind %s %s CRC=%u %s\n", + tetra_tdma_time_dump(&tup->tdma_time), + tetra_get_lchan_name(tup->lchan), + tup->crc_ok, pdu_name); + + if (!tup->crc_ok) + return 0; + + gsmtap_msg = tetra_gsmtap_makemsg(&tup->tdma_time, tup->lchan, tup->tdma_time.tn, + /* FIXME: */ 0, 0, 0, tup->mac_block, tup->mac_block_len); + if (gsmtap_msg) + tetra_gsmtap_sendmsg(gsmtap_msg); + + switch (tup->lchan) { + case TETRA_LC_AACH: + rx_aach(tmvp); + break; + case TETRA_LC_BNCH: + case TETRA_LC_UNKNOWN: + case TETRA_LC_SCH_F: + if (pdu_type == TETRA_PDU_T_BROADCAST) + rx_bcast(tmvp); + if (pdu_type == TETRA_PDU_T_MAC_RESOURCE) + rx_resrc(tmvp); + break; + } + if (pdu_type == TETRA_PDU_T_MAC_FRAG_END) { + if (tup->mac_block[3] == TETRA_MAC_FRAGE_FRAG) + rx_tm_sdu(tup->mac_block+4, 100 /*FIXME*/); + } + + return 0; +} + +int upper_mac_prim_recv(struct osmo_prim_hdr *op, void *priv) +{ + struct tetra_tmvsap_prim *tmvp; + int rc; + + switch (op->sap) { + case TETRA_SAP_TMV: + tmvp = (struct tetra_tmvsap_prim *) op; + rc = rx_tmv_unitdata_ind(tmvp); + break; + default: + printf("primitive on unknown sap\n"); + break; + } + + free(op); + + return rc; +} diff --git a/src/tetra_upper_mac.h b/src/tetra_upper_mac.h new file mode 100644 index 0000000..9e77b57 --- /dev/null +++ b/src/tetra_upper_mac.h @@ -0,0 +1,8 @@ +#ifndef TETRA_UPPER_MAC_H +#define TETRA_UPPER_MAC_H + +#include "tetra_prim.h" + +int upper_mac_prim_recv(struct osmo_prim_hdr *op, void *priv); + +#endif