mirror of
				https://github.com/brmlab/osmo-tetra.git
				synced 2025-10-31 07:24:01 +01:00 
			
		
		
		
	initial import of Osmocom TETRA phy and lower MAC code
This commit is contained in:
		
							parent
							
								
									a4c4e5a1ab
								
							
						
					
					
						commit
						7ee08faee0
					
				
					 45 changed files with 4217 additions and 0 deletions
				
			
		
							
								
								
									
										8
									
								
								src/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| *.o | ||||
| *.a | ||||
| *.pyc | ||||
| .*.swp | ||||
| conv_enc_test | ||||
| burst_test | ||||
| float_to_bits | ||||
| crc_test | ||||
							
								
								
									
										25
									
								
								src/Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Makefile
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										74
									
								
								src/burst_test.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/burst_test.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										351
									
								
								src/conv_enc_test.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								src/conv_enc_test.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										73
									
								
								src/crc_test.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/crc_test.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										117
									
								
								src/float_to_bits.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/float_to_bits.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										106
									
								
								src/lower_mac/crc_simple.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/lower_mac/crc_simple.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										48
									
								
								src/lower_mac/crc_simple.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/lower_mac/crc_simple.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										301
									
								
								src/lower_mac/tetra_conv_enc.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								src/lower_mac/tetra_conv_enc.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/lower_mac/tetra_conv_enc.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lower_mac/tetra_conv_enc.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										59
									
								
								src/lower_mac/tetra_interleave.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/lower_mac/tetra_interleave.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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]; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/lower_mac/tetra_interleave.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/lower_mac/tetra_interleave.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										257
									
								
								src/lower_mac/tetra_lower_mac.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								src/lower_mac/tetra_lower_mac.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										96
									
								
								src/lower_mac/tetra_rm3014.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/lower_mac/tetra_rm3014.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/lower_mac/tetra_rm3014.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/lower_mac/tetra_rm3014.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										99
									
								
								src/lower_mac/tetra_scramb.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/lower_mac/tetra_scramb.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/lower_mac/tetra_scramb.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/lower_mac/tetra_scramb.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										25
									
								
								src/lower_mac/viterbi.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/lower_mac/viterbi.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/lower_mac/viterbi.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/lower_mac/viterbi.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										163
									
								
								src/lower_mac/viterbi_cch.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/lower_mac/viterbi_cch.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/lower_mac/viterbi_cch.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lower_mac/viterbi_cch.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										159
									
								
								src/lower_mac/viterbi_tch.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/lower_mac/viterbi_tch.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/lower_mac/viterbi_tch.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lower_mac/viterbi_tch.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										19
									
								
								src/osmo_prim.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/osmo_prim.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										345
									
								
								src/phy/tetra_burst.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								src/phy/tetra_burst.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/phy/tetra_burst.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/phy/tetra_burst.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										141
									
								
								src/phy/tetra_burst_sync.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/phy/tetra_burst_sync.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/phy/tetra_burst_sync.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/phy/tetra_burst_sync.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 */ | ||||
							
								
								
									
										99
									
								
								src/testpdu.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/testpdu.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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))); | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/testpdu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/testpdu.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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(); | ||||
| 
 | ||||
							
								
								
									
										157
									
								
								src/tetra_common.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/tetra_common.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/tetra_common.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/tetra_common.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										134
									
								
								src/tetra_gsmtap.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/tetra_gsmtap.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/tetra_gsmtap.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/tetra_gsmtap.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										375
									
								
								src/tetra_mac_pdu.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								src/tetra_mac_pdu.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										221
									
								
								src/tetra_mac_pdu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/tetra_mac_pdu.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										38
									
								
								src/tetra_mle_pdu.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/tetra_mle_pdu.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/tetra_mle_pdu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/tetra_mle_pdu.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										46
									
								
								src/tetra_mm_pdu.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/tetra_mm_pdu.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/tetra_mm_pdu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/tetra_mm_pdu.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										48
									
								
								src/tetra_prim.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/tetra_prim.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										100
									
								
								src/tetra_tdma.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/tetra_tdma.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/tetra_tdma.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/tetra_tdma.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										222
									
								
								src/tetra_upper_mac.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/tetra_upper_mac.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/tetra_upper_mac.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/tetra_upper_mac.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Harald Welte
						Harald Welte