aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/rtp_input_preen.c
blob: 5729229f2916c44ae90bff4e637ea5f10e2ca2e2 (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
/*
 * This module implements a helper function for the RTP input path:
 * validates incoming RTP payloads, makes the accept-or-drop decision,
 * and for some codecs signals additional required actions such as
 * dropping one header octet.
 *
 * Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however,
 * Mother Mychaela's contributions are NOT subject to copyright.
 * No rights reserved, all rights relinquished.
 *
 * 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 <stdbool.h>
#include <stdint.h>

#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>

#include <osmocom/codec/codec.h>

#include <osmo-bts/lchan.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/rtp_input_preen.h>

static bool amr_is_octet_aligned(const uint8_t *rtp_pl, unsigned rtp_pl_len)
{
	/*
	 * Logic: If 1st bit padding is not zero, packet is either:
	 * - bandwidth-efficient AMR payload.
	 * - malformed packet.
	 * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0)
	 * with 4th,5ht,6th AMR payload to 0 matches padding==0.
	 * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b):
	 * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B.
	 * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B.
	 * We cannot use other fields to match since they are inside the AMR
	 * payload bits which are unknown.
	 * As a result, this function may return false positive (true) for some AMR
	 * 4,75 AMR frames, but given the length, CMR and FT read is the same as a
	 * consequence, the damage in here is harmless other than being unable to
	 * decode the audio at the other side.
	 */
	#define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f)
	#define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03)

	if (rtp_pl_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl))
		return false;

	return true;
}

static enum pl_input_decision
input_preen_fr(const uint8_t *rtp_pl, unsigned rtp_pl_len)
{
	if (rtp_pl_len != GSM_FR_BYTES)
		return PL_DECISION_DROP;
	if ((rtp_pl[0] & 0xF0) != 0xD0)
		return PL_DECISION_DROP;
	return PL_DECISION_ACCEPT;
}

static enum pl_input_decision
input_preen_efr(const uint8_t *rtp_pl, unsigned rtp_pl_len)
{
	if (rtp_pl_len != GSM_EFR_BYTES)
		return PL_DECISION_DROP;
	if ((rtp_pl[0] & 0xF0) != 0xC0)
		return PL_DECISION_DROP;
	return PL_DECISION_ACCEPT;
}

static enum pl_input_decision
input_preen_hr(const uint8_t *rtp_pl, unsigned rtp_pl_len,
		bool *rfc5993_sid_flag)
{
	switch (rtp_pl_len) {
	case GSM_HR_BYTES:
		/* RTP input matches our internal format - we are good */
		return PL_DECISION_ACCEPT;
	case GSM_HR_BYTES_RTP_RFC5993:
		/* Validate ToC octet: for payload of this length to be valid,
		 * the F bit must be 0 and the FT field must be either 0 (good
		 * speech) or 2 (good SID). */
		switch (rtp_pl[0] & 0xF0) {
		case 0x00:
			break;
		case 0x20:
			*rfc5993_sid_flag = true;
			break;
		default:
			/* invalid payload */
			return PL_DECISION_DROP;
		}
		/* Strip ToC octet, leaving only "pure" TS 101 318 payload. */
		return PL_DECISION_STRIP_HDR_OCTET;
	default:
		/* invalid payload */
		return PL_DECISION_DROP;
	}
}

enum pl_input_decision
rtp_payload_input_preen(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
			unsigned rtp_pl_len, bool *rfc5993_sid_flag)
{
	/* If rtp continuous-streaming is enabled, we shall emit RTP packets
	 * with zero-length payloads as BFI markers. In a TrFO scenario such
	 * RTP packets sent by call leg A will be received by call leg B,
	 * hence we need to handle them gracefully. For the purposes of a BTS
	 * that runs on its own TDMA timing and does not need timing ticks from
	 * an incoming RTP stream, the correct action upon receiving such
	 * timing-tick-only RTP packets should be the same as when receiving
	 * no RTP packet at all. The simplest way to produce that behavior
	 * is to treat zero-length RTP payloads as invalid. */
	if (rtp_pl_len == 0)
		return PL_DECISION_DROP;

	switch (lchan->tch_mode) {
	case GSM48_CMODE_SPEECH_V1:
		if (lchan->type == GSM_LCHAN_TCH_F)
			return input_preen_fr(rtp_pl, rtp_pl_len);
		else
			return input_preen_hr(rtp_pl, rtp_pl_len, rfc5993_sid_flag);
	case GSM48_CMODE_SPEECH_EFR:
		return input_preen_efr(rtp_pl, rtp_pl_len);
	case GSM48_CMODE_SPEECH_AMR:
		/* Avoid forwarding bw-efficient AMR to lower layers,
		 * most bts models don't support it. */
		if (!amr_is_octet_aligned(rtp_pl, rtp_pl_len)) {
			LOGPLCHAN(lchan, DL1P, LOGL_NOTICE,
				  "RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n",
				  osmo_hexdump(rtp_pl, rtp_pl_len));
			return PL_DECISION_DROP;
		}
		return PL_DECISION_ACCEPT;
	default:
		return PL_DECISION_ACCEPT;
	}
}