gsm0808_enc/dec_channel_type: support data

Related: OS#4393
Change-Id: Ib7b75c9d86aace329decf20003b68de459021c64
diff --git a/include/osmocom/gsm/protocol/gsm_08_08.h b/include/osmocom/gsm/protocol/gsm_08_08.h
index a33b01a..eacb954 100644
--- a/include/osmocom/gsm/protocol/gsm_08_08.h
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -500,6 +500,42 @@
 	GSM0808_PERM_HR6	= 0x45, /*!< OHR AMR */
 };
 
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Transparent: Data Rate */
+enum gsm0808_data_rate_transp {
+	GSM0808_DATA_RATE_TRANSP_32000	  = 0x3a,
+	GSM0808_DATA_RATE_TRANSP_28800	  = 0x39,
+	GSM0808_DATA_RATE_TRANSP_14400	  = 0x18,
+	GSM0808_DATA_RATE_TRANSP_09600	  = 0x10,
+	GSM0808_DATA_RATE_TRANSP_04800	  = 0x11,
+	GSM0808_DATA_RATE_TRANSP_02400	  = 0x12,
+	GSM0808_DATA_RATE_TRANSP_01200	  = 0x13,
+	GSM0808_DATA_RATE_TRANSP_00600	  = 0x14,
+	GSM0808_DATA_RATE_TRANSP_01200_75 = 0x15,
+};
+
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Non-Transparent: Radio Interface Data Rate (preferred) */
+enum gsm0808_data_rate_non_transp {
+	GSM0808_DATA_RATE_NON_TRANSP_12000_6000	= 0x00,
+	GSM0808_DATA_RATE_NON_TRANSP_43500	= 0x34,
+	GSM0808_DATA_RATE_NON_TRANSP_29000	= 0x31,
+	GSM0808_DATA_RATE_NON_TRANSP_14500	= 0x14,
+	GSM0808_DATA_RATE_NON_TRANSP_12000	= 0x10,
+	GSM0808_DATA_RATE_NON_TRANSP_06000	= 0x11,
+};
+
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Non-Transparent: Allowed Radio Interface Data Rate (all possible allowed) */
+enum gsm0808_data_rate_allowed_r_if {
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_43500 = 0x40,
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_32000 = 0x20,
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_29000 = 0x10,
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_14500 = 0x08,
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12000 = 0x02,
+	GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_06000 = 0x01,
+};
+
 extern const struct value_string gsm0808_permitted_speech_names[];
 static inline const char *gsm0808_permitted_speech_name(enum gsm0808_permitted_speech val)
 { return get_value_string(gsm0808_permitted_speech_names, val); }
@@ -658,13 +694,32 @@
 	uint8_t len;
 };
 
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Asymmetry Preference (used for data, non-transparent service) */
+enum gsm0808_channel_type_asym_pref {
+	GSM0808_CT_ASYM_PREF_NOT_APPLICABLE = 0,
+	GSM0808_CT_ASYM_PREF_UL		    = 1,
+	GSM0808_CT_ASYM_PREF_DL		    = 2,
+	GSM0808_CT_ASYM_PREF_SPARE	    = 3,
+};
+
 /* 3GPP TS 48.008 3.2.2.11 Channel Type */
 #define CH_TYPE_PERM_SPCH_MAXLEN 9
 struct gsm0808_channel_type {
 	uint8_t ch_indctr;
 	uint8_t ch_rate_type;
+
+	/* Speech only */
 	uint8_t perm_spch[CH_TYPE_PERM_SPCH_MAXLEN];
 	unsigned int perm_spch_len;
+
+	/* Data only */
+	bool data_transparent;
+	uint8_t data_rate;
+	bool data_rate_allowed_is_set;
+	uint8_t data_rate_allowed;
+	bool data_asym_pref_is_set;
+	enum gsm0808_channel_type_asym_pref data_asym_pref;
 };
 
 /* 3GPP TS 48.008 3.2.2.10 Encryption Information */
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 83d2ce7..4993539 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -499,12 +499,6 @@
 
 	OSMO_ASSERT(ct->perm_spch_len <= CHANNEL_TYPE_ELEMENT_MAXLEN - 2);
 
