aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:27 +0100
committerGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:27 +0100
commit14d771b90f5a7d3887e5e900d1fb4737477ad305 (patch)
treef382e3359d20916ae60d28361e59635e373224f8 /src
parenta09050a7f63a262bf90dcb1c7a41f9cfd205db43 (diff)
Imported Upstream version 0.5.2.0upstream/0.5.2.0
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.in2
-rw-r--r--src/mm-at-serial-port.c83
-rw-r--r--src/mm-at-serial-port.h7
-rw-r--r--src/mm-charsets.c320
-rw-r--r--src/mm-charsets.h9
-rw-r--r--src/mm-errors.c141
-rw-r--r--src/mm-errors.h39
-rw-r--r--src/mm-generic-cdma.c67
-rw-r--r--src/mm-generic-gsm.c1228
-rw-r--r--src/mm-generic-gsm.h11
-rw-r--r--src/mm-manager.c1
-rw-r--r--src/mm-manager.h1
-rw-r--r--src/mm-modem-base.c3
-rw-r--r--src/mm-modem-base.h1
-rw-r--r--src/mm-modem-cdma.c2
-rw-r--r--src/mm-modem-gsm-network.c2
-rw-r--r--src/mm-modem-gsm-sms.c43
-rw-r--r--src/mm-modem-gsm-sms.h9
-rw-r--r--src/mm-modem-gsm-ussd.h4
-rw-r--r--src/mm-modem-helpers.c88
-rw-r--r--src/mm-modem.c4
-rw-r--r--src/mm-plugin-base.c45
-rw-r--r--src/mm-plugin-base.h1
-rw-r--r--src/mm-port.h1
-rw-r--r--src/mm-qcdm-serial-port.c14
-rw-r--r--src/mm-qcdm-serial-port.h1
-rw-r--r--src/mm-serial-parsers.c78
-rw-r--r--src/mm-serial-port.c17
-rw-r--r--src/mm-serial-port.h1
-rw-r--r--src/mm-sms-utils.c515
-rw-r--r--src/mm-sms-utils.h24
-rw-r--r--src/tests/Makefile.am16
-rw-r--r--src/tests/Makefile.in63
-rw-r--r--src/tests/test-at-serial-port.c86
-rw-r--r--src/tests/test-charsets.c45
-rw-r--r--src/tests/test-modem-helpers.c14
-rw-r--r--src/tests/test-qcdm-serial-port.c20
-rw-r--r--src/tests/test-sms.c289
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;