gsm/sms: Moved utility functions of SMS processing to new gsm0411_utils.c

Written-by: Andreas Eversberg <jolly@eversberg.eu>
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c
new file mode 100644
index 0000000..21938bf
--- /dev/null
+++ b/src/gsm/gsm0411_utils.c
@@ -0,0 +1,306 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * 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 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 <time.h>
+#include <string.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#define GSM411_ALLOC_SIZE	1024
+#define GSM411_ALLOC_HEADROOM	128
+
+struct msgb *gsm411_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM,
+				   "GSM 04.11");
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static uint8_t bcdify(uint8_t value)
+{
+	uint8_t ret;
+
+	ret = value / 10;
+	ret |= (value % 10) << 4;
+
+	return ret;
+}
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+static uint8_t unbcdify(uint8_t value)
+{
+	uint8_t ret;
+
+	if ((value & 0x0F) > 9 || (value >> 4) > 9)
+		LOGP(DLSMS, LOGL_ERROR,
+		     "unbcdify got too big nibble: 0x%02X\n", value);
+
+	ret = (value&0x0F)*10;
+	ret += value>>4;
+
+	return ret;
+}
+
+/* Generate 03.40 TP-SCTS */
+void gsm340_gen_scts(uint8_t *scts, time_t time)
+{
+	struct tm *tm = gmtime(&time);
+
+	*scts++ = bcdify(tm->tm_year % 100);
+	*scts++ = bcdify(tm->tm_mon + 1);
+	*scts++ = bcdify(tm->tm_mday);
+	*scts++ = bcdify(tm->tm_hour);
+	*scts++ = bcdify(tm->tm_min);
+	*scts++ = bcdify(tm->tm_sec);
+	*scts++ = bcdify(0); /* GMT */
+}
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+time_t gsm340_scts(uint8_t *scts)
+{
+	struct tm tm;
+	uint8_t yr = unbcdify(*scts++);
+	int ofs;
+
+	memset(&tm, 0x00, sizeof(struct tm));
+
+	if (yr <= 80)
+		tm.tm_year = 100 + yr;
+	else
+		tm.tm_year = yr;
+	tm.tm_mon  = unbcdify(*scts++) - 1;
+	tm.tm_mday = unbcdify(*scts++);
+	tm.tm_hour = unbcdify(*scts++);
+	tm.tm_min  = unbcdify(*scts++);
+	tm.tm_sec  = unbcdify(*scts++);
+
+	/* according to gsm 03.40 time zone is
+	   "expressed in quarters of an hour" */
+	ofs = unbcdify(*scts++) * 15*60;
+
+	return mktime(&tm) - ofs;
+}
+
+/* Return the default validity period in minutes */
+static unsigned long gsm340_vp_default(void)
+{
+	unsigned long minutes;
+	/* Default validity: two days */
+	minutes = 24 * 60 * 2;
+	return minutes;
+}
+
+/* Decode validity period format 'relative' */
+static unsigned long gsm340_vp_relative(uint8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.1 */
+	uint8_t vp;
+	unsigned long minutes;
+
+	vp = *(sms_vp);
+	if (vp <= 143)
+		minutes = vp + 1 * 5;
+	else if (vp <= 167)
+		minutes = 12*60 + (vp-143) * 30;
+	else if (vp <= 196)
+		minutes = vp-166 * 60 * 24;
+	else
+		minutes = vp-192 * 60 * 24 * 7;
+	return minutes;
+}
+
+/* Decode validity period format 'absolute' */
+static unsigned long gsm340_vp_absolute(uint8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.2 */
+	time_t expires, now;
+	unsigned long minutes;
+
+	expires = gsm340_scts(sms_vp);
+	now = time(NULL);
+	if (expires <= now)
+		minutes = 0;
+	else
+		minutes = (expires-now)/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in integer representation' */
+static unsigned long gsm340_vp_relative_integer(uint8_t *sms_vp)
+{
+	uint8_t vp;
+	unsigned long minutes;
+	vp = *(sms_vp);
+	if (vp == 0) {
+		LOGP(DLSMS, LOGL_ERROR,
+		     "reserved relative_integer validity period\n");
+		return gsm340_vp_default();
+	}
+	minutes = vp/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in semi-octet representation' */
+static unsigned long gsm340_vp_relative_semioctet(uint8_t *sms_vp)
+{
+	unsigned long minutes;
+	minutes = unbcdify(*sms_vp++)*60;  /* hours */
+	minutes += unbcdify(*sms_vp++);    /* minutes */
+	minutes += unbcdify(*sms_vp++)/60; /* seconds */
+	return minutes;
+}
+
+/* decode validity period. return minutes */
+unsigned long gsm340_validity_period(uint8_t sms_vpf, uint8_t *sms_vp)
+{
+	uint8_t fi; /* functionality indicator */
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		return gsm340_vp_relative(sms_vp);
+	case GSM340_TP_VPF_ABSOLUTE:
+		return gsm340_vp_absolute(sms_vp);
+	case GSM340_TP_VPF_ENHANCED:
+		/* Chapter 9.2.3.12.3 */
+		fi = *sms_vp++;
+		/* ignore additional fi */
+		if (fi & (1<<7)) sms_vp++;
+		/* read validity period format */
+		switch (fi & 0x7) {
+		case 0x0:
+			return gsm340_vp_default(); /* no vpf specified */
+		case 0x1:
+			return gsm340_vp_relative(sms_vp);
+		case 0x2:
+			return gsm340_vp_relative_integer(sms_vp);
+		case 0x3:
+			return gsm340_vp_relative_semioctet(sms_vp);
+		default:
+			/* The GSM spec says that the SC should reject any
+			   unsupported and/or undefined values. FIXME */
+			LOGP(DLSMS, LOGL_ERROR,
+			     "Reserved enhanced validity period format\n");
+			return gsm340_vp_default();
+		}
+	case GSM340_TP_VPF_NONE:
+	default:
+		return gsm340_vp_default();
+	}
+}
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs)
+{
+	uint8_t cgbits = dcs >> 4;
+	enum sms_alphabet alpha = DCS_NONE;
+
+	if ((cgbits & 0xc) == 0) {
+		if (cgbits & 2) {
+			LOGP(DLSMS, LOGL_NOTICE,
+			     "Compressed SMS not supported yet\n");
+			return 0xffffffff;
+		}
+
+		switch ((dcs >> 2)&0x03) {
+		case 0:
+			alpha = DCS_7BIT_DEFAULT;
+			break;
+		case 1:
+			alpha = DCS_8BIT_DATA;
+			break;
+		case 2:
+			alpha = DCS_UCS2;
+			break;
+		}
+	} else if (cgbits == 0xc || cgbits == 0xd)
+		alpha = DCS_7BIT_DEFAULT;
+	else if (cgbits == 0xe)
+		alpha = DCS_UCS2;
+	else if (cgbits == 0xf) {
+		if (dcs & 4)
+			alpha = DCS_8BIT_DATA;
+		else
+			alpha = DCS_7BIT_DEFAULT;
+	}
+
+	return alpha;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type,
+	uint8_t plan, const char *number)
+{
+	int len_in_bytes;
+
+	/* prevent buffer overflows */
+	if (strlen(number) > 20)
+		number = "";
+
+//	oa[1] = 0xb9; /* networks-specific number, private numbering plan */
+	oa[1] = 0x80 | (type << 4) | plan;
+
+	len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, number);
+
+	/* GSM 03.40 tells us the length is in 'useful semi-octets' */
+	oa[0] = strlen(number) & 0xff;
+
+	return len_in_bytes;
+}
+
+/* Prefix msg with a RP header */
+int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type,
+	uint8_t rp_msg_ref)
+{
+	struct gsm411_rp_hdr *rp;
+	uint8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return 0;
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans,
+			     uint8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = proto | (trans << 4);
+	gh->msg_type = msg_type;
+
+	return 0;
+}