diff options
author | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:26 +0100 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:26 +0100 |
commit | a09050a7f63a262bf90dcb1c7a41f9cfd205db43 (patch) | |
tree | 2ea74d753bc35130916ad07c99d6a8028723c6d6 /src | |
parent | 3dbe8df8bfe8741e1b9a48b56e41517816f17dc1 (diff) |
Imported Upstream version 0.5upstream/0.5
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/Makefile.in | 16 | ||||
-rw-r--r-- | src/mm-callback-info.c | 8 | ||||
-rw-r--r-- | src/mm-callback-info.h | 1 | ||||
-rw-r--r-- | src/mm-generic-cdma.c | 167 | ||||
-rw-r--r-- | src/mm-generic-gsm.c | 660 | ||||
-rw-r--r-- | src/mm-generic-gsm.h | 2 | ||||
-rw-r--r-- | src/mm-log.c | 37 | ||||
-rw-r--r-- | src/mm-log.h | 2 | ||||
-rw-r--r-- | src/mm-manager.c | 20 | ||||
-rw-r--r-- | src/mm-modem-gsm-ussd.c | 26 | ||||
-rw-r--r-- | src/mm-modem-gsm-ussd.h | 20 | ||||
-rw-r--r-- | src/mm-plugin-base.c | 6 | ||||
-rw-r--r-- | src/mm-serial-port.c | 120 | ||||
-rw-r--r-- | src/mm-serial-port.h | 4 | ||||
-rw-r--r-- | src/mm-sms-utils.c | 389 | ||||
-rw-r--r-- | src/mm-sms-utils.h | 25 | ||||
-rw-r--r-- | src/tests/Makefile.am | 14 | ||||
-rw-r--r-- | src/tests/Makefile.in | 44 | ||||
-rw-r--r-- | src/tests/test-sms.c | 429 |
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; +} |