/*
 * oFono - Open Source Telephony
 * Copyright (C) 2008-2011  Intel Corporation
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include <ell/ell.h>
#include <glib.h>

#include "util.h"
#include "smsutil.h"

static const bool VERBOSE = false;

static const char *simple_deliver = "07911326040000F0"
		"040B911346610089F60000208062917314480CC8F71D14969741F977FD07";
static const char *alnum_sender = "0791447758100650"
		"040DD0F334FC1CA6970100008080312170224008D4F29CDE0EA7D9";
static const char *unicode_deliver = "04819999990414D0FBFD7EBFDFEFF77BFE1E001"
		"9512090801361807E00DC00FC00C400E400D600F600C500E500D800F800C"
		"600E600C700E700C900E900CA00EA00DF003100320033003400350036003"
		"7003800390030002000540068006900730020006D0065007300730061006"
		"7006500200069007300200036003300200075006E00690063006F0064006"
		"5002000630068006100720073002E";
static const char *simple_submit = "0011000B916407281553F80000AA"
		"0AE8329BFD4697D9EC37";

static const char *simple_mwi = "07913366002020F8040B913366600600F100C8318070"
				"6174148000";

static void print_scts(struct sms_scts *scts, const char *prefix)
{
	time_t ts;
	struct tm remote;
	char buf[128];

	printf("%s: (YY-MM-DD) %02d-%02d-%02d\n", prefix,
		(int)scts->year, (int)scts->month, (int)scts->day);

	printf("%s: (HH-MM-SS) %02d:%02d:%02d\n", prefix,
		(int)scts->hour, (int)scts->minute, (int)scts->second);

	printf("%s: Timezone %d hours %d minutes\n", prefix,
		(int)scts->timezone / 4,
		(int)((abs(scts->timezone) % 4) * 15));

	ts = sms_scts_to_time(scts, &remote);

	strftime(buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime(&ts));
	buf[127] = '\0';

	printf("local time: %s\n", buf);

	strftime(buf, 127, "%Y-%m-%dT%H:%M:%S%z", &remote);
	buf[127] = '\0';

	printf("remote time: %s\n", buf);
}

static void print_vpf(enum sms_validity_period_format vpf,
			struct sms_validity_period *vp)
{
	printf("Validity Period Format: %d\n", (int)vpf);

	switch (vpf) {
	case SMS_VALIDITY_PERIOD_FORMAT_ABSENT:
		printf("Validity-Period: Absent\n");
		break;
	case SMS_VALIDITY_PERIOD_FORMAT_RELATIVE:
		printf("Validity-Period: %d\n",
			(int)vp->relative);
		break;
	case SMS_VALIDITY_PERIOD_FORMAT_ABSOLUTE:
		print_scts(&vp->absolute, "Validity-Period:");
		break;
	case SMS_VALIDITY_PERIOD_FORMAT_ENHANCED:
		printf("Validity-Period: Enhanced");
		break;
	}
}

static void dump_details(struct sms *sms)
{
	if (sms->sc_addr.address[0] == '\0')
		printf("SMSC Address absent, default will be used\n");
	else
		printf("SMSC Address number_type: %d, number_plan: %d, %s\n",
			(int)sms->sc_addr.number_type,
			(int)sms->sc_addr.numbering_plan, sms->sc_addr.address);

	switch (sms->type) {
	case SMS_TYPE_DELIVER:
		printf("Type: Deliver\n");

		printf("Originator-Address: %d, %d, %s\n",
			(int)sms->deliver.oaddr.number_type,
			(int)sms->deliver.oaddr.numbering_plan,
			sms->deliver.oaddr.address);

		printf("PID: %d\n", (int)sms->deliver.pid);
		printf("DCS: %d\n", (int)sms->deliver.dcs);

		print_scts(&sms->deliver.scts, "Timestamp");

		break;
	case SMS_TYPE_SUBMIT:
		printf("Type: Submit\n");

		printf("Message Reference: %u\n", (int)sms->submit.mr);

		printf("Destination-Address: %d, %d, %s\n",
			(int)sms->submit.daddr.number_type,
			(int)sms->submit.daddr.numbering_plan,
			sms->submit.daddr.address);

		printf("PID: %d\n", (int)sms->submit.pid);
		printf("DCS: %d\n", (int)sms->submit.dcs);

		print_vpf(sms->submit.vpf, &sms->submit.vp);

		break;
	case SMS_TYPE_STATUS_REPORT:
		break;
	case SMS_TYPE_COMMAND:
	case SMS_TYPE_DELIVER_REPORT_ACK:
	case SMS_TYPE_DELIVER_REPORT_ERROR:
	case SMS_TYPE_SUBMIT_REPORT_ACK:
	case SMS_TYPE_SUBMIT_REPORT_ERROR:
		break;
	}
}

static void test_simple_deliver(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	int data_len;
	unsigned char *unpacked;
	char *utf8;

	decoded_pdu = l_util_from_hexstring(simple_deliver, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(simple_deliver) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 30, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	if (VERBOSE)
		dump_details(&sms);

	g_assert(sms.sc_addr.number_type == SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.sc_addr.numbering_plan == SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.sc_addr.address, "31624000000") == 0);

	g_assert(sms.deliver.oaddr.number_type ==
			SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.deliver.oaddr.numbering_plan ==
			SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.deliver.oaddr.address, "31641600986") == 0);

	g_assert(sms.deliver.pid == 0);
	g_assert(sms.deliver.dcs == 0);

	g_assert(sms.deliver.scts.year == 2);
	g_assert(sms.deliver.scts.month == 8);
	g_assert(sms.deliver.scts.day == 26);
	g_assert(sms.deliver.scts.hour == 19);
	g_assert(sms.deliver.scts.minute == 37);
	g_assert(sms.deliver.scts.second == 41);
	g_assert(sms.deliver.scts.timezone == -4);

	g_assert(sms.deliver.udl == 12);

	data_len = sms_udl_in_bytes(sms.deliver.udl, sms.deliver.dcs);

	g_assert(data_len == 11);

	unpacked = unpack_7bit(sms.deliver.ud, data_len, 0, false,
				sms.deliver.udl, NULL, 0xff);

	g_assert(unpacked);

	utf8 = convert_gsm_to_utf8(unpacked, -1, NULL, NULL, 0xff);
	l_free(unpacked);

	g_assert(utf8);

	if (VERBOSE)
		printf("Decoded user data is: %s\n", utf8);

	g_assert(strcmp(utf8, "How are you?") == 0);
	l_free(utf8);
}

static void test_alnum_sender(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	int data_len;
	unsigned char *unpacked;
	char *utf8;

	decoded_pdu = l_util_from_hexstring(alnum_sender, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(alnum_sender) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 27, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	if (VERBOSE)
		dump_details(&sms);

	g_assert(sms.sc_addr.number_type == SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.sc_addr.numbering_plan == SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.sc_addr.address, "447785016005") == 0);

	g_assert(sms.deliver.oaddr.number_type ==
			SMS_NUMBER_TYPE_ALPHANUMERIC);
	g_assert(sms.deliver.oaddr.numbering_plan ==
			SMS_NUMBERING_PLAN_UNKNOWN);
	g_assert(strcmp(sms.deliver.oaddr.address, "sipgate") == 0);

	g_assert(sms.deliver.pid == 0);
	g_assert(sms.deliver.dcs == 0);

	g_assert(sms.deliver.scts.year == 8);
	g_assert(sms.deliver.scts.month == 8);
	g_assert(sms.deliver.scts.day == 13);
	g_assert(sms.deliver.scts.hour == 12);
	g_assert(sms.deliver.scts.minute == 07);
	g_assert(sms.deliver.scts.second == 22);
	g_assert(sms.deliver.scts.timezone == 4);

	g_assert(sms.deliver.udl == 8);
	data_len = sms_udl_in_bytes(sms.deliver.udl, sms.deliver.dcs);
	g_assert(data_len == 7);

	unpacked = unpack_7bit(sms.deliver.ud, data_len, 0, false,
				sms.deliver.udl, NULL, 0xff);
	g_assert(unpacked);

	utf8 = convert_gsm_to_utf8(unpacked, -1, NULL, NULL, 0xff);
	l_free(unpacked);
	g_assert(utf8);

	if (VERBOSE)
		printf("Decoded user data is: %s\n", utf8);

	g_assert(strcmp(utf8, "Testmail") == 0);

	l_free(utf8);
}

static void test_deliver_encode(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	unsigned char pdu[176];
	int encoded_pdu_len;
	int encoded_tpdu_len;
	char *encoded_pdu;

	decoded_pdu = l_util_from_hexstring(simple_deliver, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(simple_deliver) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 30, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	ret = sms_encode(&sms, &encoded_pdu_len, &encoded_tpdu_len, pdu);

	if (VERBOSE) {
		int i;

		for (i = 0; i < encoded_pdu_len; i++)
			printf("%02X", pdu[i]);
		printf("\n");
	}

	g_assert(ret);
	g_assert(encoded_tpdu_len == 30);
	g_assert(encoded_pdu_len == (long)pdu_len);

	encoded_pdu = l_util_hexstring(pdu, encoded_pdu_len);
	g_assert(strcasecmp(simple_deliver, encoded_pdu) == 0);
	l_free(encoded_pdu);

	decoded_pdu = l_util_from_hexstring(alnum_sender, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(alnum_sender) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 27, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	ret = sms_encode(&sms, &encoded_pdu_len, &encoded_tpdu_len, pdu);

	if (VERBOSE) {
		int i;

		for (i = 0; i < encoded_pdu_len; i++)
			printf("%02X", pdu[i]);
		printf("\n");
	}

	g_assert(ret);
	g_assert(encoded_tpdu_len == 27);
	g_assert(encoded_pdu_len == (long)pdu_len);

	encoded_pdu = l_util_hexstring(pdu, encoded_pdu_len);
	g_assert(strcasecmp(alnum_sender, encoded_pdu) == 0);
	l_free(encoded_pdu);

	/* test unicode_deliver*/
	decoded_pdu = l_util_from_hexstring(unicode_deliver, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(unicode_deliver) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 149, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	ret = sms_encode(&sms, &encoded_pdu_len, &encoded_tpdu_len, pdu);

	if (VERBOSE) {
		int i;

		for (i = 0; i < encoded_pdu_len; i++)
			printf("%02X", pdu[i]);
		printf("\n");
	}

	g_assert(ret);
	g_assert(encoded_tpdu_len == 149);
	g_assert(encoded_pdu_len == (long)pdu_len);

	encoded_pdu = l_util_hexstring(pdu, encoded_pdu_len);
	g_assert(strcasecmp(unicode_deliver, encoded_pdu) == 0);
	l_free(encoded_pdu);
}

