gsm: implement basic SMS reception and PDU parsing
This commit is contained in:

committed by
Dan Williams

parent
48c7dac009
commit
487972c1ac
@@ -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>
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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,
|
||||
@@ -815,6 +825,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,29 @@ ciev_received (MMAtSerialPort *port,
|
||||
/* FIXME: handle roaming and service indicators */
|
||||
}
|
||||
|
||||
static void
|
||||
cmti_received (MMAtSerialPort *port,
|
||||
GMatchInfo *info,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMGenericGsm *self = MM_GENERIC_GSM (user_data);
|
||||
|
||||
guint idx=0;
|
||||
char *str;
|
||||
|
||||
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,
|
||||
@@ -1385,6 +1421,11 @@ 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);
|
||||
|
||||
mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self);
|
||||
|
||||
/* Try one more time to get the SIM ID */
|
||||
@@ -3816,6 +3857,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,
|
||||
@@ -3860,6 +3924,414 @@ 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;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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];
|
||||
|
||||
if (error) {
|
||||
info->error = g_error_copy (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* 344 == SMS_MAX_PDU_LEN */
|
||||
rv = sscanf (response->str, "+CMGR: %d,,%d %344s %n",
|
||||
&status, &tpdu_len, pdu, &offset);
|
||||
if (rv != 4) {
|
||||
info->error = g_error_new (MM_MODEM_ERROR,
|
||||
MM_MODEM_ERROR_GENERAL,
|
||||
"Failed to parse CMGR response (parsed %d items)",
|
||||
rv);
|
||||
goto out;
|
||||
}
|
||||
|
||||
properties = sms_parse_pdu (pdu);
|
||||
if (!properties) {
|
||||
info->error = g_error_new_literal (MM_MODEM_ERROR,
|
||||
MM_MODEM_ERROR_GENERAL,
|
||||
"Failed to parse SMS PDU");
|
||||
goto out;
|
||||
}
|
||||
|
||||
mm_callback_info_set_data (info, GS_HASH_TAG, properties,
|
||||
(GDestroyNotify) g_hash_table_unref);
|
||||
|
||||
out:
|
||||
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;
|
||||
char *rstr;
|
||||
|
||||
if (error)
|
||||
info->error = g_error_copy (error);
|
||||
else {
|
||||
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)
|
||||
@@ -4395,6 +4867,18 @@ simple_uint_value (guint32 i)
|
||||
return val;
|
||||
}
|
||||
|
||||
static GValue *
|
||||
simple_boolean_value (gboolean b)
|
||||
{
|
||||
GValue *val;
|
||||
|
||||
val = g_slice_new0 (GValue);
|
||||
g_value_init (val, G_TYPE_BOOLEAN);
|
||||
g_value_set_boolean (val, b);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static GValue *
|
||||
simple_string_value (const char *str)
|
||||
{
|
||||
@@ -4733,6 +5217,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
|
||||
|
@@ -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
|
||||
|
@@ -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 */
|
||||
|
@@ -202,6 +202,7 @@ typedef struct {
|
||||
GRegex *regex_custom_successful;
|
||||
/* Regular expressions for error replies */
|
||||
GRegex *regex_cme_error;
|
||||
GRegex *regex_cms_error;
|
||||
GRegex *regex_cme_error_str;
|
||||
GRegex *regex_ezx_error;
|
||||
GRegex *regex_unknown_error;
|
||||
@@ -220,6 +221,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);
|
||||
@@ -322,6 +324,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,
|
||||
|
Reference in New Issue
Block a user