From 4d5cfce20e2f23bd8e9ee95d5c16eded6dcd4978 Mon Sep 17 00:00:00 2001
From: Pavel Baturko <pabftk@gmail.com>
Date: Sat, 29 Sep 2012 01:57:06 +0000
Subject: [PATCH] USSD: Add decoding UCS2 USSD messages. UCS2 text is converted to UTF-8 and hex representation of text will be printed. Also added new DCS types in USSD decoding.

---
 src/host/layer23/src/mobile/gsm480_ss.c            |   66 +++++++++++++++-----
 .../libosmocore/include/osmocom/gsm/gsm_utils.h    |   46 ++++++++++++++
 src/shared/libosmocore/src/gsm/gsm_utils.c         |   41 ++++++++++++
 3 files changed, 137 insertions(+), 16 deletions(-)

diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c
index fda6288..0f0bdf3 100644
--- a/src/host/layer23/src/mobile/gsm480_ss.c
+++ b/src/host/layer23/src/mobile/gsm480_ss.c
@@ -733,6 +733,9 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data,
 	int i;
 	const uint8_t *tag_data;
 	int tag_len;
+	uint8_t dcs_charset;
+	uint16_t utf8_text_length;
+	uint8_t *hex_string;
 
 	/* sequence tag */
 	if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) {
@@ -755,8 +758,18 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "Expecting DSC tag\n");
 		return -EINVAL;
 	}
-	if (tag_data[0] != 0x0f) {
-		LOGP(DSS, LOGL_NOTICE, "DSC not 0x0f\n");
+	
+	switch (tag_data[0]) {
+	case DCS_GSM7DEAFULT:
+	case DCS_GSM7DEAFULT2:
+	case DCS_GSM7DEAFULT3:
+		dcs_charset = CGB_GEN_CHARSET7;
+		break;
+	case DCS_ADEL_USC2:
+		dcs_charset = CGB_GEN_CHARSET16;
+		break;
+	default:
+		LOGP(DSS, LOGL_NOTICE, "DSC <%d> unknown\n", tag_data[0]);
 		return -EINVAL;
 	}
 	len -= tag_data - data + tag_len;
@@ -771,21 +784,42 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "Expecting text tag\n");
 		return -EINVAL;
 	}
-	num_chars = tag_len * 8 / 7;
-	/* Prevent a mobile-originated buffer-overrun! */
-	if (num_chars > sizeof(text) - 1)
-		num_chars = sizeof(text) - 1;
-	text[sizeof(text) - 1] = '\0';
-	gsm_7bit_decode(text, tag_data, num_chars);
-
-	for (i = 0; text[i]; i++) {
-		if (text[i] == '\r')
-			text[i] = '\n';
+	switch (dcs_charset) {
+	case CGB_GEN_CHARSET7:
+		/* Default 7 bit charset */
+		num_chars = tag_len * 8 / 7;
+		/* Prevent a mobile-originated buffer-overrun! */
+		if (num_chars > sizeof(text) - 1)
+			num_chars = sizeof(text) - 1;
+		text[sizeof(text) - 1] = '\0';
+		gsm_7bit_decode(text, tag_data, num_chars);
+
+		for (i = 0; text[i]; i++) {
+			if (text[i] == '\r')
+				text[i] = '\n';
+		}
+		/* remove last CR, if exists */
+		if (text[0] && text[strlen(text) - 1] == '\n')
+			text[strlen(text) - 1] = '\0';
+		gsm480_ss_result(trans->ms, text, 0);
+		break;
+	case CGB_GEN_CHARSET16:
+		/* USC2 charset, see ISO/IEC 10646 */
+		num_chars = tag_len / 2;
+		utf8_text_length = ucs2_to_utf8_decode(text, sizeof(text), tag_data, num_chars);
+		hex_string = malloc(2 * utf8_text_length + 1);
+		string_to_hex(hex_string, text, utf8_text_length);
+		hex_string[2 * utf8_text_length] = '\0';
+		LOGP(DSS, LOGL_NOTICE, "Received USSD (UTF-8, hex):\n%s\n", 
+			hex_string);
+		free(hex_string);
+		sprintf(text, "USSD was printed in hex");
+
+		gsm480_ss_result(trans->ms, text, 0);
+		break;
+	default:
+		return -EINVAL;
 	}
-	/* remove last CR, if exists */
-	if (text[0] && text[strlen(text) - 1] == '\n')
-		text[strlen(text) - 1] = '\0';
-	gsm480_ss_result(trans->ms, text, 0);
 
 	return 0;
 }
