sms: split SMS parsing out into a separate file

In preparation for adding tests.

Change-Id: If1ebd0fdd6e7470c21538042ab1735357649155c
This commit is contained in:
Nathan Williams
2011-07-25 00:42:08 -05:00
committed by Dan Williams
parent 0cc813312f
commit e5faa242e9
4 changed files with 355 additions and 260 deletions

View File

@@ -23,7 +23,9 @@ libmodem_helpers_la_SOURCES = \
mm-charsets.c \
mm-charsets.h \
mm-utils.c \
mm-utils.h
mm-utils.h \
mm-sms-utils.c \
mm-sms-utils.h
libserial_la_CPPFLAGS = \
$(MM_CFLAGS) \

View File

@@ -37,6 +37,7 @@
#include "mm-properties-changed-signal.h"
#include "mm-utils.h"
#include "mm-modem-location.h"
#include "mm-sms-utils.h"
static void modem_init (MMModem *modem_class);
static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class);
@@ -185,7 +186,6 @@ static void cusd_received (MMAtSerialPort *port,
#define GS_HASH_TAG "get-sms"
static GValue *simple_string_value (const char *str);
static GValue *simple_uint_value (guint32 i);
static GValue *simple_boolean_value (gboolean b);
static void simple_free_gvalue (gpointer data);
MMModem *
@@ -4196,35 +4196,6 @@ mm_generic_gsm_get_charset (MMGenericGsm *self)
/* MMModemGsmSms interface */
#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)
#define SMS_MAX_PDU_LEN 344
static void
sms_send_done (MMAtSerialPort *port,
@@ -4275,219 +4246,7 @@ sms_send (MMModemGsmSms *modem,
g_free (command);
}
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';
}
/* 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 char *
sms_decode_text (const guint8 *text, int len, int dcs, int bit_offset)
{
char *utf8;
guint8 coding = dcs & SMS_DCS_CODING_MASK;
guint8 *unpacked;
guint32 unpacked_len;
if (coding == SMS_DCS_CODING_DEFAULT) {
unpacked = gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
utf8 = (char *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
g_free (unpacked);
} else if (coding == SMS_DCS_CODING_UCS2)
utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
else if (coding == SMS_DCS_CODING_8BIT)
utf8 = g_strndup ((const char *)text, len);
else
utf8 = g_strdup ("");
return utf8;
}
static GHashTable *
sms_parse_pdu (const char *hexpdu)
{
GHashTable *properties;
gsize pdu_len;
guint8 *pdu;
int 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, user_data_dcs, bit_offset;
char *smsc_addr, *sender_addr, *sc_timestamp, *msg_text;
/* Convert PDU from hex to binary */
pdu = (guint8 *) utils_hexstr2bin (hexpdu, &pdu_len);
if (!pdu) {
mm_err("Couldn't parse PDU of SMS GET response from hex");
return NULL;
}
/* 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) {
mm_err ("PDU too short (1): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
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) {
mm_err ("PDU too short (2): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
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_dcs = pdu[tp_dcs_offset];
if ((user_data_dcs & SMS_DCS_CODING_MASK) == SMS_DCS_CODING_DEFAULT)
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) {
mm_err ("PDU too short (3): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
return NULL;
}
/* Only handle SMS-DELIVER */
if ((pdu[msg_start_offset] & SMS_TP_MTI_MASK) != SMS_TP_MTI_SMS_DELIVER) {
mm_err ("Unhandled message type: 0x%02x", pdu[msg_start_offset]);
g_free (pdu);
return NULL;
}
smsc_addr = sms_decode_address (&pdu[1], 2 * (pdu[0] - 1));
sender_addr = sms_decode_address (&pdu[msg_start_offset + 2],
pdu[msg_start_offset + 1]);
sc_timestamp = sms_decode_timestamp (&pdu[tp_dcs_offset + 1]);
bit_offset = 0;
if (pdu[msg_start_offset] & SMS_TP_UDHI) {
/*
* Skip over the user data headers to prevent it from being
* decoded into garbage text.
*/
int udhl;
udhl = pdu[user_data_offset] + 1;
user_data_offset += udhl;
if ((user_data_dcs & SMS_DCS_CODING_MASK) == SMS_DCS_CODING_DEFAULT) {
bit_offset = 7 - (udhl * 8) % 7;
user_data_len -= (udhl * 8 + bit_offset) / 7;
} else
user_data_len -= udhl;
}
msg_text = sms_decode_text (&pdu[user_data_offset], user_data_len,
user_data_dcs, bit_offset);
properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
simple_free_gvalue);
g_hash_table_insert (properties, "number",
simple_string_value (sender_addr));
g_hash_table_insert (properties, "text",
simple_string_value (msg_text));
g_hash_table_insert (properties, "smsc",
simple_string_value (smsc_addr));
g_hash_table_insert (properties, "timestamp",
simple_string_value (sc_timestamp));
if (user_data_dcs & SMS_DCS_CLASS_VALID)
g_hash_table_insert (properties, "class",
simple_uint_value (user_data_dcs &
SMS_DCS_CLASS_MASK));
g_hash_table_insert (properties, "completed", simple_boolean_value (TRUE));
g_free (smsc_addr);
g_free (sender_addr);
g_free (sc_timestamp);
g_free (msg_text);
g_free (pdu);
return properties;
}
static void
sms_get_done (MMAtSerialPort *port,
@@ -4521,11 +4280,8 @@ sms_get_done (MMAtSerialPort *port,
goto out;
}
properties = sms_parse_pdu (pdu);
properties = sms_parse_pdu (pdu, &info->error);
if (!properties) {
info->error = g_error_new_literal (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"Failed to parse SMS PDU");
goto out;
}
@@ -4638,6 +4394,7 @@ sms_list_done (MMAtSerialPort *port,
while (*rstr) {
GHashTable *properties;
GError *local;
int idx;
char pdu[SMS_MAX_PDU_LEN + 1];
@@ -4649,11 +4406,14 @@ sms_list_done (MMAtSerialPort *port,
}
rstr += offset;
properties = sms_parse_pdu (pdu);
properties = sms_parse_pdu (pdu, &local);
if (properties) {
g_hash_table_insert (properties, "index",
simple_uint_value (idx));
g_ptr_array_add (results, properties);
} else {
/* Ignore the error */
g_clear_error(&local);
}
}
/*
@@ -5372,18 +5132,6 @@ simple_uint_value (guint32 i)
return val;
}
static GValue *
simple_boolean_value (gboolean b)
{
GValue *val;
val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_BOOLEAN);
g_value_set_boolean (val, b);
return val;
}
static GValue *
simple_string_value (const char *str)
{

320
src/mm-sms-utils.c Normal file
View File

@@ -0,0 +1,320 @@
/* -*- 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 Red Hat, Inc.
*/
#include <glib.h>
#include "mm-charsets.h"
#include "mm-errors.h"
#include "mm-utils.h"
#include "mm-sms-utils.h"
#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)
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';
}
/* 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 char *
sms_decode_text (const guint8 *text, int len, int dcs, int bit_offset)
{
char *utf8;
guint8 coding = dcs & SMS_DCS_CODING_MASK;
guint8 *unpacked;
guint32 unpacked_len;
if (coding == SMS_DCS_CODING_DEFAULT) {
unpacked = gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
utf8 = (char *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
g_free (unpacked);
} else if (coding == SMS_DCS_CODING_UCS2)
utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
else if (coding == SMS_DCS_CODING_8BIT)
utf8 = g_strndup ((const char *)text, len);
else
utf8 = g_strdup ("");
return utf8;
}
static void
simple_free_gvalue (gpointer data)
{
g_value_unset ((GValue *) data);
g_slice_free (GValue, data);
}
static GValue *
simple_uint_value (guint32 i)
{
GValue *val;
val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_UINT);
g_value_set_uint (val, i);
return val;
}
static GValue *
simple_boolean_value (gboolean b)
{
GValue *val;
val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_BOOLEAN);
g_value_set_boolean (val, b);
return val;
}
static GValue *
simple_string_value (const char *str)
{
GValue *val;
val = g_slice_new0 (GValue);
g_value_init (val, G_TYPE_STRING);
g_value_set_string (val, str);
return val;
}
GHashTable *
sms_parse_pdu (const char *hexpdu, GError **error)
{
GHashTable *properties;
gsize pdu_len;
guint8 *pdu;
int 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, user_data_dcs, bit_offset;
char *smsc_addr, *sender_addr, *sc_timestamp, *msg_text;
/* Convert PDU from hex to binary */
pdu = (guint8 *) utils_hexstr2bin (hexpdu, &pdu_len);
if (!pdu) {
*error = g_error_new_literal (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"Couldn't parse PDU of SMS GET response from hex");
return NULL;
}
/* 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) {
*error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"PDU too short (1): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
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) {
*error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"PDU too short (2): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
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_dcs = pdu[tp_dcs_offset];
if ((user_data_dcs & SMS_DCS_CODING_MASK) == SMS_DCS_CODING_DEFAULT)
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) {
*error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"PDU too short (3): %zd vs %d", pdu_len,
variable_length_items + SMS_MIN_PDU_LEN);
g_free (pdu);
return NULL;
}
/* Only handle SMS-DELIVER */
if ((pdu[msg_start_offset] & SMS_TP_MTI_MASK) != SMS_TP_MTI_SMS_DELIVER) {
*error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"Unhandled message type: 0x%02x",
pdu[msg_start_offset]);
g_free (pdu);
return NULL;
}
smsc_addr = sms_decode_address (&pdu[1], 2 * (pdu[0] - 1));
sender_addr = sms_decode_address (&pdu[msg_start_offset + 2],
pdu[msg_start_offset + 1]);
sc_timestamp = sms_decode_timestamp (&pdu[tp_dcs_offset + 1]);
bit_offset = 0;
if (pdu[msg_start_offset] & SMS_TP_UDHI) {
/*
* Skip over the user data headers to prevent it from being
* decoded into garbage text.
*/
int udhl;
udhl = pdu[user_data_offset] + 1;
user_data_offset += udhl;
if ((user_data_dcs & SMS_DCS_CODING_MASK) == SMS_DCS_CODING_DEFAULT) {
bit_offset = 7 - (udhl * 8) % 7;
user_data_len -= (udhl * 8 + bit_offset) / 7;
} else
user_data_len -= udhl;
}
msg_text = sms_decode_text (&pdu[user_data_offset], user_data_len,
user_data_dcs, bit_offset);
properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
simple_free_gvalue);
g_hash_table_insert (properties, "number",
simple_string_value (sender_addr));
g_hash_table_insert (properties, "text",
simple_string_value (msg_text));
g_hash_table_insert (properties, "smsc",
simple_string_value (smsc_addr));
g_hash_table_insert (properties, "timestamp",
simple_string_value (sc_timestamp));
if (user_data_dcs & SMS_DCS_CLASS_VALID)
g_hash_table_insert (properties, "class",
simple_uint_value (user_data_dcs &
SMS_DCS_CLASS_MASK));
g_hash_table_insert (properties, "completed", simple_boolean_value (TRUE));
g_free (smsc_addr);
g_free (sender_addr);
g_free (sc_timestamp);
g_free (msg_text);
g_free (pdu);
return properties;
}

25
src/mm-sms-utils.h Normal file
View File

@@ -0,0 +1,25 @@
/* -*- 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) 2010 Red Hat, Inc.
*/
#ifndef MM_SMS_UTILS_H
#define MM_SMS_UTILS_H
#include <glib.h>
#define SMS_MAX_PDU_LEN 344
GHashTable *sms_parse_pdu (const char *hexpdu, GError **error);
#endif /* MM_SMS_UTILS_H */