[PATCH] GSM SMS reception code



Here's the implementation I've done of SMS reception code for GSM modems, exclusive of the bug fixes discussed here earlier. It implements the SmsReceived signal and the Get, Delete, and List commands; it does not group multi-part messages together into one, but does skip over the user-data header to avoid presenting garbage to the user. 
 
Credit to Torgny Johansson <torgny johansson ericsson com> for the initial basis of this code.

    - Nathan
diff --git a/introspection/org.freedesktop.ModemManager.Modem.Gsm.SMS.xml b/introspection/org.freedesktop.ModemManager.Modem.Gsm.SMS.xml
index 081ecc5..15953b8 100644
--- a/introspection/org.freedesktop.ModemManager.Modem.Gsm.SMS.xml
+++ b/introspection/org.freedesktop.ModemManager.Modem.Gsm.SMS.xml
@@ -36,6 +36,7 @@
           validity : uint (0..255) - Specifies when the SMS expires in SMSC (optional)
           class    : uint (0..3) - Message importance and location (optional)
           completed: boolean - Whether all message parts have been received or not (optional)
+          index    : uint - Index of message (for Get and Delete) (optional)
         </tp:docstring>
       </arg>
     </method>
diff --git a/src/mm-charsets.c b/src/mm-charsets.c
index dd1ae08..cbdf388 100644
--- a/src/mm-charsets.c
+++ b/src/mm-charsets.c
@@ -427,16 +427,16 @@ mm_charset_utf8_to_unpacked_gsm (const char *utf8, guint32 *out_len)
 
 guint8 *
 gsm_unpack (const guint8 *gsm,
-            guint32 nchars,
+            guint32 num_septets,
             guint8 start_offset,  /* in _bits_ */
             guint32 *out_unpacked_len)
 {
     GByteArray *unpacked;
     int i;
 
-    unpacked = g_byte_array_sized_new (nchars + 1);
+    unpacked = g_byte_array_sized_new (num_septets + 1);
 
-    for (i = 0; i < nchars; i++) {
+    for (i = 0; i < num_septets; i++) {
         guint8 bits_here, bits_in_next, octet, offset, c;
         guint32 start_bit;
 
diff --git a/src/mm-charsets.h b/src/mm-charsets.h
index efd89f3..50b0cce 100644
--- a/src/mm-charsets.h
+++ b/src/mm-charsets.h
@@ -53,7 +53,7 @@ guint8 *mm_charset_utf8_to_unpacked_gsm (const char *utf8, guint32 *out_len);
 guint8 *mm_charset_gsm_unpacked_to_utf8 (const guint8 *gsm, guint32 len);
 
 guint8 *gsm_unpack (const guint8 *gsm,
-                    guint32 nchars, /* number of gsm characters, not octets */
+                    guint32 num_septets,
                     guint8 start_offset,  /* in bits */
                     guint32 *out_unpacked_len);
 
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index 98713b0..24c3d24 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -167,6 +167,16 @@ static void ciev_received (MMAtSerialPort *port,
                            GMatchInfo *info,
                            gpointer user_data);
 
+static void cmti_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 *
 mm_generic_gsm_new (const char *device,
                     const char *driver,
@@ -814,6 +824,9 @@ 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);
+
+        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);
 
         if (ptype == MM_PORT_TYPE_PRIMARY) {
@@ -1293,6 +1306,32 @@ ciev_received (MMAtSerialPort *port,
 }
 
 static void
+cmti_received (MMAtSerialPort *port,
+               GMatchInfo *info,
+               gpointer user_data)
+{
+    MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+
+    guint idx=0;
+    char *mem;
+    char *str;
+
+    mem = g_match_info_fetch (info, 1);
+
+    str = g_match_info_fetch (info, 2);
+    if (str)
+        idx = atoi (str);
+    g_free (str);
+
+    /* todo: parse pdu to know if the sms is complete */
+    mm_modem_gsm_sms_received (MM_MODEM_GSM_SMS (self),
+                               idx,
+                               TRUE);
+
+    /* todo: send mm_modem_gsm_sms_completed if complete */
+}
+
+static void
 cmer_cb (MMAtSerialPort *port,
          GString *response,
          GError *error,
@@ -1380,6 +1419,11 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self,
     /* Try to enable XON/XOFF flow control */
     mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL);
 
+    /* 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);
+
     mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self);
 
     /* Try one more time to get the SIM ID */
@@ -3811,6 +3855,29 @@ 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_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,
                GString *response,
@@ -3855,6 +3922,403 @@ 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 septets for gsm7 and in digits (semi-octets) for others */
+static char *
+sms_decode_address (const guint8 *address, int len)
+{
+    guint8 addrtype;
+    char *utf8;
+
+    addrtype = address[0];
+    address++;
+
+    if (addrtype == 0xd0) {
+        guint8 *unpacked;
+        guint32 unpacked_len;
+        unpacked = gsm_unpack (address + 1, len, 0, &unpacked_len);
+        utf8 = (char *)mm_charset_gsm_unpacked_to_utf8 (unpacked,
+                                                        unpacked_len);
+        g_free(unpacked);
+    } else {
+        utf8 = g_malloc (len + 2); /* may need one extra for trailing 0xf */
+        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;
+    if (coding == SMS_DCS_CODING_DEFAULT) {
+        guint8 *unpacked;
+        guint32 unpacked_len;
+        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): %d 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): %d 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): %d 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;
+    }
+
+    /* Only handle the basic protocol identifier */
+    if (pdu[tp_pid_offset] != 0) {
+        mm_err ("Unhandled protocol identifier: 0x%02x vs 0x00",
+                pdu[tp_pid_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,
+              GString *response,
+              GError *error,
+              gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+    GHashTable *properties;
+    int rv, status, tpdu_len, offset;
+    char pdu[SMS_MAX_PDU_LEN + 1];
+
+    rv = sscanf (response->str, "+CMGR: %d,,%d %344s %n",
+                 &status, &tpdu_len, pdu, &offset);
+
+    properties = sms_parse_pdu (pdu);
+
+    if (!properties) {
+        /* todo(njw): log some error */
+        return;
+    }
+
+    if (error)
+        info->error = g_error_copy (error);
+
+    mm_callback_info_set_data (info, GS_HASH_TAG, properties,
+                               (GDestroyNotify) g_hash_table_unref);
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+sms_get_invoke (MMCallbackInfo *info)
+{
+    MMModemGsmSmsGetFn callback = (MMModemGsmSmsGetFn) info->callback;
+
+    callback (MM_MODEM_GSM_SMS (info->modem),
+              (GHashTable *) mm_callback_info_get_data (info, GS_HASH_TAG),
+              info->error, info->user_data);
+}
+
+static void
+sms_get (MMModemGsmSms *modem,
+         guint idx,
+         MMModemGsmSmsGetFn callback,
+         gpointer user_data)
+{
+    MMCallbackInfo *info;
+    char *command;
+    MMAtSerialPort *port;
+
+    info = mm_callback_info_new_full (MM_MODEM (modem),
+                                      sms_get_invoke,
+                                      G_CALLBACK (callback),
+                                      user_data);
+
+    port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+    if (!port) {
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    command = g_strdup_printf ("+CMGR=%d\r\n", idx);
+    mm_at_serial_port_queue_command (port, command, 10, sms_get_done, info);
+}
+
+static void
+sms_delete_done (MMAtSerialPort *port,
+                 GString *response,
+                 GError *error,
+                 gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+    if (error)
+        info->error = g_error_copy (error);
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+sms_delete (MMModemGsmSms *modem,
+            guint idx,
+            MMModemFn callback,
+            gpointer user_data)
+{
+    MMCallbackInfo *info;
+    char *command;
+    MMAtSerialPort *port;
+
+    info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+
+    port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+    if (!port) {
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    command = g_strdup_printf ("+CMGD=%d\r\n", idx);
+    mm_at_serial_port_queue_command (port, command, 10, sms_delete_done, info);
+}
+
+static void
+sms_list_done (MMAtSerialPort *port,
+               GString *response,
+               GError *error,
+               gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+    GPtrArray *results = NULL;
+    int rv, status, tpdu_len, offset;
+
+    if (error)
+        info->error = g_error_copy (error);
+    else {
+        char *rstr;
+        results = g_ptr_array_new ();
+        rstr = response->str;
+        while (*rstr) {
+            GHashTable *properties;
+            int idx;
+            char pdu[SMS_MAX_PDU_LEN + 1];
+
+            rv = sscanf (rstr, "+CMGL: %d,%d,,%d %344s %n",
+                         &idx, &status, &tpdu_len, pdu, &offset);
+            if (4 != rv) {
+                mm_err("Couldn't parse response to SMS LIST (%d)", rv);
+                break;
+            }
+            rstr += offset;
+
+            properties = sms_parse_pdu(pdu);
+            if (properties) {
+                g_hash_table_insert (properties, "index",
+                                     simple_uint_value (idx));
+                g_ptr_array_add (results, properties);
+            }
+        }
+        /*
+         * todo(njw): mm_gsm_destroy_scan_data does what we want
+         * (destroys a GPtrArray of g_hash_tables), but it should be
+         * renamed to describe that or there should be a function
+         * named for what we're doing here.
+         */
+        if (results)
+            mm_callback_info_set_data (info, "list-sms", results,
+                                       mm_gsm_destroy_scan_data);
+    }
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+sms_list_invoke (MMCallbackInfo *info)
+{
+    MMModemGsmSmsListFn callback = (MMModemGsmSmsListFn) info->callback;
+
+    callback (MM_MODEM_GSM_SMS (info->modem),
+              (GPtrArray *) mm_callback_info_get_data (info, "list-sms"),
+              info->error, info->user_data);
+}
+
+static void
+sms_list (MMModemGsmSms *modem,
+          MMModemGsmSmsListFn callback,
+          gpointer user_data)
+{
+    MMCallbackInfo *info;
+    char *command;
+    MMAtSerialPort *port;
+
+    info = mm_callback_info_new_full (MM_MODEM (modem),
+                                      sms_list_invoke,
+                                      G_CALLBACK (callback),
+                                      user_data);
+
+    port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+    if (!port) {
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    command = g_strdup_printf ("+CMGL=4\r\n");
+    mm_at_serial_port_queue_command (port, command, 10, sms_list_done, info);
+}
+
 MMAtSerialPort *
 mm_generic_gsm_get_at_port (MMGenericGsm *modem,
                            MMPortType ptype)
@@ -4391,6 +4855,18 @@ 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;
@@ -4728,6 +5204,9 @@ static void
 modem_gsm_sms_init (MMModemGsmSms *class)
 {
     class->send = sms_send;
+    class->get = sms_get;
+    class->delete = sms_delete;
+    class->list = sms_list;
 }
 
 static void
diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c
index 271c67b..ab20d3e 100644
--- a/src/mm-modem-gsm-sms.c
+++ b/src/mm-modem-gsm-sms.c
@@ -103,6 +103,34 @@ async_call_not_supported (MMModemGsmSms *self,
     mm_callback_info_schedule (info);
 }
 
+static void
+sms_get_done (MMModemGsmSms *self,
+              GHashTable *properties,
+              GError *error,
+              gpointer user_data)
+{
+    DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+    if (error)
+        dbus_g_method_return_error (context, error);
+    else
+        dbus_g_method_return (context, properties);
+}
+
+static void
+sms_list_done (MMModemGsmSms *self,
+              GPtrArray *results,
+              GError *error,
+              gpointer user_data)
+{
+    DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+    if (error)
+        dbus_g_method_return_error (context, error);
+    else
+        dbus_g_method_return (context, results);
+}
+
 /*****************************************************************************/
 
 void
@@ -127,6 +155,110 @@ mm_modem_gsm_sms_send (MMModemGsmSms *self,
 
 }
 
+static void
+sms_get_invoke (MMCallbackInfo *info)
+{
+    MMModemGsmSmsGetFn callback = (MMModemGsmSmsGetFn) info->callback;
+
+    callback (MM_MODEM_GSM_SMS (info->modem), NULL, info->error, info->user_data);
+}
+
+void
+mm_modem_gsm_sms_get (MMModemGsmSms *self,
+                      guint idx,
+                      MMModemGsmSmsGetFn callback,
+                      gpointer user_data)
+{
+    g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+    g_return_if_fail (callback != NULL);
+
+    if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->get)
+        MM_MODEM_GSM_SMS_GET_INTERFACE (self)->get (self, idx, callback, user_data);
+    else {
+        MMCallbackInfo *info;
+
+        info = mm_callback_info_new_full (MM_MODEM (self),
+                                          sms_get_invoke,
+                                          G_CALLBACK (callback),
+                                          user_data);
+
+        info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+                                           "Operation not supported");
+        mm_callback_info_schedule (info);
+    }
+}
+
+void
+mm_modem_gsm_sms_delete (MMModemGsmSms *self,
+                         guint idx,
+                         MMModemFn callback,
+                         gpointer user_data)
+{
+    g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+    g_return_if_fail (callback != NULL);
+
+    if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->delete)
+        MM_MODEM_GSM_SMS_GET_INTERFACE (self)->delete (self, idx, callback, user_data);
+    else
+        async_call_not_supported (self, callback, user_data);
+}
+
+static void
+sms_list_invoke (MMCallbackInfo *info)
+{
+    MMModemGsmSmsListFn callback = (MMModemGsmSmsListFn) info->callback;
+
+    callback (MM_MODEM_GSM_SMS (info->modem), NULL, info->error, info->user_data);
+}
+
+void
+mm_modem_gsm_sms_list (MMModemGsmSms *self,
+                       MMModemGsmSmsListFn callback,
+                       gpointer user_data)
+{
+    g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+    g_return_if_fail (callback != NULL);
+
+    if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->list)
+        MM_MODEM_GSM_SMS_GET_INTERFACE (self)->list (self, callback, user_data);
+    else {
+        MMCallbackInfo *info;
+
+        info = mm_callback_info_new_full (MM_MODEM (self),
+                                          sms_list_invoke,
+                                          G_CALLBACK (callback),
+                                          user_data);
+
+        info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED,
+                                           "Operation not supported");
+        mm_callback_info_schedule (info);
+    }
+}
+
+void
+mm_modem_gsm_sms_received (MMModemGsmSms *self,
+                           guint idx,
+                           gboolean complete)
+{
+    g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+
+    g_signal_emit (self, signals[SMS_RECEIVED], 0,
+                   idx,
+                   complete);
+}
+
+void
+mm_modem_gsm_sms_completed (MMModemGsmSms *self,
+                            guint idx,
+                            gboolean complete)
+{
+    g_return_if_fail (MM_IS_MODEM_GSM_SMS (self));
+
+    g_signal_emit (self, signals[COMPLETED], 0,
+                   idx,
+                   complete);
+}
+
 /*****************************************************************************/
 
 typedef struct {
@@ -212,14 +344,18 @@ sms_delete_auth_cb (MMAuthRequest *req,
                     gpointer user_data)
 {
     MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner);
+    SmsAuthInfo *info = user_data;
     GError *error = NULL;
+    guint idx;
 
     /* Return any authorization error, otherwise delete the SMS */
     if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) {
         dbus_g_method_return_error (context, error);
         g_error_free (error);
-    } else
-        async_call_not_supported (self, async_call_done, context);
+    } else {
+        idx = info->num1;
+        mm_modem_gsm_sms_delete (self, idx, async_call_done, context);
+    }
 }
 
 static void
@@ -254,14 +390,18 @@ sms_get_auth_cb (MMAuthRequest *req,
                  gpointer user_data)
 {
     MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner);
+    SmsAuthInfo *info = user_data;
+    guint idx;
     GError *error = NULL;
 
     /* Return any authorization error, otherwise get the SMS */
     if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) {
         dbus_g_method_return_error (context, error);
         g_error_free (error);
-    } else
-        async_call_not_supported (self, async_call_done, context);
+    } else {
+        idx = info->num1;
+        mm_modem_gsm_sms_get (self, idx, sms_get_done, context);
+    }
 }
 
 static void
