859 lines
26 KiB
C
859 lines
26 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 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 General Public License for more details:
|
|
*
|
|
* Copyright (C) 2011 - 2012 Red Hat, Inc.
|
|
* Copyright (C) 2012 Google, Inc.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <mm-errors-types.h>
|
|
|
|
#include "mm-sms-part.h"
|
|
#include "mm-charsets.h"
|
|
#include "mm-utils.h"
|
|
#include "mm-log.h"
|
|
|
|
#define PDU_SIZE 200
|
|
|
|
#define SMS_TP_MTI_MASK 0x03
|
|
#define SMS_TP_MTI_SMS_DELIVER 0x00
|
|
#define SMS_TP_MTI_SMS_SUBMIT_REPORT 0x01
|
|
#define SMS_TP_MTI_SMS_STATUS_REPORT 0x02
|
|
|
|
#define SMS_NUMBER_TYPE_MASK 0x70
|
|
#define SMS_NUMBER_TYPE_UNKNOWN 0x00
|
|
#define SMS_NUMBER_TYPE_INTL 0x10
|
|
#define SMS_NUMBER_TYPE_ALPHA 0x50
|
|
|
|
#define SMS_NUMBER_PLAN_MASK 0x0f
|
|
#define SMS_NUMBER_PLAN_TELEPHONE 0x01
|
|
|
|
#define SMS_TP_MMS 0x04
|
|
#define SMS_TP_SRI 0x20
|
|
#define SMS_TP_UDHI 0x40
|
|
#define SMS_TP_RP 0x80
|
|
|
|
#define SMS_DCS_CODING_MASK 0xec
|
|
#define SMS_DCS_CODING_DEFAULT 0x00
|
|
#define SMS_DCS_CODING_8BIT 0x04
|
|
#define SMS_DCS_CODING_UCS2 0x08
|
|
|
|
#define SMS_DCS_CLASS_VALID 0x10
|
|
#define SMS_DCS_CLASS_MASK 0x03
|
|
|
|
#define SMS_TIMESTAMP_LEN 7
|
|
#define SMS_MIN_PDU_LEN (7 + SMS_TIMESTAMP_LEN)
|
|
|
|
typedef enum {
|
|
MM_SMS_ENCODING_UNKNOWN = 0x0,
|
|
MM_SMS_ENCODING_GSM7,
|
|
MM_SMS_ENCODING_8BIT,
|
|
MM_SMS_ENCODING_UCS2
|
|
} SmsEncoding;
|
|
|
|
static char sms_bcd_chars[] = "0123456789*#abc\0\0";
|
|
|
|
static void
|
|
sms_semi_octets_to_bcd_string (char *dest, const guint8 *octets, int num_octets)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0 ; i < num_octets; i++) {
|
|
*dest++ = sms_bcd_chars[octets[i] & 0xf];
|
|
*dest++ = sms_bcd_chars[(octets[i] >> 4) & 0xf];
|
|
}
|
|
*dest++ = '\0';
|
|
}
|
|
|
|
static gboolean
|
|
char_to_bcd (char in, guint8 *out)
|
|
{
|
|
guint32 z;
|
|
|
|
if (isdigit (in)) {
|
|
*out = in - 0x30;
|
|
return TRUE;
|
|
}
|
|
|
|
for (z = 10; z < 16; z++) {
|
|
if (in == sms_bcd_chars[z]) {
|
|
*out = z;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gsize
|
|
sms_string_to_bcd_semi_octets (guint8 *buf, gsize buflen, const char *string)
|
|
{
|
|
guint i;
|
|
guint8 bcd;
|
|
gsize addrlen, slen;
|
|
|
|
addrlen = slen = strlen (string);
|
|
if (addrlen % 2)
|
|
addrlen++;
|
|
g_return_val_if_fail (buflen >= addrlen, 0);
|
|
|
|
for (i = 0; i < addrlen; i += 2) {
|
|
if (!char_to_bcd (string[i], &bcd))
|
|
return 0;
|
|
buf[i / 2] = bcd & 0xF;
|
|
|
|
if (i >= slen - 1) {
|
|
/* PDU address gets padded with 0xF if string is odd length */
|
|
bcd = 0xF;
|
|
} else if (!char_to_bcd (string[i + 1], &bcd))
|
|
return 0;
|
|
buf[i / 2] |= bcd << 4;
|
|
}
|
|
return addrlen / 2;
|
|
}
|
|
|
|
/* len is in semi-octets */
|
|
static char *
|
|
sms_decode_address (const guint8 *address, int len)
|
|
{
|
|
guint8 addrtype, addrplan;
|
|
char *utf8;
|
|
|
|
addrtype = address[0] & SMS_NUMBER_TYPE_MASK;
|
|
addrplan = address[0] & SMS_NUMBER_PLAN_MASK;
|
|
address++;
|
|
|
|
if (addrtype == SMS_NUMBER_TYPE_ALPHA) {
|
|
guint8 *unpacked;
|
|
guint32 unpacked_len;
|
|
unpacked = gsm_unpack (address, (len * 4) / 7, 0, &unpacked_len);
|
|
utf8 = (char *)mm_charset_gsm_unpacked_to_utf8 (unpacked,
|
|
unpacked_len);
|
|
g_free(unpacked);
|
|
} else if (addrtype == SMS_NUMBER_TYPE_INTL &&
|
|
addrplan == SMS_NUMBER_PLAN_TELEPHONE) {
|
|
/* International telphone number, format as "+1234567890" */
|
|
utf8 = g_malloc (len + 3); /* '+' + digits + possible trailing 0xf + NUL */
|
|
utf8[0] = '+';
|
|
sms_semi_octets_to_bcd_string (utf8 + 1, address, (len + 1) / 2);
|
|
} else {
|
|
/*
|
|
* All non-alphanumeric types and plans are just digits, but
|
|
* don't apply any special formatting if we don't know the
|
|
* format.
|
|
*/
|
|
utf8 = g_malloc (len + 2); /* digits + possible trailing 0xf + NUL */
|
|
sms_semi_octets_to_bcd_string (utf8, address, (len + 1) / 2);
|
|
}
|
|
|
|
return utf8;
|
|
}
|
|
|
|
static char *
|
|
sms_decode_timestamp (const guint8 *timestamp)
|
|
{
|
|
/* YYMMDDHHMMSS+ZZ */
|
|
char *timestr;
|
|
int quarters, hours;
|
|
|
|
timestr = g_malloc0 (16);
|
|
sms_semi_octets_to_bcd_string (timestr, timestamp, 6);
|
|
quarters = ((timestamp[6] & 0x7) * 10) + ((timestamp[6] >> 4) & 0xf);
|
|
hours = quarters / 4;
|
|
if (timestamp[6] & 0x08)
|
|
timestr[12] = '-';
|
|
else
|
|
timestr[12] = '+';
|
|
timestr[13] = (hours / 10) + '0';
|
|
timestr[14] = (hours % 10) + '0';
|
|
/* TODO(njw): Change timestamp rep to something that includes quarter-hours */
|
|
return timestr;
|
|
}
|
|
|
|
static SmsEncoding
|
|
sms_encoding_type (int dcs)
|
|
{
|
|
SmsEncoding scheme = MM_SMS_ENCODING_UNKNOWN;
|
|
|
|
switch ((dcs >> 4) & 0xf) {
|
|
/* General data coding group */
|
|
case 0: case 1:
|
|
case 2: case 3:
|
|
switch (dcs & 0x0c) {
|
|
case 0x08:
|
|
scheme = MM_SMS_ENCODING_UCS2;
|
|
break;
|
|
case 0x00:
|
|
/* fallthrough */
|
|
/* reserved - spec says to treat it as default alphabet */
|
|
case 0x0c:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
case 0x04:
|
|
scheme = MM_SMS_ENCODING_8BIT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/* Message waiting group (default alphabet) */
|
|
case 0xc:
|
|
case 0xd:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
|
|
/* Message waiting group (UCS2 alphabet) */
|
|
case 0xe:
|
|
scheme = MM_SMS_ENCODING_UCS2;
|
|
break;
|
|
|
|
/* Data coding/message class group */
|
|
case 0xf:
|
|
switch (dcs & 0x04) {
|
|
case 0x00:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
case 0x04:
|
|
scheme = MM_SMS_ENCODING_8BIT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/* Reserved coding group values - spec says to treat it as default alphabet */
|
|
default:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
static char *
|
|
sms_decode_text (const guint8 *text, int len, SmsEncoding encoding, int bit_offset)
|
|
{
|
|
char *utf8;
|
|
guint8 *unpacked;
|
|
guint32 unpacked_len;
|
|
|
|
if (encoding == MM_SMS_ENCODING_GSM7) {
|
|
mm_dbg ("Converting SMS part text from GSM7 to UTF8...");
|
|
unpacked = gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
|
|
utf8 = (char *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
|
|
mm_dbg (" Got UTF-8 text: '%s'", utf8);
|
|
g_free (unpacked);
|
|
} else if (encoding == MM_SMS_ENCODING_UCS2) {
|
|
mm_dbg ("Converting SMS part text from UCS-2BE to UTF8...");
|
|
utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
|
|
mm_dbg (" Got UTF-8 text: '%s'", utf8);
|
|
} else {
|
|
g_warn_if_reached ();
|
|
utf8 = g_strdup ("");
|
|
}
|
|
|
|
return utf8;
|
|
}
|
|
|
|
struct _MMSmsPart {
|
|
guint index;
|
|
gchar *smsc;
|
|
gchar *timestamp;
|
|
gchar *number;
|
|
gchar *text;
|
|
GByteArray *data;
|
|
guint data_coding_scheme;
|
|
guint class;
|
|
guint validity;
|
|
|
|
gboolean should_concat;
|
|
guint concat_reference;
|
|
guint concat_max;
|
|
guint concat_sequence;
|
|
};
|
|
|
|
void
|
|
mm_sms_part_free (MMSmsPart *self)
|
|
{
|
|
g_free (self->timestamp);
|
|
g_free (self->smsc);
|
|
g_free (self->number);
|
|
g_free (self->text);
|
|
if (self->data)
|
|
g_byte_array_unref (self->data);
|
|
g_slice_free (MMSmsPart, self);
|
|
}
|
|
|
|
#define PART_GET_FUNC(type, name) \
|
|
type \
|
|
mm_sms_part_get_##name (MMSmsPart *self) \
|
|
{ \
|
|
return self->name; \
|
|
}
|
|
|
|
#define PART_SET_UINT_FUNC(name) \
|
|
void \
|
|
mm_sms_part_set_##name (MMSmsPart *self, \
|
|
guint value) \
|
|
{ \
|
|
self->name = value; \
|
|
}
|
|
|
|
#define PART_SET_TAKE_STR_FUNC(name) \
|
|
void \
|
|
mm_sms_part_set_##name (MMSmsPart *self, \
|
|
const gchar *value) \
|
|
{ \
|
|
g_free (self->name); \
|
|
self->name = g_strdup (value); \
|
|
} \
|
|
\
|
|
void \
|
|
mm_sms_part_take_##name (MMSmsPart *self, \
|
|
gchar *value) \
|
|
{ \
|
|
g_free (self->name); \
|
|
self->name = value; \
|
|
}
|
|
|
|
PART_GET_FUNC (guint, index)
|
|
PART_SET_UINT_FUNC (index)
|
|
PART_GET_FUNC (const gchar *, smsc)
|
|
PART_SET_TAKE_STR_FUNC (smsc)
|
|
PART_GET_FUNC (const gchar *, number)
|
|
PART_SET_TAKE_STR_FUNC (number)
|
|
PART_GET_FUNC (const gchar *, timestamp)
|
|
PART_SET_TAKE_STR_FUNC (timestamp)
|
|
PART_GET_FUNC (guint, concat_max)
|
|
PART_SET_UINT_FUNC (concat_max)
|
|
PART_GET_FUNC (guint, concat_sequence)
|
|
PART_SET_UINT_FUNC (concat_sequence)
|
|
PART_GET_FUNC (const gchar *, text)
|
|
PART_SET_TAKE_STR_FUNC (text)
|
|
PART_GET_FUNC (guint, data_coding_scheme)
|
|
PART_SET_UINT_FUNC (data_coding_scheme)
|
|
PART_GET_FUNC (guint, class)
|
|
PART_SET_UINT_FUNC (class)
|
|
PART_GET_FUNC (guint, validity)
|
|
PART_SET_UINT_FUNC (validity)
|
|
|
|
PART_GET_FUNC (guint, concat_reference)
|
|
|
|
void
|
|
mm_sms_part_set_concat_reference (MMSmsPart *self,
|
|
guint value)
|
|
{
|
|
self->should_concat = TRUE;
|
|
self->concat_reference = value;
|
|
}
|
|
|
|
PART_GET_FUNC (const GByteArray *, data)
|
|
|
|
void
|
|
mm_sms_part_set_data (MMSmsPart *self,
|
|
GByteArray *value)
|
|
{
|
|
if (self->data)
|
|
g_byte_array_unref (self->data);
|
|
self->data = (value ? g_byte_array_ref (value) : NULL);
|
|
}
|
|
|
|
void
|
|
mm_sms_part_take_data (MMSmsPart *self,
|
|
GByteArray *value)
|
|
{
|
|
if (self->data)
|
|
g_byte_array_unref (self->data);
|
|
self->data = value;
|
|
}
|
|
|
|
gboolean
|
|
mm_sms_part_should_concat (MMSmsPart *self)
|
|
{
|
|
return self->should_concat;
|
|
}
|
|
|
|
MMSmsPart *
|
|
mm_sms_part_new (guint index)
|
|
{
|
|
MMSmsPart *sms_part;
|
|
|
|
sms_part = g_slice_new0 (MMSmsPart);
|
|
sms_part->index = index;
|
|
|
|
return sms_part;
|
|
}
|
|
|
|
MMSmsPart *
|
|
mm_sms_part_new_from_pdu (guint index,
|
|
const gchar *hexpdu,
|
|
GError **error)
|
|
{
|
|
gsize pdu_len;
|
|
guint8 *pdu;
|
|
MMSmsPart *part;
|
|
|
|
/* Convert PDU from hex to binary */
|
|
pdu = (guint8 *) utils_hexstr2bin (hexpdu, &pdu_len);
|
|
if (!pdu) {
|
|
g_set_error_literal (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't convert PDU from hex to binary");
|
|
return NULL;
|
|
}
|
|
|
|
part = mm_sms_part_new_from_binary_pdu (index, pdu, pdu_len, error);
|
|
if (!part)
|
|
g_free (pdu);
|
|
|
|
return part;
|
|
}
|
|
|
|
MMSmsPart *
|
|
mm_sms_part_new_from_binary_pdu (guint index,
|
|
const guint8 *pdu,
|
|
gsize pdu_len,
|
|
GError **error)
|
|
{
|
|
MMSmsPart *sms_part;
|
|
guint smsc_addr_num_octets, variable_length_items, msg_start_offset,
|
|
sender_addr_num_digits, sender_addr_num_octets,
|
|
tp_pid_offset, tp_dcs_offset, user_data_offset, user_data_len,
|
|
user_data_len_offset, bit_offset;
|
|
SmsEncoding user_data_encoding;
|
|
GByteArray *raw;
|
|
|
|
/* SMSC, in address format, precedes the TPDU */
|
|
smsc_addr_num_octets = pdu[0];
|
|
variable_length_items = smsc_addr_num_octets;
|
|
if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"PDU too short (1): %zd < %d",
|
|
pdu_len,
|
|
variable_length_items + SMS_MIN_PDU_LEN);
|
|
return NULL;
|
|
}
|
|
|
|
/* where in the PDU the actual SMS protocol message begins */
|
|
msg_start_offset = 1 + smsc_addr_num_octets;
|
|
sender_addr_num_digits = pdu[msg_start_offset + 1];
|
|
|
|
/*
|
|
* round the sender address length up to an even number of
|
|
* semi-octets, and thus an integral number of octets
|
|
*/
|
|
sender_addr_num_octets = (sender_addr_num_digits + 1) >> 1;
|
|
variable_length_items += sender_addr_num_octets;
|
|
if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"PDU too short (2): %zd < %d",
|
|
pdu_len,
|
|
variable_length_items + SMS_MIN_PDU_LEN);
|
|
return NULL;
|
|
}
|
|
|
|
tp_pid_offset = msg_start_offset + 3 + sender_addr_num_octets;
|
|
tp_dcs_offset = tp_pid_offset + 1;
|
|
|
|
user_data_len_offset = tp_dcs_offset + 1 + SMS_TIMESTAMP_LEN;
|
|
user_data_offset = user_data_len_offset + 1;
|
|
user_data_len = pdu[user_data_len_offset];
|
|
user_data_encoding = sms_encoding_type(pdu[tp_dcs_offset]);
|
|
|
|
if (user_data_encoding == MM_SMS_ENCODING_GSM7)
|
|
variable_length_items += (7 * (user_data_len + 1 )) / 8;
|
|
else
|
|
variable_length_items += user_data_len;
|
|
|
|
if (pdu_len < variable_length_items + SMS_MIN_PDU_LEN) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"PDU too short (3): %zd < %d",
|
|
pdu_len,
|
|
variable_length_items + SMS_MIN_PDU_LEN);
|
|
return NULL;
|
|
}
|
|
|
|
/* Only handle SMS-DELIVER */
|
|
if ((pdu[msg_start_offset] & SMS_TP_MTI_MASK) != SMS_TP_MTI_SMS_DELIVER) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unhandled message type: 0x%02x",
|
|
pdu[msg_start_offset]);
|
|
return NULL;
|
|
}
|
|
|
|
/* Create the new MMSmsPart */
|
|
sms_part = mm_sms_part_new (index);
|
|
mm_sms_part_take_smsc (sms_part,
|
|
sms_decode_address (&pdu[1], 2 * (pdu[0] - 1)));
|
|
mm_sms_part_take_number (sms_part,
|
|
sms_decode_address (&pdu[msg_start_offset + 2],
|
|
pdu[msg_start_offset + 1]));
|
|
mm_sms_part_take_timestamp (sms_part,
|
|
sms_decode_timestamp (&pdu[tp_dcs_offset + 1]));
|
|
|
|
bit_offset = 0;
|
|
if (pdu[msg_start_offset] & SMS_TP_UDHI) {
|
|
int udhl, end, offset;
|
|
udhl = pdu[user_data_offset] + 1;
|
|
end = user_data_offset + udhl;
|
|
|
|
for (offset = user_data_offset + 1; offset < end;) {
|
|
guint8 ie_id, ie_len;
|
|
|
|
ie_id = pdu[offset++];
|
|
ie_len = pdu[offset++];
|
|
|
|
switch (ie_id) {
|
|
case 0x00:
|
|
/*
|
|
* Ignore the IE if one of the following is true:
|
|
* - it claims to be part 0 of M
|
|
* - it claims to be part N of M, N > M
|
|
*/
|
|
if (pdu[offset + 2] == 0 ||
|
|
pdu[offset + 2] > pdu[offset + 1])
|
|
break;
|
|
|
|
mm_sms_part_set_concat_reference (sms_part,
|
|
pdu[offset]);
|
|
mm_sms_part_set_concat_max (sms_part,
|
|
pdu[offset + 1]);
|
|
mm_sms_part_set_concat_sequence (sms_part,
|
|
pdu[offset + 2]);
|
|
break;
|
|
case 0x08:
|
|
/* Concatenated short message, 16-bit reference */
|
|
if (pdu[offset + 3] == 0 ||
|
|
pdu[offset + 3] > pdu[offset + 2])
|
|
break;
|
|
|
|
mm_sms_part_set_concat_reference (sms_part,
|
|
(pdu[offset] << 8) | pdu[offset + 1]);
|
|
mm_sms_part_set_concat_max (sms_part,
|
|
pdu[offset + 2]);
|
|
mm_sms_part_set_concat_sequence (sms_part,
|
|
pdu[offset + 3]);
|
|
break;
|
|
}
|
|
|
|
offset += ie_len;
|
|
}
|
|
|
|
/*
|
|
* Move past the user data headers to prevent it from being
|
|
* decoded into garbage text.
|
|
*/
|
|
user_data_offset += udhl;
|
|
if (user_data_encoding == MM_SMS_ENCODING_GSM7) {
|
|
/*
|
|
* Find the number of bits we need to add to the length of the
|
|
* user data to get a multiple of 7 (the padding).
|
|
*/
|
|
bit_offset = (7 - udhl % 7) % 7;
|
|
user_data_len -= (udhl * 8 + bit_offset) / 7;
|
|
} else
|
|
user_data_len -= udhl;
|
|
}
|
|
|
|
if ( user_data_encoding == MM_SMS_ENCODING_8BIT
|
|
|| user_data_encoding == MM_SMS_ENCODING_UNKNOWN) {
|
|
/* 8-bit encoding is usually binary data, and we have no idea what
|
|
* actual encoding the data is in so we can't convert it.
|
|
*/
|
|
mm_dbg ("Skipping SMS part text: 8-bit or Unknown encoding");
|
|
mm_sms_part_set_text (sms_part, "");
|
|
} else {
|
|
/* Otherwise if it's 7-bit or UCS2 we can decode it */
|
|
mm_sms_part_take_text (sms_part,
|
|
sms_decode_text (&pdu[user_data_offset], user_data_len,
|
|
user_data_encoding, bit_offset));
|
|
g_warn_if_fail (sms_part->text != NULL);
|
|
}
|
|
|
|
/* Add the raw PDU data */
|
|
raw = g_byte_array_sized_new (user_data_len);
|
|
g_byte_array_append (raw, &pdu[user_data_offset], user_data_len);
|
|
mm_sms_part_take_data (sms_part, raw);
|
|
mm_sms_part_set_data_coding_scheme (sms_part, pdu[tp_dcs_offset] & 0xFF);
|
|
|
|
if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID)
|
|
mm_sms_part_set_class (sms_part,
|
|
pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK);
|
|
|
|
return sms_part;
|
|
}
|
|
|
|
/**
|
|
* mm_sms_part_encode_address:
|
|
*
|
|
* @address: the phone number to encode
|
|
* @buf: the buffer to encode @address in
|
|
* @buflen: the size of @buf
|
|
* @is_smsc: if %TRUE encode size as number of octets of address infromation,
|
|
* otherwise if %FALSE encode size as number of digits of @address
|
|
*
|
|
* Returns: the size in bytes of the data added to @buf
|
|
**/
|
|
guint
|
|
mm_sms_part_encode_address (const gchar *address,
|
|
guint8 *buf,
|
|
gsize buflen,
|
|
gboolean is_smsc)
|
|
{
|
|
gsize len;
|
|
|
|
g_return_val_if_fail (address != NULL, 0);
|
|
g_return_val_if_fail (buf != NULL, 0);
|
|
g_return_val_if_fail (buflen >= 2, 0);
|
|
|
|
/* Handle number type & plan */
|
|
buf[1] = 0x80; /* Bit 7 always 1 */
|
|
if (address[0] == '+') {
|
|
buf[1] |= SMS_NUMBER_TYPE_INTL;
|
|
address++;
|
|
}
|
|
buf[1] |= SMS_NUMBER_PLAN_TELEPHONE;
|
|
|
|
len = sms_string_to_bcd_semi_octets (&buf[2], buflen, address);
|
|
|
|
if (is_smsc)
|
|
buf[0] = len + 1; /* addr length + size byte */
|
|
else
|
|
buf[0] = strlen (address); /* number of digits in address */
|
|
|
|
return len ? len + 2 : 0; /* addr length + size byte + number type/plan */
|
|
}
|
|
|
|
static guint8
|
|
validity_to_relative (guint validity)
|
|
{
|
|
if (validity == 0)
|
|
return 167; /* 24 hours */
|
|
|
|
if (validity <= 720) {
|
|
/* 5 minute units up to 12 hours */
|
|
if (validity % 5)
|
|
validity += 5;
|
|
return (validity / 5) - 1;
|
|
}
|
|
|
|
if (validity > 720 && validity <= 1440) {
|
|
/* 12 hours + 30 minute units up to 1 day */
|
|
if (validity % 30)
|
|
validity += 30; /* round up to next 30 minutes */
|
|
validity = MIN (validity, 1440);
|
|
return 143 + ((validity - 720) / 30);
|
|
}
|
|
|
|
if (validity > 1440 && validity <= 43200) {
|
|
/* 2 days up to 1 month */
|
|
if (validity % 1440)
|
|
validity += 1440; /* round up to next day */
|
|
validity = MIN (validity, 43200);
|
|
return 167 + ((validity - 1440) / 1440);
|
|
}
|
|
|
|
/* 43200 = 30 days in minutes
|
|
* 10080 = 7 days in minutes
|
|
* 635040 = 63 weeks in minutes
|
|
* 40320 = 4 weeks in minutes
|
|
*/
|
|
if (validity > 43200 && validity <= 635040) {
|
|
/* 5 weeks up to 63 weeks */
|
|
if (validity % 10080)
|
|
validity += 10080; /* round up to next week */
|
|
validity = MIN (validity, 635040);
|
|
return 196 + ((validity - 40320) / 10080);
|
|
}
|
|
|
|
return 255; /* 63 weeks */
|
|
}
|
|
|
|
/**
|
|
* mm_sms_part_get_submit_pdu:
|
|
*
|
|
* @number: the subscriber number to send this message to
|
|
* @text: the body of this SMS
|
|
* @smsc: if given, the SMSC address
|
|
* @validity: minutes until the SMS should expire in the SMSC, or 0 for a
|
|
* suitable default
|
|
* @class: unused
|
|
* @out_pdulen: on success, the size of the returned PDU in bytes
|
|
* @out_msgstart: on success, the byte index in the returned PDU where the
|
|
* message starts (ie, skipping the SMSC length byte and address, if present)
|
|
* @error: on error, filled with the error that occurred
|
|
*
|
|
* Constructs a single-part SMS message with the given details, preferring to
|
|
* use the UCS2 character set when the message will fit, otherwise falling back
|
|
* to the GSM character set.
|
|
*
|
|
* Returns: the constructed PDU data on success, or %NULL on error
|
|
**/
|
|
guint8 *
|
|
mm_sms_part_get_submit_pdu (MMSmsPart *part,
|
|
guint *out_pdulen,
|
|
guint *out_msgstart,
|
|
GError **error)
|
|
{
|
|
guint8 *pdu;
|
|
guint len, offset = 0;
|
|
MMModemCharset best_cs = MM_MODEM_CHARSET_GSM;
|
|
guint ucs2len = 0, gsm_unsupported = 0;
|
|
guint textlen = 0;
|
|
|
|
g_return_val_if_fail (part->number != NULL, NULL);
|
|
g_return_val_if_fail (part->text != NULL, NULL);
|
|
|
|
/* FIXME: support multiple fragments. */
|
|
|
|
textlen = mm_charset_get_encoded_len (part->text, MM_MODEM_CHARSET_GSM, &gsm_unsupported);
|
|
if (textlen > 160) {
|
|
g_set_error_literal (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot encode message to fit into an SMS.");
|
|
return NULL;
|
|
}
|
|
|
|
/* If there are characters that are unsupported in the GSM charset, try
|
|
* UCS2. If the UCS2 encoded string is too long to fit in an SMS, then
|
|
* just use GSM and suck up the unconverted chars.
|
|
*/
|
|
if (gsm_unsupported > 0) {
|
|
ucs2len = mm_charset_get_encoded_len (part->text, MM_MODEM_CHARSET_UCS2, NULL);
|
|
if (ucs2len <= 140) {
|
|
best_cs = MM_MODEM_CHARSET_UCS2;
|
|
textlen = ucs2len;
|
|
}
|
|
}
|
|
|
|
/* Build up the PDU */
|
|
pdu = g_malloc0 (PDU_SIZE);
|
|
|
|
if (part->smsc) {
|
|
len = mm_sms_part_encode_address (part->smsc, pdu, PDU_SIZE, TRUE);
|
|
if (len == 0) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Invalid SMSC address '%s'", part->smsc);
|
|
goto error;
|
|
}
|
|
offset += len;
|
|
} else {
|
|
/* No SMSC, use default */
|
|
pdu[offset++] = 0x00;
|
|
}
|
|
|
|
if (out_msgstart)
|
|
*out_msgstart = offset;
|
|
|
|
if (part->validity > 0)
|
|
pdu[offset] = 1 << 4; /* TP-VP present; format RELATIVE */
|
|
else
|
|
pdu[offset] = 0; /* TP-VP not present */
|
|
pdu[offset++] |= 0x01; /* TP-MTI = SMS-SUBMIT */
|
|
|
|
pdu[offset++] = 0x00; /* TP-Message-Reference: filled by device */
|
|
|
|
len = mm_sms_part_encode_address (part->number, &pdu[offset], PDU_SIZE - offset, FALSE);
|
|
if (len == 0) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Invalid number '%s'", part->number);
|
|
goto error;
|
|
}
|
|
offset += len;
|
|
|
|
/* TP-PID */
|
|
pdu[offset++] = 0x00;
|
|
|
|
/* TP-DCS */
|
|
if (best_cs == MM_MODEM_CHARSET_UCS2)
|
|
pdu[offset++] = 0x08;
|
|
else
|
|
pdu[offset++] = 0x00; /* GSM */
|
|
|
|
/* TP-Validity-Period: 4 days */
|
|
if (part->validity > 0)
|
|
pdu[offset++] = validity_to_relative (part->validity);
|
|
|
|
/* TP-User-Data-Length */
|
|
pdu[offset++] = textlen;
|
|
|
|
if (best_cs == MM_MODEM_CHARSET_GSM) {
|
|
guint8 *unpacked, *packed;
|
|
guint32 unlen = 0, packlen = 0;
|
|
|
|
unpacked = mm_charset_utf8_to_unpacked_gsm (part->text, &unlen);
|
|
if (!unpacked || unlen == 0) {
|
|
g_free (unpacked);
|
|
g_set_error_literal (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to convert message text to GSM");
|
|
goto error;
|
|
}
|
|
|
|
packed = gsm_pack (unpacked, unlen, 0, &packlen);
|
|
g_free (unpacked);
|
|
if (!packed || packlen == 0) {
|
|
g_free (packed);
|
|
g_set_error_literal (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to pack message text to GSM");
|
|
goto error;
|
|
}
|
|
|
|
memcpy (&pdu[offset], packed, packlen);
|
|
g_free (packed);
|
|
offset += packlen;
|
|
} else if (best_cs == MM_MODEM_CHARSET_UCS2) {
|
|
GByteArray *array;
|
|
|
|
array = g_byte_array_sized_new (textlen / 2);
|
|
if (!mm_modem_charset_byte_array_append (array, part->text, FALSE, best_cs)) {
|
|
g_byte_array_free (array, TRUE);
|
|
g_set_error_literal (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to convert message text to UCS2");
|
|
goto error;
|
|
}
|
|
|
|
memcpy (&pdu[offset], array->data, array->len);
|
|
offset += array->len;
|
|
g_byte_array_free (array, TRUE);
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
if (out_pdulen)
|
|
*out_pdulen = offset;
|
|
return pdu;
|
|
|
|
error:
|
|
g_free (pdu);
|
|
return NULL;
|
|
}
|