static void test_simple_submit(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	int data_len;
	unsigned char *unpacked;
	char *utf8;

	decoded_pdu = l_util_from_hexstring(simple_submit, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(simple_submit) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, TRUE, 23, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_SUBMIT);

	if (VERBOSE)
		dump_details(&sms);

	g_assert(strlen(sms.sc_addr.address) == 0);

	g_assert(sms.submit.mr == 0);

	g_assert(sms.submit.daddr.number_type ==
			SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.submit.daddr.numbering_plan ==
			SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.submit.daddr.address, "46708251358") == 0);

	g_assert(sms.submit.pid == 0);
	g_assert(sms.submit.dcs == 0);

	g_assert(sms.submit.vpf == SMS_VALIDITY_PERIOD_FORMAT_RELATIVE);
	g_assert(sms.submit.vp.relative == 0xAA);

	g_assert(sms.submit.udl == 10);

	data_len = sms_udl_in_bytes(sms.submit.udl, sms.submit.dcs);
	g_assert(data_len == 9);

	unpacked = unpack_7bit(sms.submit.ud, data_len, 0, false,
				sms.submit.udl, NULL, 0xff);
	g_assert(unpacked);
	utf8 = convert_gsm_to_utf8(unpacked, -1, NULL, NULL, 0xff);
	l_free(unpacked);
	g_assert(utf8);

	if (VERBOSE)
		printf("Decoded user data is: %s\n", utf8);

	g_assert(strcmp(utf8, "hellohello") == 0);
	l_free(utf8);
}

static void test_submit_encode(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	unsigned char pdu[176];
	int encoded_pdu_len;
	int encoded_tpdu_len;
	char *encoded_pdu;

	decoded_pdu = l_util_from_hexstring(simple_submit, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(simple_submit) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, TRUE, 23, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_SUBMIT);

	ret = sms_encode(&sms, &encoded_pdu_len, &encoded_tpdu_len, pdu);

	if (VERBOSE) {
		int i;

		for (i = 0; i < encoded_pdu_len; i++)
			printf("%02X", pdu[i]);
		printf("\n");
	}

	g_assert(ret);
	g_assert(encoded_tpdu_len == 23);
	g_assert(encoded_pdu_len == (long)pdu_len);

	encoded_pdu = l_util_hexstring(pdu, encoded_pdu_len);
	g_assert(strcasecmp(simple_submit, encoded_pdu) == 0);
	l_free(encoded_pdu);
}

static void test_simple_mwi(void)
{
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	enum sms_mwi_type type;
	gboolean active;
	gboolean discard;

	decoded_pdu = l_util_from_hexstring(simple_mwi, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(simple_mwi) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, 19, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	if (VERBOSE)
		dump_details(&sms);

	g_assert(sms.sc_addr.number_type == SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.sc_addr.numbering_plan == SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.sc_addr.address, "33660002028") == 0);

	g_assert(sms.deliver.oaddr.number_type ==
			SMS_NUMBER_TYPE_INTERNATIONAL);
	g_assert(sms.deliver.oaddr.numbering_plan ==
			SMS_NUMBERING_PLAN_ISDN);
	g_assert(strcmp(sms.deliver.oaddr.address, "33660660001") == 0);

	g_assert(sms.deliver.pid == 0);
	g_assert(sms.deliver.dcs == 200);

	g_assert(sms.deliver.scts.year == 13);
	g_assert(sms.deliver.scts.month == 8);
	g_assert(sms.deliver.scts.day == 7);
	g_assert(sms.deliver.scts.hour == 16);
	g_assert(sms.deliver.scts.minute == 47);
	g_assert(sms.deliver.scts.second == 41);
	g_assert(sms.deliver.scts.timezone == 8);

	g_assert(sms.deliver.udl == 0);

	if (sms.deliver.udhi) {
		struct sms_udh_iter iter;
		enum sms_iei iei;

		ret = sms_udh_iter_init(&sms, &iter);
		g_assert(ret);

		while ((iei = sms_udh_iter_get_ie_type(&iter)) !=
				SMS_IEI_INVALID) {
			switch (iei) {
			case SMS_IEI_ENHANCED_VOICE_MAIL_INFORMATION:
			{
				unsigned char evm_iei[140];
				sms_udh_iter_get_ie_data(&iter, evm_iei);
				sms_udh_iter_get_ie_length(&iter);

				if (VERBOSE)
					printf("Enhanced Voicemail IEI\n");
				break;
			}

			case SMS_IEI_SPECIAL_MESSAGE_INDICATION:
			{
				unsigned char special_iei[4];

				sms_udh_iter_get_ie_data(&iter, special_iei);
				sms_udh_iter_get_ie_length(&iter);

				if (VERBOSE)
					printf("Special Voicemail IEI\n");

				break;
			}

			default:
				break;
			}

			sms_udh_iter_next(&iter);
		}
	}

	ret = sms_mwi_dcs_decode(sms.deliver.dcs, &type, NULL, &active,
					&discard);
	g_assert(ret);

	if (VERBOSE) {
		printf("Type: %d, Active: %d, Discard: %d\n",
				type, active, discard);
	}
}