@@ -369,7 +509,7 @@ sms_list_auth_cb (MMAuthRequest *req,
         dbus_g_method_return_error (context, error);
         g_error_free (error);
     } else
-        async_call_not_supported (self, async_call_done, context);
+        mm_modem_gsm_sms_list (self, sms_list_done, context);
 }
 
 static void
diff --git a/src/mm-modem-gsm-sms.h b/src/mm-modem-gsm-sms.h
index 79a5bb0..41684d7 100644
--- a/src/mm-modem-gsm-sms.h
+++ b/src/mm-modem-gsm-sms.h
@@ -25,6 +25,16 @@
 
 typedef struct _MMModemGsmSms MMModemGsmSms;
 
+typedef void (*MMModemGsmSmsGetFn) (MMModemGsmSms *modem,
+                                    GHashTable *properties,
+                                    GError *error,
+                                    gpointer user_data);
+
+typedef void (*MMModemGsmSmsListFn) (MMModemGsmSms *modem,
+                                     GPtrArray *resultlist,
+                                     GError *error,
+                                     gpointer user_data);
+
 struct _MMModemGsmSms {
     GTypeInterface g_iface;
 
@@ -38,6 +48,20 @@ struct _MMModemGsmSms {
                   MMModemFn callback,
                   gpointer user_data);
 
+    void (*get) (MMModemGsmSms *modem,
+                 guint32 index,
+                 MMModemGsmSmsGetFn callback,
+                 gpointer user_data);
+
+    void (*delete) (MMModemGsmSms *modem,
+                    guint32 index,
+                    MMModemFn callback,
+                    gpointer user_data);
+
+    void (*list) (MMModemGsmSms *modem,
+                  MMModemGsmSmsListFn callback,
+                  gpointer user_data);
+
     /* Signals */
     void (*sms_received) (MMModemGsmSms *self,
                           guint32 index,
@@ -59,4 +83,27 @@ void mm_modem_gsm_sms_send (MMModemGsmSms *self,
                             MMModemFn callback,
                             gpointer user_data);
 
+void mm_modem_gsm_sms_get (MMModemGsmSms *self,
+                           guint idx,
+                           MMModemGsmSmsGetFn callback,
+                           gpointer user_data);
+
+void mm_modem_gsm_sms_delete (MMModemGsmSms *self,
+                              guint idx,
+                              MMModemFn callback,
+                              gpointer user_data);
+
+void mm_modem_gsm_sms_list (MMModemGsmSms *self,
+                            MMModemGsmSmsListFn callback,
+                            gpointer user_data);
+
+void mm_modem_gsm_sms_received (MMModemGsmSms *self,
+                                guint idx,
+                                gboolean complete);
+
+void mm_modem_gsm_sms_completed (MMModemGsmSms *self,
+                                guint idx,
+                                gboolean complete);
+
+
 #endif /* MM_MODEM_GSM_SMS_H */
diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c
index 75bcce4..3a87a70 100644
--- a/src/mm-serial-parsers.c
+++ b/src/mm-serial-parsers.c
@@ -199,6 +199,7 @@ typedef struct {
     GRegex *regex_ok;
     GRegex *regex_connect;
     GRegex *regex_cme_error;
+    GRegex *regex_cms_error;
     GRegex *regex_cme_error_str;
     GRegex *regex_ezx_error;
     GRegex *regex_unknown_error;
@@ -216,6 +217,7 @@ mm_serial_parser_v1_new (void)
     parser->regex_ok = g_regex_new ("\\r\\nOK(\\r\\n)+$", flags, 0, NULL);
     parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL);
     parser->regex_cme_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
+    parser->regex_cms_error = g_regex_new ("\\r\\n\\+CMS ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
     parser->regex_cme_error_str = g_regex_new ("\\r\\n\\+CME ERROR: ([^\\n\\r]+)\\r\\n$", flags, 0, NULL);
     parser->regex_ezx_error = g_regex_new ("\\r\\n\\MODEM ERROR: (\\d+)\\r\\n$", flags, 0, NULL);
     parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL);
@@ -270,6 +272,23 @@ mm_serial_parser_v1_parse (gpointer data,
         goto done;
     }
 
+    /* Numeric CMS errors */
+    /* Todo
+     * One should probably add message service
+     * errors explicitly in mm-errors.h/c
+     */
+    found = g_regex_match_full (parser->regex_cms_error,
+                                response->str, response->len,
+                                0, 0, &match_info, NULL);
+    if (found) {
+        str = g_match_info_fetch (match_info, 1);
+        g_assert (str);
+        local_error = mm_mobile_error_for_code (atoi (str));
+        g_free (str);
+        g_match_info_free (match_info);
+        goto done;
+    }
+
     /* String CME errors */
     found = g_regex_match_full (parser->regex_cme_error_str,
                                 response->str, response->len,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]