From 12b5508114e53621aaabf47d4834a80284dfe4dc Mon Sep 17 00:00:00 2001
From: pab <pab@pab>
Date: Wed, 19 Sep 2012 00:20:30 +0000
Subject: [PATCH] Added support of UCS2 in received USSD

---
 src/host/layer23/src/mobile/gsm480_ss.c            |  107 ++++++++++++++++----
 .../libosmocore/include/osmocom/gsm/gsm_utils.h    |   46 ++++++++-
 src/shared/libosmocore/src/gsm/gsm_utils.c         |   27 +++++
 3 files changed, 158 insertions(+), 22 deletions(-)

diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c
index fda6288..fe6be32 100644
--- a/src/host/layer23/src/mobile/gsm480_ss.c
+++ b/src/host/layer23/src/mobile/gsm480_ss.c
@@ -24,6 +24,9 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <locale.h>
 
 #include <osmocom/core/msgb.h>
 #include <osmocom/bb/common/logging.h>
@@ -439,7 +442,7 @@ static int gsm480_tx_invoke(struct gsm_trans *trans, struct msgb *msg,
 	/* Pre-pend the invoke ID */
         msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, trans->ss.invoke_id);
 
-	/* Wrap this up as invoke vomponent */
+	/* Wrap this up as invoke component */
 	if (msg_type == GSM0480_MTYPE_FACILITY)
 		msgb_wrap_with_TL_asn(msg, GSM0480_CTYPE_RETURN_RESULT);
 	else
@@ -592,7 +595,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans)
 		LOGP(DSS, LOGL_INFO, "Existing transaction.\n");
 		return gsm480_tx_ussd(trans, GSM0480_MTYPE_FACILITY, code);
 	}
-
+	
 	/* do nothing, if hangup is received */
 	if (!strcmp(code, "hangup"))
 		return 0;
@@ -733,6 +736,7 @@ 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;
 
 	/* sequence tag */
 	if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) {
@@ -751,14 +755,27 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "DSC tag too short\n");
 		return -EINVAL;
 	}
+
 	if (data[0] != ASN1_OCTET_STRING_TAG || tag_len != 1) {
 		LOGP(DSS, LOGL_NOTICE, "Expecting DSC tag\n");
 		return -EINVAL;
 	}
-	if (tag_data[0] != 0x0f) {
-		LOGP(DSS, LOGL_NOTICE, "DSC not 0x0f\n");
-		return -EINVAL;
+	
+	switch (tag_data[0])
+	{
+		case DCS_GSM7DEAFULT:
+		case DCS_GSM7DEAFULT2:
+		case DCS_0f:
+			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;
 	data = tag_data + tag_len;
 
@@ -767,25 +784,70 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "Text tag too short\n");
 		return -EINVAL;
 	}
+	
 	if (data[0] != ASN1_OCTET_STRING_TAG) {
 		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';
+	
+	if (dcs_charset == 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);
+	}
+	else if (dcs_charset == CGB_GEN_CHARSET16)
+	{
+		// USC2 charset, see ISO/IEC 10646
+		const char *old_locale = setlocale(LC_ALL, NULL);
+		const char *new_locale;
+		wchar_t text_ucs2[256];
+		uint8_t row = 0;
+	
+		num_chars = tag_len / 2; // since 1 char = 2 bytes
+		
+		// convert pairs of bytes to 2-byte chars
+		for (i = 0; i < num_chars; i++) {
+			text_ucs2[i] = ((tag_data[2*i]<<8)|tag_data[2*i+1]);
+			// hope first non-row-0 is desired row
+			if ( !row && tag_data[2*i]) row = tag_data[2*i];
+		}
+		text_ucs2[num_chars] = '\0';
+		
+		// set locale to print received symbols, print them and restore locale
+		new_locale = row2locale(row);
+		if (new_locale) {
+			setlocale(LC_ALL, new_locale);
+			printf("Received and decoded USSD:\n%ls\n", text_ucs2);
+			setlocale(LC_ALL, old_locale);
+
+			// report success
+			sprintf(text, "USSD decoded from UCS2(locale=%s)", new_locale);
+		}
+		else
+		{
+			sprintf(text, "USSD not-decoded from UCS2(unknown locale (row=0x%02x))", row);
+		}
+		gsm480_ss_result(trans->ms, text, 0);
+	}
+	else
+	{
+		/* Should not go to this section but just in case of mistake */
+		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;
 }