struct sms_charset_data {
	char *pdu;
	int data_len;
	enum gsm_dialect locking_lang;
	enum gsm_dialect single_lang;
	char expected_text[];
};

static struct sms_charset_data sms_charset_default = {
	.pdu =
		"0001000B91" "5310101010" "1000008080" "8060402818" "0E888462C1"
		"68381E9088" "6442A9582E" "988C06C4E9" "783EA09068" "442A994EA8"
		"946AC56AB9" "5EB0986C46" "ABD96EB89C" "6EC7EBF97E" "C0A070482C"
		"1A8FC8A472" "C96C3A9FD0" "A8744AAD5A" "AFD8AC76CB" "ED7ABFE0B0"
		"784C2E9BCF" "E8B47ACD6E" "BBDFF0B87C" "4EAFDBEFF8" "BC7ECFEFFB"
		"FF",
	.data_len = 112,
	.expected_text = {
		0x40, 0xc2, 0xa3, 0x24, 0xc2, 0xa5, 0xc3, 0xa8, 0xc3, 0xa9,
		0xc3, 0xb9, 0xc3, 0xac, 0xc3, 0xb2, 0xc3, 0x87, 0x0a, 0xc3,
		0x98, 0xc3, 0xb8, 0x0d, 0xc3, 0x85, 0xc3, 0xa5, 0xce, 0x94,
		0x5f, 0xce, 0xa6, 0xce, 0x93, 0xce, 0x9b, 0xce, 0xa9, 0xce,
		0xa0, 0xce, 0xa8, 0xce, 0xa3, 0xce, 0x98, 0xce, 0x9e, 0x20,
		0xc3, 0x86, 0xc3, 0xa6, 0xc3, 0x9f, 0xc3, 0x89, 0x20, 0x21,
		0x22, 0x23, 0xc2, 0xa4, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
		0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34,
		0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
		0x3f, 0xc2, 0xa1, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
		0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51,
		0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xc3,
		0x84, 0xc3, 0x96, 0xc3, 0x91, 0xc3, 0x9c, 0xc2, 0xa7, 0xc2,
		0xbf, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
		0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73,
		0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xc3, 0xa4, 0xc3,
		0xb6, 0xc3, 0xb1, 0xc3, 0xbc, 0xc3, 0xa0, 0x00
	}
};

static struct sms_charset_data sms_charset_default_ext = {
	.pdu =
		"0001000B91" "5310101010" "100000151B" "C58602DAA0" "36A9CD6BC3"
		"DBF436BE0D" "705306",
	.data_len = 19,
	.expected_text = {
		0x0c, 0x5e, 0x20, 0x7b,	0x7d, 0x5c, 0x5b, 0x7e,	0x5d, 0x7c,
		0xe2, 0x82, 0xac, 0x00
	}
};

static struct sms_charset_data sms_charset_turkey = {
	.pdu =
		"0001000B91" "5310101010" "1000008080" "8060402818" "0E888462C1"
		"68381E9088" "6442A9582E" "988C06C4E9" "783EA09068" "442A994EA8"
		"946AC56AB9" "5EB0986C46" "ABD96EB89C" "6EC7EBF97E" "C0A070482C"
		"1A8FC8A472" "C96C3A9FD0" "A8744AAD5A" "AFD8AC76CB" "ED7ABFE0B0"
		"784C2E9BCF" "E8B47ACD6E" "BBDFF0B87C" "4EAFDBEFF8" "BC7ECFEFFB"
		"FF",
	.data_len = 112,
	.locking_lang = GSM_DIALECT_TURKISH,
	.expected_text = {
		0x40, 0xc2, 0xa3, 0x24, 0xc2, 0xa5, 0xe2, 0x82, 0xac, 0xc3,
		0xa9, 0xc3, 0xb9, 0xc4, 0xb1, 0xc3, 0xb2, 0xc3, 0x87, 0x0a,
		0xc4, 0x9e, 0xc4, 0x9f, 0x0d, 0xc3, 0x85, 0xc3, 0xa5, 0xce,
		0x94, 0x5f, 0xce, 0xa6, 0xce, 0x93, 0xce, 0x9b, 0xce, 0xa9,
		0xce, 0xa0, 0xce, 0xa8, 0xce, 0xa3, 0xce, 0x98, 0xce, 0x9e,
		0x20, 0xc5, 0x9e, 0xc5, 0x9f, 0xc3, 0x9f, 0xc3, 0x89, 0x20,
		0x21, 0x22, 0x23, 0xc2, 0xa4, 0x25, 0x26, 0x27, 0x28, 0x29,
		0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33,
		0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,
		0x3e, 0x3f, 0xc4, 0xb0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
		0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
		0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
		0xc3, 0x84, 0xc3, 0x96, 0xc3, 0x91, 0xc3, 0x9c, 0xc2, 0xa7,
		0xc3, 0xa7, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
		0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
		0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xc3, 0xa4,
		0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xbc, 0xc3, 0xa0, 0x00
	}
};

static struct sms_charset_data sms_charset_turkey_ext = {
	.pdu =
		"0001000B91" "5310101010" "1000001A1B" "C586B2416D" "529BD786B7"
		"E96D7C1BE0" "02C8011318" "870E",
	.data_len = 23,
	.locking_lang = GSM_DIALECT_TURKISH,
	.single_lang = GSM_DIALECT_TURKISH,
	.expected_text = {
		0x0c, 0x5e, 0x7b, 0x7d, 0x5c, 0x5b, 0x7e, 0x5d, 0x7c, 0xc4,
		0x9e, 0xc4, 0xb0, 0xc5, 0x9e, 0xc3, 0xa7, 0xe2, 0x82, 0xac,
		0xc4, 0x9f, 0xc4, 0xb1, 0xc5, 0x9f, 0x00
	}
};

static struct sms_charset_data sms_charset_portugal = {
	.pdu =
		"0001000B91" "5310101010" "1000008080" "8060402818" "0E888462C1"
		"68381E9088" "6442A9582E" "988C06C4E9" "783EA09068" "442A994EA8"
		"946AC56AB9" "5EB0986C46" "ABD96EB89C" "6EC7EBF97E" "C0A070482C"
		"1A8FC8A472" "C96C3A9FD0" "A8744AAD5A" "AFD8AC76CB" "ED7ABFE0B0"
		"784C2E9BCF" "E8B47ACD6E" "BBDFF0B87C" "4EAFDBEFF8" "BC7ECFEFFB"
		"FF",
	.data_len = 112,
	.locking_lang = GSM_DIALECT_PORTUGUESE,
	.expected_text = {
		0x40, 0xc2, 0xa3, 0x24, 0xc2, 0xa5, 0xc3, 0xaa, 0xc3, 0xa9,
		0xc3, 0xba, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xa7, 0x0a, 0xc3,
		0x94, 0xc3, 0xb4, 0x0d, 0xc3, 0x81, 0xc3, 0xa1, 0xce, 0x94,
		0x5f, 0xc2, 0xaa, 0xc3, 0x87, 0xc3, 0x80, 0xe2, 0x88, 0x9e,
		0x5e, 0x5c, 0xe2, 0x82, 0xac, 0xc3, 0x93, 0x7c, 0x20, 0xc3,
		0x82, 0xc3, 0xa2, 0xc3, 0x8a, 0xc3, 0x89, 0x20, 0x21, 0x22,
		0x23, 0xc2, 0xba, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
		0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
		0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
		0xc3, 0x8d, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
		0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
		0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xc3, 0x83,
		0xc3, 0x95, 0xc3, 0x9a, 0xc3, 0x9c, 0xc2, 0xa7, 0x7e, 0x61,
		0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
		0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
		0x76, 0x77, 0x78, 0x79, 0x7a, 0xc3, 0xa3, 0xc3, 0xb5, 0x60,
		0xc3, 0xbc, 0xc3, 0xa0, 0x00
	}
};

