mirror of https://gerrit.osmocom.org/osmo-tetra
initial import of Osmocom TETRA phy and lower MAC code
This commit is contained in:
parent
a4c4e5a1ab
commit
7ee08faee0
|
@ -0,0 +1,8 @@
|
|||
*.o
|
||||
*.a
|
||||
*.pyc
|
||||
.*.swp
|
||||
conv_enc_test
|
||||
burst_test
|
||||
float_to_bits
|
||||
crc_test
|
|
@ -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
|
|
@ -0,0 +1,74 @@
|
|||
/* Test program for tetra burst synchronizer */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#include "tetra_common.h"
|
||||
#include <phy/tetra_burst.h>
|
||||
#include <phy/tetra_burst_sync.h>
|
||||
#include "tetra_gsmtap.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fd;
|
||||
struct tetra_rx_state *trs;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <file_with_1_byte_per_bit>\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);
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
#include <osmocore/bits.h>
|
||||
|
||||
|
||||
#include "tetra_common.h"
|
||||
#include <lower_mac/crc_simple.h>
|
||||
#include <lower_mac/tetra_conv_enc.h>
|
||||
#include <lower_mac/tetra_interleave.h>
|
||||
#include <lower_mac/tetra_scramb.h>
|
||||
#include <lower_mac/tetra_rm3014.h>
|
||||
#include <lower_mac/viterbi.h>
|
||||
#include <phy/tetra_burst.h>
|
||||
#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);
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tetra_common.h"
|
||||
#include <lower_mac/crc_simple.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
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 <infile> <outfile>\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);
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <lower_mac/crc_simple.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CRC_16
|
||||
#define CRC_16
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* 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
|
|
@ -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 <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#include <tetra_common.h>
|
||||
#include <lower_mac/tetra_conv_enc.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef TETRA_CONV_ENC_H
|
||||
#define TETRA_CONV_ENC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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 */
|
|
@ -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 <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#include <tetra_common.h>
|
||||
|
||||
#include <lower_mac/tetra_interleave.h>
|
||||
|
||||
/* 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];
|
||||
}
|
||||
}
|
|
@ -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 <stdint.h>
|
||||
|
||||
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 */
|
|
@ -0,0 +1,257 @@
|
|||
/* TETRA lower MAC layer main routine, between TP-SAP and TMV-SAP */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#include <tetra_common.h>
|
||||
#include <tetra_tdma.h>
|
||||
#include <phy/tetra_burst.h>
|
||||
#include <phy/tetra_burst_sync.h>
|
||||
#include <lower_mac/crc_simple.h>
|
||||
#include <lower_mac/tetra_scramb.h>
|
||||
#include <lower_mac/tetra_interleave.h>
|
||||
#include <lower_mac/tetra_conv_enc.h>
|
||||
#include <tetra_prim.h>
|
||||
#include "tetra_upper_mac.h"
|
||||
#include <lower_mac/viterbi.h>
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/* Shortened (30,14) Reed-Muller (RM) code */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <lower_mac/tetra_rm3014.h>
|
||||
|
||||
/* 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;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef TETRA_RM3014_H
|
||||
#define TETRA_RM3014_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
|
@ -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 <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <lower_mac/tetra_scramb.h>
|
||||
|
||||
/* 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;
|
||||
}
|
|
@ -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 <stdint.h>
|
||||
|
||||
/* 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 */
|
|
@ -0,0 +1,25 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lower_mac/viterbi_cch.h>
|
||||
|
||||
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);
|
||||
}
|
|
@ -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 */
|
|
@ -0,0 +1,163 @@
|
|||
/* (C) 2011 by Sylvain Munaut <tnt@246tNt.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lower_mac/viterbi.h>
|
||||
|
||||
|
||||
#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<n; i++) {
|
||||
int bit = input[i];
|
||||
uint8_t out = conv_cch_next_output[state][bit];
|
||||
state = conv_cch_next_state[state][bit];
|
||||
|
||||
output[(i<<2) ] = (out >> 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<CONV_CCH_N_STATES; i++) {
|
||||
ae[i] = MAX_AE;
|
||||
}
|
||||
|
||||
/* Scan the treillis */
|
||||
for (i=0; i<n; i++) {
|
||||
/* Reset next accumulated error */
|
||||
for (s=0; s<CONV_CCH_N_STATES; s++) {
|
||||
ae_next[s] = MAX_AE;
|
||||
}
|
||||
|
||||
/* Get input */
|
||||
in_sym[0] = input[(i<<2) ];
|
||||
in_sym[1] = input[(i<<2)+1];
|
||||
in_sym[2] = input[(i<<2)+2];
|
||||
in_sym[3] = input[(i<<2)+3];
|
||||
|
||||
/* Scan all states */
|
||||
for (s=0; s<CONV_CCH_N_STATES; s++)
|
||||
{
|
||||
/* Scan possible input bits */
|
||||
for (b=0; b<2; b++)
|
||||
{
|
||||
int nae;
|
||||
|
||||
/* Next output and state */
|
||||
uint8_t out = conv_cch_next_output[s][b];
|
||||
uint8_t state = conv_cch_next_state[s][b];
|
||||
|
||||
/* Expand */
|
||||
ev_sym[0] = (out >> 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<CONV_CCH_N_STATES; s++)
|
||||
{
|
||||
if (ae[s] < min_ae) {
|
||||
min_ae = ae[s];
|
||||
min_state = s;
|
||||
}
|
||||
}
|
||||
|
||||
/* Traceback */
|
||||
cur_state = min_state;
|
||||
for (i=n-1; i >= 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;
|
||||
}
|
|
@ -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 */
|
|
@ -0,0 +1,159 @@
|
|||
/* (C) 2011 by Sylvain Munaut <tnt@246tNt.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <lower_mac/viterbi_tch.h>
|
||||
|
||||
|
||||
#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<n; i++) {
|
||||
int bit = input[i];
|
||||
uint8_t out = conv_tch_next_output[state][bit];
|
||||
state = conv_tch_next_state[state][bit];
|
||||
|
||||
output[(i<<2) ] = (out >> 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<CONV_TCH_N_STATES; i++) {
|
||||
ae[i] = MAX_AE;
|
||||
}
|
||||
|
||||
/* Scan the treillis */
|
||||
for (i=0; i<n; i++) {
|
||||
/* Reset next accumulated error */
|
||||
for (s=0; s<CONV_TCH_N_STATES; s++) {
|
||||
ae_next[s] = MAX_AE;
|
||||
}
|
||||
|
||||
/* Get input */
|
||||
in_sym[0] = input[(i<<2) ];
|
||||
in_sym[1] = input[(i<<2)+1];
|
||||
in_sym[2] = input[(i<<2)+2];
|
||||
|
||||
/* Scan all states */
|
||||
for (s=0; s<CONV_TCH_N_STATES; s++)
|
||||
{
|
||||
/* Scan possible input bits */
|
||||
for (b=0; b<2; b++)
|
||||
{
|
||||
int nae;
|
||||
|
||||
/* Next output and state */
|
||||
uint8_t out = conv_tch_next_output[s][b];
|
||||
uint8_t state = conv_tch_next_state[s][b];
|
||||
|
||||
/* Expand */
|
||||
ev_sym[0] = (out >> 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<CONV_TCH_N_STATES; s++)
|
||||
{
|
||||
if (ae[s] < min_ae) {
|
||||
min_ae = ae[s];
|
||||
min_state = s;
|
||||
}
|
||||
}
|
||||
|
||||
/* Traceback */
|
||||
cur_state = min_state;
|
||||
for (i=n-1; i >= 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;
|
||||
}
|
|
@ -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 */
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef OSMO_PRIMITIVE_H
|
||||
#define OSMO_PRIMITIVE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,345 @@
|
|||
/* Implementation of TETRA Physical Layer, i.e. what is _below_
|
||||
* CRC, FEC, Interleaving and Scrambling */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <phy/tetra_burst.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef TETRA_BURST_H
|
||||
#define TETRA_BURST_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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 */
|
|
@ -0,0 +1,141 @@
|
|||
/* Implementation of TETRA burst synchronization */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#include <tetra_common.h>
|
||||
#include <phy/tetra_burst.h>
|
||||
#include <tetra_tdma.h>
|
||||
#include <phy/tetra_burst_sync.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef TETRA_BURST_SYNC_H
|
||||
#define TETRA_BURST_SYNC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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 */
|
|
@ -0,0 +1,99 @@
|
|||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <osmocore/bitvec.h>
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#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)));
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
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();
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/* Common routines for the TETRA PHY/MAC implementation */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
#include <osmocore/bits.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef TETRA_COMMON_H
|
||||
#define TETRA_COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
|
@ -0,0 +1,134 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <osmocore/msgb.h>
|
||||
#include <osmocore/gsmtap.h>
|
||||
#include <osmocore/bits.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#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 <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,375 @@
|
|||
/* Implementation of some PDU parsing of the TETRA upper MAC */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,38 @@
|
|||
/* Implementation of TETRA MLE PDU parsing */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef TETRA_MLE_PDU_H
|
||||
#define TETRA_MLE_PDU_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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
|
|
@ -0,0 +1,46 @@
|
|||
/* Implementation of TETRA MM PDU parsing */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef TETRA_PRIM_H
|
||||
#define TETRA_PRIM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
|
@ -0,0 +1,100 @@
|
|||
/* TETRA TDMA time functions, see Section 7.3 of EN 300 392-2 */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef TETRA_TDMA_H
|
||||
#define TETRA_TDMA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,222 @@
|
|||
/* TETRA upper MAC layer main routine, above TMV-SAP */
|
||||
|
||||
/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <osmocore/utils.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue