This can be used for cell broadcast messages as well Signed-off-by: Guido Günther <agx@sigxcpu.org>
1189 lines
42 KiB
C
1189 lines
42 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>
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-common-helpers.h"
|
|
#include "mm-helper-enums-types.h"
|
|
#include "mm-sms-part-3gpp.h"
|
|
#include "mm-charsets.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 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)
|
|
|
|
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 gchar *
|
|
sms_decode_address (const guint8 *address,
|
|
gint len_digits,
|
|
GError **error)
|
|
{
|
|
guint8 addrtype, addrplan;
|
|
gchar *utf8;
|
|
|
|
addrtype = address[0] & SMS_NUMBER_TYPE_MASK;
|
|
addrplan = address[0] & SMS_NUMBER_PLAN_MASK;
|
|
address++;
|
|
|
|
if (addrtype == SMS_NUMBER_TYPE_ALPHA) {
|
|
g_autoptr(GByteArray) unpacked_array = NULL;
|
|
guint8 *unpacked = NULL;
|
|
guint32 unpacked_len;
|
|
|
|
unpacked = mm_charset_gsm_unpack (address, (len_digits * 4) / 7, 0, &unpacked_len);
|
|
unpacked_array = g_byte_array_new_take (unpacked, unpacked_len);
|
|
utf8 = mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error);
|
|
} else if (addrtype == SMS_NUMBER_TYPE_INTL &&
|
|
addrplan == SMS_NUMBER_PLAN_TELEPHONE) {
|
|
/* International telphone number, format as "+1234567890" */
|
|
utf8 = g_malloc (len_digits + 3); /* '+' + digits + possible trailing 0xf + NUL */
|
|
utf8[0] = '+';
|
|
sms_semi_octets_to_bcd_string (utf8 + 1, address, (len_digits + 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_digits + 2); /* digits + possible trailing 0xf + NUL */
|
|
sms_semi_octets_to_bcd_string (utf8, address, (len_digits + 1) / 2);
|
|
}
|
|
|
|
return utf8;
|
|
}
|
|
|
|
static gchar *
|
|
sms_decode_timestamp (const guint8 *timestamp,
|
|
GError **error)
|
|
{
|
|
/* ISO8601 format: YYYY-MM-DDTHH:MM:SS+HHMM */
|
|
guint year, month, day, hour, minute, second;
|
|
gint quarters, offset_minutes;
|
|
|
|
year = 2000 + ((timestamp[0] & 0xf) * 10) + ((timestamp[0] >> 4) & 0xf);
|
|
month = ((timestamp[1] & 0xf) * 10) + ((timestamp[1] >> 4) & 0xf);
|
|
day = ((timestamp[2] & 0xf) * 10) + ((timestamp[2] >> 4) & 0xf);
|
|
hour = ((timestamp[3] & 0xf) * 10) + ((timestamp[3] >> 4) & 0xf);
|
|
minute = ((timestamp[4] & 0xf) * 10) + ((timestamp[4] >> 4) & 0xf);
|
|
second = ((timestamp[5] & 0xf) * 10) + ((timestamp[5] >> 4) & 0xf);
|
|
quarters = ((timestamp[6] & 0x7) * 10) + ((timestamp[6] >> 4) & 0xf);
|
|
offset_minutes = quarters * 15;
|
|
if (timestamp[6] & 0x08)
|
|
offset_minutes = -1 * offset_minutes;
|
|
|
|
return mm_new_iso8601_time (year, month, day, hour,
|
|
minute, second, TRUE, offset_minutes, error);
|
|
}
|
|
|
|
static MMSmsEncoding
|
|
sms_encoding_type (int dcs)
|
|
{
|
|
MMSmsEncoding 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:
|
|
/* reserved - spec says to treat it as default alphabet */
|
|
/* Fall through */
|
|
case 0x0c:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
case 0x04:
|
|
scheme = MM_SMS_ENCODING_8BIT;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
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;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
break;
|
|
|
|
/* Reserved coding group values - spec says to treat it as default alphabet */
|
|
default:
|
|
scheme = MM_SMS_ENCODING_GSM7;
|
|
break;
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
gchar *
|
|
mm_sms_decode_text (const guint8 *text,
|
|
int len,
|
|
MMSmsEncoding encoding,
|
|
int bit_offset,
|
|
gpointer log_object,
|
|
GError **error)
|
|
{
|
|
if (!text || len == 0) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Skipping SMS text: SMS text has no elements to decode");
|
|
return NULL;
|
|
}
|
|
|
|
if (encoding == MM_SMS_ENCODING_GSM7) {
|
|
g_autoptr(GByteArray) unpacked_array = NULL;
|
|
guint8 *unpacked = NULL;
|
|
guint32 unpacked_len;
|
|
gchar *utf8;
|
|
|
|
unpacked = mm_charset_gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
|
|
unpacked_array = g_byte_array_new_take (unpacked, unpacked_len);
|
|
utf8 = mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error);
|
|
if (utf8)
|
|
mm_obj_dbg (log_object, "converted SMS part text from GSM-7 to UTF-8: %s", utf8);
|
|
return utf8;
|
|
}
|
|
|
|
/* Always assume UTF-16 instead of UCS-2! */
|
|
if (encoding == MM_SMS_ENCODING_UCS2) {
|
|
g_autoptr(GByteArray) bytearray = NULL;
|
|
gchar *utf8;
|
|
|
|
bytearray = g_byte_array_append (g_byte_array_sized_new (len), (const guint8 *)text, len);
|
|
utf8 = mm_modem_charset_bytearray_to_utf8 (bytearray, MM_MODEM_CHARSET_UTF16, FALSE, error);
|
|
if (utf8)
|
|
mm_obj_dbg (log_object, "converted SMS part text from UTF-16BE to UTF-8: %s", utf8);
|
|
return utf8;
|
|
}
|
|
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't convert SMS part contents from %s to UTF-8",
|
|
mm_sms_encoding_get_string (encoding));
|
|
return NULL;
|
|
}
|
|
|
|
static guint
|
|
relative_to_validity (guint8 relative)
|
|
{
|
|
if (relative <= 143)
|
|
return (relative + 1) * 5;
|
|
|
|
if (relative <= 167)
|
|
return 720 + (relative - 143) * 30;
|
|
|
|
return (relative - 166) * 1440;
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
MMSmsPart *
|
|
mm_sms_part_3gpp_new_from_pdu (guint index,
|
|
const gchar *hexpdu,
|
|
gpointer log_object,
|
|
GError **error)
|
|
{
|
|
g_autofree guint8 *pdu = NULL;
|
|
gsize pdu_len;
|
|
|
|
/* Convert PDU from hex to binary */
|
|
pdu = mm_utils_hexstr2bin (hexpdu, -1, &pdu_len, error);
|
|
if (!pdu) {
|
|
g_prefix_error (error, "Couldn't convert 3GPP PDU from hex to binary: ");
|
|
return NULL;
|
|
}
|
|
|
|
return mm_sms_part_3gpp_new_from_binary_pdu (index, pdu, pdu_len, log_object, FALSE, error);
|
|
}
|
|
|
|
MMSmsPart *
|
|
mm_sms_part_3gpp_new_from_binary_pdu (guint index,
|
|
const guint8 *pdu,
|
|
gsize pdu_len,
|
|
gpointer log_object,
|
|
gboolean transfer_route,
|
|
GError **error)
|
|
{
|
|
MMSmsPart *sms_part;
|
|
guint8 pdu_type;
|
|
guint offset;
|
|
guint smsc_addr_size_bytes;
|
|
guint tp_addr_size_digits;
|
|
guint tp_addr_size_bytes;
|
|
guint8 validity_format = 0;
|
|
gboolean has_udh = FALSE;
|
|
/* The following offsets are OPTIONAL, as STATUS REPORTs may not have
|
|
* them; we use '0' to indicate their absence */
|
|
guint tp_pid_offset = 0;
|
|
guint tp_dcs_offset = 0;
|
|
guint tp_user_data_len_offset = 0;
|
|
MMSmsEncoding user_data_encoding = MM_SMS_ENCODING_UNKNOWN;
|
|
gchar *address;
|
|
|
|
/* Create the new MMSmsPart */
|
|
sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN);
|
|
|
|
if (index != SMS_PART_INVALID_INDEX)
|
|
mm_obj_dbg (log_object, "parsing PDU (%u)...", index);
|
|
else
|
|
mm_obj_dbg (log_object, "parsing PDU...");
|
|
|
|
#define PDU_SIZE_CHECK(required_size, check_descr_str) \
|
|
if (pdu_len < required_size) { \
|
|
g_set_error (error, \
|
|
MM_CORE_ERROR, \
|
|
MM_CORE_ERROR_FAILED, \
|
|
"PDU too short, %s: %" G_GSIZE_FORMAT " < %u", \
|
|
check_descr_str, \
|
|
pdu_len, \
|
|
required_size); \
|
|
mm_sms_part_free (sms_part); \
|
|
return NULL; \
|
|
}
|
|
|
|
offset = 0;
|
|
|
|
if (!transfer_route) {
|
|
/* ---------------------------------------------------------------------- */
|
|
/* SMSC, in address format, precedes the TPDU
|
|
* First byte represents the number of BYTES for the address value */
|
|
PDU_SIZE_CHECK (1, "cannot read SMSC address length");
|
|
smsc_addr_size_bytes = pdu[offset++];
|
|
if (smsc_addr_size_bytes > 0) {
|
|
PDU_SIZE_CHECK (offset + smsc_addr_size_bytes, "cannot read SMSC address");
|
|
/* SMSC may not be given in DELIVER PDUs */
|
|
address = sms_decode_address (&pdu[1], 2 * (smsc_addr_size_bytes - 1), error);
|
|
if (!address) {
|
|
g_prefix_error (error, "Couldn't read SMSC address: ");
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_smsc (sms_part, g_steal_pointer (&address));
|
|
mm_obj_dbg (log_object, " SMSC address parsed: '%s'", mm_sms_part_get_smsc (sms_part));
|
|
offset += smsc_addr_size_bytes;
|
|
} else
|
|
mm_obj_dbg (log_object, " no SMSC address given");
|
|
} else
|
|
mm_obj_dbg (log_object, " This is a transfer-route message");
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* TP-MTI (1 byte) */
|
|
PDU_SIZE_CHECK (offset + 1, "cannot read TP-MTI");
|
|
|
|
pdu_type = (pdu[offset] & SMS_TP_MTI_MASK);
|
|
switch (pdu_type) {
|
|
case SMS_TP_MTI_SMS_DELIVER:
|
|
mm_obj_dbg (log_object, " deliver type PDU detected");
|
|
mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_DELIVER);
|
|
break;
|
|
case SMS_TP_MTI_SMS_SUBMIT:
|
|
mm_obj_dbg (log_object, " submit type PDU detected");
|
|
mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_SUBMIT);
|
|
break;
|
|
case SMS_TP_MTI_SMS_STATUS_REPORT:
|
|
mm_obj_dbg (log_object, " status report type PDU detected");
|
|
mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_STATUS_REPORT);
|
|
break;
|
|
default:
|
|
mm_sms_part_free (sms_part);
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unhandled message type: 0x%02x",
|
|
pdu_type);
|
|
return NULL;
|
|
}
|
|
|
|
/* Delivery report was requested? */
|
|
if (pdu[offset] & 0x20)
|
|
mm_sms_part_set_delivery_report_request (sms_part, TRUE);
|
|
|
|
/* PDU with validity? (only in SUBMIT PDUs) */
|
|
if (pdu_type == SMS_TP_MTI_SMS_SUBMIT)
|
|
validity_format = pdu[offset] & 0x18;
|
|
|
|
/* PDU with user data header? */
|
|
if (pdu[offset] & 0x40)
|
|
has_udh = TRUE;
|
|
|
|
offset++;
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* TP-MR (1 byte, in STATUS_REPORT and SUBMIT PDUs */
|
|
if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT ||
|
|
pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
|
|
PDU_SIZE_CHECK (offset + 1, "cannot read message reference");
|
|
|
|
mm_obj_dbg (log_object, " message reference: %u", (guint)pdu[offset]);
|
|
mm_sms_part_set_message_reference (sms_part, pdu[offset]);
|
|
offset++;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* TP-DA or TP-OA or TP-RA
|
|
* First byte represents the number of DIGITS in the number.
|
|
* Round the sender address length up to an even number of
|
|
* semi-octets, and thus an integral number of octets.
|
|
*/
|
|
PDU_SIZE_CHECK (offset + 1, "cannot read number of digits in number");
|
|
tp_addr_size_digits = pdu[offset++];
|
|
tp_addr_size_bytes = (tp_addr_size_digits + 1) >> 1;
|
|
mm_obj_dbg (log_object, " address size: %u digits (%u bytes)",
|
|
tp_addr_size_digits, tp_addr_size_bytes);
|
|
|
|
if (tp_addr_size_bytes == 0) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't read address: field missing");
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
/* +1 due to the Type of Address byte */
|
|
PDU_SIZE_CHECK (offset + 1 + tp_addr_size_bytes, "cannot read number");
|
|
address = sms_decode_address (&pdu[offset], tp_addr_size_digits, error);
|
|
if (!address) {
|
|
g_prefix_error (error, "Couldn't read address: ");
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_number (sms_part, g_steal_pointer (&address));
|
|
mm_obj_dbg (log_object, " number parsed: %s", mm_sms_part_get_number (sms_part));
|
|
offset += (1 + tp_addr_size_bytes);
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Get timestamps and indexes for TP-PID, TP-DCS and TP-UDL/TP-UD */
|
|
|
|
if (pdu_type == SMS_TP_MTI_SMS_DELIVER) {
|
|
gchar *str = NULL;
|
|
|
|
PDU_SIZE_CHECK (offset + 9,
|
|
"cannot read PID/DCS/Timestamp"); /* 1+1+7=9 */
|
|
|
|
/* ------ TP-PID (1 byte) ------ */
|
|
tp_pid_offset = offset++;
|
|
|
|
/* ------ TP-DCS (1 byte) ------ */
|
|
tp_dcs_offset = offset++;
|
|
|
|
/* ------ Timestamp (7 bytes) ------ */
|
|
str = sms_decode_timestamp (&pdu[offset], error);
|
|
if (!str) {
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_timestamp (sms_part, str);
|
|
offset += 7;
|
|
|
|
tp_user_data_len_offset = offset;
|
|
} else if (pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
|
|
PDU_SIZE_CHECK (offset + 2 + !!validity_format,
|
|
"cannot read PID/DCS/Validity"); /* 1+1=2 */
|
|
|
|
/* ------ TP-PID (1 byte) ------ */
|
|
tp_pid_offset = offset++;
|
|
|
|
/* ------ TP-DCS (1 byte) ------ */
|
|
tp_dcs_offset = offset++;
|
|
|
|
/* ----------- TP-Validity-Period (1 byte) ----------- */
|
|
if (validity_format) {
|
|
switch (validity_format) {
|
|
case 0x10:
|
|
mm_obj_dbg (log_object, " validity available, format relative");
|
|
mm_sms_part_set_validity_relative (sms_part,
|
|
relative_to_validity (pdu[offset]));
|
|
offset++;
|
|
break;
|
|
case 0x08:
|
|
/* TODO: support enhanced format; GSM 03.40 */
|
|
mm_obj_dbg (log_object, " validity available, format enhanced (not implemented)");
|
|
/* 7 bytes for enhanced validity */
|
|
offset += 7;
|
|
break;
|
|
case 0x18:
|
|
/* TODO: support absolute format; GSM 03.40 */
|
|
mm_obj_dbg (log_object, " validity available, format absolute (not implemented)");
|
|
/* 7 bytes for absolute validity */
|
|
offset += 7;
|
|
break;
|
|
default:
|
|
/* Cannot happen as we AND with the 0x18 mask */
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
tp_user_data_len_offset = offset;
|
|
} else if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT) {
|
|
gchar *str = NULL;
|
|
|
|
/* We have 2 timestamps in status report PDUs:
|
|
* first, the timestamp for when the PDU was received in the SMSC
|
|
* second, the timestamp for when the PDU was forwarded by the SMSC
|
|
*/
|
|
PDU_SIZE_CHECK (offset + 15, "cannot read Timestamps/TP-STATUS"); /* 7+7+1=15 */
|
|
|
|
/* ------ Timestamp (7 bytes) ------ */
|
|
str = sms_decode_timestamp (&pdu[offset], error);
|
|
if (!str) {
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_timestamp (sms_part, str);
|
|
offset += 7;
|
|
|
|
/* ------ Discharge Timestamp (7 bytes) ------ */
|
|
str = sms_decode_timestamp (&pdu[offset], error);
|
|
if (!str) {
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_discharge_timestamp (sms_part, str);
|
|
offset += 7;
|
|
|
|
/* ----- TP-STATUS (1 byte) ------ */
|
|
mm_obj_dbg (log_object, " delivery state: %u", (guint)pdu[offset]);
|
|
mm_sms_part_set_delivery_state (sms_part, pdu[offset]);
|
|
offset++;
|
|
|
|
/* ------ TP-PI (1 byte) OPTIONAL ------ */
|
|
if (offset < pdu_len) {
|
|
guint next_optional_field_offset = offset + 1;
|
|
|
|
/* TP-PID? */
|
|
if (pdu[offset] & 0x01)
|
|
tp_pid_offset = next_optional_field_offset++;
|
|
|
|
/* TP-DCS? */
|
|
if (pdu[offset] & 0x02)
|
|
tp_dcs_offset = next_optional_field_offset++;
|
|
|
|
/* TP-UserData? */
|
|
if (pdu[offset] & 0x04)
|
|
tp_user_data_len_offset = next_optional_field_offset;
|
|
}
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
mm_obj_dbg (log_object, " timestamp: %s", mm_sms_part_get_timestamp (sms_part));
|
|
|
|
if (tp_pid_offset > 0) {
|
|
PDU_SIZE_CHECK (tp_pid_offset + 1, "cannot read TP-PID");
|
|
mm_obj_dbg (log_object, " PID: %u", (guint)pdu[tp_pid_offset]);
|
|
}
|
|
|
|
/* Grab user data encoding and message class */
|
|
if (tp_dcs_offset > 0) {
|
|
PDU_SIZE_CHECK (tp_dcs_offset + 1, "cannot read TP-DCS");
|
|
|
|
/* Encoding given in the 'alphabet' bits */
|
|
user_data_encoding = sms_encoding_type (pdu[tp_dcs_offset]);
|
|
switch (user_data_encoding) {
|
|
case MM_SMS_ENCODING_GSM7:
|
|
mm_obj_dbg (log_object, " user data encoding is GSM7");
|
|
break;
|
|
case MM_SMS_ENCODING_UCS2:
|
|
mm_obj_dbg (log_object, " user data encoding is UCS2");
|
|
break;
|
|
case MM_SMS_ENCODING_8BIT:
|
|
mm_obj_dbg (log_object, " user data encoding is 8bit");
|
|
break;
|
|
case MM_SMS_ENCODING_UNKNOWN:
|
|
mm_obj_dbg (log_object, " user data encoding is unknown");
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
|
|
}
|
|
mm_sms_part_set_encoding (sms_part, user_data_encoding);
|
|
|
|
/* Class */
|
|
if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID)
|
|
mm_sms_part_set_class (sms_part,
|
|
pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK);
|
|
}
|
|
|
|
if (tp_user_data_len_offset > 0) {
|
|
guint tp_user_data_size_elements;
|
|
guint tp_user_data_size_bytes;
|
|
guint tp_user_data_offset;
|
|
guint bit_offset;
|
|
|
|
PDU_SIZE_CHECK (tp_user_data_len_offset + 1, "cannot read TP-UDL");
|
|
tp_user_data_size_elements = pdu[tp_user_data_len_offset];
|
|
mm_obj_dbg (log_object, " user data length: %u elements", tp_user_data_size_elements);
|
|
|
|
if (user_data_encoding == MM_SMS_ENCODING_GSM7)
|
|
tp_user_data_size_bytes = (7 * (tp_user_data_size_elements + 1 )) / 8;
|
|
else
|
|
tp_user_data_size_bytes = tp_user_data_size_elements;
|
|
mm_obj_dbg (log_object, " user data length: %u bytes", tp_user_data_size_bytes);
|
|
|
|
tp_user_data_offset = tp_user_data_len_offset + 1;
|
|
PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read TP-UD");
|
|
|
|
bit_offset = 0;
|
|
if (has_udh) {
|
|
guint udhl_elements;
|
|
guint udhl, end;
|
|
|
|
PDU_SIZE_CHECK (tp_user_data_offset + 1, "cannot read UDH length");
|
|
udhl = pdu[tp_user_data_offset] + 1;
|
|
end = tp_user_data_offset + udhl;
|
|
|
|
PDU_SIZE_CHECK (tp_user_data_offset + udhl, "cannot read UDH");
|
|
|
|
for (offset = tp_user_data_offset + 1; (offset + 1) < end;) {
|
|
guint8 ie_id, ie_len;
|
|
|
|
ie_id = pdu[offset++];
|
|
ie_len = pdu[offset++];
|
|
|
|
switch (ie_id) {
|
|
case 0x00:
|
|
if (offset + 2 >= end)
|
|
break;
|
|
/*
|
|
* 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:
|
|
if (offset + 3 >= end)
|
|
break;
|
|
/* 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;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
offset += ie_len;
|
|
}
|
|
|
|
/*
|
|
* Move past the user data headers to prevent it from being
|
|
* decoded into garbage text.
|
|
*/
|
|
tp_user_data_offset += udhl;
|
|
tp_user_data_size_bytes -= 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;
|
|
udhl_elements = (udhl * 8 + bit_offset) / 7;
|
|
} else
|
|
udhl_elements = udhl;
|
|
|
|
if (udhl_elements >= tp_user_data_size_elements) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"udhl length (%u) is greater than data size (%u)",
|
|
udhl_elements, tp_user_data_size_elements);
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
tp_user_data_size_elements -= udhl_elements;
|
|
}
|
|
|
|
switch (user_data_encoding) {
|
|
case MM_SMS_ENCODING_GSM7:
|
|
case MM_SMS_ENCODING_UCS2:
|
|
{
|
|
gchar *text;
|
|
|
|
/* Otherwise if it's 7-bit or UCS2 we can decode it */
|
|
mm_obj_dbg (log_object, "decoding SMS text with %u elements", tp_user_data_size_elements);
|
|
text = mm_sms_decode_text (&pdu[tp_user_data_offset],
|
|
tp_user_data_size_elements,
|
|
user_data_encoding,
|
|
bit_offset,
|
|
log_object,
|
|
error);
|
|
if (!text) {
|
|
mm_sms_part_free (sms_part);
|
|
return NULL;
|
|
}
|
|
mm_sms_part_take_text (sms_part, text);
|
|
break;
|
|
}
|
|
case MM_SMS_ENCODING_8BIT:
|
|
case MM_SMS_ENCODING_UNKNOWN:
|
|
default:
|
|
{
|
|
GByteArray *raw;
|
|
|
|
mm_obj_dbg (log_object, "skipping SMS text: unknown encoding (0x%02X)", user_data_encoding);
|
|
|
|
PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read user data");
|
|
|
|
/* 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.
|
|
*/
|
|
raw = g_byte_array_sized_new (tp_user_data_size_bytes);
|
|
g_byte_array_append (raw, &pdu[tp_user_data_offset], tp_user_data_size_bytes);
|
|
mm_sms_part_take_data (sms_part, raw);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sms_part;
|
|
}
|
|
|
|
/**
|
|
* mm_sms_part_3gpp_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 information,
|
|
* 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_3gpp_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 */
|
|
}
|
|
|
|
/**
|
|
* mm_sms_part_3gpp_get_submit_pdu:
|
|
*
|
|
* @part: the SMS message part
|
|
* @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_3gpp_get_submit_pdu (MMSmsPart *part,
|
|
guint *out_pdulen,
|
|
guint *out_msgstart,
|
|
gpointer log_object,
|
|
GError **error)
|
|
{
|
|
guint8 *pdu;
|
|
guint len, offset = 0;
|
|
guint shift = 0;
|
|
guint8 *udl_ptr;
|
|
MMSmsEncoding encoding;
|
|
|
|
g_return_val_if_fail (mm_sms_part_get_number (part) != NULL, NULL);
|
|
g_return_val_if_fail (mm_sms_part_get_text (part) != NULL || mm_sms_part_get_data (part) != NULL, NULL);
|
|
|
|
if (mm_sms_part_get_pdu_type (part) != MM_SMS_PDU_TYPE_SUBMIT) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Invalid PDU type to generate a 'submit' PDU: '%s'",
|
|
mm_sms_pdu_type_get_string (mm_sms_part_get_pdu_type (part)));
|
|
return NULL;
|
|
}
|
|
|
|
mm_obj_dbg (log_object, "creating PDU for part...");
|
|
|
|
/* Build up the PDU */
|
|
pdu = g_malloc0 (PDU_SIZE);
|
|
|
|
if (mm_sms_part_get_smsc (part)) {
|
|
mm_obj_dbg (log_object, " adding SMSC to PDU...");
|
|
len = mm_sms_part_3gpp_encode_address (mm_sms_part_get_smsc (part), pdu, PDU_SIZE, TRUE);
|
|
if (len == 0) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Invalid SMSC address '%s'", mm_sms_part_get_smsc (part));
|
|
goto error;
|
|
}
|
|
offset += len;
|
|
} else {
|
|
/* No SMSC, use default */
|
|
pdu[offset++] = 0x00;
|
|
}
|
|
|
|
if (out_msgstart)
|
|
*out_msgstart = offset;
|
|
|
|
/* ----------- First BYTE ----------- */
|
|
pdu[offset] = 0;
|
|
|
|
/* TP-VP present; format RELATIVE */
|
|
if (mm_sms_part_get_validity_relative (part) > 0) {
|
|
mm_obj_dbg (log_object, " adding validity to PDU...");
|
|
pdu[offset] |= 0x10;
|
|
}
|
|
|
|
/* Concatenation sequence only found in multipart SMS */
|
|
if (mm_sms_part_get_concat_sequence (part)) {
|
|
mm_obj_dbg (log_object, " adding UDHI to PDU...");
|
|
pdu[offset] |= 0x40; /* UDHI */
|
|
}
|
|
|
|
/* Delivery report requested in singlepart messages or in the last PDU of
|
|
* multipart messages */
|
|
if (mm_sms_part_get_delivery_report_request (part) &&
|
|
(!mm_sms_part_get_concat_sequence (part) ||
|
|
mm_sms_part_get_concat_max (part) == mm_sms_part_get_concat_sequence (part))) {
|
|
mm_obj_dbg (log_object, " requesting delivery report...");
|
|
pdu[offset] |= 0x20;
|
|
}
|
|
|
|
/* TP-MTI = SMS-SUBMIT */
|
|
pdu[offset++] |= 0x01;
|
|
|
|
|
|
/* ----------- TP-MR (1 byte) ----------- */
|
|
|
|
pdu[offset++] = 0x00; /* TP-Message-Reference: filled by device */
|
|
|
|
/* ----------- Destination address ----------- */
|
|
|
|
len = mm_sms_part_3gpp_encode_address (mm_sms_part_get_number (part), &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'", mm_sms_part_get_number (part));
|
|
goto error;
|
|
}
|
|
offset += len;
|
|
|
|
/* ----------- TP-PID (1 byte) ----------- */
|
|
|
|
pdu[offset++] = 0x00;
|
|
|
|
/* ----------- TP-DCS (1 byte) ----------- */
|
|
pdu[offset] = 0x00;
|
|
|
|
if (mm_sms_part_get_class (part) >= 0 && mm_sms_part_get_class (part) <= 3) {
|
|
mm_obj_dbg (log_object, " using class %d...", mm_sms_part_get_class (part));
|
|
pdu[offset] |= SMS_DCS_CLASS_VALID;
|
|
pdu[offset] |= mm_sms_part_get_class (part);
|
|
}
|
|
|
|
encoding = mm_sms_part_get_encoding (part);
|
|
|
|
switch (encoding) {
|
|
case MM_SMS_ENCODING_UCS2:
|
|
mm_obj_dbg (log_object, " using UCS2 encoding...");
|
|
pdu[offset] |= SMS_DCS_CODING_UCS2;
|
|
break;
|
|
case MM_SMS_ENCODING_GSM7:
|
|
mm_obj_dbg (log_object, " using GSM7 encoding...");
|
|
pdu[offset] |= SMS_DCS_CODING_DEFAULT; /* GSM */
|
|
break;
|
|
case MM_SMS_ENCODING_8BIT:
|
|
case MM_SMS_ENCODING_UNKNOWN:
|
|
default:
|
|
mm_obj_dbg (log_object, " using 8bit encoding...");
|
|
pdu[offset] |= SMS_DCS_CODING_8BIT;
|
|
break;
|
|
}
|
|
offset++;
|
|
|
|
/* ----------- TP-Validity-Period (1 byte): 4 days ----------- */
|
|
/* Only if TP-VPF was set in first byte */
|
|
|
|
if (mm_sms_part_get_validity_relative (part) > 0)
|
|
pdu[offset++] = validity_to_relative (mm_sms_part_get_validity_relative (part));
|
|
|
|
/* ----------- TP-User-Data-Length ----------- */
|
|
/* Set to zero initially, and keep a ptr for easy access later */
|
|
udl_ptr = &pdu[offset];
|
|
pdu[offset++] = 0;
|
|
|
|
/* Build UDH */
|
|
if (mm_sms_part_get_concat_sequence (part)) {
|
|
mm_obj_dbg (log_object, " adding UDH header in PDU... (reference: %u, max: %u, sequence: %u)",
|
|
mm_sms_part_get_concat_reference (part),
|
|
mm_sms_part_get_concat_max (part),
|
|
mm_sms_part_get_concat_sequence (part));
|
|
pdu[offset++] = 0x05; /* udh len */
|
|
pdu[offset++] = 0x00; /* mid */
|
|
pdu[offset++] = 0x03; /* data len */
|
|
pdu[offset++] = (guint8)mm_sms_part_get_concat_reference (part);
|
|
pdu[offset++] = (guint8)mm_sms_part_get_concat_max (part);
|
|
pdu[offset++] = (guint8)mm_sms_part_get_concat_sequence (part);
|
|
|
|
/* if a UDH is present and the data encoding is the default 7-bit
|
|
* alphabet, the user data must be 7-bit word aligned after the
|
|
* UDH. This means up to 6 bits of zeros need to be inserted at the
|
|
* start of the message.
|
|
*
|
|
* In our case the UDH is 6 bytes long, 48bits. The next multiple of
|
|
* 7 is therefore 49, so we only need to include one bit of padding.
|
|
*/
|
|
shift = 1;
|
|
}
|
|
|
|
if (encoding == MM_SMS_ENCODING_GSM7) {
|
|
g_autoptr(GByteArray) unpacked = NULL;
|
|
g_autofree guint8 *packed = NULL;
|
|
guint32 packlen = 0;
|
|
|
|
unpacked = mm_modem_charset_bytearray_from_utf8 (mm_sms_part_get_text (part), MM_MODEM_CHARSET_GSM, FALSE, error);
|
|
if (!unpacked)
|
|
goto error;
|
|
|
|
if (unpacked->len == 0) {
|
|
g_set_error_literal (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to convert message text to GSM");
|
|
goto error;
|
|
}
|
|
|
|
/* Set real data length, in septets
|
|
* If we had UDH, add 7 septets
|
|
*/
|
|
*udl_ptr = mm_sms_part_get_concat_sequence (part) ? (7 + unpacked->len) : unpacked->len;
|
|
mm_obj_dbg (log_object, " user data length is %u septets (%s UDH)",
|
|
*udl_ptr,
|
|
mm_sms_part_get_concat_sequence (part) ? "with" : "without");
|
|
|
|
packed = mm_charset_gsm_pack (unpacked->data, unpacked->len, shift, &packlen);
|
|
if (!packed || packlen == 0) {
|
|
g_set_error_literal (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to pack message text to GSM");
|
|
goto error;
|
|
}
|
|
|
|
if (offset + packlen > PDU_SIZE) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Packed user data is too large for PDU (want %d bytes total, have %d)",
|
|
offset + packlen, PDU_SIZE);
|
|
goto error;
|
|
}
|
|
|
|
memcpy (&pdu[offset], packed, packlen);
|
|
offset += packlen;
|
|
} else if (encoding == MM_SMS_ENCODING_UCS2) {
|
|
g_autoptr(GByteArray) array = NULL;
|
|
g_autoptr(GError) inner_error = NULL;
|
|
|
|
/* Always assume UTF-16 instead of UCS-2! */
|
|
array = mm_modem_charset_bytearray_from_utf8 (mm_sms_part_get_text (part), MM_MODEM_CHARSET_UTF16, FALSE, &inner_error);
|
|
if (!array) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"Failed to convert message text to UTF-16: %s",
|
|
inner_error->message);
|
|
goto error;
|
|
}
|
|
|
|
/* Set real data length, in octets
|
|
* If we had UDH, add 6 octets
|
|
*/
|
|
*udl_ptr = mm_sms_part_get_concat_sequence (part) ? (6 + array->len) : array->len;
|
|
mm_obj_dbg (log_object, " user data length is %u octets (%s UDH)",
|
|
*udl_ptr,
|
|
mm_sms_part_get_concat_sequence (part) ? "with" : "without");
|
|
|
|
if (offset + array->len > PDU_SIZE) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"User data is too large for PDU (want %d bytes total, have %d)",
|
|
offset + array->len, PDU_SIZE);
|
|
goto error;
|
|
}
|
|
|
|
memcpy (&pdu[offset], array->data, array->len);
|
|
offset += array->len;
|
|
} else if (mm_sms_part_get_encoding (part) == MM_SMS_ENCODING_8BIT) {
|
|
const GByteArray *data;
|
|
|
|
data = mm_sms_part_get_data (part);
|
|
|
|
/* Set real data length, in octets
|
|
* If we had UDH, add 6 octets
|
|
*/
|
|
*udl_ptr = mm_sms_part_get_concat_sequence (part) ? (6 + data->len) : data->len;
|
|
mm_obj_dbg (log_object, " binary user data length is %u octets (%s UDH)",
|
|
*udl_ptr,
|
|
mm_sms_part_get_concat_sequence (part) ? "with" : "without");
|
|
|
|
if (offset + data->len > PDU_SIZE) {
|
|
g_set_error (error,
|
|
MM_MESSAGE_ERROR,
|
|
MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
|
|
"User data is too large for PDU (want %d bytes total, have %d)",
|
|
offset + data->len, PDU_SIZE);
|
|
goto error;
|
|
}
|
|
|
|
memcpy (&pdu[offset], data->data, data->len);
|
|
offset += data->len;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
if (out_pdulen)
|
|
*out_pdulen = offset;
|
|
return pdu;
|
|
|
|
error:
|
|
g_free (pdu);
|
|
return NULL;
|
|
}
|
|
|
|
GByteArray **
|
|
mm_sms_part_3gpp_util_split_data (const guint8 *data,
|
|
gsize data_len)
|
|
{
|
|
GByteArray **out;
|
|
|
|
/* Some info about the rules for splitting.
|
|
*
|
|
* The User Data can be up to 140 bytes in the SMS part:
|
|
* 0) If we only need one chunk, it can be of up to 140 bytes.
|
|
* If we need more than one chunk, these have to be of 140 - 6 = 134
|
|
* bytes each, as we need place for the UDH header.
|
|
*/
|
|
|
|
if (data_len <= 140) {
|
|
out = g_new0 (GByteArray *, 2);
|
|
out[0] = g_byte_array_append (g_byte_array_sized_new (data_len),
|
|
data,
|
|
data_len);
|
|
} else {
|
|
guint n_chunks;
|
|
guint i;
|
|
guint j;
|
|
|
|
n_chunks = data_len / 134;
|
|
if (data_len % 134 != 0)
|
|
n_chunks ++;
|
|
|
|
out = g_new0 (GByteArray *, n_chunks + 1);
|
|
for (i = 0, j = 0; i < n_chunks; i++, j+= 134) {
|
|
out[i] = g_byte_array_append (g_byte_array_sized_new (134),
|
|
&data[j],
|
|
MIN (data_len - j, 134));
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|