-	/* FIXME: Implement encoding support for Data
-	 * and Speech + CTM Text Telephony */
-	if ((ct->ch_indctr & 0x0f) != GSM0808_CHAN_SPEECH
-	    && (ct->ch_indctr & 0x0f) != GSM0808_CHAN_SIGN)
-		OSMO_ASSERT(false);
-
 	msgb_put_u8(msg, GSM0808_IE_CHANNEL_TYPE);
 	tlv_len = msgb_put(msg, 1);
 	old_tail = msg->tail;
@@ -512,12 +506,44 @@
 	msgb_put_u8(msg, ct->ch_indctr & 0x0f);
 	msgb_put_u8(msg, ct->ch_rate_type);
 
-	for (i = 0; i < ct->perm_spch_len; i++) {
-		byte = ct->perm_spch[i];
+	switch (ct->ch_indctr) {
+	case GSM0808_CHAN_DATA:
+		byte = ct->data_rate;
 
-		if (i < ct->perm_spch_len - 1)
-			byte |= 0x80;
+		if (ct->data_transparent)
+			byte |= 0x40; /* Set T/NT */
+
+		if (ct->data_rate_allowed_is_set) {
+			OSMO_ASSERT(!ct->data_transparent);
+			byte |= 0x80; /* Set ext */
+			msgb_put_u8(msg, byte);
+
+			byte = ct->data_rate_allowed;
+			if (ct->data_asym_pref_is_set) {
+				byte |= 0x80; /* Set ext */
+				msgb_put_u8(msg, byte);
+
+				/* Set asymmetry indication, rest is spare */
+				byte = ct->data_asym_pref << 5;
+			}
+		}
 		msgb_put_u8(msg, byte);
+		break;
+	case GSM0808_CHAN_SPEECH:
+	case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+		for (i = 0; i < ct->perm_spch_len; i++) {
+			byte = ct->perm_spch[i];
+
+			if (i < ct->perm_spch_len - 1)
+				byte |= 0x80;
+			msgb_put_u8(msg, byte);
+		}
+		break;
+	case GSM0808_CHAN_SIGN:
+		/* Octet 5 is spare */
+		break;
+	default:
+		OSMO_ASSERT(false);
 	}
 
 	*tlv_len = (uint8_t) (msg->tail - old_tail);
@@ -549,17 +575,57 @@
 	ct->ch_rate_type = (*elem) & 0x0f;
 	elem++;
 
-	for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
-		if (elem - old_elem >= len)
-			return -EOVERFLOW;
-
+	switch (ct->ch_indctr) {
+	case GSM0808_CHAN_DATA:
 		byte = *elem;
 		elem++;
-		ct->perm_spch[i] = byte & 0x7f;
-		if ((byte & 0x80) == 0x00)
-			break;
+		ct->data_transparent = byte & 0x40; /* T/NT */
+		ct->data_rate = byte & 0x3f;
+
+		/* Optional extension for non-transparent service */
+		if (byte & 0x80) {
+			if (ct->data_transparent)
+				return -EINVAL;
+			if (elem - old_elem >= len)
+				return -EOVERFLOW;
+			byte = *elem;
+			elem++;
+
+			ct->data_rate_allowed_is_set = true;
+			ct->data_rate_allowed = byte & 0x7f;
+
+			/* Optional extension */
+			if (byte & 0x80) {
+				if (elem - old_elem >= len)
+					return -EOVERFLOW;
+				byte = *elem;
+				elem++;
+
+				ct->data_asym_pref_is_set = true;
+				ct->data_asym_pref = byte & 0x60 >> 5;
+			}
+		}
+		break;
+	case GSM0808_CHAN_SPEECH:
+	case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+		for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+			if (elem - old_elem >= len)
+				return -EOVERFLOW;
+
+			byte = *elem;
+			elem++;
+			ct->perm_spch[i] = byte & 0x7f;
+			if ((byte & 0x80) == 0x00)
+				break;
+		}
+		ct->perm_spch_len = i + 1;
+		break;
+	case GSM0808_CHAN_SIGN:
+		/* Octet 5 is spare */
+		break;
+	default:
+		return -ENOTSUP;
 	}