static struct sms_charset_data sms_charset_portugal_ext = {
	.pdu =
		"0001000B91" "5310101010" "1000003184" "C446B16038" "1E1BC96662"
		"D9543696CD" "6583D9643C" "1BD42675D9" "F0C01B9F86" "02CC74B75C"
		"0EE68030EC" "F91D",
	.data_len = 43,
	.locking_lang = GSM_DIALECT_PORTUGUESE,
	.single_lang = GSM_DIALECT_PORTUGUESE,
	.expected_text = {
		0xc3, 0xaa, 0xc3, 0xa7, 0x0c, 0xc3, 0x94, 0xc3, 0xb4, 0xc3,
		0x81, 0xc3, 0xa1, 0xce, 0xa6, 0xce, 0x93, 0x5e, 0xce, 0xa9,
		0xce, 0xa0, 0xce, 0xa8, 0xce, 0xa3, 0xce, 0x98, 0xc3, 0x8a,
		0x7b, 0x7d, 0x5c, 0x5b, 0x7e, 0x5d, 0x7c, 0xc3, 0x80, 0xc3,
		0x8d, 0xc3, 0x93, 0xc3, 0x9a, 0xc3, 0x83, 0xc3, 0x95, 0xc3,
		0x82, 0xe2, 0x82, 0xac, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba,
		0xc3, 0xa3, 0xc3, 0xb5, 0xc3, 0xa2, 0x00
	}
};

static struct sms_charset_data sms_charset_spain = {
	.pdu =
		"0001000B91" "5310101010" "100000269B" "C446B1A16C" "509BD4E6B5"
		"E16D7A1BDF" "06B8096E92" "9BE7A6BA09" "6FCA9BF4E6" "BDA903",
	.data_len = 34,
	.locking_lang = GSM_DIALECT_SPANISH,
	.single_lang = GSM_DIALECT_SPANISH,
	.expected_text = {
		0xc3, 0xa7, 0x0c, 0x5e, 0x7b, 0x7d, 0x5c, 0x5b, 0x7e, 0x5d,
		0x7c, 0xc3, 0x81, 0xc3, 0x8d, 0xc3, 0x93, 0xc3, 0x9a, 0xc3,
		0xa1, 0xe2, 0x82, 0xac, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba,
		0x00
	}
};

static void test_sms_charset(gconstpointer param)
{
	gboolean ret;
	struct sms sms;
	unsigned char *pdu;
	unsigned char *unpacked;
	size_t pdu_len;
	int data_len;
	enum sms_charset sms_charset;
	gboolean sms_compressed;
	char *text;
	struct sms_charset_data *data = (struct sms_charset_data *)param;

	pdu = l_util_from_hexstring(data->pdu, &pdu_len);
	g_assert(pdu);
	g_assert(pdu_len == strlen(data->pdu) / 2);

	ret = sms_decode(pdu, pdu_len, FALSE, pdu_len, &sms);
	g_assert(ret);
	l_free(pdu);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	ret = sms_dcs_decode(sms.deliver.dcs, NULL, &sms_charset,
				&sms_compressed, NULL);

	g_assert(ret);
	g_assert(sms_charset == SMS_CHARSET_7BIT);
	g_assert(sms_compressed == FALSE);

	data_len = sms_udl_in_bytes(sms.deliver.udl, sms.deliver.dcs);
	g_assert(data_len == data->data_len);

	unpacked = unpack_7bit(sms.deliver.ud, data_len, 0, false,
				sms.deliver.udl, NULL, 0xff);
	g_assert(unpacked);

	text = convert_gsm_to_utf8_with_lang(unpacked, -1, NULL, NULL, 0xff,
		data->locking_lang, data->single_lang);

	g_assert(text);
	l_free(unpacked);
	g_assert(strcmp(data->expected_text, text) == 0);
	l_free(text);
}

struct text_format_header {
	unsigned char len;
	unsigned char start;
	unsigned char span;
	unsigned char format;
	unsigned char color;
};

struct ems_udh_test {
	const char *pdu;
	unsigned int len;
	const char *expected;
	unsigned int udl;
	unsigned int udhl;
	unsigned int data_len;
	struct text_format_header formats[];
};

static struct ems_udh_test ems_udh_test_1 = {
	.pdu = "0041000B915121551532F40000631A0A031906200A032104100A03270504"
		"0A032E05080A043807002B8ACD29A85D9ECFC3E7F21C340EBB41E3B79B1"
		"E4EBB41697A989D1EB340E2379BCC02B1C3F27399059AB7C36C3628EC26"
		"83C66FF65B5E2683E8653C1D",
	.len = 100,
	.expected = "EMS messages can contain italic, bold, large, small and"
		" colored text",
	.formats = {
		{
			.len = 3,
			.start = 0x19,
			.span = 0x06,
			.format = 0x20,
		},
		{
			.len = 3,
			.start = 0x21,
			.span = 0x04,
			.format = 0x10,
		},
		{
			.len = 3,
			.start = 0x27,
			.span = 0x05,
			.format = 0x04,
		},
		{
			.len = 3,
			.start = 0x2E,
			.span = 0x05,
			.format = 0x08,
		},
		{
			.len = 4,
			.start = 0x38,
			.span = 0x07,
			.format = 0x00,
			.color = 0x2B,
		},
		{
			.len = 0,
		}
	},
	.udl = 99,
	.udhl = 26,
	.data_len = 87,
};

static struct ems_udh_test ems_udh_test_2 = {
	.pdu = "079194712272303351030B915121340195F60000FF80230A030F07230A031"
		"806130A031E0A430A032E0D830A033D14020A035104F60A0355010600159"
		"D9E83D2735018442FCFE98A243DCC4E97C92C90F8CD26B3407537B92C67A"
		"7DD65320B1476934173BA3CBD2ED3D1F277FD8C76299CEF3B280C92A7CF6"
		"83A28CC4E9FDD6532E8FE96935D",
	.len = 126,
	.expected = "This is a test\nItalied, bold, underlined, and "
		"strikethrough.\nNow a right aligned word.",
	.formats = {
		{
			.len = 3,
			.start = 0x0f,
			.span = 0x07,
			.format = 0x23,
		},
		{
			.len = 3,
			.start = 0x18,
			.span = 0x06,
			.format = 0x13,
		},
		{
			.len = 3,
			.start = 0x1e,
			.span = 0x0a,
			.format = 0x43,
		},
		{
			.len = 3,
			.start = 0x2e,
			.span = 0x0d,
			.format = 0x83,
		},
		{
			.len = 3,
			.start = 0x3d,
			.span = 0x14,
			.format = 0x02,
		},
		{
			.len = 3,
			.start = 0x51,
			.span = 0x04,
			.format = 0xf6,
		},
		{
			.len = 3,
			.start = 0x55,
			.span = 0x01,
			.format = 0x06,
		},
	},
	.udl = 128,
	.udhl = 35,
	.data_len = 112,
};

