summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDenis Kenzior <denkenz@gmail.com>2009-07-02 19:00:27 -0500
committerDenis Kenzior <denkenz@gmail.com>2009-07-02 19:03:05 -0500
commita9f776123ead64166e8338fb3fca59fb4a6d93ab (patch)
tree6f469c91ffe9e85e451de74e2cc63b9a0214044b /src
parentbf2543b2072e6ec2af92de876a080b65695905d1 (diff)
downloadofono-a9f776123ead64166e8338fb3fca59fb4a6d93ab.tar.bz2
Add Cell Broadcast assembly utilities
Diffstat (limited to 'src')
-rw-r--r--src/smsutil.c259
-rw-r--r--src/smsutil.h20
2 files changed, 279 insertions, 0 deletions
diff --git a/src/smsutil.c b/src/smsutil.c
index c3cb0bad..161412fc 100644
--- a/src/smsutil.c
+++ b/src/smsutil.c
@@ -2848,3 +2848,262 @@ char *cbs_decode_text(GSList *cbs_list, char *iso639_lang)
g_free(buf);
return utf8;
}
+
+static inline gboolean cbs_is_update_newer(unsigned int n, unsigned int o)
+{
+ unsigned int old_update = o & 0xf;
+ unsigned int new_update = n & 0xf;
+
+ if (new_update == old_update)
+ return FALSE;
+
+ /* Any Update Number eight or less higher (modulo 16) than the last
+ * received Update Number will be considered more recent, and shall be
+ * treated as a new CBS message, provided the mobile has not been
+ * switched off.
+ */
+ if (new_update <= ((old_update + 8) % 16))
+ return TRUE;
+
+ return FALSE;
+}
+
+struct cbs_assembly *cbs_assembly_new()
+{
+ return g_new0(struct cbs_assembly, 1);
+}
+
+void cbs_assembly_free(struct cbs_assembly *assembly)
+{
+ GSList *l;
+
+ for (l = assembly->assembly_list; l; l = l->next) {
+ struct cbs_assembly_node *node = l->data;
+
+ g_slist_foreach(node->pages, (GFunc)g_free, 0);
+ g_slist_free(node->pages);
+ g_free(node);
+ }
+
+ g_slist_free(assembly->assembly_list);
+ g_slist_free(assembly->recv_plmn);
+ g_slist_free(assembly->recv_loc);
+ g_slist_free(assembly->recv_cell);
+
+ g_free(assembly);
+}
+
+static gint cbs_compare_node_by_gs(gconstpointer a, gconstpointer b)
+{
+ const struct cbs_assembly_node *node = a;
+ unsigned int gs = GPOINTER_TO_UINT(b);
+
+ if (((node->serial >> 14) & 0x3) == gs)
+ return 0;
+
+ return 1;
+}
+
+static gint cbs_compare_node_by_update(gconstpointer a, gconstpointer b)
+{
+ const struct cbs_assembly_node *node = a;
+ unsigned int serial = GPOINTER_TO_UINT(b);
+
+ if ((serial & (~0xf)) != (node->serial & (~0xf)))
+ return 1;
+
+ if (cbs_is_update_newer(node->serial, serial))
+ return 1;
+
+ return 0;
+}
+
+static gint cbs_compare_recv_by_serial(gconstpointer a, gconstpointer b)
+{
+ unsigned int old_serial = GPOINTER_TO_UINT(a);
+ unsigned int new_serial = GPOINTER_TO_UINT(b);
+
+ if ((old_serial & (~0xf)) == (new_serial & (~0xf)))
+ return 0;
+
+ return 1;
+}
+
+static void cbs_assembly_expire(struct cbs_assembly *assembly,
+ GCompareFunc func, gconstpointer *userdata)
+{
+ GSList *l;
+ GSList *prev;
+ GSList *tmp;
+
+ /* Take care of the case where several updates are being
+ * reassembled at the same time. If the newer one is assembled
+ * first, then the subsequent old update is discarded, make
+ * sure that we're also discarding the assembly node for the
+ * partially assembled ones
+ */
+ prev = NULL;
+ l = assembly->assembly_list;
+
+ while (l) {
+ struct cbs_assembly_node *node = l->data;
+
+ if (func(node, userdata) != 0) {
+ prev = l;
+ l = l->next;
+ continue;
+ }
+
+ if (prev)
+ prev->next = l->next;
+ else
+ assembly->assembly_list = l->next;
+
+ g_slist_foreach(node->pages, (GFunc)g_free, NULL);
+ g_slist_free(node->pages);
+ g_free(node->pages);
+ tmp = l;
+ l = l->next;
+ g_slist_free_1(tmp);
+ }
+}
+
+void cbs_assembly_location_changed(struct cbs_assembly *assembly,
+ gboolean lac, gboolean ci)
+{
+ /* Location Area wide (in GSM) (which means that a CBS message with the
+ * same Message Code and Update Number may or may not be "new" in the
+ * next cell according to whether the next cell is in the same Location
+ * Area as the current cell), or
+ *
+ * Service Area Wide (in UMTS) (which means that a CBS message with the
+ * same Message Code and Update Number may or may not be "new" in the
+ * next cell according to whether the next cell is in the same Service
+ * Area as the current cell)
+ *
+ * NOTE 4: According to 3GPP TS 23.003 [2] a Service Area consists of
+ * one cell only.
+ */
+
+ if (lac) {
+ /* If LAC changed, then cell id has changed */
+ ci = TRUE;
+ g_slist_free(assembly->recv_loc);
+ assembly->recv_loc = NULL;
+
+ cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
+ GUINT_TO_POINTER(CBS_GEO_SCOPE_SERVICE_AREA));
+ }
+
+ if (ci) {
+ g_slist_free(assembly->recv_cell);
+ assembly->recv_cell = NULL;
+ cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
+ GUINT_TO_POINTER(CBS_GEO_SCOPE_CELL_IMMEDIATE));
+ cbs_assembly_expire(assembly, cbs_compare_node_by_gs,
+ GUINT_TO_POINTER(CBS_GEO_SCOPE_CELL_NORMAL));
+ }
+}
+
+GSList *cbs_assembly_add_page(struct cbs_assembly *assembly,
+ const struct cbs *cbs)
+{
+ struct cbs *newcbs;
+ struct cbs_assembly_node *node;
+ GSList *completed;
+ unsigned int new_serial;
+ GSList **recv;
+ GSList *l;
+ GSList *prev;
+ int position;
+
+ new_serial = cbs->gs << 14;
+ new_serial |= cbs->message_code << 4;
+ new_serial |= cbs->update_number;
+ new_serial |= cbs->message_identifier << 16;
+
+ if (cbs->gs == CBS_GEO_SCOPE_PLMN)
+ recv = &assembly->recv_plmn;
+ else if (cbs->gs == CBS_GEO_SCOPE_SERVICE_AREA)
+ recv = &assembly->recv_loc;
+ else
+ recv = &assembly->recv_cell;
+
+ /* Have we seen this message before? */
+ l = g_slist_find_custom(*recv, GUINT_TO_POINTER(new_serial),
+ cbs_compare_recv_by_serial);
+
+ /* If we have, is the message newer? */
+ if (l && !cbs_is_update_newer(new_serial, GPOINTER_TO_UINT(l->data)))
+ return NULL;
+
+ /* Easy case first, page 1 of 1 */
+ if (cbs->max_pages == 1 && cbs->page == 1) {
+ if (l)
+ l->data = GUINT_TO_POINTER(new_serial);
+ else
+ *recv = g_slist_prepend(*recv,
+ GUINT_TO_POINTER(new_serial));
+
+ newcbs = g_new(struct cbs, 1);
+ memcpy(newcbs, cbs, sizeof(struct cbs));
+ completed = g_slist_append(NULL, newcbs);
+
+ return completed;
+ }
+
+ prev = NULL;
+ position = 0;
+
+ for (l = assembly->assembly_list; l; prev = l, l = l->next) {
+ int j;
+ node = l->data;
+
+ if (new_serial != node->serial)
+ continue;
+
+ if (node->bitmap & (1 << cbs->page))
+ return NULL;
+
+ for (j = 1; j < cbs->page; j++)
+ if (node->bitmap & (1 << j))
+ position += 1;
+
+ goto out;
+ }
+
+ node = g_new0(struct cbs_assembly_node, 1);
+ node->serial = new_serial;
+
+ assembly->assembly_list = g_slist_prepend(assembly->assembly_list,
+ node);
+
+ prev = NULL;
+ l = assembly->assembly_list;
+ position = 0;
+
+out:
+ newcbs = g_new(struct cbs, 1);
+ memcpy(newcbs, cbs, sizeof(struct cbs));
+ node->pages = g_slist_insert(node->pages, newcbs, position);
+ node->bitmap |= 1 << cbs->page;
+
+ if (g_slist_length(node->pages) < cbs->max_pages)
+ return NULL;
+
+ completed = node->pages;
+
+ if (prev)
+ prev->next = l->next;
+ else
+ assembly->assembly_list = l->next;
+
+ g_free(node);
+ g_slist_free_1(l);
+
+ cbs_assembly_expire(assembly, cbs_compare_node_by_update,
+ GUINT_TO_POINTER(new_serial));
+ *recv = g_slist_prepend(*recv, GUINT_TO_POINTER(new_serial));
+
+ return completed;
+}
diff --git a/src/smsutil.h b/src/smsutil.h
index f04434d6..89da973f 100644
--- a/src/smsutil.h
+++ b/src/smsutil.h
@@ -373,6 +373,19 @@ struct cbs {
guint8 ud[82];
};
+struct cbs_assembly_node {
+ guint32 serial;
+ guint16 bitmap;
+ GSList *pages;
+};
+
+struct cbs_assembly {
+ GSList *assembly_list;
+ GSList *recv_plmn;
+ GSList *recv_loc;
+ GSList *recv_cell;
+};
+
static inline gboolean is_bit_set(unsigned char oct, int bit)
{
int mask = 0x1 << bit;
@@ -453,3 +466,10 @@ gboolean cbs_extract_app_port(const struct cbs *cbs, int *dst, int *src,
gboolean *is_8bit);
char *cbs_decode_text(GSList *cbs_list, char *iso639_lang);
+
+struct cbs_assembly *cbs_assembly_new();
+void cbs_assembly_free(struct cbs_assembly *assembly);
+GSList *cbs_assembly_add_page(struct cbs_assembly *assembly,
+ const struct cbs *cbs);
+void cbs_assembly_location_changed(struct cbs_assembly *assembly,
+ gboolean lac, gboolean ci);