sms: Added result buffer size parameter to 7bit conv funs

The 7bit<->8bit encoding/decoding functions didn't check whether
there is still enough space in the destination buffer. Therefore a
buffer size parameter has been added to each of the functions which
is used to truncate the output if the buffer is too small.

In addition, the return value of the decoding functions has been
changed to number of characters written (excluding \0), so this
value is always equal to strlen(decoded).

The old functions are still available as wrapper functions.
diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c
index 3dd1537..e248078 100644
--- a/src/gsm/gsm_utils.c
+++ b/src/gsm/gsm_utils.c
@@ -28,7 +28,7 @@
  * This library is a collection of common code used in various
  * GSM related sub-projects inside the Osmocom family of projects.  It
  * includes A5/1 and A5/2 ciphers, COMP128v1, a LAPDm implementation,
- * a GSM TLV parser, SMS utility routines as well as 
+ * a GSM TLV parser, SMS utility routines as well as
  * protocol definitions for a series of protocols:
  * 	* Um L2 (04.06)
  * 	* Um L3 (04.08)
@@ -123,12 +123,17 @@
 }
 
 /* GSM 03.38 6.2.1 Character unpacking */
-int gsm_7bit_decode_hdr(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
+int gsm_7bit_decode_n_hdr(char *text, size_t n, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
 {
 	int i = 0;
 	int shift = 0;
-	uint8_t c;
+	uint8_t c7, c8;
 	uint8_t next_is_ext = 0;
+	const char *text_buf_begin = text;
+	const char *text_buf_end = text + n;
+	int nchars;
+
+	OSMO_ASSERT (n > 0);
 
 	/* skip the user data header */
 	if (ud_hdr_ind) {
@@ -139,50 +144,49 @@
 		septet_l = septet_l - shift;
 	}
 
-	for (i = 0; i < septet_l; i++) {
-		c =
+	for (i = 0; i < septet_l && text != text_buf_end - 1; i++) {
+		c7 =
 			((user_data[((i + shift) * 7 + 7) >> 3] <<
 			  (7 - (((i + shift) * 7 + 7) & 7))) |
 			 (user_data[((i + shift) * 7) >> 3] >>
 			  (((i + shift) * 7) & 7))) & 0x7f;
 
-		/* this is an extension character */
 		if (next_is_ext) {
+			/* this is an extension character */
 			next_is_ext = 0;
-			*(text++) = gsm_7bit_alphabet[0x7f + c];
+			c8 = gsm_7bit_alphabet[0x7f + c7];
+		} else if (c7 == 0x1b && i + 1 < septet_l) {
+			next_is_ext = 1;
 			continue;
+		} else {
+			c8 = gsm_septet_lookup(c7);
 		}
 
-		if (c == 0x1b && i + 1 < septet_l) {
-			next_is_ext = 1;
-		} else {
-			*(text++) = gsm_septet_lookup(c);
-		}
+		*(text++) = c8;
 	}
 
-	if (ud_hdr_ind)
-		i += shift;
+	nchars = text - text_buf_begin;
+
 	*text = '\0';
 
-	return i;
+	return nchars;
 }
 
-int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t septet_l)
+int gsm_7bit_decode_n(char *text, size_t n, const uint8_t *user_data, uint8_t septet_l)
 {
-	return gsm_7bit_decode_hdr(text, user_data, septet_l, 0);
+	return gsm_7bit_decode_n_hdr(text, n, user_data, septet_l, 0);
 }
 
-int gsm_7bit_decode_ussd(char *text, const uint8_t *user_data, uint8_t length)
+int gsm_7bit_decode_n_ussd(char *text, size_t n, const uint8_t *user_data, uint8_t length)
 {
-	int i;
+	int nchars;
 
-	gsm_7bit_decode_hdr(text, user_data, length, 0);
-	i = strlen(text);
+	nchars = gsm_7bit_decode_n_hdr(text, n, user_data, length, 0);
 	/* remove last <CR>, if it fits up to the end of last octet */
-	if (i && (user_data[gsm_get_octet_len(length) - 1] >> 1) == '\r')
-		text[--i] = '\0';
+	if (nchars && (user_data[gsm_get_octet_len(length) - 1] >> 1) == '\r')
+		text[--nchars] = '\0';
 
-	return i;
+	return nchars;
 }
 
 /* GSM 03.38 6.2.1 Prepare character packing */
@@ -261,38 +265,28 @@
 }
 
 /* GSM 03.38 6.2.1 Character packing */
-int gsm_7bit_encode(uint8_t *result, const char *data)
-{
-	int out;
-	return gsm_7bit_encode_oct(result, data, &out);
-}
-
-int gsm_7bit_encode_ussd(uint8_t *result, const char *data, int *octets)
-{
-	int y;
-
-	y = gsm_7bit_encode_oct(result, data, octets);
-	/* if last octet contains only one bit, add <CR> */
-	if (((y * 7) & 7) == 1)
-		result[(*octets) - 1] |= ('\r' << 1);
-	/* if last character is <CR> and completely fills last octet, add
-	 * another <CR>. */
-	if (y && ((y * 7) & 7) == 0 && (result[(*octets) - 1] >> 1) == '\r') {
-		result[(*octets)++] = '\r';
-		y++;
-	}
-
-	return y;
-}
-
-int gsm_7bit_encode_oct(uint8_t *result, const char *data, int *octets)
+int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets)
 {
 	int y = 0;
+	int o;
+	int max_septets = n * 8 / 7;
 
 	/* prepare for the worst case, every character expanding to two bytes */
 	uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t));
 	y = gsm_septet_encode(rdata, data);