static void test_ems_udh(gconstpointer data)
{
	const struct ems_udh_test *test = data;
	struct sms sms;
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	unsigned int data_len;
	unsigned int udhl;
	struct sms_udh_iter iter;
	int max_chars;
	unsigned char *unpacked;
	char *utf8;
	int i;

	decoded_pdu = l_util_from_hexstring(test->pdu, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(test->pdu) / 2);

	ret = sms_decode(decoded_pdu, pdu_len, TRUE, test->len, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_SUBMIT);

	if (VERBOSE)
		dump_details(&sms);
	udhl = sms.submit.ud[0];

	g_assert(sms.submit.udl == test->udl);
	g_assert(udhl == test->udhl);

	ret = sms_udh_iter_init(&sms, &iter);

	g_assert(ret);

	for (i = 0; test->formats[i].len; i++) {
		if (VERBOSE) {
			int j;
			unsigned char ie_data[4];

			sms_udh_iter_get_ie_data(&iter, ie_data);

			printf("Header:\n");
			for (j = 0; j < sms_udh_iter_get_ie_length(&iter); j++)
				printf("0x%02x ", ie_data[j]);

			printf("\n");
		}

		g_assert(sms_udh_iter_get_ie_type(&iter) ==
				SMS_IEI_TEXT_FORMAT);
		g_assert(sms_udh_iter_get_ie_length(&iter) ==
				test->formats[i].len);

		if (test->formats[i+1].len) {
			g_assert(sms_udh_iter_has_next(&iter) == TRUE);
			g_assert(sms_udh_iter_next(&iter) == TRUE);
		} else {
			g_assert(sms_udh_iter_has_next(&iter) == FALSE);
			g_assert(sms_udh_iter_next(&iter) == FALSE);
			g_assert(sms_udh_iter_get_ie_type(&iter) ==
					SMS_IEI_INVALID);
		}
	}

	data_len = sms_udl_in_bytes(sms.submit.udl, sms.submit.dcs);
	g_assert(data_len == test->data_len);

	max_chars = (data_len - (udhl + 1)) * 8 / 7;
	unpacked = unpack_7bit(sms.submit.ud + udhl + 1, data_len - (udhl + 1),
				udhl + 1, false, max_chars, NULL, 0xff);
	g_assert(unpacked);

	utf8 = convert_gsm_to_utf8(unpacked, -1, NULL, NULL, 0xff);
	l_free(unpacked);
	g_assert(utf8);

	if (VERBOSE)
		printf("Decoded user data is: %s\n", utf8);

	g_assert(strcmp(utf8, test->expected) == 0);
	l_free(utf8);
}

static const char *assembly_pdu1 = "038121F340048155550119906041001222048C0500"
					"031E0301041804420430043A002C002004100"
					"43B0435043A04410430043D04340440002000"
					"200441043B044304480430043B00200437043"
					"000200434043204350440044C044E00200020"
					"04380020002004320441043500200431043E0"
					"43B044C044804350020043F04400435043804"
					"41043F043E043B043D044F043B0441044F002"
					"000200433043D0435";
static int assembly_pdu_len1 = 155;

static const char *assembly_pdu2 = "038121F340048155550119906041001222048C0500"
					"031E03020432043E043C002E000A041D04300"
					"43A043E043D04350446002C0020043D043500"
					"200432002004410438043B043004450020043"
					"40430043B043504350020044204350440043F"
					"04350442044C002C0020043E043D002004410"
					"44204400435043C043804420435043B044C04"
					"3D043E002004320431043504360430043B002"
					"004320020043A043E";
static int assembly_pdu_len2 = 155;

static const char *assembly_pdu3 = "038121F340048155550119906041001222044A0500"
					"031E0303043C043D043004420443002C00200"
					"43F043E043704300431044B0432000A043404"
					"3004360435002C002004470442043E0020002"
					"00431044B043B0020043D04300433002E";
static int assembly_pdu_len3 = 89;

static void test_assembly(void)
{
	unsigned char pdu[176];
	long pdu_len;
	struct sms sms;
	struct sms_assembly *assembly = sms_assembly_new(NULL);
	guint16 ref;
	guint8 max;
	guint8 seq;
	GSList *l;
	char *utf8;
	char *reencoded;

	decode_hex_own_buf(assembly_pdu1, -1, &pdu_len, 0, pdu);
	sms_decode(pdu, pdu_len, FALSE, assembly_pdu_len1, &sms);

	sms_extract_concatenation(&sms, &ref, &max, &seq);
	l = sms_assembly_add_fragment(assembly, &sms, time(NULL),
					&sms.deliver.oaddr, ref, max, seq);

	if (VERBOSE) {
		printf("Ref: %u\n", ref);
		printf("Max: %u\n", max);
		printf("From: %s\n",
				sms_address_to_string(&sms.deliver.oaddr));
	}

	g_assert(g_slist_length(assembly->assembly_list) == 1);
	g_assert(l == NULL);

	sms_assembly_expire(assembly, time(NULL) + 40);

	g_assert(g_slist_length(assembly->assembly_list) == 0);

	sms_extract_concatenation(&sms, &ref, &max, &seq);
	l = sms_assembly_add_fragment(assembly, &sms, time(NULL),
					&sms.deliver.oaddr, ref, max, seq);
	g_assert(g_slist_length(assembly->assembly_list) == 1);
	g_assert(l == NULL);

	decode_hex_own_buf(assembly_pdu2, -1, &pdu_len, 0, pdu);
	sms_decode(pdu, pdu_len, FALSE, assembly_pdu_len2, &sms);

	sms_extract_concatenation(&sms, &ref, &max, &seq);
	l = sms_assembly_add_fragment(assembly, &sms, time(NULL),
					&sms.deliver.oaddr, ref, max, seq);
	g_assert(l == NULL);

	decode_hex_own_buf(assembly_pdu3, -1, &pdu_len, 0, pdu);
	sms_decode(pdu, pdu_len, FALSE, assembly_pdu_len3, &sms);

	sms_extract_concatenation(&sms, &ref, &max, &seq);
	l = sms_assembly_add_fragment(assembly, &sms, time(NULL),
					&sms.deliver.oaddr, ref, max, seq);

	g_assert(l != NULL);

	utf8 = sms_decode_text(l);

	g_slist_free_full(l, g_free);

	sms_assembly_free(assembly);

	if (VERBOSE)
		printf("Text:\n%s\n", utf8);

	l = sms_text_prepare("555", utf8, ref, TRUE, FALSE);
	g_assert(l);
	g_assert(g_slist_length(l) == 3);

	reencoded = sms_decode_text(l);

	g_slist_free_full(l, g_free);

	if (VERBOSE)
		printf("ReEncoded:\n%s\n", reencoded);

	g_assert(strcmp(utf8, reencoded) == 0);

	g_free(utf8);
	g_free(reencoded);
}

static const char *test_no_fragmentation_7bit = "This is testing !";
static const char *expected_no_fragmentation_7bit = "079153485002020911000C915"
			"348870420140000A71154747A0E4ACF41F4F29C9E769F4121";
