mirror of https://gerrit.osmocom.org/osmo-tetra
349 lines
8.6 KiB
C
349 lines
8.6 KiB
C
/* 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 <osmocom/core/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 };
|
|
|
|
/* Voice */
|
|
const uint8_t P_rate8_12[] = { 0, 1, 2, 4 };
|
|
const uint8_t P_rate8_18[] = { 0, 1, 2, 3, 4, 5, 7, 8, 10, 11 };
|
|
const uint8_t P_rate8_17[] = { 0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23 };
|
|
|
|
struct puncturer {
|
|
enum tetra_rcpc_puncturer type;
|
|
const uint8_t *P;
|
|
uint8_t t;
|
|
uint8_t period;
|
|
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,
|
|
.period = 8,
|
|
.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,
|
|
.period = 8,
|
|
.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,
|
|
.period = 8,
|
|
.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,
|
|
.period = 8,
|
|
.i_func = &i_func_148,
|
|
};
|
|
|
|
/* EN 300 395-2 Section 5.5.2.1 */
|
|
static const struct puncturer punct_112_168 = {
|
|
.type = TETRA_RCPC_PUNCT_112_168,
|
|
.P = P_rate8_12,
|
|
.t = 3,
|
|
.period = 6,
|
|
.i_func = &i_func_equals,
|
|
};
|
|
|
|
/* EN 300 395-2 Section 5.5.2.2 */
|
|
static const struct puncturer punct_72_162 = {
|
|
.type = TETRA_RCPC_PUNCT_72_162,
|
|
.P = P_rate8_18,
|
|
.t = 9,
|
|
.period = 12,
|
|
.i_func = &i_func_equals,
|
|
};
|
|
|
|
/* EN 300 395-2 Section 5.6.2.1 */
|
|
static const struct puncturer punct_38_80 = {
|
|
.type = TETRA_RCPC_PUNCT_72_162,
|
|
.P = P_rate8_17,
|
|
.t = 17,
|
|
.period = 24,
|
|
.i_func = &i_func_equals,
|
|
};
|
|
|
|
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,
|
|
[TETRA_RCPC_PUNCT_112_168] = &punct_112_168,
|
|
[TETRA_RCPC_PUNCT_72_162] = &punct_72_162,
|
|
[TETRA_RCPC_PUNCT_38_80] = &punct_38_80,
|
|
};
|
|
|
|
/* 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 = punct->period * ((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 = punct->period * ((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;
|
|
uint16_t mother_rate; /* rate 1/3 for speech, 1/4 for data */
|
|
enum tetra_rcpc_puncturer punct;
|
|
};
|
|
|
|
static const struct punct_test_param punct_test_params[] = {
|
|
{ 80, 120, 4, TETRA_RCPC_PUNCT_2_3 }, /* BSCH */
|
|
{ 292, 432, 4, TETRA_RCPC_PUNCT_292_432 }, /* TCH/4.8 */
|
|
{ 148, 432, 4, TETRA_RCPC_PUNCT_148_432 }, /* TCH/2.4 */
|
|
{ 144, 216, 4, TETRA_RCPC_PUNCT_2_3 }, /* SCH/HD, BNCH, STCH */
|
|
{ 112, 168, 4, TETRA_RCPC_PUNCT_2_3 }, /* SCH/HU */
|
|
{ 288, 432, 4, TETRA_RCPC_PUNCT_2_3 }, /* SCH/F */
|
|
{ 112, 168, 3, TETRA_RCPC_PUNCT_112_168 }, /* Speech class 1 */
|
|
{ 72, 162, 3, TETRA_RCPC_PUNCT_72_162 }, /* Speech class 2 */
|
|
{ 38, 80, 3, TETRA_RCPC_PUNCT_38_80 }, /* Speech class 2 in STCH */
|
|
};
|
|
|
|
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, j, mother_len;
|
|
|
|
printf("==> Testing Puncture/Depuncture mode %u (%u/%u)\n",
|
|
ptp->punct, ptp->type2_len, ptp->type3_len);
|
|
|
|
mother_len = ptp->type2_len * ptp->mother_rate;
|
|
mother_buf = malloc(mother_len);
|
|
depunct_buf = malloc(ptp->type2_len * ptp->mother_rate);
|
|
type3_buf = malloc(ptp->type3_len);
|
|
|
|
/* initialize mother buffer with sequence of bytes starting at 0 */
|
|
for (i = 0, j = 0; i < mother_len; i++, j++) {
|
|
if (j == 0xff)
|
|
j = 0;
|
|
mother_buf[i] = j;
|
|
}
|
|
|
|
/* 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", osmo_hexdump(mother_buf, mother_len));
|
|
DEBUGP("PUNC: %s\n", osmo_hexdump(type3_buf, ptp->type3_len));
|
|
DEBUGP("DEPU: %s\n", osmo_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;
|
|
}
|