-	ct->perm_spch_len = i + 1;
 
 	return (int)(elem - old_elem);
 }
diff --git a/tests/gsm0808/gsm0808_test.c b/tests/gsm0808/gsm0808_test.c
index a9f1311..a03f276 100644
--- a/tests/gsm0808/gsm0808_test.c
+++ b/tests/gsm0808/gsm0808_test.c
@@ -1086,6 +1086,88 @@
 	msgb_free(msg);
 }
 
+static void test_gsm0808_enc_dec_channel_type_data(void)
+{
+	struct gsm0808_channel_type enc_ct = {
+		.ch_indctr = GSM0808_CHAN_DATA,
+		.ch_rate_type = GSM0808_DATA_HALF_PREF,
+
+		.data_transparent = true,
+		.data_rate = GSM0808_DATA_RATE_TRANSP_04800,
+	};
+	struct gsm0808_channel_type dec_ct = {};
+	struct msgb *msg;
+	uint8_t ct_enc_expected[] = { GSM0808_IE_CHANNEL_TYPE,
+		0x03, 0x02, 0x0b, 0x51,
+	};
+	uint8_t rc_enc;
+	int rc_dec;
+
+	msg = msgb_alloc(1024, "output buffer");
+	rc_enc = gsm0808_enc_channel_type(msg, &enc_ct);
+	OSMO_ASSERT(rc_enc == 5);
+	if (memcmp(ct_enc_expected, msg->data, msg->len)) {
+		printf("   got: %s\n", osmo_hexdump(msg->data, msg->len));
+		printf("expect: %s\n", osmo_hexdump(ct_enc_expected, sizeof(ct_enc_expected)));
+		OSMO_ASSERT(false);
+	}
+
+	rc_dec = gsm0808_dec_channel_type(&dec_ct, msg->data + 2, msg->len - 2);
+	OSMO_ASSERT(rc_dec == 3);
+	OSMO_ASSERT(dec_ct.ch_indctr == enc_ct.ch_indctr);
+	OSMO_ASSERT(dec_ct.ch_rate_type == enc_ct.ch_rate_type);
+	OSMO_ASSERT(dec_ct.data_transparent == enc_ct.data_transparent);
+	OSMO_ASSERT(dec_ct.data_rate == enc_ct.data_rate);
+	OSMO_ASSERT(dec_ct.data_rate_allowed_is_set == enc_ct.data_rate_allowed_is_set);
+	OSMO_ASSERT(dec_ct.data_asym_pref_is_set == enc_ct.data_asym_pref_is_set);
+
+	msgb_free(msg);
+}
+
+static void test_gsm0808_enc_dec_channel_type_data_asym_pref(void)
+{
+	struct gsm0808_channel_type enc_ct = {
+		.ch_indctr = GSM0808_CHAN_DATA,
+		.ch_rate_type = GSM0808_DATA_HALF_PREF,
+
+		.data_transparent = false,
+		.data_rate = GSM0808_DATA_RATE_NON_TRANSP_06000,
+		.data_rate_allowed_is_set = true,
+		.data_rate_allowed = GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_06000
+				     | GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12000
+				     | GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_14500,
+		.data_asym_pref_is_set = true,
+		.data_asym_pref = GSM0808_CT_ASYM_PREF_UL,
+	};
+	struct gsm0808_channel_type dec_ct = {};
+	struct msgb *msg;
+	uint8_t ct_enc_expected[] = { GSM0808_IE_CHANNEL_TYPE,
+		0x05, 0x02, 0x0b, 0x91, 0x8b, 0x20,
+	};
+	uint8_t rc_enc;
+	int rc_dec;
+
+	msg = msgb_alloc(1024, "output buffer");
+	rc_enc = gsm0808_enc_channel_type(msg, &enc_ct);
+	OSMO_ASSERT(rc_enc == 7);
+	if (memcmp(ct_enc_expected, msg->data, msg->len)) {
+		printf("   got: %s\n", osmo_hexdump(msg->data, msg->len));
+		printf("expect: %s\n", osmo_hexdump(ct_enc_expected, sizeof(ct_enc_expected)));
+		OSMO_ASSERT(false);
+	}
+
+	rc_dec = gsm0808_dec_channel_type(&dec_ct, msg->data + 2, msg->len - 2);
+	OSMO_ASSERT(rc_dec == 5);
+	OSMO_ASSERT(dec_ct.ch_indctr == enc_ct.ch_indctr);
+	OSMO_ASSERT(dec_ct.ch_rate_type == enc_ct.ch_rate_type);
+	OSMO_ASSERT(dec_ct.data_transparent == enc_ct.data_transparent);
+	OSMO_ASSERT(dec_ct.data_rate == enc_ct.data_rate);
+	OSMO_ASSERT(dec_ct.data_rate_allowed_is_set == enc_ct.data_rate_allowed_is_set);
+	OSMO_ASSERT(dec_ct.data_asym_pref_is_set == enc_ct.data_asym_pref_is_set);
+
+	msgb_free(msg);
+}
+
 static void test_gsm0808_enc_dec_channel_type_speech(void)
 {
 	struct gsm0808_channel_type enc_ct = {
@@ -1122,9 +1204,29 @@
 	struct gsm0808_channel_type ct;
 	int rc;
 
+	/* Unknown channel indicator */
+	const uint8_t hex1[] = { 0x05, 0x0b, 0xa1, 0x25 };
+	rc = gsm0808_dec_channel_type(&ct, hex1, sizeof(hex1));
+	OSMO_ASSERT(rc == -ENOTSUP);
+
+	/* Data: ext in Octet 5 with transparent service */
+	const uint8_t hex2[] = { 0x02, 0x0b, 0xc0, 0x00 };
+	rc = gsm0808_dec_channel_type(&ct, hex2, sizeof(hex2));
+	OSMO_ASSERT(rc == -EINVAL);
+
+	/* Data: ext in Octet 5, but too short */
+	const uint8_t hex3[] = { 0x02, 0x0b, 0x80 };
+	rc = gsm0808_dec_channel_type(&ct, hex3, sizeof(hex3));
+	OSMO_ASSERT(rc == -EOVERFLOW);
+
+	/* Data: ext in Octet 5a, but too short */
+	const uint8_t hex4[] = { 0x02, 0x0b, 0x80, 0x80 };
+	rc = gsm0808_dec_channel_type(&ct, hex4, sizeof(hex4));
+	OSMO_ASSERT(rc == -EOVERFLOW);
+
 	/* Speech: extension bit is set in last byte */
-	const uint8_t hex[] = { 0x01, 0x0b, 0xa1, 0xa5 };
-	rc = gsm0808_dec_channel_type(&ct, hex, sizeof(hex));
+	const uint8_t hex5[] = { 0x01, 0x0b, 0xa1, 0xa5 };
+	rc = gsm0808_dec_channel_type(&ct, hex5, sizeof(hex5));
 	OSMO_ASSERT(rc == -EOVERFLOW);
 }
 
@@ -2579,6 +2681,8 @@
 	test_gsm0808_enc_dec_speech_codec_with_cfg();
 	test_gsm0808_enc_dec_speech_codec_list();
 	test_gsm0808_enc_dec_empty_speech_codec_list();
+	test_gsm0808_enc_dec_channel_type_data();
+	test_gsm0808_enc_dec_channel_type_data_asym_pref();
 	test_gsm0808_enc_dec_channel_type_speech();
 	test_gsm0808_dec_channel_type_err();
 	test_gsm0808_enc_dec_encrypt_info();