static const char *sc_addr = "+358405202090";
static const char *da_addr = "+358478400241";
static void test_prepare_7bit(void)
{
	GSList *r;
	struct sms *sms;
	gboolean ret;
	unsigned char pdu[176];
	int encoded_pdu_len;
	int encoded_tpdu_len;
	char *encoded_pdu;

	r = sms_text_prepare("555", test_no_fragmentation_7bit, 0,
				FALSE, FALSE);

	g_assert(r != NULL);

	sms = r->data;

	sms->sc_addr.number_type = SMS_NUMBER_TYPE_INTERNATIONAL;
	sms->sc_addr.numbering_plan = SMS_NUMBERING_PLAN_ISDN;
	strcpy(sms->sc_addr.address, sc_addr+1);

	if (VERBOSE)
		printf("sc_addr: %s\n", sms_address_to_string(&sms->sc_addr));

	g_assert(!strcmp(sc_addr, sms_address_to_string(&sms->sc_addr)));

	sms->submit.daddr.number_type = SMS_NUMBER_TYPE_INTERNATIONAL;
	sms->submit.daddr.numbering_plan = SMS_NUMBERING_PLAN_ISDN;
	strcpy(sms->submit.daddr.address, da_addr+1);

	if (VERBOSE)
		printf("da_addr: %s\n",
			sms_address_to_string(&sms->submit.daddr));

	g_assert(!strcmp(da_addr,
				sms_address_to_string(&sms->submit.daddr)));

	ret = sms_encode(sms, &encoded_pdu_len, &encoded_tpdu_len, pdu);

	g_assert(ret);

	if (VERBOSE) {
		int i;

		for (i = 0; i < encoded_pdu_len; i++)
			printf("%02X", pdu[i]);
		printf("\n");
	}

	encoded_pdu = l_util_hexstring(pdu, encoded_pdu_len);
	g_assert(strcasecmp(expected_no_fragmentation_7bit, encoded_pdu) == 0);
	l_free(encoded_pdu);
	g_slist_free_full(r, g_free);
}

struct sms_concat_data {
	const char *str;
	unsigned int segments;
};

static struct sms_concat_data shakespeare_test = {
	.str = "Shakespeare divided his time between London and Str"
	"atford during his career. In 1596, the year before he bought New Plac"
	"e as his family home in Stratford, Shakespeare was living in the pari"
	"sh of St. Helen's, Bishopsgate, north of the River Thames.",
	.segments = 2,
};

/* The string in this test should be padded at the end.  This confuses some
 * decoders which do not use udl properly
 */
static void test_prepare_concat(gconstpointer data)
{
	const struct sms_concat_data *test = data;
	GSList *r;
	GSList *l;
	char *decoded_str;
	GSList *pdus = NULL;
	unsigned char pdu[176];
	struct sms *sms;
	struct sms decoded;
	int pdu_len, tpdu_len;
	struct sms_assembly *assembly = sms_assembly_new(NULL);
	guint16 ref;
	guint8 max;
	guint8 seq;

	if (VERBOSE)
		printf("strlen: %zd\n", strlen(test->str));

	r = sms_text_prepare("+15554449999", test->str, 0, TRUE, FALSE);
	g_assert(r);
	g_assert(g_slist_length(r) == test->segments);

	for (l = r; l; l = l->next) {
		char *strpdu;

		sms = l->data;

		sms_encode(sms, &pdu_len, &tpdu_len, pdu);
		g_assert(pdu_len == (tpdu_len + 1));

		strpdu = l_util_hexstring(pdu, pdu_len);

		if (VERBOSE)
			printf("PDU: %s, len: %d, tlen: %d\n",
				strpdu, pdu_len, tpdu_len);
		pdus = g_slist_append(pdus, strpdu);
	}

	g_slist_free_full(r, g_free);

	for (l = pdus; l; l = l->next) {
		long len;
		gboolean ok;

		decode_hex_own_buf((char *)l->data, -1, &len, 0, pdu);

		if (VERBOSE)
			printf("PDU Len: %ld\n", len);

		ok = sms_decode(pdu, len, TRUE, len - 1, &decoded);
		g_assert(ok);

		if (VERBOSE)
			printf("Pdu udl: %d\n", (int)decoded.submit.udl);

		sms_extract_concatenation(&decoded, &ref, &max, &seq);
		r = sms_assembly_add_fragment(assembly, &decoded, time(NULL),
						&decoded.submit.daddr,
						ref, max, seq);
	}

	g_assert(r);

	decoded_str = sms_decode_text(r);

	if (VERBOSE)
		printf("Decoded String: %s\n", decoded_str);

	g_assert(decoded_str);
	g_assert(strcmp(decoded_str, test->str) == 0);
	g_free(decoded_str);
	g_slist_free_full(pdus, l_free);
	g_slist_free_full(r, g_free);
	sms_assembly_free(assembly);
}

static void test_limit(gunichar uni, size_t target_size, gboolean use_16bit)
{
	char *utf8;
	char *decoded;
	GSList *l;
	unsigned int i;
	char utf8_char[6];
	unsigned int stride;

	stride = g_unichar_to_utf8(uni, utf8_char);

	utf8 = g_new0(char, (target_size + 2) * stride);

	for (i = 0; i < target_size * stride; i += stride)
		memcpy(utf8 + i, utf8_char, stride);

	utf8[i] = '\0';

	l = sms_text_prepare("555", utf8, 0, use_16bit, FALSE);

	g_assert(l);
	g_assert(g_slist_length(l) == 255);

	decoded = sms_decode_text(l);
	g_assert(l_utf8_strlen(decoded) == target_size);

	g_free(decoded);
	g_slist_free_full(l, g_free);

	memcpy(utf8 + i, utf8_char, stride);
	utf8[i+stride] = '\0';

	l = sms_text_prepare("555", utf8, 0, use_16bit, FALSE);

	g_assert(l == NULL);
	g_free(utf8);
}

static void test_prepare_limits(void)
{
	gunichar ascii = 0x41;
	gunichar ucs2 = 0x416;
	unsigned int target_size;

	/* The limit for 16 bit headers is 255 * 152 for GSM7 */
	target_size = 255 * 152;
	test_limit(ascii, target_size, TRUE);

	/* The limit for 8 bit headers is 255 * 153 for GSM7 */
	target_size = 255 * 153;
	test_limit(ascii, target_size, FALSE);

	/* The limit for 16 bit headers is 255 * 66 for UCS2 */
	target_size = 255 * 66;
	test_limit(ucs2, target_size, TRUE);

	/* The limit for 8 bit headers is 255 * 67 for UCS2 */
	target_size = 255 * 67;
	test_limit(ucs2, target_size, FALSE);
}

static const char *cbs1 = "011000320111C2327BFC76BBCBEE46A3D168341A8D46A3D1683"
	"41A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168"
	"341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100";

static const char *cbs2 = "0110003201114679785E96371A8D46A3D168341A8D46A3D1683"
	"41A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168"
	"341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100";

static const char *cbs3 = "001000000111E280604028180E888462C168381E90886442A95"
	"82E988C66C3E9783EA09068442A994EA8946AC56AB95EB0986C46ABD96EB89C6EC7EBF"
	"97EC0A070482C1A8FC8A472C96C3A9FD0A8744AAD5AAFD8AC76CB05";