diff --git a/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h b/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
index 6d31672..00a4a7d 100644
--- a/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
+++ b/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
@@ -56,6 +56,9 @@ enum gsm_band {
 const char *gsm_band_name(enum gsm_band band);
 enum gsm_band gsm_band_parse(const char *mhz);
 
+int ucs2_to_utf8_decode(char *decoded, uint16_t decoded_buf_length, const uint8_t *user_data, uint8_t length);
+void string_to_hex(uint8_t *hex_string, const char *string, uint16_t string_length);
+
 int gsm_7bit_decode(char *decoded, const uint8_t *user_data, uint8_t length);
 int gsm_7bit_decode_hdr(char *decoded, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind);
 int gsm_7bit_encode(uint8_t *result, const char *data);
@@ -147,5 +150,48 @@ enum gsm_chan_t {
 	_GSM_LCHAN_MAX
 };
 
+/* Data Coding Schemes (DCS), 23.038, 4 */
+#define CGB_GEN            0x00    /* 00xxxxxxb, General Data Coding indication */
+#define CGB_GEN_COM        0x20    /* xx1xxxxxb, text is compressed (23.042) */
+#define CGB_GEN_UNCOM      0x00    /* xx0xxxxxb, text is uncompressed */
+#define CGB_GEN_CLASS      0x10    /* xxx1xxxxb, bits 1 to 0 have a message class meaning */
+#define CGB_GEN_NOCLASS    0x00    /* xxx0xxxxb, bits 1 to 0 are reserved and have no message class */
+#define CGB_GEN_CLASS0     0x00    /* xxxxxx00b, Class 0 */
+#define CGB_GEN_CLASS1     0x01    /* xxxxxx01b, Class 1, Default meaning: ME-specific */
+#define CGB_GEN_CLASS2     0x02    /* xxxxxx10b, Class 2, (U)SIM specific message */
+#define CGB_GEN_CLASS3     0x03    /* xxxxxx11b, Class 3, Default meaning: TE specific (27.005) */
+#define CGB_GEN_CHARSET7   0x00    /* xxxx00xxb, GSM 7 bit default alphabet */
+#define CGB_GEN_CHARSET8   0x04    /* xxxx01xxb, 8 bit data */
+#define CGB_GEN_CHARSET16  0x08    /* xxxx10xxb, UCS2 (16bit) */
+#define CGB_GEN_CHARSETR   0x0C    /* xxxx11xxb, Reserved */
+
+#define CGB_AUTODEL        0x40    /* 01xxxxxxb, Message Marked for Automatic Deletion Group */
+#define CGB_RESERVED       0x8B    /* 10001011b, Reserved */
+
+#define CGB_MWI_DISC       0xC0    /* 1100xxxxb, Message Waiting Indication Group: Discard Message */
+#define CGB_MWI_STOR       0xD0    /* 1101xxxxb, Message Waiting Indication Group: Store Message */
+#define CGB_MWI_INACT      0x00    /* xxxx0xxxb, Set Indication Inactive */
+#define CGB_MWI_ACT        0x08    /* xxxx1xxxb, Set Indication Active */
+#define CGB_MWI_RESERVED   0x00    /* xxxxx0xxb, Reserved */
+#define CGB_MWI_VMAIL      0x00    /* xxxxxx00b, Voicemail Message Waiting */
+#define CGB_MWI_FAX        0x01    /* xxxxxx01b, Fax Message Waiting */
+#define CGB_MWI_EMAIL      0x02    /* xxxxxx10b, Electronic Mail Message Waiting */
+#define CGB_MWI_OTHER      0x03    /* xxxxxx11b, Other Message Waiting */
+#define CGB_MWI_STOR2      0xE0    /* 1110xxxxb, as CGB_MWI_STOR, but text in UCS2 */
+
+#define CGB_DCMC           0xF0    /* 1111xxxxb,  Data coding/message class */
+#define CGB_DCMC_RESERVED  0x08    /* xxxx0xxxb,  Reserved */
+#define CGB_DCMC_ALPH7     0x00    /* xxxxx0xxb, GSM 7 bit default alphabet  */
+#define CGB_DCMC_ALPH8     0x04    /* xxxxx0xxb, 8 bit alphabet  */
+
+/* Default 7 bit GSM */
+#define DCS_GSM7DEAFULT   (CGB_GEN    | CGB_GEN_UNCOM | CGB_GEN_NOCLASS | CGB_GEN_CHARSET7)
+/* As default, but last bit is 1.
+ * It makes no sense but some providers sends this type of DCS  */
+#define DCS_GSM7DEAFULT2  (CGB_GEN    | CGB_GEN_UNCOM | CGB_GEN_NOCLASS | CGB_GEN_CHARSET7  | CGB_GEN_CLASS1)
+/* This constant was initialy in code */
+#define DCS_GSM7DEAFULT3  (CGB_GEN    | CGB_GEN_UNCOM | CGB_GEN_NOCLASS | CGB_GEN_CHARSETR  | CGB_GEN_CLASS3)
+/* Autodel 16 bit UCS2 */
+#define DCS_ADEL_USC2     (CGB_AUTODEL| CGB_GEN_UNCOM | CGB_GEN_NOCLASS | CGB_GEN_CHARSET16)
 
 #endif
diff --git a/src/shared/libosmocore/src/gsm/gsm_utils.c b/src/shared/libosmocore/src/gsm/gsm_utils.c
index 8d072a1..7a93f90 100644
--- a/src/shared/libosmocore/src/gsm/gsm_utils.c
+++ b/src/shared/libosmocore/src/gsm/gsm_utils.c
@@ -102,6 +102,47 @@ static unsigned char gsm_7bit_alphabet[] = {
 	0xff, 0x7d, 0x08, 0xff, 0xff, 0xff, 0x7c, 0xff, 0x0c, 0x06, 0xff, 0xff, 0x7e, 0xff, 0xff
 };
 
+/* Convert UCS2 char to UTF8 char. Taken from Ben Bullock (lemoda.net) */
+int ucs2_to_utf8_decode(char *decoded, uint16_t decoded_buf_length, const uint8_t *user_data, uint8_t length)
+{
+	int i, j = 0;
+
+	for (i = 0; i < length; i++) {
+		int ucs2;
+
+		ucs2 = ((user_data[2 * i] << 8) | user_data[2 * i + 1]);
+		if (ucs2 < 0x80) {
+			if (j >= decoded_buf_length-1)
+				break;
+			decoded[j++] = ucs2;
+		} else if (ucs2 >= 0x80  && ucs2 < 0x800) {
+			if (j >= decoded_buf_length-2)
+				break;
+			decoded[j++] = (ucs2 >> 6)   | 0xC0;
+			decoded[j++] = (ucs2 & 0x3F) | 0x80;
+		} else if (ucs2 >= 0x800 && ucs2 < 0xFFFF) {
+			if (j >= decoded_buf_length-3)
+				break;
+			decoded[j++] = ((ucs2 >> 12)       ) | 0xE0;
+			decoded[j++] = ((ucs2 >> 6 ) & 0x3F) | 0x80;
+			decoded[j++] = ((ucs2      ) & 0x3F) | 0x80;
+		}
+	}
+	decoded[j] = '\0';
+
+	return j;
+}
+
+void string_to_hex(uint8_t *hex_string, const char *string, uint16_t string_length)
+{
+	int i;
+	
+	for (i = 0; i < string_length; i++) {
+		sprintf(hex_string, "%02X", (uint8_t)string[i]);
+		hex_string += 2;
+	}
+}
+		
 /* GSM 03.38 6.2.1 Character lookup for decoding */
 static int gsm_septet_lookup(uint8_t ch)
 {
-- 
1.7.2.5

