diff options
author | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:27 +0100 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:27 +0100 |
commit | 14d771b90f5a7d3887e5e900d1fb4737477ad305 (patch) | |
tree | f382e3359d20916ae60d28361e59635e373224f8 /src | |
parent | a09050a7f63a262bf90dcb1c7a41f9cfd205db43 (diff) |
Imported Upstream version 0.5.2.0upstream/0.5.2.0
Diffstat (limited to 'src')
38 files changed, 2859 insertions, 436 deletions
diff --git a/src/Makefile.in b/src/Makefile.in index 1f2f622..d559bc2 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -52,7 +52,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/compiler_warnings.m4 \ $(top_srcdir)/m4/intltool.m4 $(top_srcdir)/m4/libtool.m4 \ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/nls.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d diff --git a/src/mm-at-serial-port.c b/src/mm-at-serial-port.c index 30da3a3..5bc1789 100644 --- a/src/mm-at-serial-port.c +++ b/src/mm-at-serial-port.c @@ -29,14 +29,21 @@ G_DEFINE_TYPE (MMAtSerialPort, mm_at_serial_port, MM_TYPE_SERIAL_PORT) #define MM_AT_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortPrivate)) +enum { + PROP_0, + PROP_REMOVE_ECHO, + LAST_PROP +}; + typedef struct { /* Response parser data */ MMAtSerialResponseParserFn response_parser_fn; gpointer response_parser_user_data; GDestroyNotify response_parser_notify; GSList *unsolicited_msg_handlers; -} MMAtSerialPortPrivate; + gboolean remove_echo; +} MMAtSerialPortPrivate; /*****************************************************************************/ @@ -58,6 +65,26 @@ mm_at_serial_port_set_response_parser (MMAtSerialPort *self, priv->response_parser_notify = notify; } +void +mm_at_serial_port_remove_echo (GByteArray *response) +{ + guint i; + + if (response->len <= 2) + return; + + for (i = 0; i < (response->len - 1); i++) { + /* If there is any content before the first + * <CR><LF>, assume it's echo or garbage, and skip it */ + if (response->data[i] == '\r' && response->data[i + 1] == '\n') { + if (i > 0) + g_byte_array_remove_range (response, 0, i); + /* else, good, we're already started with <CR><LF> */ + break; + } + } +} + static gboolean parse_response (MMSerialPort *port, GByteArray *response, GError **error) { @@ -68,6 +95,10 @@ parse_response (MMSerialPort *port, GByteArray *response, GError **error) g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); + /* Remove echo */ + if (priv->remove_echo) + mm_at_serial_port_remove_echo (response); + /* Construct the string that AT-parsing functions expect */ string = g_string_sized_new (response->len + 1); g_string_append_len (string, (const char *) response->data, response->len); @@ -159,6 +190,10 @@ parse_unsolicited (MMSerialPort *port, GByteArray *response) MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); GSList *iter; + /* Remove echo */ + if (priv->remove_echo) + mm_at_serial_port_remove_echo (response); + for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) iter->data; GMatchInfo *match_info; @@ -314,6 +349,42 @@ mm_at_serial_port_new (const char *name, MMPortType ptype) static void mm_at_serial_port_init (MMAtSerialPort *self) { + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + /* By default, remove echo */ + priv->remove_echo = TRUE; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REMOVE_ECHO: + priv->remove_echo = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REMOVE_ECHO: + g_value_set_boolean (value, priv->remove_echo); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } static void @@ -349,10 +420,20 @@ mm_at_serial_port_class_init (MMAtSerialPortClass *klass) g_type_class_add_private (object_class, sizeof (MMAtSerialPortPrivate)); /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; object_class->finalize = finalize; port_class->parse_unsolicited = parse_unsolicited; port_class->parse_response = parse_response; port_class->handle_response = handle_response; port_class->debug_log = debug_log; + + g_object_class_install_property + (object_class, PROP_REMOVE_ECHO, + g_param_spec_boolean (MM_AT_SERIAL_PORT_REMOVE_ECHO, + "Remove echo", + "Built-in echo removal should be applied", + TRUE, + G_PARAM_READWRITE)); } diff --git a/src/mm-at-serial-port.h b/src/mm-at-serial-port.h index 5d5f13f..3079470 100644 --- a/src/mm-at-serial-port.h +++ b/src/mm-at-serial-port.h @@ -18,7 +18,6 @@ #define MM_AT_SERIAL_PORT_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> #include "mm-serial-port.h" @@ -46,6 +45,8 @@ typedef void (*MMAtSerialResponseFn) (MMAtSerialPort *port, GError *error, gpointer user_data); +#define MM_AT_SERIAL_PORT_REMOVE_ECHO "remove-echo" + struct _MMAtSerialPort { MMSerialPort parent; }; @@ -81,5 +82,7 @@ void mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, MMAtSerialResponseFn callback, gpointer user_data); -#endif /* MM_AT_SERIAL_PORT_H */ +/* Just for unit tests */ +void mm_at_serial_port_remove_echo (GByteArray *response); +#endif /* MM_AT_SERIAL_PORT_H */ diff --git a/src/mm-charsets.c b/src/mm-charsets.c index cbdf388..832a06f 100644 --- a/src/mm-charsets.c +++ b/src/mm-charsets.c @@ -18,6 +18,7 @@ #include <stdlib.h> #include <unistd.h> #include <string.h> +#include <ctype.h> #include "mm-charsets.h" #include "mm-utils.h" @@ -36,8 +37,8 @@ static CharsetEntry charset_map[] = { { "IRA", "ASCII", "ASCII", "ASCII//TRANSLIT", MM_MODEM_CHARSET_IRA }, { "GSM", NULL, NULL, NULL, MM_MODEM_CHARSET_GSM }, { "8859-1", NULL, "ISO8859-1", "ISO8859-1//TRANSLIT", MM_MODEM_CHARSET_8859_1 }, - { "PCCP437", NULL, NULL, NULL, MM_MODEM_CHARSET_PCCP437 }, - { "PCDN", NULL, NULL, NULL, MM_MODEM_CHARSET_PCDN }, + { "PCCP437", "CP437", "CP437", "CP437//TRANSLIT", MM_MODEM_CHARSET_PCCP437 }, + { "PCDN", "CP850", "CP850", "CP850//TRANSLIT", MM_MODEM_CHARSET_PCDN }, { "HEX", NULL, NULL, NULL, MM_MODEM_CHARSET_HEX }, { NULL, NULL, NULL, NULL, MM_MODEM_CHARSET_UNKNOWN } }; @@ -160,7 +161,8 @@ mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset) g_return_val_if_fail (iconv_from != NULL, FALSE); unconverted = utils_hexstr2bin (src, &unconverted_len); - g_return_val_if_fail (unconverted != NULL, NULL); + if (!unconverted) + return NULL; if (charset == MM_MODEM_CHARSET_UTF8 || charset == MM_MODEM_CHARSET_IRA) return unconverted; @@ -425,6 +427,180 @@ mm_charset_utf8_to_unpacked_gsm (const char *utf8, guint32 *out_len) return g_byte_array_free (gsm, FALSE); } +static gboolean +gsm_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + guint8 gsm; + + *out_clen = 1; + if (utf8_to_gsm_def_char (utf8, ulen, &gsm)) + return TRUE; + if (utf8_to_gsm_ext_char (utf8, ulen, &gsm)) { + *out_clen = 2; + return TRUE; + } + return FALSE; +} + +static gboolean +ira_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + *out_clen = 1; + return (ulen == 1); +} + +static gboolean +ucs2_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + *out_clen = 2; + return (c <= 0xFFFF); +} + +static gboolean +iso88591_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + *out_clen = 1; + return (c <= 0xFF); +} + +static gboolean +pccp437_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + static const gunichar t[] = { + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, + 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, + 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, + 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, + 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, + 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, + 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, + 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, + 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, + 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, + 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, + 0x25a0, 0x00a0 + }; + int i; + + *out_clen = 1; + + if (c <= 0x7F) + return TRUE; + for (i = 0; i < sizeof (t) / sizeof (t[0]); i++) { + if (c == t[i]) + return TRUE; + } + return FALSE; +} + +static gboolean +pcdn_is_subset (gunichar c, const char *utf8, gsize ulen, guint *out_clen) +{ + static const gunichar t[] = { + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, + 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, + 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, + 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, + 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, + 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, 0x00f0, + 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, + 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 0x00d3, 0x00df, 0x00d4, + 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, + 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, + 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, + 0x25a0, 0x00a0 + }; + int i; + + *out_clen = 1; + + if (c <= 0x7F) + return TRUE; + for (i = 0; i < sizeof (t) / sizeof (t[0]); i++) { + if (c == t[i]) + return TRUE; + } + return FALSE; +} + +typedef struct { + MMModemCharset cs; + gboolean (*func) (gunichar c, const char *utf8, gsize ulen, guint *out_clen); + guint charsize; +} SubsetEntry; + +SubsetEntry subset_table[] = { + { MM_MODEM_CHARSET_GSM, gsm_is_subset }, + { MM_MODEM_CHARSET_IRA, ira_is_subset }, + { MM_MODEM_CHARSET_UCS2, ucs2_is_subset }, + { MM_MODEM_CHARSET_8859_1, iso88591_is_subset }, + { MM_MODEM_CHARSET_PCCP437, pccp437_is_subset }, + { MM_MODEM_CHARSET_PCDN, pcdn_is_subset }, + { MM_MODEM_CHARSET_UNKNOWN, NULL }, +}; + +/** + * mm_charset_get_encoded_len: + * + * @utf8: UTF-8 valid string + * @charset: the #MMModemCharset to check the length of @utf8 in + * @out_unsupported: on return, number of characters of @utf8 that are not fully + * representable in @charset + * + * Returns: the size in bytes of the string if converted from UTF-8 into @charset. + **/ +guint +mm_charset_get_encoded_len (const char *utf8, + MMModemCharset charset, + guint *out_unsupported) +{ + const char *p = utf8, *next; + guint len = 0, unsupported = 0; + SubsetEntry *e; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, 0); + g_return_val_if_fail (utf8 != NULL, 0); + + if (charset == MM_MODEM_CHARSET_UTF8) + return strlen (utf8); + + /* Find the charset in our subset table */ + for (e = &subset_table[0]; + e->cs != charset && e->cs != MM_MODEM_CHARSET_UNKNOWN; + e++); + g_return_val_if_fail (e->cs != MM_MODEM_CHARSET_UNKNOWN, 0); + + while (*p) { + gunichar c; + const char *end; + guint clen = 0; + + c = g_utf8_get_char_validated (p, -1); + g_return_val_if_fail (c != (gunichar) -1, 0); + end = next = g_utf8_find_next_char (p, NULL); + if (end == NULL) { + /* Find the end... */ + end = p; + while (*end++); + } + + if (!e->func (c, p, (end - p), &clen)) + unsupported++; + len += clen; + p = next; + } + + if (out_unsupported) + *out_unsupported = unsupported; + return len; +} + guint8 * gsm_unpack (const guint8 *gsm, guint32 num_septets, @@ -467,37 +643,127 @@ gsm_pack (const guint8 *src, guint8 start_offset, guint32 *out_packed_len) { - GByteArray *packed; - guint8 c, add_last = 0; - int i; + guint8 *packed; + guint octet = 0, lshift, plen; + int i = 0; - packed = g_byte_array_sized_new (src_len); + g_return_val_if_fail (start_offset < 8, NULL); - for (i = 0, c = 0; i < src_len; i++) { - guint8 bits_here, offset; - guint32 start_bit; + plen = (src_len * 7) + start_offset; /* total length in bits */ + if (plen % 8) + plen += 8; + plen /= 8; /* now in bytes */ - start_bit = start_offset + (i * 7); /* Overall bit offset of char in buffer */ - offset = start_bit % 8; /* Offset to start of char in this byte */ - bits_here = offset ? (8 - offset) : 7; + packed = g_malloc0 (plen); - c |= (src[i] & 0x7F) << offset; - if (offset) { - /* Add this packed byte */ - g_byte_array_append (packed, &c, 1); - c = add_last = 0; + for (i = 0, lshift = start_offset; i < src_len; i++) { + packed[octet] |= (src[i] & 0x7F) << lshift; + if (lshift > 1) { + /* Grab the lost bits and add to next octet */ + g_assert (octet + 1 < plen); + packed[octet + 1] = (src[i] & 0x7F) >> (8 - lshift); } + if (lshift) + octet++; + lshift = lshift ? lshift - 1 : 7; + } - /* Pack the rest of this char into the next byte */ - if (bits_here != 7) { - c = (src[i] & 0x7F) >> bits_here; - add_last = 1; + if (out_packed_len) + *out_packed_len = plen; + return packed; +} + +/* We do all our best to get the given string, which is possibly given in the + * specified charset, to UTF8. It may happen that the given string is really + * the hex representation of the charset-encoded string, so we need to cope with + * that case. */ +gchar * +mm_charset_take_and_convert_to_utf8 (gchar *str, + MMModemCharset charset) +{ + gchar *utf8 = NULL; + + if (!str) + return NULL; + + switch (charset) { + case MM_MODEM_CHARSET_UNKNOWN: + g_warn_if_reached (); + utf8 = str; + break; + + case MM_MODEM_CHARSET_HEX: + /* We'll assume that the HEX string is really valid ASCII at the end */ + utf8 = str; + break; + + case MM_MODEM_CHARSET_GSM: + case MM_MODEM_CHARSET_8859_1: + case MM_MODEM_CHARSET_PCCP437: + case MM_MODEM_CHARSET_PCDN: { + const gchar *iconv_from; + GError *error = NULL; + + iconv_from = charset_iconv_from (charset); + utf8 = g_convert (str, strlen (str), + "UTF-8//TRANSLIT", iconv_from, + NULL, NULL, &error); + if (!utf8 || error) { + g_clear_error (&error); + utf8 = NULL; } + + g_free (str); + break; } - if (add_last) - g_byte_array_append (packed, &c, 1); - *out_packed_len = packed->len; - return g_byte_array_free (packed, FALSE); -} + case MM_MODEM_CHARSET_UCS2: { + gsize len; + gboolean possibly_hex = TRUE; + /* If the string comes in hex-UCS-2, len needs to be a multiple of 4 */ + len = strlen (str); + if ((len < 4) || ((len % 4) != 0)) + possibly_hex = FALSE; + else { + const gchar *p = str; + + /* All chars in the string must be hex */ + while (*p && possibly_hex) + possibly_hex = isxdigit (*p++); + } + + /* If we get UCS-2, we expect the HEX representation of the string */ + if (possibly_hex) { + utf8 = mm_modem_charset_hex_to_utf8 (str, charset); + if (!utf8) { + /* If we couldn't convert the string as HEX-UCS-2, try to see if + * the string is valid UTF-8 itself. */ + utf8 = str; + } else + g_free (str); + } else + /* If we already know it's not hex, try to use the string as it is */ + utf8 = str; + + break; + } + + /* If the given charset is ASCII or UTF8, we really expect the final string + * already here */ + case MM_MODEM_CHARSET_IRA: + case MM_MODEM_CHARSET_UTF8: + utf8 = str; + break; + } + + /* Validate UTF-8 always before returning. This result will be exposed in DBus + * very likely... */ + if (!g_utf8_validate (utf8, -1, NULL)) { + /* Better return NULL than an invalid UTF-8 string */ + g_free (utf8); + utf8 = NULL; + } + + return utf8; +} diff --git a/src/mm-charsets.h b/src/mm-charsets.h index 50b0cce..ff701e5 100644 --- a/src/mm-charsets.h +++ b/src/mm-charsets.h @@ -52,6 +52,11 @@ 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); +/* Returns the size in bytes required to hold the UTF-8 string in the given charset */ +guint mm_charset_get_encoded_len (const char *utf8, + MMModemCharset charset, + guint *out_unsupported); + guint8 *gsm_unpack (const guint8 *gsm, guint32 num_septets, guint8 start_offset, /* in bits */ @@ -62,5 +67,7 @@ guint8 *gsm_pack (const guint8 *src, guint8 start_offset, /* in bits */ guint32 *out_packed_len); -#endif /* MM_CHARSETS_H */ +gchar *mm_charset_take_and_convert_to_utf8 (gchar *str, + MMModemCharset charset); +#endif /* MM_CHARSETS_H */ diff --git a/src/mm-errors.c b/src/mm-errors.c index e4fdda7..841ad0f 100644 --- a/src/mm-errors.c +++ b/src/mm-errors.c @@ -353,3 +353,144 @@ mm_mobile_error_for_string (const char *str) return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg); } +/********************************************************************/ + +GQuark +mm_msg_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_msg_error"); + + return ret; +} + +GType +mm_msg_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (MM_MSG_ERROR_ME_FAILURE, "MeFailure"), + ENUM_ENTRY (MM_MSG_ERROR_SMS_SERVICE_RESERVED, "SmsServiceReserved"), + ENUM_ENTRY (MM_MSG_ERROR_NOT_ALLOWED, "OperationNotAllowed"), + ENUM_ENTRY (MM_MSG_ERROR_NOT_SUPPORTED, "OperationNotSupported"), + ENUM_ENTRY (MM_MSG_ERROR_INVALID_PDU_PARAMETER, "InvalidPduParameter"), + ENUM_ENTRY (MM_MSG_ERROR_INVALID_TEXT_PARAMETER, "InvalidTextParameter"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_NOT_INSERTED, "SimNotInserted"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_PIN, "SimPinRequired"), + ENUM_ENTRY (MM_MSG_ERROR_PH_SIM_PIN, "PhSimPinRequired"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_FAILURE, "SimFailure"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_BUSY, "SimBusy"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_WRONG, "SimWrong"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_PUK, "SimPukRequired"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_PIN2, "SimPin2Required"), + ENUM_ENTRY (MM_MSG_ERROR_SIM_PUK2, "SimPuk2Required"), + ENUM_ENTRY (MM_MSG_ERROR_MEMORY_FAILURE, "MemoryFailure"), + ENUM_ENTRY (MM_MSG_ERROR_INVALID_INDEX, "InvalidIndex"), + ENUM_ENTRY (MM_MSG_ERROR_MEMORY_FULL, "MemoryFull"), + ENUM_ENTRY (MM_MSG_ERROR_SMSC_ADDRESS_UNKNOWN, "SmscAddressUnknown"), + ENUM_ENTRY (MM_MSG_ERROR_NO_NETWORK, "NoNetwork"), + ENUM_ENTRY (MM_MSG_ERROR_NETWORK_TIMEOUT, "NetworkTimeout"), + ENUM_ENTRY (MM_MSG_ERROR_NO_CNMA_ACK_EXPECTED, "NoCnmaAckExpected"), + ENUM_ENTRY (MM_MSG_ERROR_UNKNOWN, "Unknown"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMMsgError", values); + } + + return etype; +} + +static ErrorTable msg_errors[] = { + { MM_MSG_ERROR_ME_FAILURE, "mefailure", "ME failure" }, + { MM_MSG_ERROR_SMS_SERVICE_RESERVED, "smsservicereserved", "SMS service reserved" }, + { MM_MSG_ERROR_NOT_ALLOWED, "operationnotallowed", "Operation not allowed" }, + { MM_MSG_ERROR_NOT_SUPPORTED, "operationnotsupported", "Operation not supported" }, + { MM_MSG_ERROR_INVALID_PDU_PARAMETER, "invalidpduparameter", "Invalid PDU mode parameter" }, + { MM_MSG_ERROR_INVALID_TEXT_PARAMETER, "invalidtextparameter", "Invalid text mode parameter" }, + { MM_MSG_ERROR_SIM_NOT_INSERTED, "simnotinserted", "SIM not inserted" }, + { MM_MSG_ERROR_SIM_PIN, "simpinrequired", "SIM PIN required" }, + { MM_MSG_ERROR_PH_SIM_PIN, "phsimpinrequired", "PH-SIM PIN required" }, + { MM_MSG_ERROR_SIM_FAILURE, "simfailure", "SIM failure" }, + { MM_MSG_ERROR_SIM_BUSY, "simbusy", "SIM busy" }, + { MM_MSG_ERROR_SIM_WRONG, "simwrong", "SIM wrong" }, + { MM_MSG_ERROR_SIM_PUK, "simpukrequired", "SIM PUK required" }, + { MM_MSG_ERROR_SIM_PIN2, "simpin2required", "SIM PIN2 required" }, + { MM_MSG_ERROR_SIM_PUK2, "simpuk2required", "SIM PUK2 required" }, + { MM_MSG_ERROR_MEMORY_FAILURE, "memoryfailure", "Memory failure" }, + { MM_MSG_ERROR_INVALID_INDEX, "invalidindex", "Invalid index" }, + { MM_MSG_ERROR_MEMORY_FULL, "memoryfull", "Memory full" }, + { MM_MSG_ERROR_SMSC_ADDRESS_UNKNOWN, "smscaddressunknown", "SMSC address unknown" }, + { MM_MSG_ERROR_NO_NETWORK, "nonetwork", "No network" }, + { MM_MSG_ERROR_NETWORK_TIMEOUT, "networktimeout", "Network timeout" }, + { MM_MSG_ERROR_NO_CNMA_ACK_EXPECTED, "nocnmaackexpected", "No CNMA acknowledgement expected" }, + { MM_MSG_ERROR_UNKNOWN, "unknown", "Unknown" }, + { -1, NULL, NULL } +}; + +GError * +mm_msg_error_for_code (int error_code) +{ + const char *msg = NULL; + const ErrorTable *ptr = &msg_errors[0]; + + while (ptr->code >= 0) { + if (ptr->code == error_code) { + msg = ptr->message; + break; + } + ptr++; + } + + if (!msg) { + g_warning ("Invalid error code: %d", error_code); + error_code = MM_MSG_ERROR_UNKNOWN; + msg = "Unknown error"; + } + + return g_error_new_literal (MM_MSG_ERROR, error_code, msg); +} + +#define BUF_SIZE 100 + +GError * +mm_msg_error_for_string (const char *str) +{ + int error_code = -1; + const ErrorTable *ptr = &msg_errors[0]; + char buf[BUF_SIZE + 1]; + const char *msg = NULL, *p = str; + int i = 0; + + g_return_val_if_fail (str != NULL, NULL); + + /* Normalize the error code by stripping whitespace and odd characters */ + while (*p && i < BUF_SIZE) { + if (isalnum (*p)) + buf[i++] = tolower (*p); + p++; + } + buf[i] = '\0'; + + while (ptr->code >= 0) { + if (!strcmp (buf, ptr->error)) { + error_code = ptr->code; + msg = ptr->message; + break; + } + ptr++; + } + + if (!msg) { + g_warning ("Invalid error code: %d", error_code); + error_code = MM_MSG_ERROR_UNKNOWN; + msg = "Unknown error"; + } + + return g_error_new_literal (MM_MSG_ERROR, error_code, msg); +} + diff --git a/src/mm-errors.h b/src/mm-errors.h index 13a531d..dd11fdc 100644 --- a/src/mm-errors.h +++ b/src/mm-errors.h @@ -134,4 +134,43 @@ GType mm_mobile_error_get_type (void); GError *mm_mobile_error_for_code (int error_code); GError *mm_mobile_error_for_string (const char *str); + +/* 3GPP TS 27.005 version 10 section 3.2.5 */ +enum { + /* 0 -> 127 per 3GPP TS 24.011 [6] clause E.2 */ + /* 128 -> 255 per 3GPP TS 23.040 [3] clause 9.2.3.22 */ + MM_MSG_ERROR_ME_FAILURE = 300, + MM_MSG_ERROR_SMS_SERVICE_RESERVED = 301, + MM_MSG_ERROR_NOT_ALLOWED = 302, + MM_MSG_ERROR_NOT_SUPPORTED = 303, + MM_MSG_ERROR_INVALID_PDU_PARAMETER = 304, + MM_MSG_ERROR_INVALID_TEXT_PARAMETER = 305, + MM_MSG_ERROR_SIM_NOT_INSERTED = 310, + MM_MSG_ERROR_SIM_PIN = 311, + MM_MSG_ERROR_PH_SIM_PIN = 312, + MM_MSG_ERROR_SIM_FAILURE = 313, + MM_MSG_ERROR_SIM_BUSY = 314, + MM_MSG_ERROR_SIM_WRONG = 315, + MM_MSG_ERROR_SIM_PUK = 316, + MM_MSG_ERROR_SIM_PIN2 = 317, + MM_MSG_ERROR_SIM_PUK2 = 318, + MM_MSG_ERROR_MEMORY_FAILURE = 320, + MM_MSG_ERROR_INVALID_INDEX = 321, + MM_MSG_ERROR_MEMORY_FULL = 322, + MM_MSG_ERROR_SMSC_ADDRESS_UNKNOWN = 330, + MM_MSG_ERROR_NO_NETWORK = 331, + MM_MSG_ERROR_NETWORK_TIMEOUT = 332, + MM_MSG_ERROR_NO_CNMA_ACK_EXPECTED = 340, + MM_MSG_ERROR_UNKNOWN = 500, +}; + + +#define MM_MSG_ERROR (mm_msg_error_quark ()) +#define MM_TYPE_MSG_ERROR (mm_msg_error_get_type ()) + +GQuark mm_msg_error_quark (void); +GType mm_msg_error_get_type (void); +GError *mm_msg_error_for_code (int error_code); +GError *mm_msg_error_for_string (const char *str); + #endif /* MM_MODEM_ERROR_H */ diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c index 11987a2..515cfdd 100644 --- a/src/mm-generic-cdma.c +++ b/src/mm-generic-cdma.c @@ -30,6 +30,7 @@ #include "mm-serial-parsers.h" #include "mm-modem-helpers.h" #include "libqcdm/src/commands.h" +#include "libqcdm/src/errors.h" #include "mm-log.h" #define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state" @@ -988,7 +989,6 @@ get_signal_quality_done (MMAtSerialPort *port, { MMGenericCdmaPrivate *priv; MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *reply = response->str; /* If the modem has already been removed, return without * scheduling callback */ @@ -1008,6 +1008,7 @@ get_signal_quality_done (MMAtSerialPort *port, return; } } else { + const char *reply = response->str; int quality, ber; /* Got valid reply */ @@ -1047,9 +1048,10 @@ qcdm_pilot_sets_cb (MMQcdmSerialPort *port, { MMCallbackInfo *info = user_data; MMGenericCdmaPrivate *priv; - QCDMResult *result; + QcdmResult *result; guint32 num = 0, quality = 0, i; float best_db = -28; + int err = QCDM_SUCCESS; if (error) { info->error = g_error_copy (error); @@ -1059,9 +1061,12 @@ qcdm_pilot_sets_cb (MMQcdmSerialPort *port, priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); /* Parse the response */ - result = qcdm_cmd_pilot_sets_result ((const char *) response->data, response->len, &info->error); - if (!result) + result = qcdm_cmd_pilot_sets_result ((const char *) response->data, response->len, &err); + if (!result) { + g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse pilot sets command result: %d", err); goto done; + } qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num); for (i = 0; i < num; i++) { @@ -1128,7 +1133,7 @@ get_signal_quality (MMModemCdma *modem, /* Use CDMA1x pilot EC/IO if we can */ pilot_sets = g_byte_array_sized_new (25); - pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25, NULL); + pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25); g_assert (pilot_sets->len); mm_qcdm_serial_port_queue_command (priv->qcdm, pilot_sets, 3, qcdm_pilot_sets_cb, info); } @@ -1273,7 +1278,7 @@ serving_system_done (MMAtSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *reply = response->str; + char *reply; int class = 0, sid = 99999, num; unsigned char band = 'Z'; gboolean success = FALSE; @@ -1288,6 +1293,7 @@ serving_system_done (MMAtSerialPort *port, goto out; } + reply = response->str; if (strstr (reply, "+CSS: ")) reply += 6; @@ -1399,19 +1405,23 @@ cdma_status_cb (MMQcdmSerialPort *port, gpointer user_data) { MMCallbackInfo *info = user_data; - QCDMResult *result; + QcdmResult *result; guint32 sid, rxstate; + int err = QCDM_SUCCESS; if (error) goto error; /* Parse the response */ - result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &info->error); - if (!result) + result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &err); + if (!result) { + g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse cdma status command result: %d", err); goto error; + } - qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate); - qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid); + qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate); + qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid); qcdm_result_unref (result); if (rxstate == QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA) @@ -1448,7 +1458,7 @@ get_serving_system (MMModemCdma *modem, GByteArray *cdma_status; cdma_status = g_byte_array_sized_new (25); - cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25, NULL); + cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25); g_assert (cdma_status->len); mm_qcdm_serial_port_queue_command (priv->qcdm, cdma_status, 3, cdma_status_cb, info); } else @@ -1826,7 +1836,7 @@ reg_hdrstate_cb (MMQcdmSerialPort *port, gpointer user_data) { MMCallbackInfo *info = user_data; - QCDMResult *result = NULL; + QcdmResult *result = NULL; guint32 sysmode; MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; @@ -1847,9 +1857,9 @@ reg_hdrstate_cb (MMQcdmSerialPort *port, guint8 almp_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE; guint8 hybrid_mode = 0; - if ( qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session_state) - && qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp_state) - && qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &hybrid_mode)) { + if ( qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session_state) + && qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp_state) + && qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &hybrid_mode)) { /* EVDO state is registered if the HDR subsystem is registered, and * we're in hybrid mode, and the Call Manager system mode is @@ -1918,31 +1928,32 @@ reg_cmstate_cb (MMQcdmSerialPort *port, { MMCallbackInfo *info = user_data; MMAtSerialPort *at_port = NULL; - QCDMResult *result = NULL; + QcdmResult *result = NULL; guint32 opmode = 0, sysmode = 0; - GError *qcdm_error = NULL; + int err = QCDM_SUCCESS; /* Parse the response */ if (!error) - result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &qcdm_error); + result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &err); if (!result) { /* If there was some error, fall back to use +CAD like we did before QCDM */ if (info->modem) at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error); - else - info->error = g_error_copy (qcdm_error); + else { + g_set_error (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse CM subsys state info command result: %d", err); + } if (at_port) mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info); else mm_callback_info_schedule (info); - g_clear_error (&qcdm_error); return; } - qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode); - qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode); + qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode); + qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode); qcdm_result_unref (result); if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) { @@ -1952,7 +1963,7 @@ reg_cmstate_cb (MMQcdmSerialPort *port, /* Get HDR subsystem state */ hdrstate = g_byte_array_sized_new (25); - hdrstate->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) hdrstate->data, 25, NULL); + hdrstate->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) hdrstate->data, 25); g_assert (hdrstate->len); mm_qcdm_serial_port_queue_command (port, hdrstate, 3, reg_hdrstate_cb, info); } else { @@ -1997,7 +2008,7 @@ get_registration_state (MMModemCdma *modem, GByteArray *cmstate; cmstate = g_byte_array_sized_new (25); - cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmstate->data, 25, NULL); + cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmstate->data, 25); g_assert (cmstate->len); mm_qcdm_serial_port_queue_command (priv->qcdm, cmstate, 3, reg_cmstate_cb, info); } else @@ -2142,6 +2153,7 @@ simple_reg_callback (MMModemCdma *modem, /* Fail immediately on anything but "no service" */ if (error && !no_service_error) { simple_state_machine (MM_MODEM (modem), error, info); + g_error_free (error); return; } @@ -2300,7 +2312,7 @@ simple_connect (MMModemSimple *simple, MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "Connection is already in progress"); callback (MM_MODEM (simple), error, user_data); - g_error_free (error); + g_clear_error (&error); return; } @@ -2317,6 +2329,7 @@ simple_connect (MMModemSimple *simple, } simple_state_machine (MM_MODEM (simple), error, info); + g_clear_error (&error); } static void diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index cee1bd6..23378b2 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -20,6 +20,7 @@ #include <stdio.h> #include <string.h> #include <ctype.h> +#include <errno.h> #include "mm-generic-gsm.h" #include "mm-modem-gsm-card.h" @@ -129,6 +130,17 @@ typedef struct { /* SMS */ GHashTable *sms_present; + /* Map from SMS index numbers to parsed PDUs (themselves as hash tables) */ + GHashTable *sms_contents; + /* + * Map from multipart SMS reference numbers to SMSMultiPartMessage + * structures. + */ + GHashTable *sms_parts; + gboolean sms_pdu_mode; + gboolean sms_pdu_supported; + + guint sms_fetch_pending; } MMGenericGsmPrivate; static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info); @@ -146,11 +158,6 @@ static void reg_state_changed (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data); -static void get_reg_status_done (MMAtSerialPort *port, - GString *response, - GError *error, - gpointer user_data); - static gboolean handle_reg_status_response (MMGenericGsm *self, GString *response, GError **error); @@ -334,7 +341,7 @@ pin_check_done (MMAtSerialPort *port, info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Could not parse PIN request response '%s'", - response->str); + response ? response->str : "(unknown)"); } } @@ -1329,6 +1336,242 @@ ciev_received (MMAtSerialPort *port, /* FIXME: handle roaming and service indicators */ } +typedef struct { + /* + * The key index number that refers to this multipart message - + * usually the index number of the first part received. + */ + guint index; + + /* Number of parts in the complete message */ + guint numparts; + + /* Number of parts missing from the message */ + guint missing; + + /* Array of (index numbers of) message parts, in order */ + guint *parts; +} SMSMultiPartMessage; + +static void +sms_cache_insert (MMModem *modem, GHashTable *properties, guint idx) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + GHashTable *old_properties; + GValue *ref; + + ref = g_hash_table_lookup (properties, "concat-reference"); + if (ref != NULL) { + GValue *max, *seq; + guint refnum, maxnum, seqnum; + SMSMultiPartMessage *mpm; + + max = g_hash_table_lookup (properties, "concat-max"); + seq = g_hash_table_lookup (properties, "concat-sequence"); + if (max == NULL || seq == NULL) { + /* Internal error - not all required data present */ + return; + } + + refnum = g_value_get_uint (ref); + maxnum = g_value_get_uint (max); + seqnum = g_value_get_uint (seq); + + if (seqnum > maxnum) { + /* Error - SMS says "part N of M", but N > M */ + return; + } + + mpm = g_hash_table_lookup (priv->sms_parts, GUINT_TO_POINTER (refnum)); + if (mpm == NULL) { + /* Create a new one */ + if (maxnum > 255) + maxnum = 255; + mpm = g_malloc0 (sizeof (*mpm)); + mpm->index = idx; + mpm->numparts = maxnum; + mpm->missing = maxnum; + mpm->parts = g_malloc0 (maxnum * sizeof(*mpm->parts)); + g_hash_table_insert (priv->sms_parts, GUINT_TO_POINTER (refnum), + mpm); + } + + if (maxnum != mpm->numparts) { + /* Error - other messages with this refnum claim a different number of parts */ + return; + } + + if (mpm->parts[seqnum - 1] != 0) { + /* Error - two SMS segments have claimed to be the same part of the same message. */ + return; + } + + mpm->parts[seqnum - 1] = idx; + mpm->missing--; + } + + old_properties = g_hash_table_lookup (priv->sms_contents, GUINT_TO_POINTER (idx)); + if (old_properties != NULL) + g_hash_table_unref (old_properties); + + g_hash_table_insert (priv->sms_contents, GUINT_TO_POINTER (idx), + g_hash_table_ref (properties)); +} + +/* + * Takes a hash table representing a (possibly partial) SMS and + * determines if it is the key part of a complete SMS. The complete + * SMS, if any, is returned. If there is no such SMS (for example, not + * all parts are present yet), NULL is returned. The passed-in hash + * table is dereferenced, and the returned hash table is referenced. + */ +static GHashTable * +sms_cache_lookup_full (MMModem *modem, + GHashTable *properties, + GError **error) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + int i, refnum, indexnum; + SMSMultiPartMessage *mpm; + GHashTable *full, *part, *first; + GHashTableIter iter; + gpointer key, value; + char *fulltext; + char **textparts; + GValue *ref, *idx, *text; + + ref = g_hash_table_lookup (properties, "concat-reference"); + if (ref == NULL) + return properties; + refnum = g_value_get_uint (ref); + + idx = g_hash_table_lookup (properties, "index"); + if (idx == NULL) { + g_hash_table_unref (properties); + return NULL; + } + + indexnum = g_value_get_uint (idx); + g_hash_table_unref (properties); + + mpm = g_hash_table_lookup (priv->sms_parts, + GUINT_TO_POINTER (refnum)); + if (mpm == NULL) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Internal error - no multipart structure for multipart SMS"); + return NULL; + } + + /* Check that this is the key */ + if (indexnum != mpm->index) + return NULL; + + if (mpm->missing != 0) + return NULL; + + /* Complete multipart message is present. Assemble it */ + textparts = g_malloc0((1 + mpm->numparts) * sizeof (*textparts)); + for (i = 0 ; i < mpm->numparts ; i++) { + part = g_hash_table_lookup (priv->sms_contents, + GUINT_TO_POINTER (mpm->parts[i])); + if (part == NULL) { + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Internal error - part %d (index %d) is missing", + i, mpm->parts[i]); + g_free (textparts); + return NULL; + } + text = g_hash_table_lookup (part, "text"); + if (text == NULL) { + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Internal error - part %d (index %d) has no text element", + i, mpm->parts[i]); + g_free (textparts); + return NULL; + } + textparts[i] = g_value_dup_string (text); + } + textparts[i] = NULL; + fulltext = g_strjoinv (NULL, textparts); + g_strfreev (textparts); + + first = g_hash_table_lookup (priv->sms_contents, + GUINT_TO_POINTER (mpm->parts[0])); + full = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + simple_free_gvalue); + g_hash_table_iter_init (&iter, first); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const char *keystr = key; + if (strncmp (keystr, "concat-", 7) == 0) + continue; + if (strcmp (keystr, "text") == 0 || + strcmp (keystr, "index") == 0) + continue; + if (strcmp (keystr, "class") == 0) { + GValue *val; + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_copy (value, val); + g_hash_table_insert (full, key, val); + } else { + GValue *val; + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_copy (value, val); + g_hash_table_insert (full, key, val); + } + } + + g_hash_table_insert (full, "index", simple_uint_value (mpm->index)); + g_hash_table_insert (full, "text", simple_string_value (fulltext)); + g_free (fulltext); + + return full; +} + +static void +cmti_received_has_sms (MMModemGsmSms *modem, + GHashTable *properties, + GError *error, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + guint idx; + gboolean complete; + GValue *ref; + + if (properties == NULL) + return; + + ref = g_hash_table_lookup (properties, "concat-reference"); + if (ref == NULL) { + /* single-part message */ + GValue *idxval = g_hash_table_lookup (properties, "index"); + if (idxval == NULL) + return; + idx = g_value_get_uint (idxval); + complete = TRUE; + } else { + SMSMultiPartMessage *mpm; + mpm = g_hash_table_lookup (priv->sms_parts, + GUINT_TO_POINTER (g_value_get_uint (ref))); + if (mpm == NULL) + return; + idx = mpm->index; + complete = (mpm->missing == 0); + } + + if (complete) + mm_modem_gsm_sms_completed (MM_MODEM_GSM_SMS (self), idx, TRUE); + + mm_modem_gsm_sms_received (MM_MODEM_GSM_SMS (self), idx, complete); +} + +static void sms_get_invoke (MMCallbackInfo *info); +static void sms_get_done (MMAtSerialPort *port, GString *response, + GError *error, gpointer user_data); + static void cmti_received (MMAtSerialPort *port, GMatchInfo *info, @@ -1336,8 +1579,9 @@ cmti_received (MMAtSerialPort *port, { MMGenericGsm *self = MM_GENERIC_GSM (user_data); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *cbinfo; guint idx = 0; - char *str; + char *str, *command; str = g_match_info_fetch (info, 2); if (str) @@ -1351,12 +1595,24 @@ cmti_received (MMAtSerialPort *port, /* Nothing is currently stored in the hash table - presence is all that matters. */ g_hash_table_insert (priv->sms_present, GINT_TO_POINTER (idx), NULL); - /* todo: parse pdu to know if the sms is complete */ - mm_modem_gsm_sms_received (MM_MODEM_GSM_SMS (self), - idx, - TRUE); + /* Retrieve the message */ + cbinfo = mm_callback_info_new_full (MM_MODEM (user_data), + sms_get_invoke, + G_CALLBACK (cmti_received_has_sms), + user_data); + mm_callback_info_set_data (cbinfo, + "complete-sms-only", + GUINT_TO_POINTER (FALSE), + NULL); - /* todo: send mm_modem_gsm_sms_completed if complete */ + if (priv->sms_fetch_pending != 0) { + mm_err("sms_fetch_pending is %d, not 0", priv->sms_fetch_pending); + } + priv->sms_fetch_pending = idx; + + command = g_strdup_printf ("+CMGR=%d", idx); + mm_at_serial_port_queue_command (port, command, 10, sms_get_done, cbinfo); + /* Don't want to signal received here before we have the contents */ } static void @@ -1428,6 +1684,96 @@ cusd_enable_cb (MMAtSerialPort *port, MM_GENERIC_GSM_GET_PRIVATE (user_data)->ussd_enabled = TRUE; } +static void +sms_set_format_cb (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (error) { + mm_warn ("(%s): failed to set SMS mode, assuming text mode", + mm_port_get_device (MM_PORT (port))); + priv->sms_pdu_mode = FALSE; + priv->sms_pdu_supported = FALSE; + } else { + mm_info ("(%s): using %s mode for SMS", + mm_port_get_device (MM_PORT (port)), + priv->sms_pdu_mode ? "PDU" : "text"); + } +} + + +#define CMGF_TAG "+CMGF:" + +static void +sms_get_format_cb (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsm *self; + MMGenericGsmPrivate *priv; + const char *reply; + GRegex *r; + GMatchInfo *match_info; + char *s; + int min = -1, max = -1; + + if (error) { + mm_warn ("(%s): failed to query SMS mode, assuming text mode", + mm_port_get_device (MM_PORT (port))); + return; + } + + /* Strip whitespace and response tag */ + reply = response->str; + if (g_str_has_prefix (reply, CMGF_TAG)) + reply += strlen (CMGF_TAG); + while (isspace (*reply)) + reply++; + + r = g_regex_new ("\\(?\\s*(\\d+)\\s*[-,]?\\s*(\\d+)?\\s*\\)?", 0, 0, NULL); + if (!r) { + mm_warn ("(%s): failed to parse CMGF query result", mm_port_get_device (MM_PORT (port))); + return; + } + + self = MM_GENERIC_GSM (user_data); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) { + s = g_match_info_fetch (match_info, 1); + if (s) + min = atoi (s); + g_free (s); + + s = g_match_info_fetch (match_info, 2); + if (s) + max = atoi (s); + g_free (s); + + /* If the modem only supports PDU mode, use PDUs. + * FIXME: when the PDU code is more robust, default to PDU if the + * modem supports it. + */ + if (min == 0 && max < 1) { + /* Will get reset to FALSE on receipt of error */ + priv->sms_pdu_mode = TRUE; + mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=0", 3, sms_set_format_cb, self); + } else + mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=1", 3, sms_set_format_cb, self); + + /* Save whether PDU mode is supported so we can fall back to it if text fails */ + if (min == 0) + priv->sms_pdu_supported = TRUE; + } + g_match_info_free (match_info); + + g_regex_unref (r); +} + void mm_generic_gsm_enable_complete (MMGenericGsm *self, GError *error, @@ -1472,6 +1818,9 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self, /* Enable SMS notifications */ mm_at_serial_port_queue_command (priv->primary, "+CNMI=2,1,2,1,0", 3, NULL, NULL); + /* Check and enable the right SMS mode */ + mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=?", 3, sms_get_format_cb, self); + /* Enable USSD notifications */ mm_at_serial_port_queue_command (priv->primary, "+CUSD=1", 3, cusd_enable_cb, self); @@ -1524,6 +1873,27 @@ enable_done (MMAtSerialPort *port, } static void +enable_power_up_check_needed_done (MMModem *self, + guint32 needed, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + char *cmd = NULL; + + if (needed) + g_object_get (G_OBJECT (self), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL); + else + mm_dbg ("Power-up not needed, skipping..."); + + if (cmd && strlen (cmd)) + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (priv->primary), cmd, 5, enable_done, user_data); + else + enable_done (MM_AT_SERIAL_PORT (priv->primary), NULL, NULL, user_data); + g_free (cmd); +} + +static void init_done (MMAtSerialPort *port, GString *response, GError *error, @@ -1556,12 +1926,13 @@ init_done (MMAtSerialPort *port, mm_at_serial_port_queue_command (port, cmd, 2, NULL, NULL); g_free (cmd); - g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL); - if (cmd && strlen (cmd)) - mm_at_serial_port_queue_command (port, cmd, 5, enable_done, user_data); + /* Plugins can now check if they need the power up command or not */ + if (MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_check_needed) + MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_check_needed (MM_GENERIC_GSM (info->modem), + enable_power_up_check_needed_done, + info); else - enable_done (port, NULL, NULL, user_data); - g_free (cmd); + enable_power_up_check_needed_done (info->modem, TRUE, NULL, info); } static void @@ -1734,12 +2105,29 @@ disable_flash_done (MMSerialPort *port, } static void +mark_disabled (gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLING, + MM_MODEM_STATE_REASON_NONE); + + if (mm_port_get_connected (MM_PORT (priv->primary))) + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); + else + disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); +} + +static void secondary_unsolicited_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { mm_serial_port_close_force (MM_SERIAL_PORT (port)); + mark_disabled (user_data); } static void @@ -1779,13 +2167,6 @@ disable (MMModem *modem, update_lac_ci (self, 0, 0, 1); _internal_update_access_technology (self, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); - /* Clean up the secondary port if it's open */ - if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) { - mm_at_serial_port_queue_command (priv->secondary, "+CREG=0", 3, NULL, NULL); - mm_at_serial_port_queue_command (priv->secondary, "+CGREG=0", 3, NULL, NULL); - mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, secondary_unsolicited_done, NULL); - } - info = mm_callback_info_new (modem, callback, user_data); /* Cache the previous state so we can reset it if the operation fails */ @@ -1795,14 +2176,14 @@ disable (MMModem *modem, GUINT_TO_POINTER (state), NULL); - mm_modem_set_state (MM_MODEM (info->modem), - MM_MODEM_STATE_DISABLING, - MM_MODEM_STATE_REASON_NONE); - - if (mm_port_get_connected (MM_PORT (priv->primary))) - mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); - else - disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); + /* Clean up the secondary port if it's open */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) { + mm_dbg("Shutting down secondary port"); + mm_at_serial_port_queue_command (priv->secondary, "+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (priv->secondary, "+CGREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, secondary_unsolicited_done, info); + } else + mark_disabled (info); } static void @@ -2470,35 +2851,6 @@ reg_info_updated (MMGenericGsm *self, } } -static void -convert_operator_from_ucs2 (char **operator) -{ - const char *p; - char *converted; - size_t len; - - g_return_if_fail (operator != NULL); - g_return_if_fail (*operator != NULL); - - p = *operator; - len = strlen (p); - - /* Len needs to be a multiple of 4 for UCS2 */ - if ((len < 4) || ((len % 4) != 0)) - return; - - while (*p) { - if (!isxdigit (*p++)) - return; - } - - converted = mm_modem_charset_hex_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2); - if (converted) { - g_free (*operator); - *operator = converted; - } -} - static char * parse_operator (const char *reply, MMModemCharset cur_charset) { @@ -2528,7 +2880,7 @@ parse_operator (const char *reply, MMModemCharset cur_charset) * character set. */ if (cur_charset == MM_MODEM_CHARSET_UCS2) - convert_operator_from_ucs2 (&operator); + operator = mm_charset_take_and_convert_to_utf8 (operator, MM_MODEM_CHARSET_UCS2); /* Ensure the operator name is valid UTF-8 so that we can send it * through D-Bus and such. @@ -2828,7 +3180,7 @@ handle_reg_status_response (MMGenericGsm *self, guint32 status = 0; gulong lac = 0, ci = 0; gint act = -1; - gboolean cgreg = FALSE; + gboolean cgreg = FALSE, parsed; guint i; /* Try to match the response */ @@ -2848,47 +3200,38 @@ handle_reg_status_response (MMGenericGsm *self, } /* And parse it */ - if (!mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error)) { - g_match_info_free (match_info); - return FALSE; - } - - /* Success; update cached location information */ - update_lac_ci (self, lac, ci, cgreg ? 1 : 0); - - /* Only update access technology if it appeared in the CREG/CGREG response */ - if (act != -1) - mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); - - if (status >= 0) { - /* Update cached registration status */ - reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL); + parsed = mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error); + g_match_info_free (match_info); + if (parsed) { + /* Success; update cached location information */ + update_lac_ci (self, lac, ci, cgreg ? 1 : 0); + + /* Only update access technology if it appeared in the CREG/CGREG response */ + if (act != -1) + mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); + + if (status >= 0) { + /* Update cached registration status */ + reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL); + } } - return TRUE; + return parsed; } -#define CGREG_TRIED_TAG "cgreg-tried" +#define CS_ERROR_TAG "cs-error" +#define CS_DONE_TAG "cs-complete" +#define PS_ERROR_TAG "ps-error" +#define PS_DONE_TAG "ps-complete" static void -get_reg_status_done (MMAtSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +check_reg_status_done (MMCallbackInfo *info) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMGenericGsm *self; - MMGenericGsmPrivate *priv; - guint id; + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GError *cs_error, *ps_error; MMModemGsmNetworkRegStatus status; - - /* If the modem has already been removed, return without - * scheduling callback */ - if (mm_callback_info_check_modem_removed (info)) - return; - - self = MM_GENERIC_GSM (info->modem); - priv = MM_GENERIC_GSM_GET_PRIVATE (self); + guint id; /* This function should only get called during the connect sequence when * polling for registration state, since explicit registration requests @@ -2896,46 +3239,38 @@ get_reg_status_done (MMAtSerialPort *port, */ g_return_if_fail (info == priv->pending_reg_info); - if (error) { - gboolean cgreg_tried = !!mm_callback_info_get_data (info, CGREG_TRIED_TAG); - - /* If this was a +CREG error, try +CGREG. Some devices (blackberries) - * respond to +CREG with an error but return a valid +CGREG response. - * So try both. If we get an error from both +CREG and +CGREG, that's - * obviously a hard fail. - */ - if (cgreg_tried == FALSE) { - mm_callback_info_set_data (info, CGREG_TRIED_TAG, GUINT_TO_POINTER (TRUE), NULL); - mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_reg_status_done, info); - return; - } else { - info->error = g_error_copy (error); - goto reg_done; - } - } - - /* The unsolicited registration state handlers will intercept the CREG - * response and update the cached registration state for us, so we usually - * just need to check the cached state here. - */ + /* Only process when both CS and PS checks are both done */ + if ( !mm_callback_info_get_data (info, CS_DONE_TAG) + || !mm_callback_info_get_data (info, PS_DONE_TAG)) + return; - if (strlen (response->str)) { - /* But just in case the unsolicited handlers doesn't do it... */ - if (!handle_reg_status_response (self, response, &info->error)) - goto reg_done; + /* If both CS and PS registration checks returned errors we fail */ + cs_error = mm_callback_info_get_data (info, CS_ERROR_TAG); + ps_error = mm_callback_info_get_data (info, PS_ERROR_TAG); + if (cs_error && ps_error) { + /* Prefer the PS error */ + info->error = g_error_copy (ps_error); + goto reg_done; } status = gsm_reg_status (self, NULL); if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME && status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING && status != MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED) { + + /* Clear state that should be reset every poll */ + mm_callback_info_set_data (info, CS_DONE_TAG, NULL, NULL); + mm_callback_info_set_data (info, CS_ERROR_TAG, NULL, NULL); + mm_callback_info_set_data (info, PS_DONE_TAG, NULL, NULL); + mm_callback_info_set_data (info, PS_ERROR_TAG, NULL, NULL); + /* If we're still waiting for automatic registration to complete or * fail, check again in a few seconds. */ id = g_timeout_add_seconds (1, reg_status_again, info); mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG, - GUINT_TO_POINTER (id), - reg_status_again_remove); + GUINT_TO_POINTER (id), + reg_status_again_remove); return; } @@ -2945,9 +3280,63 @@ reg_done: } static void +generic_reg_status_done (MMCallbackInfo *info, + GString *response, + GError *error, + const char *error_tag, + const char *done_tag) +{ + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); + GError *local = NULL; + + if (error) + local = g_error_copy (error); + else if (strlen (response->str)) { + /* Unsolicited registration status handlers will usually process the + * response for us, but just in case they don't, do that here. + */ + if (handle_reg_status_response (self, response, &local) == TRUE) + g_assert_no_error (local); + } + + if (local) + mm_callback_info_set_data (info, error_tag, local, (GDestroyNotify) g_error_free); + mm_callback_info_set_data (info, done_tag, GUINT_TO_POINTER (1), NULL); +} + +static void +get_ps_reg_status_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (mm_callback_info_check_modem_removed (info) == FALSE) { + generic_reg_status_done (info, response, error, PS_ERROR_TAG, PS_DONE_TAG); + check_reg_status_done (info); + } +} + +static void +get_cs_reg_status_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (mm_callback_info_check_modem_removed (info) == FALSE) { + generic_reg_status_done (info, response, error, CS_ERROR_TAG, CS_DONE_TAG); + check_reg_status_done (info); + } +} + +static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info) { - mm_at_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); + mm_at_serial_port_queue_command (port, "+CREG?", 10, get_cs_reg_status_done, info); + mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_ps_reg_status_done, info); } static void @@ -2976,6 +3365,10 @@ register_done (MMAtSerialPort *port, if (priv->pending_reg_info) { g_warn_if_fail (info == priv->pending_reg_info); + if (error) { + g_clear_error (&info->error); + info->error = g_error_copy (error); + } /* Don't use cached registration state here since it could be up to * 30 seconds old. Get fresh registration state. @@ -3064,9 +3457,9 @@ do_register (MMModemGsmNetwork *modem, * complete. For those devices that delay the +COPS response (hso) the * callback will be called from register_done(). For those devices that * return the +COPS response immediately, we'll poll the registration state - * and call the callback from get_reg_status_done() in response to the - * polled response. The registration timeout will only be triggered when - * the +COPS response is never received. + * and call the callback from get_[cs|ps]_reg_status_done() in response to + * the polled response. The registration timeout will only be triggered + * when the +COPS response is never received. */ mm_callback_info_ref (info); @@ -3547,9 +3940,13 @@ cid_range_read (MMAtSerialPort *port, g_match_info_next (match_info, NULL); } - if (cid == 0) + if (cid == 0) { /* Choose something */ cid = 1; + } + + g_match_info_free (match_info); + g_regex_unref (r); } } else info->error = g_error_new_literal (MM_MODEM_ERROR, @@ -3780,7 +4177,7 @@ get_csq_done (MMAtSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *reply = response->str; + char *reply; gboolean parsed = FALSE; /* If the modem has already been removed, return without @@ -3793,6 +4190,7 @@ get_csq_done (MMAtSerialPort *port, goto done; } + reply = response->str; if (!strncmp (reply, "+CSQ: ", 6)) { /* Got valid reply */ int quality; @@ -4191,7 +4589,22 @@ mm_generic_gsm_get_charset (MMGenericGsm *self) /*****************************************************************************/ /* MMModemGsmSms interface */ +static void +sms_send_invoke (MMCallbackInfo *info) +{ + MMModemGsmSmsSendFn callback = (MMModemGsmSmsSendFn) info->callback; + + callback (MM_MODEM_GSM_SMS (info->modem), + mm_callback_info_get_data (info, "indexes"), + info->error, + info->user_data); +} +static void +free_indexes (gpointer data) +{ + g_array_free ((GArray *) data, TRUE); +} static void sms_send_done (MMAtSerialPort *port, @@ -4200,15 +4613,59 @@ sms_send_done (MMAtSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p, *pdu; + unsigned long num; + GArray *indexes = NULL; + guint32 idx = 0; + guint cmgs_pdu_size; + char *command; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return; - if (error) - info->error = g_error_copy (error); + if (error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + /* If there was an error sending in text mode the retry with the PDU; + * text mode is pretty dumb on most devices and often fails. Later we'll + * just use text mode exclusively. + */ + pdu = mm_callback_info_get_data (info, "pdu"); + if (priv->sms_pdu_mode == FALSE && priv->sms_pdu_supported && pdu) { + cmgs_pdu_size = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cmgs-pdu-size")); + g_assert (cmgs_pdu_size); + command = g_strdup_printf ("+CMGS=%d\r%s\x1a", cmgs_pdu_size, pdu); + mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info); + g_free (command); + + /* Clear the PDU data so we don't keep getting here */ + mm_callback_info_set_data (info, "pdu", NULL, NULL); + return; + } + + /* Otherwise it's a hard error */ + info->error = g_error_copy (error); + } else { + /* If the response happens to have a ">" in it from the interactive + * handling of the CMGS command, skip it. + */ + p = strchr (response->str, '+'); + if (p && *p) { + /* Check for the message index */ + p = mm_strip_tag (p, "+CMGS:"); + if (p && *p) { + errno = 0; + num = strtoul (p, NULL, 10); + if ((num < G_MAXUINT32) && (errno == 0)) + idx = (guint32) num; + } + } + indexes = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 1); + g_array_append_val (indexes, idx); + mm_callback_info_set_data (info, "indexes", indexes, free_indexes); + } mm_callback_info_schedule (info); } @@ -4219,31 +4676,59 @@ sms_send (MMModemGsmSms *modem, const char *smsc, guint validity, guint class, - MMModemFn callback, + MMModemGsmSmsSendFn callback, gpointer user_data) { MMCallbackInfo *info; + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); char *command; MMAtSerialPort *port; + guint8 *pdu; + guint pdulen = 0, msgstart = 0; + char *hex; - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + info = mm_callback_info_new_full (MM_MODEM (modem), + sms_send_invoke, + G_CALLBACK (callback), + user_data); - port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + port = mm_generic_gsm_get_best_at_port (self, &info->error); if (!port) { mm_callback_info_schedule (info); return; } - /* FIXME: use the PDU mode instead */ - mm_at_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL); + /* Always create a PDU since we might need it for fallback from text mode */ + pdu = sms_create_submit_pdu (number, text, smsc, validity, class, &pdulen, &msgstart, &info->error); + if (!pdu) { + mm_callback_info_schedule (info); + return; + } + + hex = utils_bin2hexstr (pdu, pdulen); + g_free (pdu); + if (hex == NULL) { + g_set_error_literal (&info->error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Not enough memory to send SMS PDU"); + mm_callback_info_schedule (info); + return; + } + + mm_callback_info_set_data (info, "pdu", hex, g_free); + mm_callback_info_set_data (info, "cmgs-pdu-size", GUINT_TO_POINTER (pdulen - msgstart), NULL); + if (priv->sms_pdu_mode) { + /* CMGS length is the size of the PDU without SMSC information */ + command = g_strdup_printf ("+CMGS=%d\r%s\x1a", pdulen - msgstart, hex); + } else + command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text); - command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text); mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info); g_free (command); } - - static void sms_get_done (MMAtSerialPort *port, GString *response, @@ -4251,9 +4736,15 @@ sms_get_done (MMAtSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); GHashTable *properties; - int rv, status, tpdu_len, offset; + int rv, status, tpdu_len; + guint idx; char pdu[SMS_MAX_PDU_LEN + 1]; + gboolean look_for_complete; + + idx = priv->sms_fetch_pending; + priv->sms_fetch_pending = 0; /* If the modem has already been removed, return without * scheduling callback */ @@ -4266,9 +4757,9 @@ sms_get_done (MMAtSerialPort *port, } /* 344 == SMS_MAX_PDU_LEN */ - rv = sscanf (response->str, "+CMGR: %d,,%d %344s %n", - &status, &tpdu_len, pdu, &offset); - if (rv != 4) { + rv = sscanf (response->str, "+CMGR: %d,,%d %344s", + &status, &tpdu_len, pdu); + if (rv != 3) { info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Failed to parse CMGR response (parsed %d items)", @@ -4281,8 +4772,24 @@ sms_get_done (MMAtSerialPort *port, goto out; } - mm_callback_info_set_data (info, GS_HASH_TAG, properties, - (GDestroyNotify) g_hash_table_unref); + g_hash_table_insert (properties, "index", simple_uint_value (idx)); + sms_cache_insert (info->modem, properties, idx); + + look_for_complete = GPOINTER_TO_UINT (mm_callback_info_get_data(info, + "complete-sms-only")); + + if (look_for_complete == TRUE) { + /* + * If this is a standalone message, or the key part of a + * multipart message, pass it along, otherwise report that there's + * no such message. + */ + properties = sms_cache_lookup_full (info->modem, properties, + &info->error); + } + if (properties) + mm_callback_info_set_data (info, GS_HASH_TAG, properties, + (GDestroyNotify) g_hash_table_unref); out: mm_callback_info_schedule (info); @@ -4307,11 +4814,35 @@ sms_get (MMModemGsmSms *modem, MMCallbackInfo *info; char *command; MMAtSerialPort *port; + MMGenericGsmPrivate *priv = + MM_GENERIC_GSM_GET_PRIVATE (MM_GENERIC_GSM (modem)); + GHashTable *properties; + GError *error = NULL; + + properties = g_hash_table_lookup (priv->sms_contents, GUINT_TO_POINTER (idx)); + if (properties != NULL) { + g_hash_table_ref (properties); + properties = sms_cache_lookup_full (MM_MODEM (modem), properties, &error); + if (properties == NULL) { + error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "No SMS found"); + } + callback (modem, properties, error, user_data); + if (properties != NULL) + g_hash_table_unref (properties); + g_error_free (error); + return; + } info = mm_callback_info_new_full (MM_MODEM (modem), sms_get_invoke, G_CALLBACK (callback), user_data); + mm_callback_info_set_data (info, + "complete-sms-only", + GUINT_TO_POINTER (TRUE), + NULL); port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); if (!port) { @@ -4319,10 +4850,18 @@ sms_get (MMModemGsmSms *modem, return; } - command = g_strdup_printf ("+CMGR=%d\r\n", idx); + command = g_strdup_printf ("+CMGR=%d", idx); + priv->sms_fetch_pending = idx; mm_at_serial_port_queue_command (port, command, 10, sms_get_done, info); } +typedef struct { + MMGenericGsmPrivate *priv; + MMCallbackInfo *info; + SMSMultiPartMessage *mpm; + int deleting; +} SMSDeleteProgress; + static void sms_delete_done (MMAtSerialPort *port, GString *response, @@ -4343,6 +4882,50 @@ sms_delete_done (MMAtSerialPort *port, } static void +sms_delete_multi_next (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + SMSDeleteProgress *progress = (SMSDeleteProgress *)user_data; + + /* If the modem has already been removed, return without + * scheduling callback */ + if (mm_callback_info_check_modem_removed (progress->info)) + goto done; + + if (error) + progress->info->error = g_error_copy (error); + + for (progress->deleting++ ; + progress->deleting < progress->mpm->numparts ; + progress->deleting++) + if (progress->mpm->parts[progress->deleting] != 0) + break; + if (progress->deleting < progress->mpm->numparts) { + GHashTable *properties; + char *command; + guint idx; + + idx = progress->mpm->parts[progress->deleting]; + command = g_strdup_printf ("+CMGD=%d", idx); + mm_at_serial_port_queue_command (port, command, 10, + sms_delete_multi_next, progress); + properties = g_hash_table_lookup (progress->priv->sms_contents, GUINT_TO_POINTER (idx)); + g_hash_table_remove (progress->priv->sms_contents, GUINT_TO_POINTER (idx)); + g_hash_table_remove (progress->priv->sms_present, GUINT_TO_POINTER (idx)); + g_hash_table_unref (properties); + return; + } + + mm_callback_info_schedule (progress->info); +done: + g_free (progress->mpm->parts); + g_free (progress->mpm); + g_free (progress); +} + +static void sms_delete (MMModemGsmSms *modem, guint idx, MMModemFn callback, @@ -4352,18 +4935,262 @@ sms_delete (MMModemGsmSms *modem, char *command; MMAtSerialPort *port; MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (MM_GENERIC_GSM (modem)); + GHashTable *properties; + MMAtSerialResponseFn next_callback; + GValue *ref; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - g_hash_table_remove (priv->sms_present, GINT_TO_POINTER (idx)); + + properties = g_hash_table_lookup (priv->sms_contents, GINT_TO_POINTER (idx)); + if (properties == NULL) { + /* + * TODO(njw): This assumes our cache is valid. If we doubt this, we should just + * run the delete anyway and let that return the nonexistent-message error. + */ + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "No SMS to delete"); + mm_callback_info_schedule (info); + return; + } port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); if (!port) { mm_callback_info_schedule (info); + g_hash_table_remove (priv->sms_contents, GINT_TO_POINTER (idx)); + g_hash_table_unref (properties); return; } - command = g_strdup_printf ("+CMGD=%d\r\n", idx); - mm_at_serial_port_queue_command (port, command, 10, sms_delete_done, info); + user_data = info; + next_callback = sms_delete_done; + ref = g_hash_table_lookup (properties, "concat-reference"); + if (ref != NULL) { + SMSMultiPartMessage *mpm; + SMSDeleteProgress *progress; + guint refnum; + + refnum = g_value_get_uint (ref); + mpm = g_hash_table_lookup (priv->sms_parts, GUINT_TO_POINTER (refnum)); + if (mpm == NULL) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Internal error - no part array for multipart SMS"); + mm_callback_info_schedule (info); + g_hash_table_remove (priv->sms_contents, GINT_TO_POINTER (idx)); + g_hash_table_unref (properties); + return; + } + /* Only allow the delete operation on the main index number. */ + if (idx != mpm->index) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "No SMS to delete"); + mm_callback_info_schedule (info); + return; + } + + g_hash_table_remove (priv->sms_parts, GUINT_TO_POINTER (refnum)); + progress = g_malloc0 (sizeof(*progress)); + progress->priv = priv; + progress->info = info; + progress->mpm = mpm; + for (progress->deleting = 0 ; + progress->deleting < mpm->numparts ; + progress->deleting++) + if (mpm->parts[progress->deleting] != 0) + break; + user_data = progress; + next_callback = sms_delete_multi_next; + idx = progress->mpm->parts[progress->deleting]; + properties = g_hash_table_lookup (priv->sms_contents, GINT_TO_POINTER (idx)); + } + g_hash_table_remove (priv->sms_contents, GUINT_TO_POINTER (idx)); + g_hash_table_remove (priv->sms_present, GUINT_TO_POINTER (idx)); + g_hash_table_unref (properties); + + command = g_strdup_printf ("+CMGD=%d", idx); + mm_at_serial_port_queue_command (port, command, 10, next_callback, + user_data); +} + +static gboolean +pdu_parse_cmgl (MMGenericGsm *self, const char *response, GError **error) +{ + int rv, status, tpdu_len, offset; + GHashTable *properties; + + while (*response) { + int idx; + char pdu[SMS_MAX_PDU_LEN + 1]; + + rv = sscanf (response, "+CMGL: %d,%d,,%d %344s %n", + &idx, &status, &tpdu_len, pdu, &offset); + if (4 != rv) { + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse CMGL response: expected 4 results got %d", rv); + mm_err("Couldn't parse response to SMS LIST (%d)", rv); + return FALSE; + } + response += offset; + + properties = sms_parse_pdu (pdu, NULL); + if (properties) { + g_hash_table_insert (properties, "index", simple_uint_value (idx)); + sms_cache_insert (MM_MODEM (self), properties, idx); + /* The cache holds a reference, so we don't need it anymore */ + g_hash_table_unref (properties); + } + } + + return TRUE; +} + +static gboolean +get_match_uint (GMatchInfo *m, guint match_index, guint *out_val) +{ + char *s; + unsigned long num; + + g_return_val_if_fail (out_val != NULL, FALSE); + + s = g_match_info_fetch (m, match_index); + g_return_val_if_fail (s != NULL, FALSE); + + errno = 0; + num = strtoul (s, NULL, 10); + g_free (s); + + if (num <= 1000 && errno == 0) { + *out_val = (guint) num; + return TRUE; + } + return FALSE; +} + +static char * +get_match_string_unquoted (GMatchInfo *m, guint match_index) +{ + char *s, *p, *q, *ret = NULL; + + q = s = g_match_info_fetch (m, match_index); + g_return_val_if_fail (s != NULL, FALSE); + + /* remove quotes */ + if (*q == '"') + q++; + p = strchr (q, '"'); + if (p) + *p = '\0'; + if (*q) + ret = g_strdup (q); + g_free (s); + return ret; +} + +static gboolean +text_parse_cmgl (MMGenericGsm *self, const char *response, GError **error) +{ + MMGenericGsmPrivate *priv; + GRegex *r; + GMatchInfo *match_info = NULL; + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + /* +CMGL: <index>,<stat>,<oa/da>,[alpha],<scts><CR><LF><data><CR><LF> */ + r = g_regex_new ("\\+CMGL:\\s*(\\d+)\\s*,\\s*([^,]*),\\s*([^,]*),\\s*([^,]*),\\s*([^\\r\\n]*)\\r\\n([^\\r\\n]*)", 0, 0, NULL); + g_assert (r); + + if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse CMGL response"); + mm_err("Couldn't parse response to SMS LIST"); + g_regex_unref (r); + return FALSE; + } + + while (g_match_info_matches (match_info)) { + GHashTable *properties; + guint matches, idx; + char *number = NULL, *timestamp, *text, *ucs2_text; + gsize ucs2_len = 0; + GByteArray *data; + + matches = g_match_info_get_match_count (match_info); + if (matches != 7) { + mm_dbg ("Failed to match entire CMGL response (count %d)", matches); + goto next; + } + + if (!get_match_uint (match_info, 1, &idx)) { + mm_dbg ("Failed to convert message index"); + goto next; + } + + /* <stat is ignored for now> */ + + /* Get and parse number */ + number = get_match_string_unquoted (match_info, 3); + if (!number) { + mm_dbg ("Failed to get message sender number"); + goto next; + } + number = mm_charset_take_and_convert_to_utf8 (number, + priv->cur_charset); + + /* Get and parse timestamp (always expected in ASCII) */ + timestamp = get_match_string_unquoted (match_info, 5); + + /* Get and parse text */ + text = mm_charset_take_and_convert_to_utf8 (g_match_info_fetch (match_info, 6), + priv->cur_charset); + + /* The raw SMS data can only be GSM, UCS2, or unknown (8-bit), so we + * need to convert to UCS2 here. + */ + ucs2_text = g_convert (text, -1, "UCS-2BE//TRANSLIT", "UTF-8", NULL, &ucs2_len, NULL); + g_assert (ucs2_text); + data = g_byte_array_sized_new (ucs2_len); + g_byte_array_append (data, (const guint8 *) ucs2_text, ucs2_len); + g_free (ucs2_text); + + properties = sms_properties_hash_new (NULL, + number, + timestamp, + text, + data, + 2, /* DCS = UCS2 */ + 0); /* class */ + g_assert (properties); + + g_free (number); + g_free (timestamp); + g_free (text); + g_byte_array_free (data, TRUE); + + g_hash_table_insert (properties, "index", simple_uint_value (idx)); + sms_cache_insert (MM_MODEM (self), properties, idx); + /* The cache holds a reference, so we don't need it anymore */ + g_hash_table_unref (properties); + +next: + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + + g_regex_unref (r); + return TRUE; +} + +static void +free_list_results (gpointer data) +{ + GPtrArray *results = (GPtrArray *) data; + + g_ptr_array_foreach (results, (GFunc) g_hash_table_unref, NULL); + g_ptr_array_free (results, TRUE); } static void @@ -4373,54 +5200,44 @@ sms_list_done (MMAtSerialPort *port, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GHashTableIter iter; + GHashTable *properties = NULL; GPtrArray *results = NULL; - int rv, status, tpdu_len, offset; - char *rstr; + gboolean success; /* If the modem has already been removed, return without * scheduling callback */ if (mm_callback_info_check_modem_removed (info)) return; - if (error) + if (error) { info->error = g_error_copy (error); - else { + mm_callback_info_schedule (info); + return; + } + + if (priv->sms_pdu_mode) + success = pdu_parse_cmgl (self, response->str, &info->error); + else + success = text_parse_cmgl (self, response->str, &info->error); + + if (success) { results = g_ptr_array_new (); - rstr = response->str; - - while (*rstr) { - GHashTable *properties; - GError *local; - 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, &local); - if (properties) { - g_hash_table_insert (properties, "index", - simple_uint_value (idx)); + /* Add all the complete messages to the results */ + g_hash_table_iter_init (&iter, priv->sms_contents); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &properties)) { + g_hash_table_ref (properties); + g_clear_error (&info->error); + properties = sms_cache_lookup_full (info->modem, properties, NULL); + if (properties) g_ptr_array_add (results, properties); - } else { - /* Ignore the error */ - g_clear_error(&local); - } } - /* - * 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_set_data (info, "list-sms", results, free_list_results); } mm_callback_info_schedule (info); @@ -4456,7 +5273,10 @@ sms_list (MMModemGsmSms *modem, return; } - command = g_strdup_printf ("+CMGL=4\r\n"); + if (MM_GENERIC_GSM_GET_PRIVATE (modem)->sms_pdu_mode) + command = g_strdup_printf ("+CMGL=4"); + else + command = g_strdup_printf ("+CMGL=\"ALL\""); mm_at_serial_port_queue_command (port, command, 10, sms_list_done, info); } @@ -4544,6 +5364,7 @@ decode_ussd_response (MMGenericGsm *self, char **items, **iter, *p; char *str = NULL; gint encoding = -1; + char *decoded; /* Look for the first ',' */ p = strchr (reply, ','); @@ -4563,6 +5384,9 @@ decode_ussd_response (MMGenericGsm *self, } } + if (!str) + return NULL; + /* Strip quotes */ if (str[0] == '"') str++; @@ -4570,8 +5394,9 @@ decode_ussd_response (MMGenericGsm *self, if (p) *p = '\0'; - return mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str, - cur_charset); + decoded = mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str, cur_charset); + g_strfreev (items); + return decoded; } static char* @@ -4708,7 +5533,7 @@ ussd_send_done (MMAtSerialPort *port, ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE); } - /* Otherwise if no error wait for the response to show up via the + /* Otherwise if no error wait for the response to show up via the * unsolicited response code. */ } @@ -5150,10 +5975,20 @@ simple_status_got_signal_quality (MMModem *modem, { MMCallbackInfo *info = (MMCallbackInfo *) user_data; GHashTable *properties; + gboolean error_no_network = FALSE; - if (!error) { + /* Treat "no network" as zero strength */ + if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_NO_NETWORK)) { + error_no_network = TRUE; + result = 0; + } + + if (!error || error_no_network) { properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); g_hash_table_insert (properties, "signal_quality", simple_uint_value (result)); + } else { + g_clear_error (&info->error); + info->error = g_error_copy (error); } mm_callback_info_chain_complete_one (info); } @@ -5170,6 +6005,9 @@ simple_status_got_band (MMModem *modem, if (!error) { properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); g_hash_table_insert (properties, "band", simple_uint_value (result)); + } else { + g_clear_error (&info->error); + info->error = g_error_copy (error); } mm_callback_info_chain_complete_one (info); } @@ -5189,9 +6027,10 @@ simple_status_got_reg_info (MMModemGsmNetwork *modem, if (!modem || mm_callback_info_check_modem_removed (info)) return; - if (error) + if (error) { + g_clear_error (&info->error); info->error = g_error_copy (error); - else { + } else { properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); g_hash_table_insert (properties, "registration_status", simple_uint_value (status)); @@ -5503,6 +6342,8 @@ mm_generic_gsm_init (MMGenericGsm *self) priv->reg_regex = mm_gsm_creg_regex_get (TRUE); priv->roam_allowed = TRUE; priv->sms_present = g_hash_table_new (g_direct_hash, g_direct_equal); + priv->sms_contents = g_hash_table_new (g_direct_hash, g_direct_equal); + priv->sms_parts = g_hash_table_new (g_direct_hash, g_direct_equal); mm_properties_changed_signal_register_property (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ALLOWED_MODE, @@ -5722,6 +6563,8 @@ finalize (GObject *object) g_free (priv->oper_name); g_free (priv->simid); g_hash_table_destroy (priv->sms_present); + g_hash_table_destroy (priv->sms_contents); + g_hash_table_destroy (priv->sms_parts); G_OBJECT_CLASS (mm_generic_gsm_parent_class)->finalize (object); } @@ -5841,4 +6684,3 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) "+IFC=1,1", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } - diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h index 4238628..8f6d587 100644 --- a/src/mm-generic-gsm.h +++ b/src/mm-generic-gsm.h @@ -84,7 +84,15 @@ typedef struct { * encountered during the process and the MMCallbackInfo created from the * callback and user_data passed in here. */ - void (*do_enable) (MMGenericGsm *self, MMModemFn callback, gpointer user_data); + void (*do_enable) (MMGenericGsm *self, + MMModemFn callback, + gpointer user_data); + + /* Called before issuing the power-up command, to check whether it should + * really be issued or not. */ + void (*do_enable_power_up_check_needed) (MMGenericGsm *self, + MMModemUIntFn callback, + gpointer user_data); /* Called after the generic class has attempted to power up the modem. * Subclasses can handle errors here if they know the device supports their @@ -137,6 +145,7 @@ typedef struct { void (*get_sim_iccid) (MMGenericGsm *self, MMModemStringFn callback, gpointer user_data); + } MMGenericGsmClass; GType mm_generic_gsm_get_type (void); diff --git a/src/mm-manager.c b/src/mm-manager.c index 1e9403c..41bed53 100644 --- a/src/mm-manager.c +++ b/src/mm-manager.c @@ -1101,5 +1101,6 @@ mm_manager_class_init (MMManagerClass *manager_class) dbus_g_error_domain_register (MM_MODEM_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_ERROR); dbus_g_error_domain_register (MM_MODEM_CONNECT_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_CONNECT_ERROR); dbus_g_error_domain_register (MM_MOBILE_ERROR, "org.freedesktop.ModemManager.Modem.Gsm", MM_TYPE_MOBILE_ERROR); + dbus_g_error_domain_register (MM_MSG_ERROR, "org.freedesktop.ModemManager.Modem.Gsm.SMS", MM_TYPE_MSG_ERROR); } diff --git a/src/mm-manager.h b/src/mm-manager.h index 1c98458..c6a64bd 100644 --- a/src/mm-manager.h +++ b/src/mm-manager.h @@ -17,7 +17,6 @@ #ifndef MM_MANAGER_H #define MM_MANAGER_H -#include <glib/gtypes.h> #include <glib-object.h> #include <dbus/dbus-glib.h> #include "mm-modem.h" diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c index 451e2eb..740dc13 100644 --- a/src/mm-modem-base.c +++ b/src/mm-modem-base.c @@ -480,9 +480,10 @@ info_item_done (MMCallbackInfo *info, const char *tag2, const char *desc) { - const char *p = response->str; + const char *p; if (!error) { + p = response->str; if (tag) p = mm_strip_tag (p, tag); if (tag2) diff --git a/src/mm-modem-base.h b/src/mm-modem-base.h index 0409957..d0cda3d 100644 --- a/src/mm-modem-base.h +++ b/src/mm-modem-base.h @@ -18,7 +18,6 @@ #define MM_MODEM_BASE_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> #include "mm-port.h" diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c index 485e5f2..7583760 100644 --- a/src/mm-modem-cdma.c +++ b/src/mm-modem-cdma.c @@ -131,6 +131,8 @@ serving_system_call_done (MMModemCdma *self, g_value_unset (&value); dbus_g_method_return (context, array); + + g_value_array_free (array); } } diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c index c152ddf..6748b4e 100644 --- a/src/mm-modem-gsm-network.c +++ b/src/mm-modem-gsm-network.c @@ -202,6 +202,8 @@ reg_info_call_done (MMModemGsmNetwork *self, g_value_unset (&value); dbus_g_method_return (context, array); + + g_value_array_free (array); } } diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c index ab20d3e..22c42d0 100644 --- a/src/mm-modem-gsm-sms.c +++ b/src/mm-modem-gsm-sms.c @@ -133,6 +133,14 @@ sms_list_done (MMModemGsmSms *self, /*****************************************************************************/ +static void +sms_send_invoke (MMCallbackInfo *info) +{ + MMModemGsmSmsSendFn callback = (MMModemGsmSmsSendFn) info->callback; + + callback (MM_MODEM_GSM_SMS (info->modem), NULL, info->error, info->user_data); +} + void mm_modem_gsm_sms_send (MMModemGsmSms *self, const char *number, @@ -140,7 +148,7 @@ mm_modem_gsm_sms_send (MMModemGsmSms *self, const char *smsc, guint validity, guint class, - MMModemFn callback, + MMModemGsmSmsSendFn callback, gpointer user_data) { g_return_if_fail (MM_IS_MODEM_GSM_SMS (self)); @@ -150,9 +158,18 @@ mm_modem_gsm_sms_send (MMModemGsmSms *self, if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send) MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send (self, number, text, smsc, validity, class, callback, user_data); - else - async_call_not_supported (self, callback, user_data); + else { + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), + sms_send_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); + } } static void @@ -576,6 +593,20 @@ impl_gsm_modem_sms_save (MMModemGsmSms *modem, /*****************************************************************************/ static void +send_sms_call_done (MMModemGsmSms *modem, + GArray *indexes, + 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, indexes); +} + +static void sms_send_auth_cb (MMAuthRequest *req, GObject *owner, DBusGMethodInvocation *context, @@ -607,7 +638,7 @@ sms_send_auth_cb (MMAuthRequest *req, if (value) smsc = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (info->hash, "validity"); + value = (GValue *) g_hash_table_lookup (info->hash, "relative-validity"); if (value) validity = g_value_get_uint (value); @@ -625,10 +656,10 @@ sms_send_auth_cb (MMAuthRequest *req, done: if (error) { - async_call_done (MM_MODEM (self), error, context); + send_sms_call_done (self, NULL, error, context); g_error_free (error); } else - mm_modem_gsm_sms_send (self, number, text, smsc, validity, class, async_call_done, context); + mm_modem_gsm_sms_send (self, number, text, smsc, validity, class, send_sms_call_done, context); } static void diff --git a/src/mm-modem-gsm-sms.h b/src/mm-modem-gsm-sms.h index 41684d7..11a1024 100644 --- a/src/mm-modem-gsm-sms.h +++ b/src/mm-modem-gsm-sms.h @@ -35,6 +35,11 @@ typedef void (*MMModemGsmSmsListFn) (MMModemGsmSms *modem, GError *error, gpointer user_data); +typedef void (*MMModemGsmSmsSendFn) (MMModemGsmSms *modem, + GArray *indexes, + GError *error, + gpointer user_data); + struct _MMModemGsmSms { GTypeInterface g_iface; @@ -45,7 +50,7 @@ struct _MMModemGsmSms { const char *smsc, guint validity, guint class, - MMModemFn callback, + MMModemGsmSmsSendFn callback, gpointer user_data); void (*get) (MMModemGsmSms *modem, @@ -80,7 +85,7 @@ void mm_modem_gsm_sms_send (MMModemGsmSms *self, const char *smsc, guint validity, guint class, - MMModemFn callback, + MMModemGsmSmsSendFn callback, gpointer user_data); void mm_modem_gsm_sms_get (MMModemGsmSms *self, diff --git a/src/mm-modem-gsm-ussd.h b/src/mm-modem-gsm-ussd.h index 04d2be8..f46be97 100644 --- a/src/mm-modem-gsm-ussd.h +++ b/src/mm-modem-gsm-ussd.h @@ -81,8 +81,8 @@ void mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self, 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; +#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, diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index f6a0ffa..5291ed5 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 - 2010 Red Hat, Inc. + * Copyright (C) 2009 - 2011 Red Hat, Inc. */ #include <config.h> @@ -112,9 +112,9 @@ mm_gsm_parse_scan_response (const char *reply, GError **error) * +COPS: (2,"","T-Mobile","31026",0),(1,"AT&T","AT&T","310410"),0) */ - r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^,\\)]*)[\\)]?,(\\d)\\)", G_REGEX_UNGREEDY, 0, NULL); + r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^,\\)]*)[\\)]?,(\\d)\\)", G_REGEX_UNGREEDY, 0, &err); if (err) { - g_error ("Invalid regular expression: %s", err->message); + mm_err ("Invalid regular expression: %s", err->message); g_error_free (err); g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, @@ -125,10 +125,8 @@ mm_gsm_parse_scan_response (const char *reply, GError **error) /* If we didn't get any hits, try the pre-UMTS format match */ if (!g_regex_match (r, reply, 0, &match_info)) { g_regex_unref (r); - if (match_info) { - g_match_info_free (match_info); - match_info = NULL; - } + g_match_info_free (match_info); + match_info = NULL; /* Pre-UMTS format doesn't include the cell access technology after * the numeric operator element. @@ -143,9 +141,9 @@ mm_gsm_parse_scan_response (const char *reply, GError **error) * +COPS: (2,"T - Mobile",,"31026"),(1,"Einstein PCS",,"31064"),(1,"Cingular",,"31041"),,(0,1,3),(0,2) */ - r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, NULL); + r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, &err); if (err) { - g_error ("Invalid regular expression: %s", err->message); + mm_err ("Invalid regular expression: %s", err->message); g_error_free (err); g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, @@ -247,6 +245,9 @@ mm_gsm_destroy_scan_data (gpointer data) /* '<CR><LF>+CREG: 2,1,000B,2816, B, C2816<CR><LF><CR><LF>OK<CR><LF>' */ #define CREG7 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*[^,\\s]*" +/* +CREG: <stat>,<lac>,<ci>,<AcT>,<RAC> (ETSI 27.007 v9.20 CREG=2 unsolicited with RAC) */ +#define CREG8 "\\+(CREG|CGREG):\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})\\s*,\\s*([^,\\s]*)" + GPtrArray * mm_gsm_creg_regex_get (gboolean solicited) { @@ -309,6 +310,14 @@ mm_gsm_creg_regex_get (gboolean solicited) g_assert (regex); g_ptr_array_add (array, regex); + /* #8 */ + if (solicited) + regex = g_regex_new (CREG8 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG8 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + return array; } @@ -346,6 +355,20 @@ parse_uint (char *str, int base, glong nmin, glong nmax, gboolean *valid) return *valid ? (guint) ret : 0; } +static gboolean +item_is_lac_not_stat (GMatchInfo *info, guint32 item) +{ + char *str; + gboolean is_lac = FALSE; + + /* A <stat> will always be a single digit, without quotes */ + str = g_match_info_fetch (info, item); + g_assert (str); + is_lac = (strchr (str, '"') || strlen (str) > 1); + g_free (str); + return is_lac; +} + gboolean mm_gsm_parse_creg_response (GMatchInfo *info, guint32 *out_reg_state, @@ -371,6 +394,7 @@ mm_gsm_parse_creg_response (GMatchInfo *info, str = g_match_info_fetch (info, 1); if (str && strstr (str, "CGREG")) *out_cgreg = TRUE; + g_free (str); /* Normally the number of matches could be used to determine what each * item is, but we have overlap in one case. @@ -392,13 +416,8 @@ mm_gsm_parse_creg_response (GMatchInfo *info, * CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci> */ - /* To distinguish, check length of the third match item. If it's - * more than one digit or has quotes in it then it's a LAC and we - * got the first format. - */ - str = g_match_info_fetch (info, 3); - if (str && (strchr (str, '"') || strlen (str) > 1)) { - g_free (str); + /* Check if the third item is the LAC to distinguish the two cases */ + if (item_is_lac_not_stat (info, 3)) { istat = 2; ilac = 3; ici = 4; @@ -409,12 +428,23 @@ mm_gsm_parse_creg_response (GMatchInfo *info, ici = 5; } } else if (n_matches == 7) { - /* CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci>,<AcT> */ - istat = 3; - ilac = 4; - ici = 5; - iact = 6; - } + /* CREG=2 (solicited): +CREG: <n>,<stat>,<lac>,<ci>,<AcT> + * CREG=2 (unsolicited with RAC): +CREG: <stat>,<lac>,<ci>,<AcT>,<RAC> + */ + + /* Check if the third item is the LAC to distinguish the two cases */ + if (item_is_lac_not_stat (info, 3)) { + istat = 2; + ilac = 3; + ici = 4; + iact = 5; + } else { + istat = 3; + ilac = 4; + ici = 5; + iact = 6; + } + } /* Status */ str = g_match_info_fetch (info, istat); @@ -778,8 +808,8 @@ mm_gsm_parse_cscs_support_response (const char *reply, g_match_info_next (match_info, NULL); success = TRUE; } - g_match_info_free (match_info); } + g_match_info_free (match_info); g_regex_unref (r); if (success) @@ -855,8 +885,10 @@ mm_create_device_identifier (guint vid, if (manf) g_string_append (devid, manf); - if (!strlen (devid->str)) + if (!strlen (devid->str)) { + g_string_free (devid, TRUE); return NULL; + } p = devid->str; msg = g_string_sized_new (strlen (devid->str) + 17); @@ -888,6 +920,7 @@ mm_create_device_identifier (guint vid, mm_dbg ("Device ID source '%s'", msg->str); mm_dbg ("Device ID '%s'", ret); g_string_free (msg, TRUE); + g_string_free (devid, TRUE); return ret; } @@ -1020,8 +1053,8 @@ mm_parse_cind_test_response (const char *reply, GError **error) g_match_info_next (match_info, NULL); } - g_match_info_free (match_info); } + g_match_info_free (match_info); g_regex_unref (r); return hash; @@ -1086,11 +1119,10 @@ mm_parse_cind_query_response(const char *reply, GError **error) g_free (str); g_match_info_next (match_info, NULL); } - g_match_info_free (match_info); done: - if (r) - g_regex_unref (r); + g_match_info_free (match_info); + g_regex_unref (r); return array; } diff --git a/src/mm-modem.c b/src/mm-modem.c index b3c1677..33b8116 100644 --- a/src/mm-modem.c +++ b/src/mm-modem.c @@ -323,6 +323,8 @@ get_ip4_done (MMModem *modem, value_array_add_uint (array, dns3); dbus_g_method_return (context, array); + + g_value_array_free (array); } } @@ -411,6 +413,8 @@ info_call_done (MMModem *self, g_value_unset (&value); dbus_g_method_return (context, array); + + g_value_array_free (array); } } diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 0777021..5248181 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -36,6 +36,7 @@ #include "mm-utils.h" #include "libqcdm/src/commands.h" #include "libqcdm/src/utils.h" +#include "libqcdm/src/errors.h" #include "mm-log.h" static void plugin_init (MMPlugin *plugin_class); @@ -403,6 +404,11 @@ static const char *dq_strings[] = { NULL }; +static guint8 zerobuf[32] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + static void port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) { @@ -412,6 +418,13 @@ port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) size_t iter_len; int i; + /* Some devices (observed on a ZTE branded "QUALCOMM INCORPORATED" model + * "154") spew NULLs from some ports. + */ + if ( (buffer->len >= sizeof (zerobuf)) + && (memcmp (buffer->data, zerobuf, sizeof (zerobuf)) == 0)) + goto stop_probing; + /* Check for an immediate disqualification response. There are some * ports (Option Icera-based chipsets have them, as do Qualcomm Gobi * devices before their firmware is loaded) that just shouldn't be @@ -428,13 +441,16 @@ port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) for (i = 0; i < buffer->len - iter_len; i++) { if (!memcmp (&buffer->data[i], *iter, iter_len)) { /* Immediately close the port and complete probing */ - priv->probed_caps = 0; - mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); - probe_complete (task); - return; + goto stop_probing; } } } + return; + +stop_probing: + priv->probed_caps = 0; + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); + probe_complete (task); } static gboolean @@ -482,8 +498,8 @@ qcdm_verinfo_cb (MMQcdmSerialPort *port, { MMPluginBaseSupportsTask *task; MMPluginBaseSupportsTaskPrivate *priv; - QCDMResult *result; - GError *dm_error = NULL; + QcdmResult *result; + int err = QCDM_SUCCESS; /* Just the initial poke; ignore it */ if (!user_data) @@ -498,13 +514,10 @@ qcdm_verinfo_cb (MMQcdmSerialPort *port, } /* Parse the response */ - result = qcdm_cmd_version_info_result ((const char *) response->data, response->len, &dm_error); + result = qcdm_cmd_version_info_result ((const char *) response->data, response->len, &err); if (!result) { - g_warning ("(%s) failed to parse QCDM version info command result: (%d) %s.", - g_udev_device_get_name (priv->port), - dm_error ? dm_error->code : -1, - dm_error && dm_error->message ? dm_error->message : "(unknown)"); - g_clear_error (&dm_error); + g_warning ("(%s) failed to parse QCDM version info command result: %d", + g_udev_device_get_name (priv->port), err); goto done; } @@ -554,14 +567,10 @@ try_qcdm_probe (MMPluginBaseSupportsTask *task) /* Build up the probe command */ verinfo = g_byte_array_sized_new (50); - len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error); + len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50); if (len <= 0) { g_byte_array_free (verinfo, TRUE); - g_warning ("(%s) failed to create QCDM version info command: (%d) %s.", - name, - error ? error->code : -1, - error && error->message ? error->message : "(unknown)"); - g_clear_error (&error); + g_warning ("(%s) failed to create QCDM version info command", name); probe_complete (task); return; } diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h index 799f681..e69ad17 100644 --- a/src/mm-plugin-base.h +++ b/src/mm-plugin-base.h @@ -17,7 +17,6 @@ #define MM_PLUGIN_BASE_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> #define G_UDEV_API_IS_SUBJECT_TO_CHANGE diff --git a/src/mm-port.h b/src/mm-port.h index 4bcffd4..df935db 100644 --- a/src/mm-port.h +++ b/src/mm-port.h @@ -17,7 +17,6 @@ #define MM_PORT_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> typedef enum { diff --git a/src/mm-qcdm-serial-port.c b/src/mm-qcdm-serial-port.c index e467f2a..0d763bf 100644 --- a/src/mm-qcdm-serial-port.c +++ b/src/mm-qcdm-serial-port.c @@ -23,6 +23,7 @@ #include "mm-errors.h" #include "libqcdm/src/com.h" #include "libqcdm/src/utils.h" +#include "libqcdm/src/errors.h" #include "mm-log.h" G_DEFINE_TYPE (MMQcdmSerialPort, mm_qcdm_serial_port, MM_TYPE_SERIAL_PORT) @@ -81,7 +82,8 @@ handle_response (MMSerialPort *port, GError *dm_error = NULL; gsize used = 0; gsize start = 0; - gboolean success = FALSE, more = FALSE; + gboolean success = FALSE; + qcdmbool more = FALSE; gsize unescaped_len = 0; if (error) @@ -200,7 +202,15 @@ debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len) static gboolean config_fd (MMSerialPort *port, int fd, GError **error) { - return qcdm_port_setup (fd, error); + int err; + + err = qcdm_port_setup (fd); + if (err != QCDM_SUCCESS) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, + "Failed to open QCDM port: %d", err); + return FALSE; + } + return TRUE; } /*****************************************************************************/ diff --git a/src/mm-qcdm-serial-port.h b/src/mm-qcdm-serial-port.h index 2786ee8..605016d 100644 --- a/src/mm-qcdm-serial-port.h +++ b/src/mm-qcdm-serial-port.h @@ -18,7 +18,6 @@ #define MM_QCDM_SERIAL_PORT_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> #include "mm-serial-port.h" diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c index 344e1bc..4212e19 100644 --- a/src/mm-serial-parsers.c +++ b/src/mm-serial-parsers.c @@ -82,6 +82,7 @@ remove_matches (GRegex *r, GString *string) typedef struct { GRegex *generic_response; GRegex *detailed_error; + GRegex *cms_error; } MMSerialParserV0; gpointer @@ -94,6 +95,7 @@ mm_serial_parser_v0_new (void) parser->generic_response = g_regex_new ("(\\d)\\0?\\r$", flags, 0, NULL); parser->detailed_error = g_regex_new ("\\+CME ERROR:\\s*(\\d+)\\r\\n$", flags, 0, NULL); + parser->cms_error = g_regex_new ("\\+CMS ERROR:\\s*(\\d+)\\r\\n$", flags, 0, NULL); return parser; } @@ -125,8 +127,6 @@ mm_serial_parser_v0_parse (gpointer data, } else code = MM_MOBILE_ERROR_UNKNOWN; - g_match_info_free (match_info); - switch (code) { case 0: /* OK */ break; @@ -155,9 +155,10 @@ mm_serial_parser_v0_parse (gpointer data, remove_matches (parser->generic_response, response); } + g_match_info_free (match_info); + if (!found) { found = g_regex_match_full (parser->detailed_error, response->str, response->len, 0, 0, &match_info, NULL); - if (found) { str = g_match_info_fetch (match_info, 1); if (str) { @@ -166,9 +167,24 @@ mm_serial_parser_v0_parse (gpointer data, } else code = MM_MOBILE_ERROR_UNKNOWN; - g_match_info_free (match_info); local_error = mm_mobile_error_for_code (code); } + g_match_info_free (match_info); + + if (!found) { + found = g_regex_match_full (parser->cms_error, response->str, response->len, 0, 0, &match_info, NULL); + if (found) { + str = g_match_info_fetch (match_info, 1); + if (str) { + code = atoi (str); + g_free (str); + } else + code = MM_MSG_ERROR_UNKNOWN; + + local_error = mm_msg_error_for_code (code); + } + g_match_info_free (match_info); + } } if (found) @@ -191,6 +207,7 @@ mm_serial_parser_v0_destroy (gpointer data) g_regex_unref (parser->generic_response); g_regex_unref (parser->detailed_error); + g_regex_unref (parser->cms_error); g_slice_free (MMSerialParserV0, data); } @@ -204,6 +221,7 @@ typedef struct { GRegex *regex_cme_error; GRegex *regex_cms_error; GRegex *regex_cme_error_str; + GRegex *regex_cms_error_str; GRegex *regex_ezx_error; GRegex *regex_unknown_error; GRegex *regex_connect_failed; @@ -223,6 +241,7 @@ mm_serial_parser_v1_new (void) parser->regex_cme_error = g_regex_new ("\\r\\n\\+CME ERROR:\\s*(\\d+)\\r\\n$", flags, 0, NULL); parser->regex_cms_error = g_regex_new ("\\r\\n\\+CMS ERROR:\\s*(\\d+)\\r\\n$", flags, 0, NULL); parser->regex_cme_error_str = g_regex_new ("\\r\\n\\+CME ERROR:\\s*([^\\n\\r]+)\\r\\n$", flags, 0, NULL); + parser->regex_cms_error_str = g_regex_new ("\\r\\n\\+CMS ERROR:\\s*([^\\n\\r]+)\\r\\n$", flags, 0, NULL); parser->regex_ezx_error = g_regex_new ("\\r\\n\\MODEM ERROR:\\s*(\\d+)\\r\\n$", flags, 0, NULL); parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL); parser->regex_connect_failed = g_regex_new ("\\r\\n(NO CARRIER)|(BUSY)|(NO ANSWER)|(NO DIALTONE)\\r\\n$", flags, 0, NULL); @@ -260,13 +279,17 @@ mm_serial_parser_v1_parse (gpointer data, GMatchInfo *match_info; GError *local_error = NULL; gboolean found = FALSE; - char *str; + char *str = NULL; int code; g_return_val_if_fail (parser != NULL, FALSE); g_return_val_if_fail (response != NULL, FALSE); - if (G_UNLIKELY (!response->len || !strlen (response->str))) + /* Skip NUL bytes if they are found leading the response */ + while (response->len > 0 && response->str[0] == '\0') + g_string_erase (response, 0, 1); + + if (G_UNLIKELY (!response->len)) return FALSE; /* First, check for successful responses */ @@ -306,10 +329,9 @@ mm_serial_parser_v1_parse (gpointer data, 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; } + g_match_info_free (match_info); } /* Numeric CME errors */ @@ -320,27 +342,21 @@ mm_serial_parser_v1_parse (gpointer data, 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; } + g_match_info_free (match_info); /* 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); + local_error = mm_msg_error_for_code (atoi (str)); goto done; } + g_match_info_free (match_info); /* String CME errors */ found = g_regex_match_full (parser->regex_cme_error_str, @@ -350,10 +366,21 @@ mm_serial_parser_v1_parse (gpointer data, str = g_match_info_fetch (match_info, 1); g_assert (str); local_error = mm_mobile_error_for_string (str); - g_free (str); - g_match_info_free (match_info); goto done; } + g_match_info_free (match_info); + + /* String CMS errors */ + found = g_regex_match_full (parser->regex_cms_error_str, + 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_msg_error_for_string (str); + goto done; + } + g_match_info_free (match_info); /* Motorola EZX errors */ found = g_regex_match_full (parser->regex_ezx_error, @@ -363,19 +390,19 @@ mm_serial_parser_v1_parse (gpointer data, str = g_match_info_fetch (match_info, 1); g_assert (str); local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); - g_free (str); - g_match_info_free (match_info); goto done; } + g_match_info_free (match_info); /* Last resort; unknown error */ found = g_regex_match_full (parser->regex_unknown_error, response->str, response->len, - 0, 0, NULL, NULL); + 0, 0, &match_info, NULL); if (found) { local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); goto done; } + g_match_info_free (match_info); /* Connection failures */ found = g_regex_match_full (parser->regex_connect_failed, @@ -398,13 +425,12 @@ mm_serial_parser_v1_parse (gpointer data, code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; } - g_free (str); - g_match_info_free (match_info); - local_error = mm_modem_connect_error_for_code (code); } done: + g_free (str); + g_match_info_free (match_info); if (found) response_clean (response); @@ -426,7 +452,9 @@ mm_serial_parser_v1_destroy (gpointer data) g_regex_unref (parser->regex_ok); g_regex_unref (parser->regex_connect); g_regex_unref (parser->regex_cme_error); + g_regex_unref (parser->regex_cms_error); g_regex_unref (parser->regex_cme_error_str); + g_regex_unref (parser->regex_cms_error_str); g_regex_unref (parser->regex_ezx_error); g_regex_unref (parser->regex_unknown_error); g_regex_unref (parser->regex_connect_failed); diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c index 46050cf..b7b5a73 100644 --- a/src/mm-serial-port.c +++ b/src/mm-serial-port.c @@ -958,6 +958,15 @@ internal_queue_command (MMSerialPort *self, g_return_if_fail (MM_IS_SERIAL_PORT (self)); g_return_if_fail (command != NULL); + if (priv->open_count == 0) { + GError *error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_SEND_FAILED, + "Sending command failed: device is not enabled"); + callback (self, NULL, error, user_data); + g_error_free (error); + return; + } + info = g_slice_new0 (MMQueueData); if (take_command) info->command = command; @@ -1290,7 +1299,11 @@ set_property (GObject *object, guint prop_id, priv->bits = g_value_get_uint (value); break; case PROP_PARITY: +#if GLIB_CHECK_VERSION(2,31,0) + priv->parity = g_value_get_schar (value); +#else priv->parity = g_value_get_char (value); +#endif break; case PROP_STOPBITS: priv->stopbits = g_value_get_uint (value); @@ -1327,7 +1340,11 @@ get_property (GObject *object, guint prop_id, g_value_set_uint (value, priv->bits); break; case PROP_PARITY: +#if GLIB_CHECK_VERSION(2,31,0) + g_value_set_schar (value, priv->parity); +#else g_value_set_char (value, priv->parity); +#endif break; case PROP_STOPBITS: g_value_set_uint (value, priv->stopbits); diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h index ae38017..f988af3 100644 --- a/src/mm-serial-port.h +++ b/src/mm-serial-port.h @@ -18,7 +18,6 @@ #define MM_SERIAL_PORT_H #include <glib.h> -#include <glib/gtypes.h> #include <glib-object.h> #include "mm-port.h" diff --git a/src/mm-sms-utils.c b/src/mm-sms-utils.c index 3f56a64..4e52ec4 100644 --- a/src/mm-sms-utils.c +++ b/src/mm-sms-utils.c @@ -13,12 +13,17 @@ * Copyright (C) 2011 Red Hat, Inc. */ +#include <ctype.h> +#include <string.h> + #include <glib.h> #include "mm-charsets.h" #include "mm-errors.h" #include "mm-utils.h" #include "mm-sms-utils.h" +#include "mm-log.h" +#include "dbus/dbus-glib.h" #define SMS_TP_MTI_MASK 0x03 #define SMS_TP_MTI_SMS_DELIVER 0x00 @@ -70,6 +75,93 @@ sms_semi_octets_to_bcd_string (char *dest, const guint8 *octets, int num_octets) *dest++ = '\0'; } +static gboolean +char_to_bcd (char in, guint8 *out) +{ + guint32 z; + + if (isdigit (in)) { + *out = in - 0x30; + return TRUE; + } + + for (z = 10; z < 16; z++) { + if (in == sms_bcd_chars[z]) { + *out = z; + return TRUE; + } + } + return FALSE; +} + +static gsize +sms_string_to_bcd_semi_octets (guint8 *buf, gsize buflen, const char *string) +{ + guint i; + guint8 bcd; + gsize addrlen, slen; + + addrlen = slen = strlen (string); + if (addrlen % 2) + addrlen++; + g_return_val_if_fail (buflen >= addrlen, 0); + + for (i = 0; i < addrlen; i += 2) { + if (!char_to_bcd (string[i], &bcd)) + return 0; + buf[i / 2] = bcd & 0xF; + + if (i >= slen - 1) { + /* PDU address gets padded with 0xF if string is odd length */ + bcd = 0xF; + } else if (!char_to_bcd (string[i + 1], &bcd)) + return 0; + buf[i / 2] |= bcd << 4; + } + return addrlen / 2; +} + +/** + * sms_encode_address: + * + * @address: the phone number to encode + * @buf: the buffer to encode @address in + * @buflen: the size of @buf + * @is_smsc: if %TRUE encode size as number of octets of address infromation, + * otherwise if %FALSE encode size as number of digits of @address + * + * Returns: the size in bytes of the data added to @buf + **/ +guint +sms_encode_address (const char *address, + guint8 *buf, + size_t buflen, + gboolean is_smsc) +{ + gsize len; + + g_return_val_if_fail (address != NULL, 0); + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (buflen >= 2, 0); + + /* Handle number type & plan */ + buf[1] = 0x80; /* Bit 7 always 1 */ + if (address[0] == '+') { + buf[1] |= SMS_NUMBER_TYPE_INTL; + address++; + } + buf[1] |= SMS_NUMBER_PLAN_TELEPHONE; + + len = sms_string_to_bcd_semi_octets (&buf[2], buflen, address); + + if (is_smsc) + buf[0] = len + 1; /* addr length + size byte */ + else + buf[0] = strlen (address); /* number of digits in address */ + + return len ? len + 2 : 0; /* addr length + size byte + number type/plan */ +} + /* len is in semi-octets */ static char * sms_decode_address (const guint8 *address, int len) @@ -200,10 +292,10 @@ sms_decode_text (const guint8 *text, int len, SmsEncoding encoding, int bit_offs 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 + else { + g_warn_if_reached (); utf8 = g_strdup (""); + } return utf8; } @@ -230,48 +322,83 @@ simple_uint_value (guint32 i) } static GValue * -simple_boolean_value (gboolean b) +simple_string_value (const char *str) { GValue *val; val = g_slice_new0 (GValue); - g_value_init (val, G_TYPE_BOOLEAN); - g_value_set_boolean (val, b); + g_value_init (val, G_TYPE_STRING); + g_value_set_string (val, str); return val; } static GValue * -simple_string_value (const char *str) +byte_array_value (const GByteArray *array) { GValue *val; val = g_slice_new0 (GValue); - g_value_init (val, G_TYPE_STRING); - g_value_set_string (val, str); + g_value_init (val, DBUS_TYPE_G_UCHAR_ARRAY); + g_value_set_boxed (val, array); return val; } GHashTable * +sms_properties_hash_new (const char *smsc, + const char *number, + const char *timestamp, + const char *text, + const GByteArray *data, + guint data_coding_scheme, + guint *class) +{ + GHashTable *properties; + + g_return_val_if_fail (number != NULL, NULL); + g_return_val_if_fail (text != NULL, NULL); + g_return_val_if_fail (data != NULL, NULL); + + 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 (number)); + g_hash_table_insert (properties, "data", byte_array_value (data)); + g_hash_table_insert (properties, "data-coding-scheme", simple_uint_value (data_coding_scheme)); + g_hash_table_insert (properties, "text", simple_string_value (text)); + + if (smsc) + g_hash_table_insert (properties, "smsc", simple_string_value (smsc)); + + if (timestamp) + g_hash_table_insert (properties, "timestamp", simple_string_value (timestamp)); + + if (class) + g_hash_table_insert (properties, "class", simple_uint_value (*class)); + + return properties; +} + +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, + guint 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; + GByteArray *pdu_data; + guint concat_ref = 0, concat_max = 0, concat_seq = 0, msg_class = 0; + gboolean multipart = FALSE, class_valid = FALSE; /* 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"); + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Couldn't parse PDU of SMS GET response from hex"); return NULL; } @@ -279,10 +406,10 @@ sms_parse_pdu (const char *hexpdu, GError **error) 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_set_error (error, 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; } @@ -297,10 +424,10 @@ sms_parse_pdu (const char *hexpdu, GError **error) 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_set_error (error, 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; } @@ -317,36 +444,71 @@ sms_parse_pdu (const char *hexpdu, GError **error) 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_set_error (error, 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_set_error (error, 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) { + int udhl, end, offset; + udhl = pdu[user_data_offset] + 1; + end = user_data_offset + udhl; + + for (offset = user_data_offset + 1; offset < end;) { + guint8 ie_id, ie_len; + + ie_id = pdu[offset++]; + ie_len = pdu[offset++]; + + switch (ie_id) { + case 0x00: + /* + * Ignore the IE if one of the following is true: + * - it claims to be part 0 of M + * - it claims to be part N of M, N > M + */ + if (pdu[offset + 2] == 0 || + pdu[offset + 2] > pdu[offset + 1]) + break; + + concat_ref = pdu[offset]; + concat_max = pdu[offset + 1]; + concat_seq = pdu[offset + 2]; + multipart = TRUE; + break; + case 0x08: + /* Concatenated short message, 16-bit reference */ + if (pdu[offset + 3] == 0 || + pdu[offset + 3] > pdu[offset + 2]) + break; + + concat_ref = (pdu[offset] << 8) | pdu[offset + 1]; + concat_max = pdu[offset + 2]; + concat_seq = pdu[offset + 3]; + multipart = TRUE; + break; + } + + offset += ie_len; + } + /* - * Skip over the user data headers to prevent it from being + * Move past 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) { /* @@ -359,31 +521,274 @@ sms_parse_pdu (const char *hexpdu, GError **error) 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)); + if ( user_data_encoding == MM_SMS_ENCODING_8BIT + || user_data_encoding == MM_SMS_ENCODING_UNKNOWN) { + /* 8-bit encoding is usually binary data, and we have no idea what + * actual encoding the data is in so we can't convert it. + */ + msg_text = g_strdup (""); + } else { + /* Otherwise if it's 7-bit or UCS2 we can decode it */ + msg_text = sms_decode_text (&pdu[user_data_offset], user_data_len, + user_data_encoding, bit_offset); + g_warn_if_fail (msg_text != NULL); + } + + /* Raw PDU data */ + pdu_data = g_byte_array_sized_new (user_data_len); + g_byte_array_append (pdu_data, &pdu[user_data_offset], user_data_len); + + if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID) { + msg_class = pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK; + class_valid = TRUE; + } + + 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]); + + properties = sms_properties_hash_new (smsc_addr, + sender_addr, + sc_timestamp, + msg_text, + pdu_data, + pdu[tp_dcs_offset] & 0xFF, + class_valid ? &msg_class : NULL); + g_assert (properties); + if (multipart) { + g_hash_table_insert (properties, "concat-reference", simple_uint_value (concat_ref)); + g_hash_table_insert (properties, "concat-max", simple_uint_value (concat_max)); + g_hash_table_insert (properties, "concat-sequence", simple_uint_value (concat_seq)); + } g_free (smsc_addr); g_free (sender_addr); g_free (sc_timestamp); g_free (msg_text); + g_byte_array_free (pdu_data, TRUE); g_free (pdu); - return properties; } + +static guint8 +validity_to_relative (guint validity) +{ + if (validity == 0) + return 167; /* 24 hours */ + + if (validity <= 720) { + /* 5 minute units up to 12 hours */ + if (validity % 5) + validity += 5; + return (validity / 5) - 1; + } + + if (validity > 720 && validity <= 1440) { + /* 12 hours + 30 minute units up to 1 day */ + if (validity % 30) + validity += 30; /* round up to next 30 minutes */ + validity = MIN (validity, 1440); + return 143 + ((validity - 720) / 30); + } + + if (validity > 1440 && validity <= 43200) { + /* 2 days up to 1 month */ + if (validity % 1440) + validity += 1440; /* round up to next day */ + validity = MIN (validity, 43200); + return 167 + ((validity - 1440) / 1440); + } + + /* 43200 = 30 days in minutes + * 10080 = 7 days in minutes + * 635040 = 63 weeks in minutes + * 40320 = 4 weeks in minutes + */ + if (validity > 43200 && validity <= 635040) { + /* 5 weeks up to 63 weeks */ + if (validity % 10080) + validity += 10080; /* round up to next week */ + validity = MIN (validity, 635040); + return 196 + ((validity - 40320) / 10080); + } + + return 255; /* 63 weeks */ +} + +#define PDU_SIZE 200 + +/** + * sms_create_submit_pdu: + * + * @number: the subscriber number to send this message to + * @text: the body of this SMS + * @smsc: if given, the SMSC address + * @validity: minutes until the SMS should expire in the SMSC, or 0 for a + * suitable default + * @class: unused + * @out_pdulen: on success, the size of the returned PDU in bytes + * @out_msgstart: on success, the byte index in the returned PDU where the + * message starts (ie, skipping the SMSC length byte and address, if present) + * @error: on error, filled with the error that occurred + * + * Constructs a single-part SMS message with the given details, preferring to + * use the UCS2 character set when the message will fit, otherwise falling back + * to the GSM character set. + * + * Returns: the constructed PDU data on success, or %NULL on error + **/ +guint8 * +sms_create_submit_pdu (const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + guint *out_pdulen, + guint *out_msgstart, + GError **error) +{ + guint8 *pdu; + guint len, offset = 0; + MMModemCharset best_cs = MM_MODEM_CHARSET_GSM; + guint ucs2len = 0, gsm_unsupported = 0; + guint textlen = 0; + + g_return_val_if_fail (number != NULL, NULL); + g_return_val_if_fail (text != NULL, NULL); + + /* FIXME: support multiple fragments */ + + textlen = mm_charset_get_encoded_len (text, MM_MODEM_CHARSET_GSM, &gsm_unsupported); + if (textlen > 160) { + g_set_error_literal (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Cannot encode message to fit into an SMS."); + return NULL; + } + + /* If there are characters that are unsupported in the GSM charset, try + * UCS2. If the UCS2 encoded string is too long to fit in an SMS, then + * just use GSM and suck up the unconverted chars. + */ + if (gsm_unsupported > 0) { + ucs2len = mm_charset_get_encoded_len (text, MM_MODEM_CHARSET_UCS2, NULL); + if (ucs2len <= 140) { + best_cs = MM_MODEM_CHARSET_UCS2; + textlen = ucs2len; + } + } + + /* Build up the PDU */ + pdu = g_malloc0 (PDU_SIZE); + g_return_val_if_fail (pdu != NULL, NULL); + + if (smsc) { + len = sms_encode_address (smsc, pdu, PDU_SIZE, TRUE); + if (len == 0) { + g_set_error (error, + MM_MSG_ERROR, + MM_MSG_ERROR_INVALID_PDU_PARAMETER, + "Invalid SMSC address '%s'", smsc); + goto error; + } + offset += len; + } else { + /* No SMSC, use default */ + pdu[offset++] = 0x00; + } + + if (out_msgstart) + *out_msgstart = offset; + + if (validity > 0) + pdu[offset] = 1 << 4; /* TP-VP present; format RELATIVE */ + else + pdu[offset] = 0; /* TP-VP not present */ + pdu[offset++] |= 0x01; /* TP-MTI = SMS-SUBMIT */ + + pdu[offset++] = 0x00; /* TP-Message-Reference: filled by device */ + + len = sms_encode_address (number, &pdu[offset], PDU_SIZE - offset, FALSE); + if (len == 0) { + g_set_error (error, + MM_MSG_ERROR, + MM_MSG_ERROR_INVALID_PDU_PARAMETER, + "Invalid send-to number '%s'", number); + goto error; + } + offset += len; + + /* TP-PID */ + pdu[offset++] = 0x00; + + /* TP-DCS */ + if (best_cs == MM_MODEM_CHARSET_UCS2) + pdu[offset++] = 0x08; + else + pdu[offset++] = 0x00; /* GSM */ + + /* TP-Validity-Period: 4 days */ + if (validity > 0) + pdu[offset++] = validity_to_relative (validity); + + /* TP-User-Data-Length */ + pdu[offset++] = textlen; + + if (best_cs == MM_MODEM_CHARSET_GSM) { + guint8 *unpacked, *packed; + guint32 unlen = 0, packlen = 0; + + unpacked = mm_charset_utf8_to_unpacked_gsm (text, &unlen); + if (!unpacked || unlen == 0) { + g_free (unpacked); + g_set_error_literal (error, + MM_MSG_ERROR, + MM_MSG_ERROR_INVALID_PDU_PARAMETER, + "Failed to convert message text to GSM."); + goto error; + } + + packed = gsm_pack (unpacked, unlen, 0, &packlen); + g_free (unpacked); + if (!packed || packlen == 0) { + g_free (packed); + g_set_error_literal (error, + MM_MSG_ERROR, + MM_MSG_ERROR_INVALID_PDU_PARAMETER, + "Failed to pack message text to GSM."); + goto error; + } + + memcpy (&pdu[offset], packed, packlen); + g_free (packed); + offset += packlen; + } else if (best_cs == MM_MODEM_CHARSET_UCS2) { + GByteArray *array; + + array = g_byte_array_sized_new (textlen / 2); + if (!mm_modem_charset_byte_array_append (array, text, FALSE, best_cs)) { + g_byte_array_free (array, TRUE); + g_set_error_literal (error, + MM_MSG_ERROR, + MM_MSG_ERROR_INVALID_PDU_PARAMETER, + "Failed to convert message text to UCS2."); + goto error; + } + + memcpy (&pdu[offset], array->data, array->len); + offset += array->len; + g_byte_array_free (array, TRUE); + } else + g_assert_not_reached (); + + if (out_pdulen) + *out_pdulen = offset; + return pdu; + +error: + g_free (pdu); + return NULL; +} + diff --git a/src/mm-sms-utils.h b/src/mm-sms-utils.h index 26d9829..46f475b 100644 --- a/src/mm-sms-utils.h +++ b/src/mm-sms-utils.h @@ -22,4 +22,28 @@ GHashTable *sms_parse_pdu (const char *hexpdu, GError **error); +guint8 *sms_create_submit_pdu (const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + guint *out_pdulen, + guint *out_msgstart, + GError **error); + +GHashTable *sms_properties_hash_new (const char *smsc, + const char *number, + const char *timestamp, + const char *text, + const GByteArray *data, + guint data_coding_scheme, + guint *class); + +/* For testcases only */ +guint sms_encode_address (const char *address, + guint8 *buf, + size_t buflen, + gboolean is_smsc); + + #endif /* MM_SMS_UTILS_H */ diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index cc47e66..43b3eb1 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -5,6 +5,7 @@ noinst_PROGRAMS = \ test-modem-helpers \ test-charsets \ test-qcdm-serial-port \ + test-at-serial-port \ test-sms test_modem_helpers_SOURCES = \ @@ -41,6 +42,20 @@ test_qcdm_serial_port_LDADD = \ $(top_builddir)/libqcdm/src/libqcdm.la \ -lutil +test_at_serial_port_SOURCES = \ + test-at-serial-port.c + +test_at_serial_port_CPPFLAGS = \ + $(MM_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src + +test_at_serial_port_LDADD = \ + $(MM_LIBS) \ + $(top_builddir)/src/libserial.la \ + $(top_builddir)/src/libmodem-helpers.la \ + -lutil + test_sms_SOURCES = \ test-sms.c @@ -60,4 +75,3 @@ check-local: test-modem-helpers $(abs_builddir)/test-sms endif - diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index 94793ef..d6c4d1a 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -35,7 +35,8 @@ POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ noinst_PROGRAMS = test-modem-helpers$(EXEEXT) test-charsets$(EXEEXT) \ - test-qcdm-serial-port$(EXEEXT) test-sms$(EXEEXT) + test-qcdm-serial-port$(EXEEXT) test-at-serial-port$(EXEEXT) \ + test-sms$(EXEEXT) subdir = src/tests DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -43,7 +44,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/compiler_warnings.m4 \ $(top_srcdir)/m4/intltool.m4 $(top_srcdir)/m4/libtool.m4 \ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/nls.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d @@ -51,14 +52,20 @@ CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = PROGRAMS = $(noinst_PROGRAMS) -am_test_charsets_OBJECTS = test_charsets-test-charsets.$(OBJEXT) -test_charsets_OBJECTS = $(am_test_charsets_OBJECTS) +am_test_at_serial_port_OBJECTS = \ + test_at_serial_port-test-at-serial-port.$(OBJEXT) +test_at_serial_port_OBJECTS = $(am_test_at_serial_port_OBJECTS) am__DEPENDENCIES_1 = -test_charsets_DEPENDENCIES = $(top_builddir)/src/libmodem-helpers.la \ - $(am__DEPENDENCIES_1) +test_at_serial_port_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(top_builddir)/src/libserial.la \ + $(top_builddir)/src/libmodem-helpers.la AM_V_lt = $(am__v_lt_$(V)) am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) am__v_lt_0 = --silent +am_test_charsets_OBJECTS = test_charsets-test-charsets.$(OBJEXT) +test_charsets_OBJECTS = $(am_test_charsets_OBJECTS) +test_charsets_DEPENDENCIES = $(top_builddir)/src/libmodem-helpers.la \ + $(am__DEPENDENCIES_1) am_test_modem_helpers_OBJECTS = \ test_modem_helpers-test-modem-helpers.$(OBJEXT) test_modem_helpers_OBJECTS = $(am_test_modem_helpers_OBJECTS) @@ -104,10 +111,12 @@ am__v_CCLD_0 = @echo " CCLD " $@; 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_sms_SOURCES) -DIST_SOURCES = $(test_charsets_SOURCES) $(test_modem_helpers_SOURCES) \ - $(test_qcdm_serial_port_SOURCES) $(test_sms_SOURCES) +SOURCES = $(test_at_serial_port_SOURCES) $(test_charsets_SOURCES) \ + $(test_modem_helpers_SOURCES) $(test_qcdm_serial_port_SOURCES) \ + $(test_sms_SOURCES) +DIST_SOURCES = $(test_at_serial_port_SOURCES) $(test_charsets_SOURCES) \ + $(test_modem_helpers_SOURCES) $(test_qcdm_serial_port_SOURCES) \ + $(test_sms_SOURCES) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) @@ -299,6 +308,20 @@ test_qcdm_serial_port_LDADD = \ $(top_builddir)/libqcdm/src/libqcdm.la \ -lutil +test_at_serial_port_SOURCES = \ + test-at-serial-port.c + +test_at_serial_port_CPPFLAGS = \ + $(MM_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src + +test_at_serial_port_LDADD = \ + $(MM_LIBS) \ + $(top_builddir)/src/libserial.la \ + $(top_builddir)/src/libmodem-helpers.la \ + -lutil + test_sms_SOURCES = \ test-sms.c @@ -352,6 +375,9 @@ clean-noinstPROGRAMS: list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list +test-at-serial-port$(EXEEXT): $(test_at_serial_port_OBJECTS) $(test_at_serial_port_DEPENDENCIES) + @rm -f test-at-serial-port$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_at_serial_port_OBJECTS) $(test_at_serial_port_LDADD) $(LIBS) test-charsets$(EXEEXT): $(test_charsets_OBJECTS) $(test_charsets_DEPENDENCIES) @rm -f test-charsets$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_charsets_OBJECTS) $(test_charsets_LDADD) $(LIBS) @@ -371,6 +397,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_at_serial_port-test-at-serial-port.Po@am__quote@ @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@ @@ -403,6 +430,22 @@ distclean-compile: @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< +test_at_serial_port-test-at-serial-port.o: test-at-serial-port.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_at_serial_port_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_at_serial_port-test-at-serial-port.o -MD -MP -MF $(DEPDIR)/test_at_serial_port-test-at-serial-port.Tpo -c -o test_at_serial_port-test-at-serial-port.o `test -f 'test-at-serial-port.c' || echo '$(srcdir)/'`test-at-serial-port.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_at_serial_port-test-at-serial-port.Tpo $(DEPDIR)/test_at_serial_port-test-at-serial-port.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-at-serial-port.c' object='test_at_serial_port-test-at-serial-port.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_at_serial_port_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_at_serial_port-test-at-serial-port.o `test -f 'test-at-serial-port.c' || echo '$(srcdir)/'`test-at-serial-port.c + +test_at_serial_port-test-at-serial-port.obj: test-at-serial-port.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_at_serial_port_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_at_serial_port-test-at-serial-port.obj -MD -MP -MF $(DEPDIR)/test_at_serial_port-test-at-serial-port.Tpo -c -o test_at_serial_port-test-at-serial-port.obj `if test -f 'test-at-serial-port.c'; then $(CYGPATH_W) 'test-at-serial-port.c'; else $(CYGPATH_W) '$(srcdir)/test-at-serial-port.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_at_serial_port-test-at-serial-port.Tpo $(DEPDIR)/test_at_serial_port-test-at-serial-port.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-at-serial-port.c' object='test_at_serial_port-test-at-serial-port.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_at_serial_port_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_at_serial_port-test-at-serial-port.obj `if test -f 'test-at-serial-port.c'; then $(CYGPATH_W) 'test-at-serial-port.c'; else $(CYGPATH_W) '$(srcdir)/test-at-serial-port.c'; fi` + test_charsets-test-charsets.o: test-charsets.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_charsets_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_charsets-test-charsets.o -MD -MP -MF $(DEPDIR)/test_charsets-test-charsets.Tpo -c -o test_charsets-test-charsets.o `test -f 'test-charsets.c' || echo '$(srcdir)/'`test-charsets.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_charsets-test-charsets.Tpo $(DEPDIR)/test_charsets-test-charsets.Po diff --git a/src/tests/test-at-serial-port.c b/src/tests/test-at-serial-port.c new file mode 100644 index 0000000..c46fa88 --- /dev/null +++ b/src/tests/test-at-serial-port.c @@ -0,0 +1,86 @@ +/* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <config.h> +#include <string.h> +#include <glib.h> + +#include "mm-errors.h" +#include "mm-at-serial-port.h" +#include "mm-log.h" + +typedef struct { + gchar *original; + gchar *without_echo; +} EchoRemovalTest; + +static const EchoRemovalTest echo_removal_tests[] = { + { "\r\n", "\r\n" }, + { "\r", "\r" }, + { "\n", "\n" }, + { "this is a string that ends just with <CR>\r", "this is a string that ends just with <CR>\r" }, + { "this is a string that ends just with <CR>\n", "this is a string that ends just with <CR>\n" }, + { "\r\nthis is valid", "\r\nthis is valid" }, + { "a\r\nthis is valid", "\r\nthis is valid" }, + { "a\r\n", "\r\n" }, + { "all this string is to be considered echo\r\n", "\r\n" }, + { "all this string is to be considered echo\r\nthis is valid", "\r\nthis is valid" }, + { "echo echo\r\nthis is valid\r\nand so is this", "\r\nthis is valid\r\nand so is this" }, + { "\r\nthis is valid\r\nand so is this", "\r\nthis is valid\r\nand so is this" }, + { "\r\nthis is valid\r\nand so is this\r\n", "\r\nthis is valid\r\nand so is this\r\n" }, +}; + +static void +at_serial_echo_removal (void) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (echo_removal_tests); i++) { + GByteArray *ba; + + /* Note that we add last NUL also to the byte array, so that we can compare + * C strings later on */ + ba = g_byte_array_sized_new (strlen (echo_removal_tests[i].original) + 1); + g_byte_array_prepend (ba, + (guint8 *)echo_removal_tests[i].original, + strlen (echo_removal_tests[i].original) + 1); + + mm_at_serial_port_remove_echo (ba); + + g_assert_cmpstr ((gchar *)ba->data, ==, echo_removal_tests[i].without_echo); + + g_byte_array_unref (ba); + } +} + +void +_mm_log (const char *loc, + const char *func, + guint32 level, + const char *fmt, + ...) +{ + /* Dummy log function */ +} + +int main (int argc, char **argv) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/ModemManager/AT-serial/echo-removal", at_serial_echo_removal); + + return g_test_run (); +} diff --git a/src/tests/test-charsets.c b/src/tests/test-charsets.c index 70c796a..f954d93 100644 --- a/src/tests/test-charsets.c +++ b/src/tests/test-charsets.c @@ -284,6 +284,48 @@ test_pack_gsm7_24_chars (void *f, gpointer d) g_free (packed); } +static void +test_pack_gsm7_last_septet_alone (void *f, gpointer d) +{ + static const guint8 unpacked[] = { + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x61, 0x6C, + 0x6C, 0x79, 0x20, 0x63, 0x6F, 0x6F, 0x6C, 0x20, 0x10, 0x10, 0x10, 0x10, + 0x10 + }; + static const guint8 expected[] = { + 0x54, 0x74, 0x7A, 0x0E, 0x4A, 0xCF, 0x41, 0xF2, 0x72, 0x98, 0xCD, 0xCE, + 0x83, 0xC6, 0xEF, 0x37, 0x1B, 0x04, 0x81, 0x40, 0x20, 0x10 + }; + guint8 *packed; + guint32 packed_len = 0; + + /* Tests that a 25-character unpacked string (where, when packed, the last + * septet will be in an octet by itself) packs correctly. + */ + + packed = gsm_pack (unpacked, sizeof (unpacked), 0, &packed_len); + g_assert (packed); + g_assert_cmpint (packed_len, ==, sizeof (expected)); + + g_free (packed); +} + +static void +test_pack_gsm7_7_chars_offset (void *f, gpointer d) +{ + static const guint8 unpacked[] = { 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x10, 0x2F }; + static const guint8 expected[] = { 0x00, 0x5D, 0x66, 0xB3, 0xDF, 0x90, 0x17 }; + guint8 *packed; + guint32 packed_len = 0; + + packed = gsm_pack (unpacked, sizeof (unpacked), 5, &packed_len); + g_assert (packed); + g_assert_cmpint (packed_len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (packed, expected, packed_len), ==, 0); + + g_free (packed); +} + #if GLIB_CHECK_VERSION(2,25,12) typedef GTestFixtureFunc TCFunc; @@ -314,6 +356,9 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_pack_gsm7_7_chars, NULL)); g_test_suite_add (suite, TESTCASE (test_pack_gsm7_all_chars, NULL)); g_test_suite_add (suite, TESTCASE (test_pack_gsm7_24_chars, NULL)); + g_test_suite_add (suite, TESTCASE (test_pack_gsm7_last_septet_alone, NULL)); + + g_test_suite_add (suite, TESTCASE (test_pack_gsm7_7_chars_offset, NULL)); result = g_test_run (); diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c index d07f0d4..3b8e2e2 100644 --- a/src/tests/test-modem-helpers.c +++ b/src/tests/test-modem-helpers.c @@ -491,8 +491,9 @@ test_creg_match (const char *test, g_assert (data); g_assert (result); - g_print ("\nTesting %s +CREG %s response...\n", + g_print ("\nTesting %s +C%sREG %s response...\n", test, + result->cgreg ? "G" : "", solicited ? "solicited" : "unsolicited"); array = solicited ? data->solicited_creg : data->unsolicited_creg; @@ -743,6 +744,16 @@ test_creg2_gobi_weird_solicited (void *f, gpointer d) } static void +test_cgreg2_unsolicited_with_rac (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 1,\"1422\",\"00000142\",3,\"00\"\r\n"; + const CregResult result = { 1, 0x1422, 0x0142, 3, 8, TRUE }; + + test_creg_match ("CGREG=2 with RAC", FALSE, reply, data, &result); +} + +static void test_cscs_icon225_support_response (void *f, gpointer d) { const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\")\r\n"; @@ -1269,6 +1280,7 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_unsolicited, data)); g_test_suite_add (suite, TESTCASE (test_cgreg2_md400_unsolicited, data)); g_test_suite_add (suite, TESTCASE (test_cgreg2_x220_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_unsolicited_with_rac, data)); g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi_unsolicited, data)); g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi2_unsolicited, data)); diff --git a/src/tests/test-qcdm-serial-port.c b/src/tests/test-qcdm-serial-port.c index 3aeed6a..c31011b 100644 --- a/src/tests/test-qcdm-serial-port.c +++ b/src/tests/test-qcdm-serial-port.c @@ -31,6 +31,7 @@ #include "libqcdm/src/commands.h" #include "libqcdm/src/utils.h" #include "libqcdm/src/com.h" +#include "libqcdm/src/errors.h" #include "mm-log.h" typedef struct { @@ -135,8 +136,9 @@ server_wait_request (int fd, char *buf, gsize len) retries++; continue; } else if (bytes_read == 1) { - gboolean more = FALSE, success; + gboolean success; gsize used = 0; + qcdmbool more = FALSE; total++; decap_len = 0; @@ -187,17 +189,14 @@ qcdm_verinfo_expect_success_cb (MMQcdmSerialPort *port, static void qcdm_request_verinfo (MMQcdmSerialPort *port, VerInfoCb cb, GMainLoop *loop) { - GError *error = NULL; GByteArray *verinfo; gint len; /* Build up the probe command */ verinfo = g_byte_array_sized_new (50); - len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error); - if (len <= 0) { + len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50); + if (len <= 0) g_byte_array_free (verinfo, TRUE); - g_assert_no_error (error); - } verinfo->len = len; mm_qcdm_serial_port_queue_command (port, verinfo, 3, cb, loop); @@ -400,9 +399,7 @@ test_pty_create (gpointer user_data) { TestData *d = user_data; struct termios stbuf; - int ret; - GError *error = NULL; - gboolean success; + int ret, err; ret = openpty (&d->master, &d->slave, NULL, NULL, NULL); g_assert (ret == 0); @@ -417,9 +414,8 @@ test_pty_create (gpointer user_data) fcntl (d->slave, F_SETFL, O_NONBLOCK); fcntl (d->master, F_SETFL, O_NONBLOCK); - success = qcdm_port_setup (d->master, &error); - g_assert_no_error (error); - g_assert (success); + err = qcdm_port_setup (d->master); + g_assert_cmpint (err, ==, QCDM_SUCCESS); } static void diff --git a/src/tests/test-sms.c b/src/tests/test-sms.c index bd18c0b..5c32ad1 100644 --- a/src/tests/test-sms.c +++ b/src/tests/test-sms.c @@ -20,6 +20,7 @@ #include "mm-sms-utils.h" #include "mm-utils.h" +#include "dbus/dbus-glib.h" #define TEST_ENTRY_EQ(hash, key, expectvalue) do { \ @@ -30,6 +31,27 @@ g_assert_cmpstr(g_value_get_string(value), ==, (expectvalue)); \ } while (0) +#define TEST_UINT_ENTRY_EQ(hash, key, expectvalue) do { \ + GValue *value; \ + value = g_hash_table_lookup((hash), (key)); \ + g_assert(value); \ + g_assert(G_VALUE_HOLDS_UINT(value)); \ + g_assert_cmpint(g_value_get_uint(value), ==, (expectvalue)); \ + } while (0) + +#define TEST_ARRAY_ENTRY_EQ(hash, key, expectvalue) do { \ + GValue *value; \ + GByteArray *tmp; \ + guint32 i; \ + value = g_hash_table_lookup((hash), (key)); \ + g_assert(value); \ + g_assert(G_VALUE_HOLDS(value, DBUS_TYPE_G_UCHAR_ARRAY)); \ + tmp = g_value_get_boxed (value); \ + g_assert_cmpint (tmp->len, ==, sizeof (expectvalue)); \ + for (i = 0; i < tmp->len; i++) \ + g_assert_cmpint (tmp->data[i], ==, expectvalue[i]); \ + } while (0) + static void test_pdu1 (void *f, gpointer d) { @@ -52,7 +74,7 @@ test_pdu1 (void *f, gpointer d) 0x28, 0xec, 0x26, 0x83, 0xbe, 0x60, 0x50, 0x78, 0x0e, 0xba, 0x97, 0xd9, 0x6c, 0x17}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -80,7 +102,7 @@ test_pdu2 (void *f, gpointer d) 0x30, 0x92, 0x91, 0x02, 0x40, 0x61, 0x08, 0x04, 0x42, 0x04, 0x35, 0x04, 0x41, 0x04, 0x42}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -106,7 +128,7 @@ test_pdu3 (void *f, gpointer d) 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -134,7 +156,7 @@ test_pdu3_nzpid (void *f, gpointer d) 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -163,7 +185,7 @@ test_pdu3_mms (void *f, gpointer d) 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -191,7 +213,7 @@ test_pdu3_natl (void *f, gpointer d) 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -217,8 +239,10 @@ test_pdu3_8bit (void *f, gpointer d) 0xf2, 0x00, 0x04, 0x11, 0x10, 0x10, 0x21, 0x43, 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37, 0xde}; + static const guint8 expected_data[] = { + 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37, 0xde }; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -228,7 +252,9 @@ test_pdu3_8bit (void *f, gpointer d) 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"); + TEST_ENTRY_EQ (sms, "text", ""); + TEST_UINT_ENTRY_EQ (sms, "data-coding-scheme", 0x04); + TEST_ARRAY_ENTRY_EQ (sms, "data", expected_data); g_free (hexpdu); g_hash_table_unref (sms); @@ -272,7 +298,7 @@ test_pdu_dcsf1 (void *f, gpointer d) 0x00, 0x47, 0xBF, 0xDD, 0x65, 0x50, 0xB8, 0x0E, 0xCA, 0xD9, 0x66}; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -301,8 +327,10 @@ test_pdu_dcsf_8bit (void *f, gpointer d) 0xf2, 0x00, 0xf4, 0x11, 0x10, 0x10, 0x21, 0x43, 0x65, 0x00, 0x0a, 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37, 0xde}; + static const guint8 expected_data[] = { + 0xe8, 0x32, 0x9b, 0xfd, 0x46, 0x97, 0xd9, 0xec, 0x37, 0xde }; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -312,7 +340,9 @@ test_pdu_dcsf_8bit (void *f, gpointer d) 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"); + TEST_ENTRY_EQ (sms, "text", ""); + TEST_UINT_ENTRY_EQ (sms, "data-coding-scheme", 0xF4); + TEST_ARRAY_ENTRY_EQ (sms, "data", expected_data); g_free (hexpdu); g_hash_table_unref (sms); @@ -329,7 +359,7 @@ test_pdu_insufficient_data (void *f, gpointer d) 0x97, 0xd9, 0xec, 0x37 }; GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu, sizeof(pdu)); @@ -351,7 +381,7 @@ test_pdu_udhi (void *f, gpointer d) "5C7683D27350984D4FABC9A0B33C4C4FCF5D20EBFB2D079DCB62793DBD06D9C36E50FB2D4E97D9" "A0B49B5E96BBCB"; GHashTable *sms; - GError *error; + GError *error = NULL; sms = sms_parse_pdu (hexpdu, &error); g_assert (sms); @@ -372,7 +402,7 @@ static void test_pduX (void *f, gpointer d) { GHashTable *sms; - GError *error; + GError *error = NULL; char *hexpdu; hexpdu = utils_bin2hexstr (pdu1, sizeof(pdu1)); @@ -390,7 +420,225 @@ test_pduX (void *f, gpointer d) } #endif +static void +test_encode_sms_addr_encode_smsc_intl (void *f, gpointer d) +{ + static const char *addr = "+19037029920"; + static const guint8 expected[] = { 0x07, 0x91, 0x91, 0x30, 0x07, 0x92, 0x29, 0xF0 }; + guint enclen; + guint8 buf[20]; + + enclen = sms_encode_address (addr, buf, sizeof (buf), TRUE); + g_assert_cmpint (enclen, ==, sizeof (expected)); + g_assert_cmpint (memcmp (buf, expected, sizeof (expected)), ==, 0); +} + +static void +test_encode_sms_addr_encode_smsc_unknown (void *f, gpointer d) +{ + static const char *addr = "9037029920"; + static const guint8 expected[] = { 0x06, 0x81, 0x09, 0x73, 0x20, 0x99, 0x02 }; + guint enclen; + guint8 buf[20]; + + enclen = sms_encode_address (addr, buf, sizeof (buf), TRUE); + g_assert_cmpint (enclen, ==, sizeof (expected)); + g_assert_cmpint (memcmp (buf, expected, sizeof (expected)), ==, 0); +} + +static void +test_encode_sms_addr_encode_intl (void *f, gpointer d) +{ + static const char *addr = "+19037029920"; + static const guint8 expected[] = { 0x0B, 0x91, 0x91, 0x30, 0x07, 0x92, 0x29, 0xF0 }; + guint enclen; + guint8 buf[20]; + + enclen = sms_encode_address (addr, buf, sizeof (buf), FALSE); + g_assert_cmpint (enclen, ==, sizeof (expected)); + g_assert_cmpint (memcmp (buf, expected, sizeof (expected)), ==, 0); +} + +static void +test_encode_sms_addr_encode_unknown (void *f, gpointer d) +{ + static const char *addr = "9037029920"; + static const guint8 expected[] = { 0x0A, 0x81, 0x09, 0x73, 0x20, 0x99, 0x02 }; + guint enclen; + guint8 buf[20]; + + enclen = sms_encode_address (addr, buf, sizeof (buf), FALSE); + g_assert_cmpint (enclen, ==, sizeof (expected)); + g_assert_cmpint (memcmp (buf, expected, sizeof (expected)), ==, 0); +} + +static void +test_create_pdu_ucs2_with_smsc (void *f, gpointer d) +{ + static const char *smsc = "+19037029920"; + static const char *number = "+15555551234"; + static const char *text = "Да здравствует король, детка!"; + static const guint8 expected[] = { + 0x07, 0x91, 0x91, 0x30, 0x07, 0x92, 0x29, 0xF0, 0x11, 0x00, 0x0B, 0x91, + 0x51, 0x55, 0x55, 0x15, 0x32, 0xF4, 0x00, 0x08, 0x00, 0x3A, 0x04, 0x14, + 0x04, 0x30, 0x00, 0x20, 0x04, 0x37, 0x04, 0x34, 0x04, 0x40, 0x04, 0x30, + 0x04, 0x32, 0x04, 0x41, 0x04, 0x42, 0x04, 0x32, 0x04, 0x43, 0x04, 0x35, + 0x04, 0x42, 0x00, 0x20, 0x04, 0x3A, 0x04, 0x3E, 0x04, 0x40, 0x04, 0x3E, + 0x04, 0x3B, 0x04, 0x4C, 0x00, 0x2C, 0x00, 0x20, 0x04, 0x34, 0x04, 0x35, + 0x04, 0x42, 0x04, 0x3A, 0x04, 0x30, 0x00, 0x21 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + pdu = sms_create_submit_pdu (number, text, smsc, 5, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 8); +} + +static void +test_create_pdu_ucs2_no_smsc (void *f, gpointer d) +{ + static const char *number = "+15555551234"; + static const char *text = "Да здравствует король, детка!"; + static const guint8 expected[] = { + 0x00, 0x11, 0x00, 0x0B, 0x91, 0x51, 0x55, 0x55, 0x15, 0x32, 0xF4, 0x00, + 0x08, 0x00, 0x3A, 0x04, 0x14, 0x04, 0x30, 0x00, 0x20, 0x04, 0x37, 0x04, + 0x34, 0x04, 0x40, 0x04, 0x30, 0x04, 0x32, 0x04, 0x41, 0x04, 0x42, 0x04, + 0x32, 0x04, 0x43, 0x04, 0x35, 0x04, 0x42, 0x00, 0x20, 0x04, 0x3A, 0x04, + 0x3E, 0x04, 0x40, 0x04, 0x3E, 0x04, 0x3B, 0x04, 0x4C, 0x00, 0x2C, 0x00, + 0x20, 0x04, 0x34, 0x04, 0x35, 0x04, 0x42, 0x04, 0x3A, 0x04, 0x30, 0x00, + 0x21 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + pdu = sms_create_submit_pdu (number, text, NULL, 5, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 1); +} + +static void +test_create_pdu_gsm_with_smsc (void *f, gpointer d) +{ + static const char *smsc = "+19037029920"; + static const char *number = "+15555551234"; + static const char *text = "Hi there...Tue 17th Jan 2012 05:30.18 pm (GMT+1) ΔΔΔΔΔ"; + static const guint8 expected[] = { + 0x07, 0x91, 0x91, 0x30, 0x07, 0x92, 0x29, 0xF0, 0x11, 0x00, 0x0B, 0x91, + 0x51, 0x55, 0x55, 0x15, 0x32, 0xF4, 0x00, 0x00, 0x00, 0x36, 0xC8, 0x34, + 0x88, 0x8E, 0x2E, 0xCB, 0xCB, 0x2E, 0x97, 0x8B, 0x5A, 0x2F, 0x83, 0x62, + 0x37, 0x3A, 0x1A, 0xA4, 0x0C, 0xBB, 0x41, 0x32, 0x58, 0x4C, 0x06, 0x82, + 0xD5, 0x74, 0x33, 0x98, 0x2B, 0x86, 0x03, 0xC1, 0xDB, 0x20, 0xD4, 0xB1, + 0x49, 0x5D, 0xC5, 0x52, 0x20, 0x08, 0x04, 0x02, 0x81, 0x00 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + pdu = sms_create_submit_pdu (number, text, smsc, 5, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 8); +} + +static void +test_create_pdu_gsm_no_smsc (void *f, gpointer d) +{ + static const char *number = "+15555551234"; + static const char *text = "Hi there...Tue 17th Jan 2012 05:30.18 pm (GMT+1) ΔΔΔΔΔ"; + static const guint8 expected[] = { + 0x00, 0x11, 0x00, 0x0B, 0x91, 0x51, 0x55, 0x55, 0x15, 0x32, 0xF4, 0x00, + 0x00, 0x00, 0x36, 0xC8, 0x34, 0x88, 0x8E, 0x2E, 0xCB, 0xCB, 0x2E, 0x97, + 0x8B, 0x5A, 0x2F, 0x83, 0x62, 0x37, 0x3A, 0x1A, 0xA4, 0x0C, 0xBB, 0x41, + 0x32, 0x58, 0x4C, 0x06, 0x82, 0xD5, 0x74, 0x33, 0x98, 0x2B, 0x86, 0x03, + 0xC1, 0xDB, 0x20, 0xD4, 0xB1, 0x49, 0x5D, 0xC5, 0x52, 0x20, 0x08, 0x04, + 0x02, 0x81, 0x00 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + pdu = sms_create_submit_pdu (number, text, NULL, 5, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 1); +} + +static void +test_create_pdu_gsm_3 (void *f, gpointer d) +{ + static const char *number = "+15556661234"; + static const char *text = "This is really cool ΔΔΔΔΔ"; + static const guint8 expected[] = { + 0x00, 0x11, 0x00, 0x0B, 0x91, 0x51, 0x55, 0x66, 0x16, 0x32, 0xF4, 0x00, + 0x00, 0x00, 0x19, 0x54, 0x74, 0x7A, 0x0E, 0x4A, 0xCF, 0x41, 0xF2, 0x72, + 0x98, 0xCD, 0xCE, 0x83, 0xC6, 0xEF, 0x37, 0x1B, 0x04, 0x81, 0x40, 0x20, + 0x10 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + /* Tests that a 25-character message (where the last septet is packed into + * an octet by itself) is created correctly. Previous to + * "core: fix some bugs in GSM7 packing code" the GSM packing code would + * leave off the last octet. + */ + + pdu = sms_create_submit_pdu (number, text, NULL, 5, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 1); +} + +static void +test_create_pdu_gsm_no_validity (void *f, gpointer d) +{ + static const char *number = "+15556661234"; + static const char *text = "This is really cool ΔΔΔΔΔ"; + static const guint8 expected[] = { + 0x00, 0x01, 0x00, 0x0B, 0x91, 0x51, 0x55, 0x66, 0x16, 0x32, 0xF4, 0x00, + 0x00, 0x19, 0x54, 0x74, 0x7A, 0x0E, 0x4A, 0xCF, 0x41, 0xF2, 0x72, 0x98, + 0xCD, 0xCE, 0x83, 0xC6, 0xEF, 0x37, 0x1B, 0x04, 0x81, 0x40, 0x20, 0x10 + }; + guint8 *pdu; + guint len = 0, msgstart = 0; + GError *error = NULL; + + pdu = sms_create_submit_pdu (number, text, NULL, 0, 0, &len, &msgstart, &error); + g_assert_no_error (error); + g_assert (pdu); + g_assert_cmpint (len, ==, sizeof (expected)); + g_assert_cmpint (memcmp (pdu, expected, len), ==, 0); + g_assert_cmpint (msgstart, ==, 1); +} +#if 0 +{ +int i; +g_print ("\n "); +for (i = 0; i < len; i++) { + g_print (" 0x%02X", pdu[i]); + if (((i + 1) % 12) == 0) + g_print ("\n "); +} +g_print ("\n"); +} +#endif #if GLIB_CHECK_VERSION(2,25,12) typedef GTestFixtureFunc TCFunc; @@ -423,6 +671,19 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_pdu_insufficient_data, NULL)); g_test_suite_add (suite, TESTCASE (test_pdu_udhi, NULL)); + g_test_suite_add (suite, TESTCASE (test_encode_sms_addr_encode_smsc_intl, NULL)); + g_test_suite_add (suite, TESTCASE (test_encode_sms_addr_encode_smsc_unknown, NULL)); + g_test_suite_add (suite, TESTCASE (test_encode_sms_addr_encode_intl, NULL)); + g_test_suite_add (suite, TESTCASE (test_encode_sms_addr_encode_unknown, NULL)); + + g_test_suite_add (suite, TESTCASE (test_create_pdu_ucs2_with_smsc, NULL)); + g_test_suite_add (suite, TESTCASE (test_create_pdu_ucs2_no_smsc, NULL)); + g_test_suite_add (suite, TESTCASE (test_create_pdu_gsm_with_smsc, NULL)); + g_test_suite_add (suite, TESTCASE (test_create_pdu_gsm_no_smsc, NULL)); + + g_test_suite_add (suite, TESTCASE (test_create_pdu_gsm_3, NULL)); + g_test_suite_add (suite, TESTCASE (test_create_pdu_gsm_no_validity, NULL)); + result = g_test_run (); return result; |