aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:26 +0100
committerGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:26 +0100
commita09050a7f63a262bf90dcb1c7a41f9cfd205db43 (patch)
tree2ea74d753bc35130916ad07c99d6a8028723c6d6 /src
parent3dbe8df8bfe8741e1b9a48b56e41517816f17dc1 (diff)
Imported Upstream version 0.5upstream/0.5
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/Makefile.in16
-rw-r--r--src/mm-callback-info.c8
-rw-r--r--src/mm-callback-info.h1
-rw-r--r--src/mm-generic-cdma.c167
-rw-r--r--src/mm-generic-gsm.c660
-rw-r--r--src/mm-generic-gsm.h2
-rw-r--r--src/mm-log.c37
-rw-r--r--src/mm-log.h2
-rw-r--r--src/mm-manager.c20
-rw-r--r--src/mm-modem-gsm-ussd.c26
-rw-r--r--src/mm-modem-gsm-ussd.h20
-rw-r--r--src/mm-plugin-base.c6
-rw-r--r--src/mm-serial-port.c120
-rw-r--r--src/mm-serial-port.h4
-rw-r--r--src/mm-sms-utils.c389
-rw-r--r--src/mm-sms-utils.h25
-rw-r--r--src/tests/Makefile.am14
-rw-r--r--src/tests/Makefile.in44
-rw-r--r--src/tests/test-sms.c429
20 files changed, 1570 insertions, 424 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index e813e7e..71008c9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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) \
diff --git a/src/Makefile.in b/src/Makefile.in
index 8413fd1..1f2f622 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -64,7 +64,8 @@ libmodem_helpers_la_LIBADD =
am_libmodem_helpers_la_OBJECTS = libmodem_helpers_la-mm-errors.lo \
libmodem_helpers_la-mm-modem-helpers.lo \
libmodem_helpers_la-mm-charsets.lo \
- libmodem_helpers_la-mm-utils.lo
+ libmodem_helpers_la-mm-utils.lo \
+ libmodem_helpers_la-mm-sms-utils.lo
libmodem_helpers_la_OBJECTS = $(am_libmodem_helpers_la_OBJECTS)
AM_V_lt = $(am__v_lt_$(V))
am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY))
@@ -394,7 +395,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) \
@@ -542,6 +545,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmodem_helpers_la-mm-charsets.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmodem_helpers_la-mm-errors.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmodem_helpers_la-mm-modem-helpers.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmodem_helpers_la-mm-sms-utils.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmodem_helpers_la-mm-utils.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libserial_la-mm-at-serial-port.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libserial_la-mm-port.Plo@am__quote@
@@ -631,6 +635,14 @@ libmodem_helpers_la-mm-utils.lo: mm-utils.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmodem_helpers_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libmodem_helpers_la-mm-utils.lo `test -f 'mm-utils.c' || echo '$(srcdir)/'`mm-utils.c
+libmodem_helpers_la-mm-sms-utils.lo: mm-sms-utils.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmodem_helpers_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libmodem_helpers_la-mm-sms-utils.lo -MD -MP -MF $(DEPDIR)/libmodem_helpers_la-mm-sms-utils.Tpo -c -o libmodem_helpers_la-mm-sms-utils.lo `test -f 'mm-sms-utils.c' || echo '$(srcdir)/'`mm-sms-utils.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmodem_helpers_la-mm-sms-utils.Tpo $(DEPDIR)/libmodem_helpers_la-mm-sms-utils.Plo
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mm-sms-utils.c' object='libmodem_helpers_la-mm-sms-utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmodem_helpers_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libmodem_helpers_la-mm-sms-utils.lo `test -f 'mm-sms-utils.c' || echo '$(srcdir)/'`mm-sms-utils.c
+
libserial_la-mm-port.lo: mm-port.c
@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libserial_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libserial_la-mm-port.lo -MD -MP -MF $(DEPDIR)/libserial_la-mm-port.Tpo -c -o libserial_la-mm-port.lo `test -f 'mm-port.c' || echo '$(srcdir)/'`mm-port.c
@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libserial_la-mm-port.Tpo $(DEPDIR)/libserial_la-mm-port.Plo
diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c
index a230e69..302a816 100644
--- a/src/mm-callback-info.c
+++ b/src/mm-callback-info.c
@@ -151,6 +151,14 @@ mm_callback_info_string_new (MMModem *modem,
return mm_callback_info_new_full (modem, invoke_mm_modem_string_fn, (GCallback) callback, user_data);
}
+gpointer
+mm_callback_info_get_result (MMCallbackInfo *info)
+{
+ g_return_val_if_fail (info != NULL, NULL);
+
+ return mm_callback_info_get_data (info, CALLBACK_INFO_RESULT);
+}
+
void
mm_callback_info_set_result (MMCallbackInfo *info,
gpointer data,
diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h
index 42d9908..a00181c 100644
--- a/src/mm-callback-info.h
+++ b/src/mm-callback-info.h
@@ -58,6 +58,7 @@ MMCallbackInfo *mm_callback_info_string_new (MMModem *modem,
gpointer user_data);
void mm_callback_info_schedule (MMCallbackInfo *info);
+gpointer mm_callback_info_get_result (MMCallbackInfo *info);
void mm_callback_info_set_result (MMCallbackInfo *info,
gpointer data,
GDestroyNotify destroy);
diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c
index 378555b..11987a2 100644
--- a/src/mm-generic-cdma.c
+++ b/src/mm-generic-cdma.c
@@ -34,6 +34,15 @@
#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state"
+typedef enum {
+ RM_PROTO_ASYNC = 0,
+ RM_PROTO_RELAY = 1,
+ RM_PROTO_NETWORK_PPP = 2,
+ RM_PROTO_NETWORK_SLIP = 3,
+ RM_PROTO_STU_III = 4
+} RmProtocol;
+
+
static void simple_reg_callback (MMModemCdma *modem,
MMModemCdmaRegistrationState cdma_1x_reg_state,
MMModemCdmaRegistrationState evdo_reg_state,
@@ -67,6 +76,10 @@ typedef struct {
gboolean has_spservice;
gboolean has_speri;
+ /* Original and current Rm interface protocol */
+ RmProtocol orig_crm;
+ RmProtocol cur_crm;
+
guint poll_id;
char *meid;
@@ -563,6 +576,29 @@ speri_done (MMAtSerialPort *port,
}
static void
+crm_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ const char *p;
+ unsigned long num;
+
+ if (error)
+ return;
+
+ p = mm_strip_tag (response->str, "+CRM:");
+ if (p) {
+ errno = 0;
+ num = strtoul (p, NULL, 10);
+ if (num >= 0 && num <= 4 && (errno == 0)) {
+ MM_GENERIC_CDMA_GET_PRIVATE (user_data)->orig_crm = (guint32) num;
+ MM_GENERIC_CDMA_GET_PRIVATE (user_data)->cur_crm = (guint32) num;
+ }
+ }
+}
+
+static void
enable_all_done (MMModem *modem, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
@@ -599,6 +635,9 @@ enable_all_done (MMModem *modem, GError *error, gpointer user_data)
/* Check for support of Sprint-specific phone commands */
mm_at_serial_port_queue_command (priv->primary, "+SPSERVICE?", 3, spservice_done, self);
mm_at_serial_port_queue_command (priv->primary, "$SPERI?", 3, speri_done, self);
+
+ /* Grab default CRM */
+ mm_at_serial_port_queue_command (priv->primary, "+CRM?", 3, crm_done, self);
}
out:
@@ -1661,11 +1700,17 @@ real_query_registration_state (MMGenericCdma *self,
/* Try Sprint-specific commands */
mm_at_serial_port_queue_command (port, "+SPSERVICE?", 3, reg_query_spservice_done, info);
} else {
- /* Assume we're registered on the 1x network if we passed +CAD, +CSS,
- * and QCDM Call Manager checking.
+ /* Assume we're at least registered on the 1x network if we passed
+ * +CAD, +CSS, and QCDM Call Manager checking. But don't override a
+ * more specific registration state passed from a caller.
+ */
+ if (cur_cdma_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
+
+ /* Don't touch EVDO state; it's already either UNKNOWN, or been set
+ * by generic checking earlier.
*/
- mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED);
- mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+
mm_callback_info_schedule (info);
}
}
@@ -1960,12 +2005,63 @@ get_registration_state (MMModemCdma *modem,
}
/*****************************************************************************/
+
+static void
+set_rm_proto_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (mm_callback_info_check_modem_removed (info) == FALSE) {
+ if (error)
+ info->error = g_error_copy (error);
+
+ mm_callback_info_schedule (info);
+ }
+}
+
+static void
+mm_generic_cdma_set_rm_protocol (MMGenericCdma *self,
+ RmProtocol proto,
+ MMModemFn callback,
+ gpointer user_data)
+{
+ MMCallbackInfo *info;
+ MMAtSerialPort *port;
+ char *cmd;
+
+ info = mm_callback_info_new (MM_MODEM (self), callback, user_data);
+
+ port = mm_generic_cdma_get_best_at_port (self, &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+ g_clear_error (&info->error);
+
+ if (proto < RM_PROTO_ASYNC || proto > RM_PROTO_STU_III) {
+ g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid Rm interface protocol %d",
+ proto);
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+CRM=%d", proto);
+ mm_at_serial_port_queue_command (port, cmd, 3, set_rm_proto_done, info);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
/* MMModemSimple interface */
typedef enum {
SIMPLE_STATE_BEGIN = 0,
SIMPLE_STATE_ENABLE,
SIMPLE_STATE_REGISTER,
+ SIMPLE_STATE_PRE_CONNECT,
SIMPLE_STATE_CONNECT,
SIMPLE_STATE_DONE
} SimpleState;
@@ -1991,6 +2087,32 @@ simple_get_string_property (MMCallbackInfo *info, const char *name, GError **err
}
static gboolean
+simple_get_uint_property (MMCallbackInfo *info,
+ const char *name,
+ guint32 *out_val,
+ GError **error)
+{
+ GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
+ GValue *value;
+
+ g_return_val_if_fail (out_val != NULL, FALSE);
+
+ value = (GValue *) g_hash_table_lookup (properties, name);
+ if (value) {
+ if (G_VALUE_HOLDS_UINT (value)) {
+ *out_val = g_value_get_uint (value);
+ return TRUE;
+ }
+
+ g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Invalid property type for '%s': %s (uint expected)",
+ name, G_VALUE_TYPE_NAME (value));
+ }
+
+ return FALSE;
+}
+
+static gboolean
simple_reg_retry (gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
@@ -2083,10 +2205,11 @@ static void
simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
+ MMGenericCdma *self;
+ MMGenericCdmaPrivate *priv;
SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
const char *str;
- guint id;
+ guint id, rm_protocol = 0;
/* Do nothing if modem removed */
if (!modem || mm_callback_info_check_modem_removed (info))
@@ -2097,12 +2220,17 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
goto out;
}
+ self = MM_GENERIC_CDMA (info->modem);
+ priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
switch (state) {
case SIMPLE_STATE_BEGIN:
+ /* Enable state */
state = set_simple_state (info, SIMPLE_STATE_ENABLE);
mm_modem_enable (modem, simple_state_machine, info);
break;
case SIMPLE_STATE_ENABLE:
+ /* Register state */
state = set_simple_state (info, SIMPLE_STATE_REGISTER);
mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (modem),
simple_reg_callback,
@@ -2114,14 +2242,34 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
priv->reg_state_changed_id = id;
break;
case SIMPLE_STATE_REGISTER:
+ /* Pre Connect state */
registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0);
- state = set_simple_state (info, SIMPLE_STATE_CONNECT);
+ state = set_simple_state (info, SIMPLE_STATE_PRE_CONNECT);
mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE);
+ /* Change the Rm interface protocol due to manager request if needed */
+ if (simple_get_uint_property (info, "rm-protocol", &rm_protocol, &info->error)) {
+ mm_generic_cdma_set_rm_protocol (self, rm_protocol, simple_state_machine, info);
+ break;
+ }
+
+ /* Or if the Rm protocol isn't the default, and there was no request
+ * to change it, do that now.
+ */
+ if (priv->cur_crm != priv->orig_crm) {
+ mm_generic_cdma_set_rm_protocol (self, priv->orig_crm, simple_state_machine, info);
+ break;
+ }
+
+ /* Fall through */
+ case SIMPLE_STATE_PRE_CONNECT:
+ /* Connect state */
+ state = set_simple_state (info, SIMPLE_STATE_CONNECT);
str = simple_get_string_property (info, "number", &info->error);
mm_modem_connect (modem, str, simple_state_machine, info);
break;
case SIMPLE_STATE_CONNECT:
+ /* All done! */
state = set_simple_state (info, SIMPLE_STATE_DONE);
break;
case SIMPLE_STATE_DONE:
@@ -2306,10 +2454,15 @@ modem_simple_init (MMModemSimple *class)
static void
mm_generic_cdma_init (MMGenericCdma *self)
{
+ MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self);
+
g_signal_connect (self, "notify::" MM_MODEM_VALID,
G_CALLBACK (modem_valid_changed), NULL);
g_signal_connect (self, "notify::" MM_MODEM_STATE,
G_CALLBACK (modem_state_changed), NULL);
+
+ /* Default to Network Layer Rm interface/PPP */
+ priv->orig_crm = priv->cur_crm = RM_PROTO_NETWORK_PPP;
}
static void
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index 0b7ea01..cee1bd6 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -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);
@@ -120,7 +121,11 @@ typedef struct {
gboolean loc_enabled;
gboolean loc_signal;
+ gboolean ussd_enabled;
+ MMCallbackInfo *pending_ussd_info;
MMModemGsmUssdState ussd_state;
+ char *ussd_network_request;
+ char *ussd_network_notification;
/* SMS */
GHashTable *sms_present;
@@ -174,10 +179,13 @@ static void cmti_received (MMAtSerialPort *port,
GMatchInfo *info,
gpointer user_data);
+static void cusd_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ gpointer user_data);
+
#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 *
@@ -838,11 +846,16 @@ mm_generic_gsm_grab_port (MMGenericGsm *self,
regex = g_regex_new ("\\r\\n\\+CIEV: (\\d+),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, ciev_received, self, NULL);
+ g_regex_unref (regex);
regex = g_regex_new ("\\r\\n\\+CMTI: \"(\\S+)\",(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cmti_received, self, NULL);
g_regex_unref (regex);
+ regex = g_regex_new ("\\r\\n\\+CUSD:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cusd_received, self, NULL);
+ g_regex_unref (regex);
+
if (ptype == MM_PORT_TYPE_PRIMARY) {
priv->primary = MM_AT_SERIAL_PORT (port);
if (!priv->data) {
@@ -1400,6 +1413,21 @@ cind_cb (MMAtSerialPort *port,
}
}
+static void
+cusd_enable_cb (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ if (error) {
+ mm_warn ("(%s): failed to enable USSD notifications.",
+ mm_port_get_device (MM_PORT (port)));
+ return;
+ }
+
+ MM_GENERIC_GSM_GET_PRIVATE (user_data)->ussd_enabled = TRUE;
+}
+
void
mm_generic_gsm_enable_complete (MMGenericGsm *self,
GError *error,
@@ -1438,11 +1466,15 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self,
mm_at_serial_port_queue_command (priv->primary, cmd, 3, NULL, NULL);
g_free (cmd);
- /* Enable SMS notifications */
- mm_at_serial_port_queue_command (priv->primary, "+CNMI=2,1,2,1,0", 3, NULL, NULL);
/* Set SMS storage location to ME */
mm_at_serial_port_queue_command (priv->primary, "+CPMS=\"ME\",\"ME\",\"ME\"", 3, NULL, NULL);
+ /* Enable SMS notifications */
+ mm_at_serial_port_queue_command (priv->primary, "+CNMI=2,1,2,1,0", 3, NULL, NULL);
+
+ /* Enable USSD notifications */
+ mm_at_serial_port_queue_command (priv->primary, "+CUSD=1", 3, cusd_enable_cb, self);
+
mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self);
/* Try one more time to get the SIM ID */
@@ -1543,7 +1575,13 @@ enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
return;
}
+ /* Send the init command twice; some devices (Nokia N900) appear to take a
+ * few commands before responding correctly. Instead of penalizing them for
+ * being stupid the first time by failing to enable the device, just
+ * try again.
+ */
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL);
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 3, NULL, NULL);
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 3, init_done, user_data);
g_free (cmd);
}
@@ -1672,6 +1710,11 @@ disable_flash_done (MMSerialPort *port,
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CREG=0", 3, NULL, NULL);
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CGREG=0", 3, NULL, NULL);
+ if (priv->ussd_enabled) {
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CUSD=0", 3, NULL, NULL);
+ priv->ussd_enabled = FALSE;
+ }
+
if (priv->cmer_enabled) {
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CMER=0", 3, NULL, NULL);
@@ -1715,6 +1758,8 @@ disable (MMModem *modem,
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+ mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (modem));
+
if (priv->poll_id) {
g_source_remove (priv->poll_id);
priv->poll_id = 0;
@@ -1873,8 +1918,8 @@ get_operator_id_imsi_done (MMModem *modem,
GError *error,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMAtSerialPort *port;
if (error) {
info->error = g_error_copy (error);
@@ -1882,10 +1927,17 @@ get_operator_id_imsi_done (MMModem *modem,
return;
}
+ g_clear_error (&info->error);
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
mm_callback_info_set_data (info, "imsi", g_strdup (result), g_free);
/* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */
- mm_at_serial_port_queue_command_cached (priv->primary,
+ mm_at_serial_port_queue_command_cached (port,
"+CRSM=176,28589,0,0,4",
3,
get_mnc_length_done,
@@ -1948,7 +2000,7 @@ get_spn_done (MMAtSerialPort *port,
}
/* Remove the FF filler at the end */
- while (bin[buflen - 1] == (char)0xff)
+ while (buflen > 1 && bin[buflen - 1] == (char)0xff)
buflen--;
/* First byte is metadata; remainder is GSM-7 unpacked into octets; convert to UTF8 */
@@ -1972,11 +2024,18 @@ get_imei (MMModemGsmCard *modem,
MMModemStringFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
+ MMAtSerialPort *port;
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
- mm_at_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info);
+
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port)
+ mm_callback_info_schedule (info);
+ else {
+ g_clear_error (&info->error);
+ mm_at_serial_port_queue_command_cached (port, "+CGSN", 3, get_string_done, info);
+ }
}
static void
@@ -1984,11 +2043,18 @@ get_imsi (MMModemGsmCard *modem,
MMModemStringFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
+ MMAtSerialPort *port;
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
- mm_at_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info);
+
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port)
+ mm_callback_info_schedule (info);
+ else {
+ g_clear_error (&info->error);
+ mm_at_serial_port_queue_command_cached (port, "+CIMI", 3, get_string_done, info);
+ }
}
static void
@@ -2009,17 +2075,24 @@ get_spn (MMModemGsmCard *modem,
MMModemStringFn callback,
gpointer user_data)
{
- MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMCallbackInfo *info;
+ MMAtSerialPort *port;
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
- /* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */
- mm_at_serial_port_queue_command_cached (priv->primary,
- "+CRSM=176,28486,0,0,17",
- 3,
- get_spn_done,
- info);
+ port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ if (!port)
+ mm_callback_info_schedule (info);
+ else {
+ g_clear_error (&info->error);
+
+ /* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */
+ mm_at_serial_port_queue_command_cached (port,
+ "+CRSM=176,28486,0,0,17",
+ 3,
+ get_spn_done,
+ info);
+ }
}
static void
@@ -2360,6 +2433,19 @@ reg_info_updated (MMGenericGsm *self,
changed = TRUE;
}
+ /* Don't clear oper code or oper num if at least one of CS or PS state
+ * is home or roaming.
+ */
+ if ( priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ || priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING
+ || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
+ || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) {
+ if (update_code && oper_code == NULL)
+ update_code = FALSE;
+ if (update_name && oper_name == NULL)
+ update_name = FALSE;
+ }
+
if (update_code) {
if (g_strcmp0 (oper_code, priv->oper_code) != 0) {
g_free (priv->oper_code);
@@ -3499,7 +3585,12 @@ existing_apns_read (MMAtSerialPort *port,
return;
if (error) {
- info->error = g_error_copy (error);
+ /* Some Android phones don't support querying existing PDP contexts,
+ * but will accept setting the APN. So if CGDCONT? isn't supported,
+ * just ignore that error and hope for the best. (bgo #637327)
+ */
+ if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_NOT_SUPPORTED) == FALSE)
+ info->error = g_error_copy (error);
} else if (g_str_has_prefix (response->str, "+CGDCONT:")) {
GRegex *r;
GMatchInfo *match_info;
@@ -4101,35 +4192,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,
@@ -4180,219 +4242,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,
@@ -4426,11 +4276,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;
}
@@ -4543,6 +4390,7 @@ sms_list_done (MMAtSerialPort *port,
while (*rstr) {
GHashTable *properties;
+ GError *local;
int idx;
char pdu[SMS_MAX_PDU_LEN + 1];
@@ -4554,11 +4402,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);
}
}
/*
@@ -4659,93 +4510,207 @@ ussd_update_state (MMGenericGsm *self, MMModemGsmUssdState new_state)
}
}
+void
+mm_generic_gsm_ussd_cleanup (MMGenericGsm *self)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (priv->pending_ussd_info) {
+ /* And schedule the callback */
+ g_clear_error (&priv->pending_ussd_info->error);
+ priv->pending_ussd_info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "USSD session terminated without reply.");
+ mm_callback_info_schedule (priv->pending_ussd_info);
+ priv->pending_ussd_info = NULL;
+ }
+
+ ussd_update_state (self, MM_MODEM_GSM_USSD_STATE_IDLE);
+
+ g_free (priv->ussd_network_request);
+ priv->ussd_network_request = NULL;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST);
+
+ g_free (priv->ussd_network_notification);
+ priv->ussd_network_notification = NULL;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION);
+}
+
+static char *
+decode_ussd_response (MMGenericGsm *self,
+ const char *reply,
+ MMModemCharset cur_charset)
+{
+ char **items, **iter, *p;
+ char *str = NULL;
+ gint encoding = -1;
+
+ /* Look for the first ',' */
+ p = strchr (reply, ',');
+ if (p == NULL)
+ return NULL;
+
+ items = g_strsplit_set (p + 1, " ,", -1);
+ for (iter = items; iter && *iter; iter++) {
+ if (*iter[0] == '\0')
+ continue;
+ if (str == NULL)
+ str = *iter;
+ else if (encoding == -1) {
+ encoding = atoi (*iter);
+ mm_dbg ("USSD data coding scheme %d", encoding);
+ break; /* All done */
+ }
+ }
+
+ /* Strip quotes */
+ if (str[0] == '"')
+ str++;
+ p = strchr (str, '"');
+ if (p)
+ *p = '\0';
+
+ return mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str,
+ cur_charset);
+}
+
+static char*
+ussd_encode (MMModemGsmUssd *modem, const char* command, guint *scheme)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ GByteArray *ussd_command = g_byte_array_new();
+ gboolean success;
+ char *hex = NULL;
+
+ /* encode to cur_charset */
+ success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE,
+ priv->cur_charset);
+ g_warn_if_fail (success == TRUE);
+ if (!success)
+ goto out;
+
+ *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+ /* convert to hex representation */
+ hex = utils_bin2hexstr (ussd_command->data, ussd_command->len);
+
+ out:
+ g_byte_array_free (ussd_command, TRUE);
+ return hex;
+}
+
+static char*
+ussd_decode (MMModemGsmUssd *modem, const char* reply, guint scheme)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ char *converted;
+
+ converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset);
+ return converted;
+}
+
static void
-ussd_send_done (MMAtSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+cusd_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ gpointer user_data)
{
- MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- MMGenericGsmPrivate *priv;
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ GError *error = NULL;
gint status;
- gboolean parsed = FALSE;
MMModemGsmUssdState ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
- const char *str, *start = NULL, *end = NULL;
char *reply = NULL, *converted;
- /* If the modem has already been removed, return without
- * scheduling callback */
- if (mm_callback_info_check_modem_removed (info))
+ reply = g_match_info_fetch (info, 1);
+ if (!reply || !isdigit (*reply)) {
+ mm_warn ("Recieved invalid USSD response: '%s'", reply ? reply : "(none)");
+ g_free (reply);
return;
-
- if (error) {
- info->error = g_error_copy (error);
- goto done;
}
- priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- ussd_state = priv->ussd_state;
-
- str = mm_strip_tag (response->str, "+CUSD:");
- if (!str || !isdigit (*str))
- goto done;
-
- status = g_ascii_digit_value (*str);
+ status = g_ascii_digit_value (*reply);
switch (status) {
case 0: /* no further action required */
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ converted = decode_ussd_response (self, reply, priv->cur_charset);
+ if (priv->pending_ussd_info) {
+ /* Response to the user's request */
+ mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
+ } else {
+ /* Network-initiated USSD-Notify */
+ g_free (priv->ussd_network_notification);
+ priv->ussd_network_notification = converted;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION);
+ }
break;
case 1: /* further action required */
ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE;
+ converted = decode_ussd_response (self, reply, priv->cur_charset);
+ if (priv->pending_ussd_info) {
+ mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
+ } else {
+ /* Network-initiated USSD-Request */
+ g_free (priv->ussd_network_request);
+ priv->ussd_network_request = converted;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST);
+ }
break;
case 2:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "USSD terminated by network.");
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "USSD terminated by network.");
break;
case 4:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Operiation not supported.");
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Operation not supported.");
break;
default:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Unknown USSD reply %d", status);
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Unhandled USSD reply %d", status);
break;
}
- if (info->error)
- goto done;
-
- /* look for the reply */
- if ((start = strchr (str, '"')) && (end = strrchr (str, '"')) && (start != end))
- reply = g_strndup (start + 1, end - start -1);
- if (reply) {
- /* look for the reply data coding scheme */
- if ((start = strrchr (end, ',')) != NULL)
- mm_dbg ("USSD data coding scheme %d", atoi (start + 1));
+ ussd_update_state (self, ussd_state);
- converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset);
- mm_callback_info_set_result (info, converted, g_free);
- parsed = TRUE;
- g_free (reply);
+ if (priv->pending_ussd_info) {
+ if (error)
+ priv->pending_ussd_info->error = g_error_copy (error);
+ mm_callback_info_schedule (priv->pending_ussd_info);
+ priv->pending_ussd_info = NULL;
}
-done:
- if (!parsed && !info->error) {
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Could not parse USSD reply '%s'",
- response->str);
+ g_clear_error (&error);
+ g_free (reply);
+}
+
+static void
+ussd_send_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv;
+
+ /* If the modem has already been removed, return without
+ * scheduling callback */
+ if (mm_callback_info_check_modem_removed (info))
+ return;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ if (error) {
+ /* Some immediate error happened when sending the USSD request */
+ info->error = g_error_copy (error);
+ priv->pending_ussd_info = NULL;
+ mm_callback_info_schedule (info);
+
+ ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE);
}
- mm_callback_info_schedule (info);
- if (info->modem)
- ussd_update_state (MM_GENERIC_GSM (info->modem), ussd_state);
+ /* Otherwise if no error wait for the response to show up via the
+ * unsolicited response code.
+ */
}
static void
@@ -4757,10 +4722,11 @@ ussd_send (MMModemGsmUssd *modem,
MMCallbackInfo *info;
char *atc_command;
char *hex;
- GByteArray *ussd_command = g_byte_array_new();
+ guint scheme = 0;
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
MMAtSerialPort *port;
- gboolean success;
+
+ g_warn_if_fail (priv->pending_ussd_info == NULL);
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
@@ -4770,14 +4736,19 @@ ussd_send (MMModemGsmUssd *modem,
return;
}
- /* encode to cur_charset */
- success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset);
- g_warn_if_fail (success == TRUE);
+ /* Cache the callback info since the response is an unsolicited one */
+ priv->pending_ussd_info = info;
- /* convert to hex representation */
- hex = utils_bin2hexstr (ussd_command->data, ussd_command->len);
- g_byte_array_free (ussd_command, TRUE);
- atc_command = g_strdup_printf ("+CUSD=1,\"%s\",15", hex);
+ hex = mm_modem_gsm_ussd_encode (MM_MODEM_GSM_USSD (modem), command, &scheme);
+ if (!hex) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Failed to encode USSD command '%s'",
+ command);
+ mm_callback_info_schedule (info);
+ return;
+ }
+ atc_command = g_strdup_printf ("+CUSD=1,\"%s\",%d", hex, scheme);
g_free (hex);
mm_at_serial_port_queue_command (port, atc_command, 10, ussd_send_done, info);
@@ -4795,9 +4766,8 @@ ussd_initiate (MMModemGsmUssd *modem,
MMCallbackInfo *info;
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
- info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
-
if (priv->ussd_state != MM_MODEM_GSM_USSD_STATE_IDLE) {
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
info->error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"USSD session already active.");
@@ -4816,9 +4786,8 @@ ussd_respond (MMModemGsmUssd *modem,
MMCallbackInfo *info;
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
- info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
-
if (priv->ussd_state != MM_MODEM_GSM_USSD_STATE_USER_RESPONSE) {
+ info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
info->error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"No active USSD session, cannot respond.");
@@ -5160,18 +5129,6 @@ simple_uint_value (guint32 i)
}
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;
@@ -5526,6 +5483,8 @@ modem_gsm_ussd_init (MMModemGsmUssd *class)
class->initiate = ussd_initiate;
class->respond = ussd_respond;
class->cancel = ussd_cancel;
+ class->encode = ussd_encode;
+ class->decode = ussd_decode;
}
static void
@@ -5719,10 +5678,10 @@ get_property (GObject *object, guint prop_id,
g_value_set_string (value, ussd_state_to_string (priv->ussd_state));
break;
case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST:
- g_value_set_string (value, "");
+ g_value_set_string (value, priv->ussd_network_request);
break;
case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION:
- g_value_set_string (value, "");
+ g_value_set_string (value, priv->ussd_network_notification);
break;
case MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD:
/* By default, try to set XOFF/XON flow control */
@@ -5740,6 +5699,7 @@ finalize (GObject *object)
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object);
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object));
+ mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (object));
if (priv->pin_check_timeout) {
g_source_remove (priv->pin_check_timeout);
diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h
index c6cb7fb..4238628 100644
--- a/src/mm-generic-gsm.h
+++ b/src/mm-generic-gsm.h
@@ -153,6 +153,8 @@ MMModem *mm_generic_gsm_new (const char *device,
void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem);
+void mm_generic_gsm_ussd_cleanup (MMGenericGsm *modem);
+
gint mm_generic_gsm_get_cid (MMGenericGsm *modem);
void mm_generic_gsm_set_reg_status (MMGenericGsm *modem,
diff --git a/src/mm-log.c b/src/mm-log.c
index 779afe7..f99f51f 100644
--- a/src/mm-log.c
+++ b/src/mm-log.c
@@ -163,6 +163,24 @@ log_handler (const gchar *log_domain,
}
gboolean
+mm_log_set_level (const char *level, GError **error)
+{
+ gboolean found = FALSE;
+ const LogDesc *diter;
+
+ for (diter = &level_descs[0]; diter->name; diter++) {
+ if (!strcasecmp (diter->name, level)) {
+ log_level = diter->num;
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found)
+ g_set_error (error, 0, 0, "Unknown log level '%s'", level);
+ return found;
+}
+
+gboolean
mm_log_setup (const char *level,
const char *log_file,
gboolean show_timestamps,
@@ -170,23 +188,8 @@ mm_log_setup (const char *level,
GError **error)
{
/* levels */
- if (level && strlen (level)) {
- gboolean found = FALSE;
- const LogDesc *diter;
-
- for (diter = &level_descs[0]; diter->name; diter++) {
- if (!strcasecmp (diter->name, level)) {
- log_level = diter->num;
- found = TRUE;
- break;
- }
- }
-
- if (!found) {
- g_set_error (error, 0, 0, "Unknown log level '%s'", level);
- return FALSE;
- }
- }
+ if (level && strlen (level) && !mm_log_set_level (level, error))
+ return FALSE;
if (show_timestamps)
ts_flags = TS_FLAG_WALL;
diff --git a/src/mm-log.h b/src/mm-log.h
index 9b0d875..6024c08 100644
--- a/src/mm-log.h
+++ b/src/mm-log.h
@@ -47,6 +47,8 @@ void _mm_log (const char *loc,
const char *fmt,
...) __attribute__((__format__ (__printf__, 4, 5)));
+gboolean mm_log_set_level (const char *level, GError **error);
+
gboolean mm_log_setup (const char *level,
const char *log_file,
gboolean show_ts,
diff --git a/src/mm-manager.c b/src/mm-manager.c
index b9076ee..1e9403c 100644
--- a/src/mm-manager.c
+++ b/src/mm-manager.c
@@ -30,6 +30,10 @@ static gboolean impl_manager_enumerate_devices (MMManager *manager,
GPtrArray **devices,
GError **err);
+static gboolean impl_manager_set_logging (MMManager *manager,
+ const char *level,
+ GError **error);
+
#include "mm-manager-glue.h"
G_DEFINE_TYPE (MMManager, mm_manager, G_TYPE_OBJECT)
@@ -912,12 +916,26 @@ handle_uevent (GUdevClient *client,
/* We only care about tty/net devices when adding modem ports,
* but for remove, also handle usb parent device remove events
*/
- if ((!strcmp (action, "add") || !strcmp (action, "move")) && strcmp (subsys, "usb") !=0 )
+ if ( (!strcmp (action, "add") || !strcmp (action, "move") || !strcmp (action, "change"))
+ && (strcmp (subsys, "usb") != 0))
device_added (self, device);
else if (!strcmp (action, "remove"))
device_removed (self, device);
}
+static gboolean
+impl_manager_set_logging (MMManager *manager,
+ const char *level,
+ GError **error)
+{
+ if (mm_log_set_level (level, error)) {
+ mm_info ("logging: level '%s'", level);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
void
mm_manager_start (MMManager *manager)
{
diff --git a/src/mm-modem-gsm-ussd.c b/src/mm-modem-gsm-ussd.c
index f90a845..614999c 100644
--- a/src/mm-modem-gsm-ussd.c
+++ b/src/mm-modem-gsm-ussd.c
@@ -137,6 +137,32 @@ mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
}
+char*
+mm_modem_gsm_ussd_encode (MMModemGsmUssd *self,
+ const char* command,
+ guint *schema)
+{
+ if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->encode)
+ return MM_MODEM_GSM_USSD_GET_INTERFACE (self)->encode(self,
+ command,
+ schema);
+ else
+ return NULL;
+}
+
+char*
+mm_modem_gsm_ussd_decode (MMModemGsmUssd *self,
+ const char* reply,
+ guint schema)
+{
+ if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->decode)
+ return MM_MODEM_GSM_USSD_GET_INTERFACE (self)->decode(self,
+ reply,
+ schema);
+ else
+ return NULL;
+}
+
/*****************************************************************************/
typedef struct {
diff --git a/src/mm-modem-gsm-ussd.h b/src/mm-modem-gsm-ussd.h
index c8f652b..04d2be8 100644
--- a/src/mm-modem-gsm-ussd.h
+++ b/src/mm-modem-gsm-ussd.h
@@ -54,6 +54,14 @@ struct _MMModemGsmUssd {
void (*cancel) (MMModemGsmUssd *modem,
MMModemFn callback,
gpointer user_data);
+
+ gchar* (*encode) (MMModemGsmUssd *modem,
+ const char* command,
+ guint *scheme);
+
+ gchar* (*decode) (MMModemGsmUssd *modem,
+ const char* command,
+ guint scheme);
};
GType mm_modem_gsm_ussd_get_type (void);
@@ -72,4 +80,16 @@ void mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
MMModemFn callback,
gpointer user_data);
+/* CBS data coding scheme - 3GPP TS 23.038 */
+#define MM_MODEM_GSM_USSD_SCHEME_7BIT 0b00001111;
+#define MM_MODEM_GSM_USSD_SCHEME_UCS2 0b01001000;
+
+char *mm_modem_gsm_ussd_encode (MMModemGsmUssd *self,
+ const char* command,
+ guint *scheme);
+
+char *mm_modem_gsm_ussd_decode (MMModemGsmUssd *self,
+ const char* reply,
+ guint scheme);
+
#endif /* MM_MODEM_GSM_USSD_H */
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c
index 8d033a7..0777021 100644
--- a/src/mm-plugin-base.c
+++ b/src/mm-plugin-base.c
@@ -272,8 +272,6 @@ supports_task_dispose (GObject *object)
g_object_unref (priv->port);
g_free (priv->physdev_path);
g_free (priv->driver);
- g_free (priv->probe_resp);
- g_clear_error (&(priv->probe_error));
for (iter = priv->custom; iter; iter = g_slist_next (iter)) {
CustomInit *custom = iter->data;
@@ -299,6 +297,9 @@ supports_task_dispose (GObject *object)
g_object_unref (priv->qcdm_port);
}
+ g_free (priv->probe_resp);
+ g_clear_error (&(priv->probe_error));
+
G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object);
}
@@ -398,6 +399,7 @@ static const char *dq_strings[] = {
"os_logids.h",
/* Sierra CnS port */
"NETWORK SERVICE CHANGE",
+ "/SRC/AMSS",
NULL
};
diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c
index 18a616d..46050cf 100644
--- a/src/mm-serial-port.c
+++ b/src/mm-serial-port.c
@@ -45,6 +45,7 @@ enum {
PROP_SEND_DELAY,
PROP_FD,
PROP_SPEW_CONTROL,
+ PROP_FLASH_OK,
LAST_PROP
};
@@ -69,6 +70,7 @@ typedef struct {
guint stopbits;
guint64 send_delay;
gboolean spew_control;
+ gboolean flash_ok;
guint queue_id;
guint watch_id;
@@ -627,8 +629,12 @@ data_available (GIOChannel *source,
gsize bytes_read;
GIOStatus status;
MMQueueData *info;
+ const char *device;
if (condition & G_IO_HUP) {
+ device = mm_port_get_device (MM_PORT (self));
+ mm_dbg ("(%s) unexpected port hangup!", device);
+
if (priv->response->len)
g_byte_array_remove_range (priv->response, 0, priv->response->len);
mm_serial_port_close_force (self);
@@ -719,6 +725,7 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
char *devfile;
const char *device;
struct serial_struct sinfo;
+ GTimeVal tv_start, tv_end;
g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
@@ -733,6 +740,8 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
mm_info ("(%s) opening serial port...", device);
+ g_get_current_time (&tv_start);
+
/* Only open a new file descriptor if we weren't given one already */
if (priv->fd < 0) {
devfile = g_strdup_printf ("/dev/%s", device);
@@ -781,6 +790,11 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
ioctl (priv->fd, TIOCSSERIAL, &sinfo);
}
+ g_get_current_time (&tv_end);
+
+ if (tv_end.tv_sec - tv_start.tv_sec > 7)
+ mm_warn ("(%s): open blocked by driver for more than 7 seconds!", device);
+
priv->channel = g_io_channel_unix_new (priv->fd);
g_io_channel_set_encoding (priv->channel, NULL, NULL);
priv->watch_id = g_io_add_watch (priv->channel,
@@ -870,8 +884,8 @@ mm_serial_port_close (MMSerialPort *self)
* that data to send before giving up and returning from close().
* Log that. See GNOME bug #630670 for more details.
*/
- if (tv_end.tv_sec - tv_start.tv_sec > 20)
- mm_warn ("(%s): close blocked by driver for more than 20 seconds!", device);
+ if (tv_end.tv_sec - tv_start.tv_sec > 7)
+ mm_warn ("(%s): close blocked by driver for more than 7 seconds!", device);
}
/* Clear the command queue */
@@ -995,13 +1009,6 @@ mm_serial_port_queue_command_cached (MMSerialPort *self,
internal_queue_command (self, command, take_command, TRUE, timeout_seconds, callback, user_data);
}
-typedef struct {
- MMSerialPort *port;
- speed_t current_speed;
- MMSerialFlashFn callback;
- gpointer user_data;
-} FlashInfo;
-
static gboolean
get_speed (MMSerialPort *self, speed_t *speed, GError **error)
{
@@ -1076,6 +1083,13 @@ set_speed (MMSerialPort *self, speed_t speed, GError **error)
return TRUE;
}
+typedef struct {
+ MMSerialPort *port;
+ speed_t current_speed;
+ MMSerialFlashFn callback;
+ gpointer user_data;
+} FlashInfo;
+
static gboolean
flash_do (gpointer data)
{
@@ -1085,13 +1099,15 @@ flash_do (gpointer data)
priv->flash_id = 0;
- if (info->current_speed) {
- if (!set_speed (info->port, info->current_speed, &error))
- g_assert (error);
- } else {
- error = g_error_new_literal (MM_SERIAL_ERROR,
- MM_SERIAL_ERROR_FLASH_FAILED,
- "Failed to retrieve current speed");
+ if (priv->flash_ok) {
+ if (info->current_speed) {
+ if (!set_speed (info->port, info->current_speed, &error))
+ g_assert (error);
+ } else {
+ error = g_error_new_literal (MM_SERIAL_ERROR,
+ MM_SERIAL_ERROR_FLASH_FAILED,
+ "Failed to retrieve current speed");
+ }
}
info->callback (info->port, error, info->user_data);
@@ -1107,9 +1123,8 @@ mm_serial_port_flash (MMSerialPort *self,
MMSerialFlashFn callback,
gpointer user_data)
{
- FlashInfo *info;
+ FlashInfo *info = NULL;
MMSerialPortPrivate *priv;
- speed_t cur_speed = 0;
GError *error = NULL;
gboolean success;
@@ -1122,43 +1137,45 @@ mm_serial_port_flash (MMSerialPort *self,
error = g_error_new_literal (MM_SERIAL_ERROR,
MM_SERIAL_ERROR_NOT_OPEN,
"The serial port is not open.");
- callback (self, error, user_data);
- g_error_free (error);
- return FALSE;
+ goto error;
}
if (priv->flash_id > 0) {
error = g_error_new_literal (MM_MODEM_ERROR,
MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
"Modem is already being flashed.");
- callback (self, error, user_data);
- g_error_free (error);
- return FALSE;
- }
-
- success = get_speed (self, &cur_speed, &error);
- if (!success && !ignore_errors) {
- callback (self, error, user_data);
- g_error_free (error);
- return FALSE;
+ goto error;
}
- g_clear_error (&error);
info = g_slice_new0 (FlashInfo);
info->port = self;
- info->current_speed = cur_speed;
info->callback = callback;
info->user_data = user_data;
- success = set_speed (self, B0, &error);
- if (!success && !ignore_errors) {
- callback (self, error, user_data);
- g_error_free (error);
- return FALSE;
- }
+ if (priv->flash_ok) {
+ /* Grab current speed so we can reset it after flashing */
+ success = get_speed (self, &info->current_speed, &error);
+ if (!success && !ignore_errors)
+ goto error;
+ g_clear_error (&error);
+
+ success = set_speed (self, B0, &error);
+ if (!success && !ignore_errors)
+ goto error;
+ g_clear_error (&error);
+
+ priv->flash_id = g_timeout_add (flash_time, flash_do, info);
+ } else
+ priv->flash_id = g_idle_add (flash_do, info);
- priv->flash_id = g_timeout_add (flash_time, flash_do, info);
return TRUE;
+
+error:
+ callback (self, error, user_data);
+ g_clear_error (&error);
+ if (info)
+ g_slice_free (FlashInfo, info);
+ return FALSE;
}
void
@@ -1176,6 +1193,14 @@ mm_serial_port_flash_cancel (MMSerialPort *self)
}
}
+gboolean
+mm_serial_port_get_flash_ok (MMSerialPort *self)
+{
+ g_return_val_if_fail (MM_IS_SERIAL_PORT (self), TRUE);
+
+ return MM_SERIAL_PORT_GET_PRIVATE (self)->flash_ok;
+}
+
/*****************************************************************************/
MMSerialPort *
@@ -1276,6 +1301,9 @@ set_property (GObject *object, guint prop_id,
case PROP_SPEW_CONTROL:
priv->spew_control = g_value_get_boolean (value);
break;
+ case PROP_FLASH_OK:
+ priv->flash_ok = g_value_get_boolean (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1310,6 +1338,9 @@ get_property (GObject *object, guint prop_id,
case PROP_SPEW_CONTROL:
g_value_set_boolean (value, priv->spew_control);
break;
+ case PROP_FLASH_OK:
+ g_value_set_boolean (value, priv->flash_ok);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1420,6 +1451,15 @@ mm_serial_port_class_init (MMSerialPortClass *klass)
FALSE,
G_PARAM_READWRITE));
+ g_object_class_install_property
+ (object_class, PROP_FLASH_OK,
+ g_param_spec_boolean (MM_SERIAL_PORT_FLASH_OK,
+ "FlaskOk",
+ "Flashing the port (0 baud for a short period) "
+ "is allowed.",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
/* Signals */
g_signal_new ("buffer-full",
G_OBJECT_CLASS_TYPE (object_class),
diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h
index 57ef94b..ae38017 100644
--- a/src/mm-serial-port.h
+++ b/src/mm-serial-port.h
@@ -37,6 +37,7 @@
#define MM_SERIAL_PORT_SEND_DELAY "send-delay"
#define MM_SERIAL_PORT_FD "fd" /* Construct-only */
#define MM_SERIAL_PORT_SPEW_CONTROL "spew-control" /* Construct-only */
+#define MM_SERIAL_PORT_FLASH_OK "flash-ok" /* Construct-only */
typedef struct _MMSerialPort MMSerialPort;
typedef struct _MMSerialPortClass MMSerialPortClass;
@@ -121,8 +122,11 @@ gboolean mm_serial_port_flash (MMSerialPort *self,
gboolean ignore_errors,
MMSerialFlashFn callback,
gpointer user_data);
+
void mm_serial_port_flash_cancel (MMSerialPort *self);
+gboolean mm_serial_port_get_flash_ok (MMSerialPort *self);
+
void mm_serial_port_queue_command (MMSerialPort *self,
GByteArray *command,
gboolean take_command,
diff --git a/src/mm-sms-utils.c b/src/mm-sms-utils.c
new file mode 100644
index 0000000..3f56a64
--- /dev/null
+++ b/src/mm-sms-utils.c
@@ -0,0 +1,389 @@
+/* -*- 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)
+
+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';
+}
+
+/* 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) {
+ 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 (encoding == MM_SMS_ENCODING_UCS2)
+ utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
+ else if (encoding == MM_SMS_ENCODING_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, bit_offset;
+ char *smsc_addr, *sender_addr, *sc_timestamp, *msg_text;
+ SmsEncoding user_data_encoding;
+
+ /* 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_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) {
+ *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_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;
+ }
+
+ msg_text = sms_decode_text (&pdu[user_data_offset], user_data_len,
+ user_data_encoding, 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 (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID)
+ g_hash_table_insert (properties, "class",
+ simple_uint_value (pdu[tp_dcs_offset] &
+ 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;
+}
diff --git a/src/mm-sms-utils.h b/src/mm-sms-utils.h
new file mode 100644
index 0000000..26d9829
--- /dev/null
+++ b/src/mm-sms-utils.h
@@ -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 */
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index e265bc1..cc47e66 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -4,7 +4,8 @@ INCLUDES = \
noinst_PROGRAMS = \
test-modem-helpers \
test-charsets \
- test-qcdm-serial-port
+ test-qcdm-serial-port \
+ test-sms
test_modem_helpers_SOURCES = \
test-modem-helpers.c
@@ -40,12 +41,23 @@ test_qcdm_serial_port_LDADD = \
$(top_builddir)/libqcdm/src/libqcdm.la \
-lutil
+test_sms_SOURCES = \
+ test-sms.c
+
+test_sms_CFLAGS = \
+ $(MM_CFLAGS)
+
+test_sms_LDADD = \
+ $(top_builddir)/src/libmodem-helpers.la \
+ $(MM_LIBS)
+
if WITH_TESTS
check-local: test-modem-helpers
$(abs_builddir)/test-modem-helpers
$(abs_builddir)/test-charsets
$(abs_builddir)/test-qcdm-serial-port
+ $(abs_builddir)/test-sms
endif
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 9be9f06..94793ef 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -35,7 +35,7 @@ POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
noinst_PROGRAMS = test-modem-helpers$(EXEEXT) test-charsets$(EXEEXT) \
- test-qcdm-serial-port$(EXEEXT)
+ test-qcdm-serial-port$(EXEEXT) test-sms$(EXEEXT)
subdir = src/tests
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
@@ -71,6 +71,13 @@ test_qcdm_serial_port_DEPENDENCIES = $(am__DEPENDENCIES_1) \
$(top_builddir)/src/libserial.la \
$(top_builddir)/src/libmodem-helpers.la \
$(top_builddir)/libqcdm/src/libqcdm.la
+am_test_sms_OBJECTS = test_sms-test-sms.$(OBJEXT)
+test_sms_OBJECTS = $(am_test_sms_OBJECTS)
+test_sms_DEPENDENCIES = $(top_builddir)/src/libmodem-helpers.la \
+ $(am__DEPENDENCIES_1)
+test_sms_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_sms_CFLAGS) \
+ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__depfiles_maybe = depfiles
@@ -98,9 +105,9 @@ AM_V_GEN = $(am__v_GEN_$(V))
am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
am__v_GEN_0 = @echo " GEN " $@;
SOURCES = $(test_charsets_SOURCES) $(test_modem_helpers_SOURCES) \
- $(test_qcdm_serial_port_SOURCES)
+ $(test_qcdm_serial_port_SOURCES) $(test_sms_SOURCES)
DIST_SOURCES = $(test_charsets_SOURCES) $(test_modem_helpers_SOURCES) \
- $(test_qcdm_serial_port_SOURCES)
+ $(test_qcdm_serial_port_SOURCES) $(test_sms_SOURCES)
ETAGS = etags
CTAGS = ctags
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
@@ -292,6 +299,16 @@ test_qcdm_serial_port_LDADD = \
$(top_builddir)/libqcdm/src/libqcdm.la \
-lutil
+test_sms_SOURCES = \
+ test-sms.c
+
+test_sms_CFLAGS = \
+ $(MM_CFLAGS)
+
+test_sms_LDADD = \
+ $(top_builddir)/src/libmodem-helpers.la \
+ $(MM_LIBS)
+
all: all-am
.SUFFIXES:
@@ -344,6 +361,9 @@ test-modem-helpers$(EXEEXT): $(test_modem_helpers_OBJECTS) $(test_modem_helpers_
test-qcdm-serial-port$(EXEEXT): $(test_qcdm_serial_port_OBJECTS) $(test_qcdm_serial_port_DEPENDENCIES)
@rm -f test-qcdm-serial-port$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(test_qcdm_serial_port_OBJECTS) $(test_qcdm_serial_port_LDADD) $(LIBS)
+test-sms$(EXEEXT): $(test_sms_OBJECTS) $(test_sms_DEPENDENCIES)
+ @rm -f test-sms$(EXEEXT)
+ $(AM_V_CCLD)$(test_sms_LINK) $(test_sms_OBJECTS) $(test_sms_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -354,6 +374,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_charsets-test-charsets.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_modem_helpers-test-modem-helpers.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_qcdm_serial_port-test-qcdm-serial-port.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sms-test-sms.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
@@ -430,6 +451,22 @@ test_qcdm_serial_port-test-qcdm-serial-port.obj: test-qcdm-serial-port.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_qcdm_serial_port_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_qcdm_serial_port-test-qcdm-serial-port.obj `if test -f 'test-qcdm-serial-port.c'; then $(CYGPATH_W) 'test-qcdm-serial-port.c'; else $(CYGPATH_W) '$(srcdir)/test-qcdm-serial-port.c'; fi`
+test_sms-test-sms.o: test-sms.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_sms_CFLAGS) $(CFLAGS) -MT test_sms-test-sms.o -MD -MP -MF $(DEPDIR)/test_sms-test-sms.Tpo -c -o test_sms-test-sms.o `test -f 'test-sms.c' || echo '$(srcdir)/'`test-sms.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_sms-test-sms.Tpo $(DEPDIR)/test_sms-test-sms.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-sms.c' object='test_sms-test-sms.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_sms_CFLAGS) $(CFLAGS) -c -o test_sms-test-sms.o `test -f 'test-sms.c' || echo '$(srcdir)/'`test-sms.c
+
+test_sms-test-sms.obj: test-sms.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_sms_CFLAGS) $(CFLAGS) -MT test_sms-test-sms.obj -MD -MP -MF $(DEPDIR)/test_sms-test-sms.Tpo -c -o test_sms-test-sms.obj `if test -f 'test-sms.c'; then $(CYGPATH_W) 'test-sms.c'; else $(CYGPATH_W) '$(srcdir)/test-sms.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_sms-test-sms.Tpo $(DEPDIR)/test_sms-test-sms.Po
+@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-sms.c' object='test_sms-test-sms.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_sms_CFLAGS) $(CFLAGS) -c -o test_sms-test-sms.obj `if test -f 'test-sms.c'; then $(CYGPATH_W) 'test-sms.c'; else $(CYGPATH_W) '$(srcdir)/test-sms.c'; fi`
+
mostlyclean-libtool:
-rm -f *.lo
@@ -641,6 +678,7 @@ uninstall-am:
@WITH_TESTS_TRUE@ $(abs_builddir)/test-modem-helpers
@WITH_TESTS_TRUE@ $(abs_builddir)/test-charsets
@WITH_TESTS_TRUE@ $(abs_builddir)/test-qcdm-serial-port
+@WITH_TESTS_TRUE@ $(abs_builddir)/test-sms
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/tests/test-sms.c b/src/tests/test-sms.c
new file mode 100644
index 0000000..bd18c0b
--- /dev/null
+++ b/src/tests/test-sms.c
@@ -0,0 +1,429 @@
+/* -*- 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.
+ * Copyright (C) 2011 The Chromium OS Authors.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+
+#include "mm-sms-utils.h"
+#include "mm-utils.h"
+
+
+#define TEST_ENTRY_EQ(hash, key, expectvalue) do { \
+ GValue *value; \
+ value = g_hash_table_lookup((hash), (key)); \
+ g_assert(value); \
+ g_assert(G_VALUE_HOLDS_STRING(value)); \
+ g_assert_cmpstr(g_value_get_string(value), ==, (expectvalue)); \
+ } while (0)
+
+static void
+test_pdu1 (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x04, 0x44, 0x29, 0x61, 0xf4,
+ 0x04, 0x0b, 0x91, 0x61, 0x71, 0x95, 0x72, 0x91,
+ 0xf8, 0x00, 0x00, 0x11, 0x20, 0x82, 0x11, 0x05,
+ 0x05, 0x0a,
+ // user data:
+ 0x6a, 0xc8, 0xb2, 0xbc, 0x7c, 0x9a, 0x83, 0xc2,
+ 0x20, 0xf6, 0xdb, 0x7d, 0x2e, 0xcb, 0x41, 0xed,
+ 0xf2, 0x7c, 0x1e, 0x3e, 0x97, 0x41, 0x1b, 0xde,
+ 0x06, 0x75, 0x4f, 0xd3, 0xd1, 0xa0, 0xf9, 0xbb,
+ 0x5d, 0x06, 0x95, 0xf1, 0xf4, 0xb2, 0x9b, 0x5c,
+ 0x26, 0x83, 0xc6, 0xe8, 0xb0, 0x3c, 0x3c, 0xa6,
+ 0x97, 0xe5, 0xf3, 0x4d, 0x6a, 0xe3, 0x03, 0xd1,
+ 0xd1, 0xf2, 0xf7, 0xdd, 0x0d, 0x4a, 0xbb, 0x59,
+ 0xa0, 0x79, 0x7d, 0x8c, 0x06, 0x85, 0xe7, 0xa0,
+ 0x00, 0x28, 0xec, 0x26, 0x83, 0x2a, 0x96, 0x0b,
+ 0x28, 0xec, 0x26, 0x83, 0xbe, 0x60, 0x50, 0x78,
+ 0x0e, 0xba, 0x97, 0xd9, 0x6c, 0x17};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12404492164");
+ TEST_ENTRY_EQ (sms, "number", "+16175927198");
+ TEST_ENTRY_EQ (sms, "timestamp", "110228115050-05");
+ TEST_ENTRY_EQ (sms, "text",
+ "Here's a longer message [{with some extended characters}] "
+ "thrown in, such as £ and ΩΠΨ and §¿ as well.");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+static void
+test_pdu2 (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x97, 0x30, 0x07, 0x11, 0x11, 0xf1,
+ 0x04, 0x14, 0xd0, 0x49, 0x37, 0xbd, 0x2c, 0x77,
+ 0x97, 0xe9, 0xd3, 0xe6, 0x14, 0x00, 0x08, 0x11,
+ 0x30, 0x92, 0x91, 0x02, 0x40, 0x61, 0x08, 0x04,
+ 0x42, 0x04, 0x35, 0x04, 0x41, 0x04, 0x42};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+79037011111");
+ TEST_ENTRY_EQ (sms, "number", "InternetSMS");
+ TEST_ENTRY_EQ (sms, "timestamp", "110329192004+04");
+ TEST_ENTRY_EQ (sms, "text", "тест");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+static void
+test_pdu3 (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0x00, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "+18005551212");
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "hellohello");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+static void
+test_pdu3_nzpid (void *f, gpointer d)
+{
+ /* pid is nonzero (00 -> ff) */
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0xff, 0x00, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "+18005551212");
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "hellohello");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+
+static void
+test_pdu3_mms (void *f, gpointer d)
+{
+ /* mms is clear (04 -> 00) */
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x00, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0x00, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "+18005551212");
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "hellohello");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+static void
+test_pdu3_natl (void *f, gpointer d)
+{
+ /* number is natl (91 -> 81) */
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x81, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0x00, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "18005551212"); /* no plus */
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "hellohello");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+static void
+test_pdu3_8bit (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0x04, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37, 0xde};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "+18005551212");
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "\xe8\x32\x9b\xfd\x46\x97\xd9\xec\x37\xde");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+static void
+test_pdu_dcsf1 (void *f, gpointer d)
+{
+ /* TP-DCS coding scheme is group F */
+ static const guint8 pdu[] = {
+ 0x07, // length of SMSC info
+ 0x91, // type of address of SMSC (E.164)
+ 0x33, 0x06, 0x09, 0x10, 0x93, 0xF0, // SMSC address (+33 60 90 01 39 0)
+ 0x04, // SMS-DELIVER
+ 0x04, // address length
+ 0x85, // type of address
+ 0x81, 0x00, // sender address (1800)
+ 0x00, // TP-PID protocol identifier
+ 0xF1, // TP-DCS data coding scheme
+ 0x11, 0x60, 0x42, 0x31, 0x80, 0x51, 0x80, // timestamp 11-06-24 13:08:51
+ 0xA0, // TP-UDL user data length (160)
+ // Content:
+ 0x49,
+ 0xB7, 0xF9, 0x0D, 0x9A, 0x1A, 0xA5, 0xA0, 0x16,
+ 0x68, 0xF8, 0x76, 0x9B, 0xD3, 0xE4, 0xB2, 0x9B,
+ 0x9E, 0x2E, 0xB3, 0x59, 0xA0, 0x3F, 0xC8, 0x5D,
+ 0x06, 0xA9, 0xC3, 0xED, 0x70, 0x7A, 0x0E, 0xA2,
+ 0xCB, 0xC3, 0xEE, 0x79, 0xBB, 0x4C, 0xA7, 0xCB,
+ 0xCB, 0xA0, 0x56, 0x43, 0x61, 0x7D, 0xA7, 0xC7,
+ 0x69, 0x90, 0xFD, 0x4D, 0x97, 0x97, 0x41, 0xEE,
+ 0x77, 0xDD, 0x5E, 0x0E, 0xD7, 0x41, 0xED, 0x37,
+ 0x1D, 0x44, 0x2E, 0x83, 0xE0, 0xE1, 0xF9, 0xBC,
+ 0x0C, 0xD2, 0x81, 0xE6, 0x77, 0xD9, 0xB8, 0x4C,
+ 0x06, 0xC1, 0xDF, 0x75, 0x39, 0xE8, 0x5C, 0x90,
+ 0x97, 0xE5, 0x20, 0xFB, 0x9B, 0x2E, 0x2F, 0x83,
+ 0xC6, 0xEF, 0x36, 0x9C, 0x5E, 0x06, 0x4D, 0x8D,
+ 0x52, 0xD0, 0xBC, 0x2E, 0x07, 0xDD, 0xEF, 0x77,
+ 0xD7, 0xDC, 0x2C, 0x77, 0x99, 0xE5, 0xA0, 0x77,
+ 0x1D, 0x04, 0x0F, 0xCB, 0x41, 0xF4, 0x02, 0xBB,
+ 0x00, 0x47, 0xBF, 0xDD, 0x65, 0x50, 0xB8, 0x0E,
+ 0xCA, 0xD9, 0x66};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+33609001390");
+ TEST_ENTRY_EQ (sms, "number", "1800");
+ TEST_ENTRY_EQ (sms, "timestamp", "110624130815+02");
+ TEST_ENTRY_EQ (sms, "text",
+ "Info SFR - Confidentiel, à ne jamais transmettre -\r\n"
+ "Voici votre nouveau mot de passe : sw2ced pour gérer "
+ "votre compte SFR sur www.sfr.fr ou par téléphone au 963");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+
+static void
+test_pdu_dcsf_8bit (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0xf4, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37, 0xde};
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+12345678901");
+ TEST_ENTRY_EQ (sms, "number", "+18005551212");
+ TEST_ENTRY_EQ (sms, "timestamp", "110101123456+00");
+ TEST_ENTRY_EQ (sms, "text", "\xe8\x32\x9b\xfd\x46\x97\xd9\xec\x37\xde");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+
+static void
+test_pdu_insufficient_data (void *f, gpointer d)
+{
+ static const guint8 pdu[] = {
+ 0x07, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09, 0xf1,
+ 0x04, 0x0b, 0x91, 0x81, 0x00, 0x55, 0x15, 0x12,
+ 0xf2, 0x00, 0x00, 0x11, 0x10, 0x10, 0x21, 0x43,
+ 0x65, 0x00, 0x0b, 0xe8, 0x32, 0x9b, 0xfd, 0x46,
+ 0x97, 0xd9, 0xec, 0x37
+ };
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu, sizeof(pdu));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms == NULL);
+
+ g_free (hexpdu);
+}
+
+
+static void
+test_pdu_udhi (void *f, gpointer d)
+{
+ /* Welcome message from KPN NL */
+ static const char *hexpdu =
+"07911356131313F64004850120390011609232239180A006080400100201D7327BFD6EB340E232"
+"1BF46E83EA7790F59D1E97DBE1341B442F83C465763D3DA797E56537C81D0ECB41AB59CC1693C1"
+"6031D96C064241E5656838AF03A96230982A269BCD462917C8FA4E8FCBED709A0D7ABBE9F6B0FB"
+"5C7683D27350984D4FABC9A0B33C4C4FCF5D20EBFB2D079DCB62793DBD06D9C36E50FB2D4E97D9"
+"A0B49B5E96BBCB";
+ GHashTable *sms;
+ GError *error;
+
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "+31653131316");
+ TEST_ENTRY_EQ (sms, "number", "1002");
+ TEST_ENTRY_EQ (sms, "timestamp", "110629233219+02");
+ TEST_ENTRY_EQ (sms, "text",
+ "Welkom, bel om uw Voicemail te beluisteren naar +31612001233"
+ " (PrePay: *100*1233#). Voicemail ontvangen is altijd gratis."
+ " Voor gebruik van mobiel interne");
+
+ g_hash_table_unref (sms);
+}
+
+#if 0
+static void
+test_pduX (void *f, gpointer d)
+{
+ GHashTable *sms;
+ GError *error;
+ char *hexpdu;
+
+ hexpdu = utils_bin2hexstr (pdu1, sizeof(pdu1));
+ sms = sms_parse_pdu (hexpdu, &error);
+ g_assert (sms);
+
+ TEST_ENTRY_EQ (sms, "smsc", "");
+ TEST_ENTRY_EQ (sms, "number", "");
+ TEST_ENTRY_EQ (sms, "timestamp", "");
+ TEST_ENTRY_EQ (sms, "text",
+ "");
+
+ g_free (hexpdu);
+ g_hash_table_unref (sms);
+}
+#endif
+
+
+
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFixtureFunc TCFunc;
+#else
+typedef void (*TCFunc)(void);
+#endif
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+ gint result;
+
+ g_type_init ();
+
+ g_test_init (&argc, &argv, NULL);
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE (test_pdu1, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu2, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu3, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu3_nzpid, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu3_mms, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu3_natl, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu3_8bit, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu_dcsf1, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu_dcsf_8bit, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu_insufficient_data, NULL));
+ g_test_suite_add (suite, TESTCASE (test_pdu_udhi, NULL));
+
+ result = g_test_run ();
+
+ return result;
+}