aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/msg_utils.c
blob: 5c6bbbeaf11f676012dae77d74b1e2f4464938d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/* (C) 2014 by sysmocom s.f.m.c. GmbH
 *
 * 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 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 <osmo-bts/msg_utils.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/amr.h>

#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
#include <osmocom/gsm/abis_nm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/trau/osmo_ortp.h>

#include <arpa/inet.h>


static int check_fom(struct abis_om_hdr *omh, size_t len)
{
	if (omh->length != len) {
		LOGP(DL1C, LOGL_ERROR, "Incorrect OM hdr length value %d %zu\n",
		     omh->length, len);
		return -1;
	}

	if (len < sizeof(struct abis_om_fom_hdr)) {
		LOGP(DL1C, LOGL_ERROR, "FOM header insufficient space %zu %zu\n",
		     len, sizeof(struct abis_om_fom_hdr));
		return -1;
	}

	return 0;
}

static int check_manuf(struct msgb *msg, struct abis_om_hdr *omh, size_t msg_size)
{
	int type;
	size_t size;

	if (msg_size < 1) {
		LOGP(DL1C, LOGL_ERROR, "No ManId Length Indicator %zu\n",
		     msg_size);
		return -1;
	}

	if (omh->data[0] >= msg_size - 1) {
		LOGP(DL1C, LOGL_ERROR,
		     "Insufficient message space for this ManId Length %d %zu\n",
		     omh->data[0], msg_size - 1);
		return -1;
	}

	if (omh->data[0] == sizeof(abis_nm_ipa_magic) &&
		strncmp(abis_nm_ipa_magic, (const char *)omh->data + 1,
			sizeof(abis_nm_ipa_magic)) == 0) {
		type = OML_MSG_TYPE_IPA;
		size = sizeof(abis_nm_ipa_magic) + 1;
	} else if (omh->data[0] == sizeof(abis_nm_osmo_magic) &&
		strncmp(abis_nm_osmo_magic, (const char *) omh->data + 1,
			sizeof(abis_nm_osmo_magic)) == 0) {
		type = OML_MSG_TYPE_OSMO;
		size = sizeof(abis_nm_osmo_magic) + 1;
	} else {
		LOGP(DL1C, LOGL_ERROR, "Manuf Label Unknown\n");
		return -1;
	}

	/* we have verified that the vendor string fits */
	msg->l3h = omh->data + size;
	if (check_fom(omh, msgb_l3len(msg)) != 0)
		return -1;
	return type;
}

/* update lchan SID status */
void lchan_set_marker(bool t, struct gsm_lchan *lchan)
{
	if (t)
		lchan->tch.ul_sid = true;
	else if (lchan->tch.ul_sid) {
		lchan->tch.ul_sid = false;
		lchan->rtp_tx_marker = true;
	}
}

/* store the last SID frame in lchan context */
/*! \brief Store the last SID frame in lchan context
 *  \param[in] lchan Logical channel on which we check scheduling
 *  \param[in] l1_payload buffer with SID data
 *  \param[in] length length of l1_payload
 *  \param[in] fn Frame Number for which we check scheduling
 *  \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID
 *  \param[in] cmr Codec Mode Request (AMR-specific, ignored otherwise)
 *  \param[in] cmi Codec Mode Indication (AMR-specific, ignored otherwise)
 */
void save_last_sid(struct gsm_lchan *lchan, const uint8_t *l1_payload,
		   size_t length, uint32_t fn, int update, uint8_t cmr,
		   int8_t cmi)
{
	size_t amr = (update < 0) ? 0 : 2,
	    copy_len = OSMO_MIN(length + 1, ARRAY_SIZE(lchan->tch.last_sid.buf));

	lchan->tch.last_sid.len = copy_len + amr;
	lchan->tch.last_sid.fn = fn;
	lchan->tch.last_sid.is_update = update;

	if (amr)
		amr_set_mode_pref(lchan->tch.last_sid.buf, &lchan->tch.amr_mr,
				  cmi, cmr);
	memcpy(lchan->tch.last_sid.buf + amr, l1_payload, copy_len);
}

/*! \brief Check if enough time has passed since last SID (if any) to repeat it
 *  \param[in] lchan Logical channel on which we check scheduling
 *  \param[in] fn Frame Number for which we check scheduling
 *  \returns true if transmission can be omitted, false otherwise
 */
static inline bool dtx_amr_sid_optional(const struct gsm_lchan *lchan,
					uint32_t fn)
{
	/* Compute approx. time delta based on Fn duration */
	uint32_t delta = GSM_FN_TO_MS(fn - lchan->tch.last_sid.fn);

	/* according to 3GPP TS 26.093 A.5.1.1: */
	if (lchan->tch.last_sid.is_update) {
		/* SID UPDATE should be repeated every 8th RTP frame */
		if (delta < GSM_RTP_FRAME_DURATION_MS * 8)
			return true;
		return false;
	}
	/* 3rd frame after SID FIRST should be SID UPDATE */
	if (delta < GSM_RTP_FRAME_DURATION_MS * 3)
		return true;
	return false;
}

static inline bool fn_chk(const uint8_t *t, uint32_t fn)
{
	uint8_t i;
	for (i = 0; i < ARRAY_SIZE(t); i++)
		if (fn % 104 == t[i])
			return false;
	return true;
}

