mirror of git://git.sysmocom.de/ofono
Add Cell Broadcast assembly utilities
This commit is contained in:
parent
bf2543b207
commit
a9f776123e
259
src/smsutil.c
259
src/smsutil.c
|
@ -2848,3 +2848,262 @@ char *cbs_decode_text(GSList *cbs_list, char *iso639_lang)
|
||||||
g_free(buf);
|
g_free(buf);
|
||||||
return utf8;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -373,6 +373,19 @@ struct cbs {
|
||||||
guint8 ud[82];
|
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)
|
static inline gboolean is_bit_set(unsigned char oct, int bit)
|
||||||
{
|
{
|
||||||
int mask = 0x1 << 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);
|
gboolean *is_8bit);
|
||||||
|
|
||||||
char *cbs_decode_text(GSList *cbs_list, char *iso639_lang);
|
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);
|
||||||
|
|
Loading…
Reference in New Issue