static void test_cbs_encode_decode(void)
{
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	struct cbs cbs;
	unsigned char pdu[88];
	int len;
	char *encoded_pdu;
	GSList *l;
	char iso639_lang[3];
	char *utf8;

	decoded_pdu = l_util_from_hexstring(cbs1, &pdu_len);

	g_assert(decoded_pdu);
	g_assert(pdu_len == strlen(cbs1) / 2);
	g_assert(pdu_len == 88);

	ret = cbs_decode(decoded_pdu, pdu_len, &cbs);
	l_free(decoded_pdu);

	g_assert(ret);

	g_assert(cbs.gs == CBS_GEO_SCOPE_CELL_IMMEDIATE);
	g_assert(cbs.message_code == 17);
	g_assert(cbs.update_number == 0);
	g_assert(cbs.message_identifier == 50);
	g_assert(cbs.dcs == 1);
	g_assert(cbs.max_pages == 1);
	g_assert(cbs.page == 1);

	l = g_slist_append(NULL, &cbs);

	utf8 = cbs_decode_text(l, iso639_lang);

	g_assert(utf8);

	if (VERBOSE) {
		printf("%s\n", utf8);
		if (iso639_lang[0] == '\0')
			printf("Lang: Unspecified\n");
		else
			printf("Lang: %s\n", iso639_lang);
	}

	g_assert(strcmp(utf8, "Belconnen") == 0);
	g_assert(strcmp(iso639_lang, "en") == 0);

	l_free(utf8);

	g_slist_free(l);

	ret = cbs_encode(&cbs, &len, pdu);

	g_assert(ret);

	encoded_pdu = l_util_hexstring(pdu, len);
	g_assert(strcasecmp(cbs1, encoded_pdu) == 0);
	l_free(encoded_pdu);
}

static void test_cbs_assembly(void)
{
	unsigned char *decoded_pdu;
	size_t pdu_len;
	struct cbs dec1;
	struct cbs dec2;
	struct cbs_assembly *assembly;
	char iso639_lang[3];
	GSList *l;
	char *utf8;

	assembly = cbs_assembly_new();

	g_assert(assembly);

	decoded_pdu = l_util_from_hexstring(cbs1, &pdu_len);
	cbs_decode(decoded_pdu, pdu_len, &dec1);
	l_free(decoded_pdu);

	decoded_pdu = l_util_from_hexstring(cbs2, &pdu_len);
	cbs_decode(decoded_pdu, pdu_len, &dec2);
	l_free(decoded_pdu);

	/* Add an initial page to the assembly */
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l);
	g_assert(g_slist_length(assembly->recv_cell) == 1);
	g_slist_free_full(l, g_free);

	/* Can we receive new updates ? */
	dec1.update_number = 8;
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l);
	g_assert(g_slist_length(assembly->recv_cell) == 1);
	g_slist_free_full(l, g_free);

	/* Do we ignore old pages ? */
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l == NULL);

	/* Do we ignore older pages ? */
	dec1.update_number = 5;
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l == NULL);

	cbs_assembly_location_changed(assembly, TRUE, TRUE, TRUE);
	g_assert(assembly->recv_cell == NULL);

	dec1.update_number = 9;
	dec1.page = 3;
	dec1.max_pages = 3;

	dec2.update_number = 9;
	dec2.page = 2;
	dec2.max_pages = 3;

	l = cbs_assembly_add_page(assembly, &dec2);
	g_assert(l == NULL);
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l == NULL);

	dec1.page = 1;
	l = cbs_assembly_add_page(assembly, &dec1);
	g_assert(l);

	utf8 = cbs_decode_text(l, iso639_lang);

	g_assert(utf8);

	if (VERBOSE) {
		printf("%s\n", utf8);
		if (iso639_lang[0] == '\0')
			printf("Lang: Unspecified\n");
		else
			printf("Lang: %s\n", iso639_lang);
	}

	g_assert(strcmp(utf8, "BelconnenFraserBelconnen") == 0);

	l_free(utf8);
	g_slist_free_full(l, g_free);

	cbs_assembly_free(assembly);
}

static void test_cbs_padding_character(void)
{
	unsigned char *decoded_pdu;
	size_t pdu_len;
	gboolean ret;
	struct cbs cbs;
	GSList *l;
	char iso639_lang[3];
	char *utf8;

	decoded_pdu = l_util_from_hexstring(cbs3, &pdu_len);
	g_assert(decoded_pdu);
	g_assert(pdu_len == 88);

	ret = cbs_decode(decoded_pdu, pdu_len, &cbs);
	l_free(decoded_pdu);
	g_assert(ret);

	g_assert(cbs.gs == CBS_GEO_SCOPE_CELL_IMMEDIATE);
	g_assert(cbs.message_code == 1);
	g_assert(cbs.update_number == 0);
	g_assert(cbs.message_identifier == 0);
	g_assert(cbs.dcs == 1);
	g_assert(cbs.max_pages == 1);
	g_assert(cbs.page == 1);

	l = g_slist_append(NULL, &cbs);

	utf8 = cbs_decode_text(l, iso639_lang);
	g_assert(utf8);

	if (VERBOSE) {
		printf("%s\n", utf8);
		if (iso639_lang[0] == '\0')
			printf("Lang: Unspecified\n");
		else
			printf("Lang: %s\n", iso639_lang);
	}

	g_assert(strcmp(utf8, "b£$¥èéùìòÇ\x0AØø\x0DÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤"
				"\x25&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLM"
				"NOPQRSTUVWXYZÄÖ") == 0);
	g_assert(strcmp(iso639_lang, "en") == 0);

	l_free(utf8);
	g_slist_free(l);
}

static const char *ranges[] = { "1-5, 2, 3, 600, 569-900, 999",
				"0-20, 33, 44, 50-60, 20-50, 1-5, 5, 3, 5",
				NULL };
static const char *inv_ranges[] = { "1-5, 3333", "1-5, afbcd", "1-5, 3-5,,",
					"1-5, 3-5, c", NULL };

static void test_range_minimizer(void)
{
	int i = 0;

	while (inv_ranges[i]) {
		GSList *l = cbs_extract_topic_ranges(inv_ranges[i]);

		g_assert(l == NULL);
		i++;
	}

	i = 0;

	while (ranges[i]) {
		GSList *r = cbs_extract_topic_ranges(ranges[i]);
		char *rangestr;

		g_assert(r != NULL);
		i++;

		rangestr = cbs_topic_ranges_to_string(r);

		g_assert(rangestr);

		if (VERBOSE)
			printf("range: %s\n", rangestr);

		g_free(rangestr);
		g_slist_free_full(r, g_free);
	}
}