-	*octets = gsm_septets2octets(result, rdata, y, 0);
+
+	if (y > max_septets) {
+		/*
+		 * Limit the number of septets to avoid the generation
+		 * of more than n octets.
+		 */
+		y = max_septets;
+	}
+
+	o = gsm_septets2octets(result, rdata, y, 0);
+
+	if (octets)
+		*octets = o;
 
 	free(rdata);
 
@@ -309,6 +303,24 @@
 	return y;
 }
 
+int gsm_7bit_encode_n_ussd(uint8_t *result, size_t n, const char *data, int *octets)
+{
+	int y;
+
+	y = gsm_7bit_encode_n(result, n, data, octets);
+	/* if last octet contains only one bit, add <CR> */
+	if (((y * 7) & 7) == 1)
+		result[(*octets) - 1] |= ('\r' << 1);
+	/* if last character is <CR> and completely fills last octet, add
+	 * another <CR>. */
+	if (y && ((y * 7) & 7) == 0 && (result[(*octets) - 1] >> 1) == '\r' && *octets < n - 1) {
+		result[(*octets)++] = '\r';
+		y++;
+	}
+
+	return y;
+}
+
 /* convert power class to dBm according to GSM TS 05.05 */
 unsigned int ms_class_gmsk_dbm(enum gsm_band band, int class)
 {
@@ -373,7 +385,7 @@
 	case GSM_BAND_1800:
 		if (dbm >= 36)
 			return 29;
-		else if (dbm >= 34)	
+		else if (dbm >= 34)
 			return 30;
 		else if (dbm >= 32)
 			return 31;
@@ -662,3 +674,34 @@
 	}
 	return tlli;
 }
+
+/* Wrappers for deprecated functions: */
+
+int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t septet_l)
+{
+	gsm_7bit_decode_n(text, SIZE_MAX, user_data, septet_l);
+
+	/* Mimic the original behaviour. */
+	return septet_l;
+}
+
+int gsm_7bit_decode_ussd(char *text, const uint8_t *user_data, uint8_t length)
+{
+	return gsm_7bit_decode_n_ussd(text, SIZE_MAX, user_data, length);
+}
+
+int gsm_7bit_encode(uint8_t *result, const char *data)
+{
+	int out;
+	return gsm_7bit_encode_n(result, SIZE_MAX, data, &out);
+}
+
+int gsm_7bit_encode_ussd(uint8_t *result, const char *data, int *octets)
+{
+	return gsm_7bit_encode_n_ussd(result, SIZE_MAX, data, octets);
+}
+
+int gsm_7bit_encode_oct(uint8_t *result, const char *data, int *octets)
+{
+	return gsm_7bit_encode_n(result, SIZE_MAX, data, octets);
+}