/*! \brief Check if TX scheduling is optional for a given FN in case of DTX
 *  \param[in] lchan Logical channel on which we check scheduling
 *  \param[in] fn Frame Number for which we check scheduling
 *  \returns true if transmission can be omitted, false otherwise
 */
static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn)
{
	/* According to 3GPP TS 45.008 § 8.3: */
	static const uint8_t f[] = { 52, 53, 54, 55, 56, 57, 58, 59 },
				h0[] = { 0, 2, 4, 6, 52, 54, 56, 58 },
				h1[] = { 14, 16, 18, 20, 66, 68, 70, 72 };
	if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1) {
		if (lchan->type == GSM_LCHAN_TCH_F)
			return fn_chk(f, fn);
		else
			return fn_chk(lchan->nr ? h1 : h0, fn);
	}
	return false;
}

/* repeat last SID if possible, returns SID length + 1 or 0 */
/*! \brief Repeat last SID if possible in case of DTX
 *  \param[in] lchan Logical channel on which we check scheduling
 *  \param[in] dst Buffer to copy last SID into
 *  \returns Number of bytes copied + 1 (to accommodate for extra byte with
 *           payload type), 0 if there's nothing to copy
 */
uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn)
{
	/* FIXME: add EFR support */
	if (lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)
		return 0;

	if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
		if (dtx_sched_optional(lchan, fn))
			return 0;
	} else
		if (dtx_amr_sid_optional(lchan, fn))
			return 0;

	if (lchan->tch.last_sid.len) {
		memcpy(dst, lchan->tch.last_sid.buf, lchan->tch.last_sid.len);
		lchan->tch.last_sid.fn = fn;
		return lchan->tch.last_sid.len + 1;
	}

	LOGP(DL1C, LOGL_DEBUG, "Have to send %s frame on TCH but SID buffer "
	     "is empty - sent nothing\n",
	     get_value_string(gsm48_chan_mode_names, lchan->tch_mode));

	return 0;
}

/**
 * Return 0 in case the IPA structure is okay and in this
 * case the l2h will be set to the beginning of the data.
 */
int msg_verify_ipa_structure(struct msgb *msg)
{
	struct ipaccess_head *hh;

	if (msgb_l1len(msg) < sizeof(struct ipaccess_head)) {
		LOGP(DL1C, LOGL_ERROR,
			"Ipa header insufficient space %d %zu\n",
			msgb_l1len(msg), sizeof(struct ipaccess_head));
		return -1;
	}

	hh = (struct ipaccess_head *) msg->l1h;

	if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) {
		LOGP(DL1C, LOGL_ERROR,
			"Incorrect ipa header msg size %d %zu\n",
			ntohs(hh->len), msgb_l1len(msg) - sizeof(struct ipaccess_head));
		return -1;
	}

	if (hh->proto == IPAC_PROTO_OSMO) {
		struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data;
		if (ntohs(hh->len) < sizeof(*hh_ext)) {
			LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n");
			return -1;
		}
		msg->l2h = hh_ext->data;
	} else
		msg->l2h = hh->data;

	return 0;
}

/**
 * \brief Verify the structure of the OML message and set l3h
 *
 * This function verifies that the data in \param in msg is a proper
 * OML message. This code assumes that msg->l2h points to the
 * beginning of the OML message. In the successful case the msg->l3h
 * will be set and will point to the FOM header. The value is undefined
 * in all other cases.
 *
 * \param msg The message to analyze starting from msg->l2h.
 * \return In case the structure is correct a positive number will be
 * returned and msg->l3h will point to the FOM. The number is a
 * classification of the vendor type of the message.
 */
int msg_verify_oml_structure(struct msgb *msg)
{
	struct abis_om_hdr *omh;

	if (msgb_l2len(msg) < sizeof(*omh)) {
		LOGP(DL1C, LOGL_ERROR, "Om header insufficient space %d %zu\n",
		     msgb_l2len(msg), sizeof(*omh));
		return -1;
	}

	omh = (struct abis_om_hdr *) msg->l2h;
	if (omh->mdisc != ABIS_OM_MDISC_FOM &&
	    omh->mdisc != ABIS_OM_MDISC_MANUF) {
		LOGP(DL1C, LOGL_ERROR, "Incorrect om mdisc value %x\n",
		     omh->mdisc);
		return -1;
	}

	if (omh->placement != ABIS_OM_PLACEMENT_ONLY) {
		LOGP(DL1C, LOGL_ERROR, "Incorrect om placement value %x %x\n",
		     omh->placement, ABIS_OM_PLACEMENT_ONLY);
		return -1;
	}

	if (omh->sequence != 0) {
		LOGP(DL1C, LOGL_ERROR, "Incorrect om sequence value %d\n",
		     omh->sequence);
		return -1;
	}

	if (omh->mdisc == ABIS_OM_MDISC_MANUF)
		return check_manuf(msg, omh, msgb_l2len(msg) - sizeof(*omh));

	msg->l3h = omh->data;
	if (check_fom(omh, msgb_l3len(msg)) != 0)
		return -1;
	return OML_MSG_TYPE_ETSI;
}