aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--introspection/org.freedesktop.ModemManager1.Modem.CellBroadcast.xml22
-rw-r--r--libmm-glib/mm-common-helpers.c71
-rw-r--r--libmm-glib/mm-common-helpers.h7
-rw-r--r--libmm-glib/mm-helper-types.h16
-rw-r--r--src/mm-broadband-modem.c123
-rw-r--r--src/mm-iface-modem-cell-broadcast.c141
-rw-r--r--src/mm-iface-modem-cell-broadcast.h18
-rw-r--r--src/mm-modem-helpers.c136
-rw-r--r--src/mm-modem-helpers.h5
-rw-r--r--src/tests/test-modem-helpers.c96
10 files changed, 635 insertions, 0 deletions
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.CellBroadcast.xml b/introspection/org.freedesktop.ModemManager1.Modem.CellBroadcast.xml
index 9e8584a1..572e094c 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.CellBroadcast.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.CellBroadcast.xml
@@ -87,5 +87,27 @@
-->
<property name="CellBroadcasts" type="ao" access="read" />
+ <!--
+ SetChannels:
+ @channels: The list of channels
+
+ Set the list of channels to receive Cell Broadcasts for.
+
+ Since: 1.24
+ -->
+ <method name="SetChannels">
+ <arg name="channels" type="a(uu)" direction="in" />
+ </method>
+
+ <!--
+ Channels:
+
+ The list of channels that cell broadcast messages are
+ received for.
+
+ Since: 1.24
+ -->
+ <property name="Channels" type="a(uu)" access="read" />
+
</interface>
</node>
diff --git a/libmm-glib/mm-common-helpers.c b/libmm-glib/mm-common-helpers.c
index 72007ab5..e249a2fc 100644
--- a/libmm-glib/mm-common-helpers.c
+++ b/libmm-glib/mm-common-helpers.c
@@ -1281,6 +1281,77 @@ mm_common_build_oma_pending_network_initiated_sessions_default (void)
}
/******************************************************************************/
+/* MMModemCellbroadcastChannel array management */
+
+GArray *
+mm_common_cell_broadcast_channels_variant_to_garray (GVariant *variant)
+{
+ GArray *array = NULL;
+
+ if (variant) {
+ GVariantIter iter;
+ guint n;
+
+ g_variant_iter_init (&iter, variant);
+ n = g_variant_iter_n_children (&iter);
+
+ if (n > 0) {
+ MMCellBroadcastChannels channels;
+
+ array = g_array_sized_new (FALSE, FALSE, sizeof (MMCellBroadcastChannels), n);
+ while (g_variant_iter_loop (&iter, "(uu)", &channels.start, &channels.end))
+ g_array_append_val (array, channels);
+ }
+ }
+
+ /* If nothing set, fallback to empty */
+ if (!array)
+ array = g_array_new (FALSE, FALSE, sizeof (MMCellBroadcastChannels));
+
+ return array;
+}
+
+GVariant *
+mm_common_cell_broadcast_channels_array_to_variant (const MMCellBroadcastChannels *channels,
+ guint n_sessions)
+{
+ if (n_sessions > 0) {
+ GVariantBuilder builder;
+ guint i;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));
+
+ for (i = 0; i < n_sessions; i++)
+ g_variant_builder_add_value (&builder,
+ g_variant_new ("(uu)",
+ ((guint32)channels[i].start),
+ ((guint32)channels[i].end)));
+ return g_variant_builder_end (&builder);
+ }
+
+ return mm_common_build_cell_broadcast_channels_default ();
+}
+
+GVariant *
+mm_common_cell_broadcast_channels_garray_to_variant (GArray *array)
+{
+ if (array)
+ return mm_common_cell_broadcast_channels_array_to_variant ((const MMCellBroadcastChannels *)array->data,
+ array->len);
+
+ return mm_common_cell_broadcast_channels_array_to_variant (NULL, 0);
+}
+
+GVariant *
+mm_common_build_cell_broadcast_channels_default (void)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));
+ return g_variant_builder_end (&builder);
+}
+
+/******************************************************************************/
/* Common parsers */
/* Expecting input as:
diff --git a/libmm-glib/mm-common-helpers.h b/libmm-glib/mm-common-helpers.h
index 6ff89e21..bea220e0 100644
--- a/libmm-glib/mm-common-helpers.h
+++ b/libmm-glib/mm-common-helpers.h
@@ -164,6 +164,13 @@ GVariant *mm_common_oma_pending_network_initiated_sessions_array_to_variant (co
GVariant *mm_common_oma_pending_network_initiated_sessions_garray_to_variant (GArray *array);
GVariant *mm_common_build_oma_pending_network_initiated_sessions_default (void);
+/* MMModemCellbroadcastChannel array management */
+GArray *mm_common_cell_broadcast_channels_variant_to_garray (GVariant *variant);
+GVariant *mm_common_cell_broadcast_channels_array_to_variant (const MMCellBroadcastChannels *channels,
+ guint n_sessions);
+GVariant *mm_common_cell_broadcast_channels_garray_to_variant (GArray *array);
+GVariant *mm_common_build_cell_broadcast_channels_default (void);
+
/******************************************************************************/
const gchar *mm_common_str_boolean (gboolean value);
diff --git a/libmm-glib/mm-helper-types.h b/libmm-glib/mm-helper-types.h
index 0d9de269..cc0d3a28 100644
--- a/libmm-glib/mm-helper-types.h
+++ b/libmm-glib/mm-helper-types.h
@@ -80,4 +80,20 @@ struct _MMOmaPendingNetworkInitiatedSession {
guint session_id;
};
+/**
+ * MMCellBroadcastChannels:
+ * @start: The first channel
+ * @end: The last channel
+ *
+ * #MMCellBroadcastChannels is a simple struct specifying the start and end
+ * of a channel interval.
+ *
+ * Since: 1.24
+ */
+typedef struct _MMCellBroadcastChannels MMCellBroadcastChannels;
+struct _MMCellBroadcastChannels {
+ guint start;
+ guint end;
+};
+
#endif /* _MM_HELPER_TYPES_H_ */
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 4b13fd09..5e026449 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -10500,6 +10500,65 @@ modem_cdma_register_in_network (MMIfaceModemCdma *_self,
}
/*****************************************************************************/
+/* Load currently active channels (CellBroadcast interface) */
+
+static GArray *
+modem_cell_broadcast_load_channels_finish (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+cscb_channels_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GArray *result;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Parse reply */
+ result = mm_3gpp_parse_cscb_response (response, &error);
+ if (!result) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_pointer (task,
+ result,
+ (GDestroyNotify)g_array_unref);
+ g_object_unref (task);
+}
+
+static void
+modem_cell_broadcast_load_channels (MMIfaceModemCellBroadcast *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Load configured channels */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSCB?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cscb_channels_format_check_ready,
+ task);
+}
+
+/*****************************************************************************/
static gboolean
modem_cell_broadcast_setup_cleanup_unsolicited_events_finish (MMIfaceModemCellBroadcast *self,
@@ -10603,6 +10662,66 @@ modem_cell_broadcast_create_cbm (MMIfaceModemCellBroadcast *self)
return mm_base_cbm_new (MM_BASE_MODEM (self));
}
+/***********************************************************************************/
+/* Get channels (CellBroadcast interface) */
+
+static gboolean
+modem_cell_broadcast_set_channels_finish (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_cell_broadcast_set_channels_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_cell_broadcast_set_channels (MMIfaceModemCellBroadcast *self,
+ GArray *channels,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ g_autoptr (GString) cmd = g_string_new ("+CSCB=0,\"");
+ guint i;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ for (i = 0; i < channels->len; i++) {
+ MMCellBroadcastChannels ch = g_array_index (channels, MMCellBroadcastChannels, i);
+
+ if (i > 0)
+ g_string_append_c (cmd, ',');
+
+ if (ch.start == ch.end)
+ g_string_append_printf (cmd, "%u", ch.start);
+ else
+ g_string_append_printf (cmd, "%u-%u", ch.start, ch.end);
+ }
+ g_string_append (cmd, "\",\"\"");
+
+ mm_obj_dbg (self, "Setting channels...");
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ cmd->str,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)modem_cell_broadcast_set_channels_ready,
+ task);
+}
+
/*********************************************************/
/* Check CellBroadcast support (CellBroadcast interface) */
@@ -14342,8 +14461,12 @@ iface_modem_cell_broadcast_init (MMIfaceModemCellBroadcastInterface *iface)
iface->check_support_finish = modem_cell_broadcast_check_support_finish;
iface->setup_unsolicited_events = modem_cell_broadcast_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_cell_broadcast_setup_cleanup_unsolicited_events_finish;
+ iface->load_channels = modem_cell_broadcast_load_channels;
+ iface->load_channels_finish = modem_cell_broadcast_load_channels_finish;
iface->cleanup_unsolicited_events = modem_cell_broadcast_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_cell_broadcast_setup_cleanup_unsolicited_events_finish;
+ iface->set_channels = modem_cell_broadcast_set_channels;
+ iface->set_channels_finish = modem_cell_broadcast_set_channels_finish;
iface->create_cbm = modem_cell_broadcast_create_cbm;
}
diff --git a/src/mm-iface-modem-cell-broadcast.c b/src/mm-iface-modem-cell-broadcast.c
index 8e5af67c..f543d8f9 100644
--- a/src/mm-iface-modem-cell-broadcast.c
+++ b/src/mm-iface-modem-cell-broadcast.c
@@ -42,6 +42,102 @@ mm_iface_modem_cell_broadcast_bind_simple_status (MMIfaceModemCellBroadcast *sel
/*****************************************************************************/
+
+typedef struct {
+ MmGdbusModemCellBroadcast *skeleton;
+ GDBusMethodInvocation *invocation;
+ MMIfaceModemCellBroadcast *self;
+ GArray *channels;
+} HandleSetChannelsCellBroadcastContext;
+
+static void
+handle_set_channels_context_free (HandleSetChannelsCellBroadcastContext *ctx)
+{
+ g_object_unref (ctx->skeleton);
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->self);
+ g_array_unref (ctx->channels);
+ g_slice_free (HandleSetChannelsCellBroadcastContext, ctx);
+}
+
+static void
+set_channels_ready (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ HandleSetChannelsCellBroadcastContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->set_channels_finish (self, res, &error))
+ mm_dbus_method_invocation_take_error (ctx->invocation, error);
+ else {
+ mm_gdbus_modem_cell_broadcast_set_channels (ctx->skeleton,
+ mm_common_cell_broadcast_channels_garray_to_variant (ctx->channels));
+ mm_gdbus_modem_cell_broadcast_complete_set_channels (ctx->skeleton, ctx->invocation);
+ }
+
+ handle_set_channels_context_free (ctx);
+}
+
+static void
+handle_set_channels_auth_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ HandleSetChannelsCellBroadcastContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (self, res, &error)) {
+ mm_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_set_channels_context_free (ctx);
+ return;
+ }
+
+ /* Validate channels (either number or range) */
+ if (!mm_validate_cbs_channels (ctx->channels, &error)) {
+ mm_dbus_method_invocation_return_gerror (ctx->invocation, error);
+ handle_set_channels_context_free (ctx);
+ return;
+ }
+
+ /* Check if plugin implements it */
+ if (!MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->set_channels ||
+ !MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->set_channels_finish) {
+ mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot set channels: not implemented");
+ handle_set_channels_context_free (ctx);
+ return;
+ }
+
+ /* Request to change channels */
+ mm_obj_info (self, "processing user request to set channels...");
+ MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->set_channels (ctx->self,
+ ctx->channels,
+ (GAsyncReadyCallback)set_channels_ready,
+ ctx);
+}
+
+static gboolean
+handle_set_channels (MmGdbusModemCellBroadcast *skeleton,
+ GDBusMethodInvocation *invocation,
+ GVariant *channels,
+ MMIfaceModemCellBroadcast *self)
+{
+ HandleSetChannelsCellBroadcastContext *ctx;
+
+ ctx = g_slice_new0 (HandleSetChannelsCellBroadcastContext);
+ ctx->skeleton = g_object_ref (skeleton);
+ ctx->invocation = g_object_ref (invocation);
+ ctx->self = g_object_ref (self);
+ ctx->channels = mm_common_cell_broadcast_channels_variant_to_garray (channels);
+ mm_base_modem_authorize (MM_BASE_MODEM (self),
+ invocation,
+ MM_AUTHORIZATION_DEVICE_CONTROL,
+ (GAsyncReadyCallback)handle_set_channels_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
typedef struct {
MmGdbusModemCellBroadcast *skeleton;
GDBusMethodInvocation *invocation;
@@ -315,6 +411,12 @@ interface_initialization_step (GTask *task)
case INITIALIZATION_STEP_LAST:
/* We are done without errors! */
+
+ /* Handle method invocations */
+ g_signal_connect (ctx->skeleton,
+ "handle-set-channels",
+ G_CALLBACK (handle_set_channels),
+ self);
g_signal_connect (ctx->skeleton,
"handle-delete",
G_CALLBACK (handle_delete),
@@ -425,6 +527,7 @@ typedef enum {
ENABLING_STEP_FIRST,
ENABLING_STEP_SETUP_UNSOLICITED_EVENTS,
ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS,
+ ENABLING_STEP_GET_CHANNELS,
ENABLING_STEP_LAST
} EnablingStep;
@@ -491,6 +594,31 @@ enable_unsolicited_events_ready (MMIfaceModemCellBroadcast *self,
}
static void
+load_channels_ready (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ EnablingContext *ctx;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GArray) channels = NULL;
+
+ ctx = g_task_get_task_data (task);
+ channels = MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->load_channels_finish (self, res, &error);
+ if (channels) {
+ mm_gdbus_modem_cell_broadcast_set_channels (
+ ctx->skeleton,
+ mm_common_cell_broadcast_channels_garray_to_variant (channels));
+ } else {
+ /* Not critical! */
+ mm_obj_warn (self, "Couldn't load current channel list: %s", error->message);
+ }
+
+ /* Go on with next step */
+ ctx->step++;
+ interface_enabling_step (task);
+}
+
+static void
interface_enabling_step (GTask *task)
{
MMIfaceModemCellBroadcast *self;
@@ -552,6 +680,19 @@ interface_enabling_step (GTask *task)
ctx->step++;
/* fall through */
+ case ENABLING_STEP_GET_CHANNELS:
+ /* Read current channel list */
+ if (MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->load_channels &&
+ MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->load_channels_finish) {
+ MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->load_channels (
+ self,
+ (GAsyncReadyCallback)load_channels_ready,
+ task);
+ return;
+ }
+ ctx->step++;
+ /* fall through */
+
case ENABLING_STEP_LAST:
/* We are done without errors! */
g_task_return_boolean (task, TRUE);
diff --git a/src/mm-iface-modem-cell-broadcast.h b/src/mm-iface-modem-cell-broadcast.h
index 5990b865..32170b7b 100644
--- a/src/mm-iface-modem-cell-broadcast.h
+++ b/src/mm-iface-modem-cell-broadcast.h
@@ -74,8 +74,26 @@ struct _MMIfaceModemCellBroadcastInterface {
GAsyncResult *res,
GError **error);
+ /* Asynchronous loading of channel list */
+ GArray * (*load_channels_finish) (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ GError **error);
+
+ void (*load_channels) (MMIfaceModemCellBroadcast *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
/* Create Cbm objects */
MMBaseCbm * (* create_cbm) (MMIfaceModemCellBroadcast *self);
+
+ /* Set channel list */
+ void (* set_channels) (MMIfaceModemCellBroadcast *self,
+ GArray *channels,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* set_channels_finish) (MMIfaceModemCellBroadcast *self,
+ GAsyncResult *res,
+ GError **error);
};
/* Initialize CellBroadcast interface (async) */
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index 772a2de8..4f0c6248 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -2937,6 +2937,110 @@ out:
}
/*************************************************************************/
+
+#define CBS_MAX_CHANNEL G_MAXUINT16
+
+GArray *
+mm_3gpp_parse_cscb_response (const char *response, GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *inner_error = NULL;
+ gsize len;
+ g_autoptr (GArray) array = g_array_new (FALSE, FALSE, sizeof (MMCellBroadcastChannels));
+ g_autofree char *str = NULL;
+ g_auto (GStrv) intervals = NULL;
+ int i;
+
+ len = strlen (response);
+ if (!len) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "empty channel list");
+ goto out;
+ }
+
+ /*
+ * AT+CSCB=[0|1],"<channels>","<coding-scheme>"
+ */
+ r = g_regex_new ("\\+CSCB:\\s*"
+ "(\\d),\\s*" /* [0|1] */
+ "\"([\\d,\\-]*)\"," /* channel list */
+ "\"\"", /* encodings */
+ G_REGEX_NEWLINE_CRLF,
+ 0,
+ NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, -1, 0, 0, &match_info, &inner_error);
+ if (inner_error)
+ goto out;
+
+ if (!g_match_info_matches (match_info)) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match CSCB response");
+ goto out;
+ }
+
+ str = g_match_info_fetch (match_info, 1);
+ if (!g_str_equal (str, "0")) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match type of CSCB response: '%s'", str);
+ goto out;
+ }
+
+ str = g_match_info_fetch (match_info, 2);
+ intervals = g_strsplit (str, ",", -1);
+ for (i = 0; intervals[i]; i++) {
+ gchar *interval_separator;
+
+ g_strstrip (intervals[i]);
+ interval_separator = strstr (intervals[i], "-");
+ if (interval_separator) {
+ /* Add an interval */
+ gchar *end;
+ g_autofree gchar *start = NULL;
+ MMCellBroadcastChannels channels;
+
+ start = g_strdup (intervals[i]);
+ interval_separator = strstr (start, "-");
+ *(interval_separator++) = '\0';
+ end = interval_separator;
+
+ if (mm_get_uint_from_str (start, &channels.start) &&
+ mm_get_uint_from_str (end, &channels.end) &&
+ channels.start <= channels.end &&
+ channels.end <= CBS_MAX_CHANNEL)
+ g_array_append_val (array, channels);
+ else {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse CSCB interval '%s'", intervals[i]);
+ goto out;
+ }
+ } else {
+ guint channel;
+
+ /* Add single value */
+ if (mm_get_uint_from_str (intervals[i], &channel)) {
+ MMCellBroadcastChannels channels = {
+ .start = channel,
+ .end = channel
+ };
+ g_array_append_val (array, channels);
+ } else {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse CSCB value '%s'", intervals[i]);
+ goto out;
+ }
+ }
+ }
+
+ return g_steal_pointer (&array);
+
+ out:
+ g_assert (inner_error);
+ g_propagate_error (error, inner_error);
+ return NULL;
+}
+
+/*************************************************************************/
/* CGATT helpers */
gchar *
@@ -5201,6 +5305,38 @@ out:
/*****************************************************************************/
gboolean
+mm_validate_cbs_channels (GArray *channels, GError **error)
+{
+ guint i;
+
+ for (i = 0; i < channels->len; i++) {
+ MMCellBroadcastChannels ch = g_array_index (channels, MMCellBroadcastChannels, i);
+
+ if (ch.end < ch.start) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid channels: End channel smaller than start channel");
+ return FALSE;
+ }
+
+ if (ch.start > CBS_MAX_CHANNEL) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid channels: Start channel %u too large", ch.start);
+ return FALSE;
+ }
+
+ if (ch.end > CBS_MAX_CHANNEL) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid channels: End channel %u too large", ch.end);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
mm_sim_parse_cpol_query_response (const gchar *response,
guint *out_index,
gchar **out_operator_code,
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index f4b2ae8b..133c7006 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -418,6 +418,8 @@ gboolean mm_3gpp_parse_ccwa_service_query_response (const gchar *response,
gboolean *status,
GError **error);
+GArray *mm_3gpp_parse_cscb_response (const char *raw, GError **error);
+
/* CGATT helpers */
gchar *mm_3gpp_build_cgatt_set_request (MMModem3gppPacketServiceState state);
@@ -550,6 +552,9 @@ gboolean mm_parse_supl_address (const gchar *supl,
guint16 *out_port,
GError **error);
+gboolean mm_validate_cbs_channels (GArray *channels,
+ GError **error);
+
/*****************************************************************************/
/* SIM specific helpers and utilities */
/*****************************************************************************/
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index 538aedfe..6692e56d 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -4278,6 +4278,100 @@ test_ccwa_response (void)
}
/*****************************************************************************/
+/* Test +CSCB channel lists */
+
+typedef struct {
+ const gchar *response;
+ const MMCellBroadcastChannels *channels;
+ guint len;
+ gboolean error;
+} TestCscb;
+
+static void
+common_test_cscb_response (const gchar *response, TestCscb *expected)
+{
+ GError *error = NULL;
+ GArray *result;
+
+ g_debug ("Testing '%s'", response);
+ result = mm_3gpp_parse_cscb_response (response, &error);
+
+ if (expected->error) {
+ g_assert (!result);
+ g_assert_nonnull (error);
+ g_error_free (error);
+ } else {
+ guint i;
+
+ g_assert_no_error (error);
+ g_assert (result);
+ g_assert_cmpint (result->len, ==, expected->len);
+ for (i = 0; i < expected->len; i++) {
+ MMCellBroadcastChannels ch = g_array_index (result, MMCellBroadcastChannels, i);
+
+ g_assert_cmpuint (ch.start, ==, expected->channels[i].start);
+ g_assert_cmpuint (ch.end, ==, expected->channels[i].end);
+ }
+ }
+}
+
+static const MMCellBroadcastChannels cscb_one_channel[] = {
+ { .start = 0, .end = 0 },
+};
+
+static const MMCellBroadcastChannels cscb_all_channels[] = {
+ { .start = 0, .end = 65535 },
+};
+
+static const MMCellBroadcastChannels cscb_interval_channels[] = {
+ { .start = 0, .end = 1 },
+ { .start = 100, .end = 200 },
+};
+
+static const MMCellBroadcastChannels cscb_dell5821e_channels[] = {
+ { .start = 4383, .end = 4383 },
+ { .start = 4400, .end = 4400 },
+ { .start = 4370, .end = 4370 },
+ { .start = 4371, .end = 4378 },
+ { .start = 4384, .end = 4391 },
+ { .start = 4396, .end = 4397 },
+};
+
+static TestCscb test_cscb[] = {
+ { "\r\n+CSCB: 0, \"0\",\"\"\r\n\r\nOK\r\n", /* one channel */
+ cscb_one_channel,
+ G_N_ELEMENTS (cscb_all_channels),
+ FALSE },
+ { "+CSCB: 0,\"0-65535\",\"\"\r\n", /* all channels */
+ cscb_all_channels,
+ G_N_ELEMENTS (cscb_all_channels),
+ FALSE },
+ { "+CSCB: 0,\"0-1,100-200\",\"\"\r\n", /* intervals */
+ cscb_interval_channels,
+ G_N_ELEMENTS (cscb_interval_channels),
+ FALSE },
+ /* Dell 5821e/T77W968 defaults */
+ { "+CSCB: 0, \"4383,4400,4370,4371-4378,4384-4391,4396-4397\",\"\"",
+ cscb_dell5821e_channels,
+ G_N_ELEMENTS (cscb_dell5821e_channels),
+ FALSE },
+ { "+CSCB: 0,\"0-\",\"\"\r\n", /* broken interval */
+ NULL,
+ 0,
+ TRUE },
+};
+
+static void
+test_cscb_response (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (test_cscb); i++)
+ common_test_cscb_response (test_cscb[i].response, &test_cscb[i]);
+}
+
+
+/*****************************************************************************/
/* Test +CLCC URCs */
static void
@@ -4994,6 +5088,8 @@ int main (int argc, char **argv)
g_test_suite_add (suite, TESTCASE (test_ccwa_indication, NULL));
g_test_suite_add (suite, TESTCASE (test_ccwa_response, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cscb_response, NULL));
+
g_test_suite_add (suite, TESTCASE (test_clcc_response_empty, NULL));
g_test_suite_add (suite, TESTCASE (test_clcc_response_single, NULL));
g_test_suite_add (suite, TESTCASE (test_clcc_response_single_long, NULL));