static void test_sr_assembly(void)
{
	const char *sr_pdu1 = "06040D91945152991136F00160124130340A0160124130"
				"940A00";
	const char *sr_pdu2 = "06050D91945152991136F00160124130640A0160124130"
				"450A00";
	const char *sr_pdu3 = "0606098121436587F9019012413064A0019012413045A0"
				"00";
        struct sms sr1;
	struct sms sr2;
	struct sms sr3;
	unsigned char pdu[176];
	long pdu_len;
	struct status_report_assembly *sra;
	gboolean delivered;
	struct sms_address addr;
	unsigned char sha1[SMS_MSGID_LEN] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
						10, 11, 12, 13, 14, 15,
						16, 17, 18, 19 };
	unsigned char id[SMS_MSGID_LEN];

	/* international address, mr 4 & mr 5 */

        decode_hex_own_buf(sr_pdu1, -1, &pdu_len, 0, pdu);
	g_assert(sms_decode(pdu, pdu_len, FALSE, 26, &sr1) == TRUE);

	decode_hex_own_buf(sr_pdu2, -1, &pdu_len, 0, pdu);
	g_assert(sms_decode(pdu, pdu_len, FALSE, 26, &sr2) == TRUE);

	/* national address, mr 6 */

	decode_hex_own_buf(sr_pdu3, -1, &pdu_len, 0, pdu);
	g_assert(sms_decode(pdu, pdu_len, FALSE, 24, &sr3) == TRUE);

	if (VERBOSE) {
		printf("sr1 address: %s, mr: %d\n",
			sms_address_to_string(&sr1.status_report.raddr),
			sr1.status_report.mr);

		printf("sr2 address: %s, mr: %d\n",
			sms_address_to_string(&sr2.status_report.raddr),
			sr2.status_report.mr);

		printf("sr3 address: %s, mr: %d\n",
			sms_address_to_string(&sr3.status_report.raddr),
			sr3.status_report.mr);
	}

	__sms_address_from_string(&addr, "+4915259911630");

	sra = status_report_assembly_new(NULL);

	status_report_assembly_add_fragment(sra, sha1, &addr, 4, time(NULL), 2);
	status_report_assembly_add_fragment(sra, sha1, &addr, 5, time(NULL), 2);

	status_report_assembly_expire(sra, time(NULL) + 40);
	g_assert(g_hash_table_size(sra->assembly_table) == 0);

	status_report_assembly_add_fragment(sra, sha1, &addr, 4, time(NULL), 2);
	status_report_assembly_add_fragment(sra, sha1, &addr, 5, time(NULL), 2);

	g_assert(!status_report_assembly_report(sra, &sr1, id, &delivered));
	g_assert(status_report_assembly_report(sra, &sr2, id, &delivered));

	g_assert(memcmp(id, sha1, SMS_MSGID_LEN) == 0);
	g_assert(delivered == TRUE);

	/*
	 * Send sms-message in the national address-format,
	 * but receive in the international address-format.
	 */
	__sms_address_from_string(&addr, "9911630");
	status_report_assembly_add_fragment(sra, sha1, &addr, 4, time(NULL), 2);
	status_report_assembly_add_fragment(sra, sha1, &addr, 5, time(NULL), 2);

	g_assert(!status_report_assembly_report(sra, &sr1, id, &delivered));
	g_assert(status_report_assembly_report(sra, &sr2, id, &delivered));

	g_assert(memcmp(id, sha1, SMS_MSGID_LEN) == 0);
	g_assert(delivered == TRUE);
	g_assert(g_hash_table_size(sra->assembly_table) == 0);

	/*
	 * Send sms-message in the international address-format,
	 * but receive in the national address-format.
	 */
	__sms_address_from_string(&addr, "+358123456789");
	status_report_assembly_add_fragment(sra, sha1, &addr, 6, time(NULL), 1);

	g_assert(status_report_assembly_report(sra, &sr3, id, &delivered));

	g_assert(memcmp(id, sha1, SMS_MSGID_LEN) == 0);
	g_assert(delivered == TRUE);
	g_assert(g_hash_table_size(sra->assembly_table) == 0);

	status_report_assembly_free(sra);
}

struct wap_push_data {
	const char *pdu;
	int len;
};

static struct wap_push_data wap_push_1 = {
	.pdu = "0791947122725014440185F039F501801140311480720605040B8423F00106"
		"246170706C69636174696F6E2F766E642E7761702E6D6D732D6D657373616"
		"76500AF84B4868C82984F67514B4B42008D9089088045726F74696B009650"
		"696E2D557073008A808E0240008805810303F48083687474703A2F2F65707"
		"3332E64652F4F2F5A39495A4F00",
	.len = 128,
};

static void test_wap_push(gconstpointer data)
{
	const struct wap_push_data *test = data;
	struct sms sms;
	unsigned char *decoded_pdu;
	gboolean ret;
	size_t pdu_len;
	long data_len;
	enum sms_class cls;
	enum sms_charset charset;
	GSList *list;
	unsigned char *wap_push;
	int dst_port, src_port;
	gboolean is_8bit;

	decoded_pdu = l_util_from_hexstring(test->pdu, &pdu_len);
	g_assert(decoded_pdu);

	ret = sms_decode(decoded_pdu, pdu_len, FALSE, test->len, &sms);
	l_free(decoded_pdu);
	g_assert(ret);
	g_assert(sms.type == SMS_TYPE_DELIVER);

	if (VERBOSE)
		dump_details(&sms);

	ret = sms_dcs_decode(sms.deliver.dcs, &cls, &charset, NULL, NULL);

	g_assert(ret == TRUE);
	g_assert(charset == SMS_CHARSET_8BIT);

	g_assert(sms_extract_app_port(&sms, &dst_port, &src_port, &is_8bit));

	if (VERBOSE) {
		printf("8bit: %d\n", is_8bit);
		printf("src: %d, dst: %d\n", src_port, dst_port);
	}

	g_assert(is_8bit == FALSE);
	g_assert(dst_port == 2948);

	list = g_slist_append(NULL, &sms);

	wap_push = sms_decode_datagram(list, &data_len);

	if (VERBOSE) {
		int i;

		printf("data_len: %ld\n", data_len);

		for (i = 0; i < data_len; i++) {
			printf("%02x", wap_push[i]);

			if ((i % 16) == 15)
				printf("\n");
		}

		printf("\n");
	}

	g_assert(wap_push);

	g_free(wap_push);
	g_slist_free(list);
}

int main(int argc, char **argv)
{
	char long_string[152*33 + 1];
	struct sms_concat_data long_string_test;

	g_test_init(&argc, &argv, NULL);

	g_test_add_func("/testsms/Test Simple Deliver", test_simple_deliver);
	g_test_add_func("/testsms/Test Alnum Deliver", test_alnum_sender);
	g_test_add_func("/testsms/Test Deliver Encode", test_deliver_encode);
	g_test_add_func("/testsms/Test Simple Submit", test_simple_submit);
	g_test_add_func("/testsms/Test Submit Encode", test_submit_encode);
	g_test_add_func("/testsms/Test Simple MWI", test_simple_mwi);

	g_test_add_data_func("/testsms/Test "
		"GSM 7 bit Default Alphabet Decode",
		&sms_charset_default, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"GSM 7 bit Default Alphabet Extension Table Decode",
		&sms_charset_default_ext, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"Turkish National Language Locking Shift Table Decode",
		&sms_charset_turkey, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"Turkish National Language Single Shift Table Decode",
		&sms_charset_turkey_ext, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"Portuguese National Language Locking Shift Table Decode",
		&sms_charset_portugal, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"Portuguese National Language Single Shift Table Decode",
		&sms_charset_portugal_ext, test_sms_charset);

	g_test_add_data_func("/testsms/Test "
		"Spanish National Language Single Shift Table Decode",
		&sms_charset_spain, test_sms_charset);

	g_test_add_data_func("/testsms/Test EMS UDH 1",
			&ems_udh_test_1, test_ems_udh);
	g_test_add_data_func("/testsms/Test EMS UDH 2",
			&ems_udh_test_2, test_ems_udh);

	g_test_add_func("/testsms/Test Assembly", test_assembly);
	g_test_add_func("/testsms/Test Prepare 7Bit", test_prepare_7bit);

	g_test_add_data_func("/testsms/Test Prepare Concat",
			&shakespeare_test, test_prepare_concat);

	memset(long_string, 'a', 152*30);
	memset(long_string + 152*30, 'b', 152);
	memset(long_string + 152*31, 'c', 152);
	memset(long_string + 152*32, 'd', 152);
	long_string[152*33] = '\0';

	long_string_test.str = long_string;
	long_string_test.segments = 33;

	g_test_add_data_func("/testsms/Test Prepare Concat 30+ segments",
			&long_string_test, test_prepare_concat);

	g_test_add_func("/testsms/Test Prepare Limits", test_prepare_limits);

	g_test_add_func("/testsms/Test CBS Encode / Decode",
			test_cbs_encode_decode);
	g_test_add_func("/testsms/Test CBS Assembly", test_cbs_assembly);

	g_test_add_func("/testsms/Test CBS Padding Character",
			test_cbs_padding_character);

	g_test_add_func("/testsms/Range minimizer", test_range_minimizer);

	g_test_add_func("/testsms/Status Report Assembly", test_sr_assembly);

	g_test_add_data_func("/testsms/Test WAP Push 1", &wap_push_1,
				test_wap_push);

	return g_test_run();
}