@@ -935,6 +997,7 @@ static int gsm480_rx_result(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "Invoke ID too short\n");
 		return -EINVAL;
 	}
+
 	if (data[0] != GSM0480_COMPIDTAG_INVOKE_ID || tag_len != 1) {
 		LOGP(DSS, LOGL_NOTICE, "Expecting invoke ID\n");
 		return -EINVAL;
@@ -960,6 +1023,7 @@ static int gsm480_rx_result(struct gsm_trans *trans, const uint8_t *data,
 		LOGP(DSS, LOGL_NOTICE, "Sequence tag too short\n");
 		return -EINVAL;
 	}
+
 	if (data[0] != GSM_0480_SEQUENCE_TAG) {
 		LOGP(DSS, LOGL_NOTICE, "Expecting Sequence Tag, trying "
 			"Operation Tag\n");
@@ -974,6 +1038,7 @@ operation:
 		LOGP(DSS, LOGL_NOTICE, "Operation too short\n");
 		return -EINVAL;
 	}
+
 	if (data[0] != GSM0480_OPERATION_CODE || tag_len != 1) {
 		LOGP(DSS, LOGL_NOTICE, "Expecting Operation Code\n");
 		return -EINVAL;
@@ -1223,8 +1288,8 @@ static int gsm480_mmss_ind(int mmss_msg, struct gsm_trans *trans,
 	/* pull the MMSS header */
 	msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));
 
-	LOGP(DSS, LOGL_INFO, "(ms %s) Received est/data '%u'\n", ms->name,
-		msg_type);
+	LOGP(DSS, LOGL_INFO, "(ms %s) Received est/data '%u'='%X'\n", ms->name,
+		msg_type, msg_type);
 
 	switch (msg_type) {
 	case GSM0480_MTYPE_RELEASE_COMPLETE:
diff --git a/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h b/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
index 6d31672..dcf3ea2 100644
--- a/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
+++ b/src/shared/libosmocore/include/osmocom/gsm/gsm_utils.h
@@ -56,6 +56,8 @@ enum gsm_band {
 const char *gsm_band_name(enum gsm_band band);
 enum gsm_band gsm_band_parse(const char *mhz);
 
+const char * row2locale(uint8_t row);
+
 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);
@@ -143,9 +145,51 @@ enum gsm_chan_t {
 	GSM_LCHAN_TCH_H,
 	GSM_LCHAN_UNKNOWN,
 	GSM_LCHAN_CCCH,
-	GSM_LCHAN_PDTCH,
 	_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 uncompressed */
+#define CGB_GEN_UNCOM      0x00    /* xx0xxxxxb, text is compressed (23.042) */
+#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_0f            (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..114c2cd 100644
--- a/src/shared/libosmocore/src/gsm/gsm_utils.c
+++ b/src/shared/libosmocore/src/gsm/gsm_utils.c
@@ -102,6 +102,33 @@ static unsigned char gsm_7bit_alphabet[] = {
 	0xff, 0x7d, 0x08, 0xff, 0xff, 0xff, 0x7c, 0xff, 0x0c, 0x06, 0xff, 0xff, 0x7e, 0xff, 0xff
 };
 
+// BMP row to locale conversion, for new locale update this list
+// see ISO/IEC 10646, 26 'Structure of the Basic Multilingual Plane'
+static struct {
+	uint8_t row;
+	const char * locale;
+} BMP_row2locale[] = {
+	{0x00, "en_US.UTF-8"},
+	{0x04, "ru_RU.UTF-8"},
+	// end of list flag
+	{0, NULL}
+};
+
+/* Get locale for UCS2 row */
+const char * row2locale(uint8_t row)
+{
+	uint8_t i = 0;
+	
+	while (BMP_row2locale[i].locale) {
+		if (BMP_row2locale[i].row == row) {
+			break;
+		}
+		i++;
+	}
+	
+	return BMP_row2locale[i].locale;
+}
+		
 /* GSM 03.38 6.2.1 Character lookup for decoding */
 static int gsm_septet_lookup(uint8_t ch)
 {
-- 
1.7.2.5

