/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* Copyright (C) 2009 Novell, Inc.
* Author: Tambet Ingo (tambet@gmail.com).
*
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2012 Lanedo GmbH
*/
#include "nm-default.h"
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "nm-mobile-providers.h"
#define ISO_3166_COUNTRY_CODES ISO_CODES_PREFIX"/share/xml/iso-codes/iso_3166.xml"
#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX"/share/locale"
/******************************************************************************/
/* Access method type */
G_DEFINE_BOXED_TYPE (NMAMobileAccessMethod,
nma_mobile_access_method,
nma_mobile_access_method_ref,
nma_mobile_access_method_unref)
struct _NMAMobileAccessMethod {
volatile gint refs;
char *name;
/* maps lang (char *) -> name (char *) */
GHashTable *lcl_names;
char *username;
char *password;
char *gateway;
GPtrArray *dns; /* GPtrArray of 'char *' */
/* Only used with 3GPP family type providers */
char *apn;
NMAMobileFamily family;
};
static NMAMobileAccessMethod *
access_method_new (void)
{
NMAMobileAccessMethod *method;
method = g_slice_new0 (NMAMobileAccessMethod);
method->refs = 1;
method->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
return method;
}
NMAMobileAccessMethod *
nma_mobile_access_method_ref (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
g_return_val_if_fail (method->refs > 0, NULL);
g_atomic_int_inc (&method->refs);
return method;
}
void
nma_mobile_access_method_unref (NMAMobileAccessMethod *method)
{
g_return_if_fail (method != NULL);
g_return_if_fail (method->refs > 0);
if (g_atomic_int_dec_and_test (&method->refs)) {
g_free (method->name);
g_hash_table_destroy (method->lcl_names);
g_free (method->username);
g_free (method->password);
g_free (method->gateway);
g_free (method->apn);
if (method->dns)
g_ptr_array_unref (method->dns);
g_slice_free (NMAMobileAccessMethod, method);
}
}
/**
* nma_mobile_access_method_get_name:
*
* Returns: (transfer none): the name of the method.
*/
const gchar *
nma_mobile_access_method_get_name (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->name;
}
/**
* nma_mobile_access_method_get_username:
*
* Returns: (transfer none): the username.
*/
const gchar *
nma_mobile_access_method_get_username (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->username;
}
/**
* nma_mobile_access_method_get_password:
*
* Returns: (transfer none): the password.
*/
const gchar *
nma_mobile_access_method_get_password (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->password;
}
/**
* nma_mobile_access_method_get_gateway:
*
* Returns: (transfer none): the gateway.
*/
const gchar *
nma_mobile_access_method_get_gateway (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->gateway;
}
/**
* nma_mobile_access_method_get_dns:
*
* Returns: (transfer none) (array zero-terminated=1) (element-type utf8): the list of DNS.
*/
const gchar **
nma_mobile_access_method_get_dns (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->dns ? (const gchar **)method->dns->pdata : NULL;
}
/**
* nma_mobile_access_method_get_3gpp_apn:
*
* Returns: (transfer none): the 3GPP APN.
*/
const gchar *
nma_mobile_access_method_get_3gpp_apn (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NULL);
return method->apn;
}
/**
* nma_mobile_access_method_get_family:
*
* Returns: a #NMAMobileFamily.
*/
NMAMobileFamily
nma_mobile_access_method_get_family (NMAMobileAccessMethod *method)
{
g_return_val_if_fail (method != NULL, NMA_MOBILE_FAMILY_UNKNOWN);
return method->family;
}
/******************************************************************************/
/* Mobile provider type */
G_DEFINE_BOXED_TYPE (NMAMobileProvider,
nma_mobile_provider,
nma_mobile_provider_ref,
nma_mobile_provider_unref)
struct _NMAMobileProvider {
volatile gint refs;
char *name;
/* maps lang (char *) -> name (char *) */
GHashTable *lcl_names;
GSList *methods; /* GSList of NmaMobileAccessMethod */
GPtrArray *mcc_mnc; /* GPtrArray of strings */
GArray *cdma_sid; /* GArray of guint32 */
};
static NMAMobileProvider *
provider_new (void)
{
NMAMobileProvider *provider;
provider = g_slice_new0 (NMAMobileProvider);
provider->refs = 1;
provider->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
return provider;
}
NMAMobileProvider *
nma_mobile_provider_ref (NMAMobileProvider *provider)
{
g_return_val_if_fail (provider != NULL, NULL);
g_return_val_if_fail (provider->refs > 0, NULL);
g_atomic_int_inc (&provider->refs);
return provider;
}
void
nma_mobile_provider_unref (NMAMobileProvider *provider)
{
if (g_atomic_int_dec_and_test (&provider->refs)) {
g_free (provider->name);
g_hash_table_destroy (provider->lcl_names);
g_slist_free_full (provider->methods, (GDestroyNotify) nma_mobile_access_method_unref);
if (provider->mcc_mnc)
g_ptr_array_unref (provider->mcc_mnc);
if (provider->cdma_sid)
g_array_unref (provider->cdma_sid);
g_slice_free (NMAMobileProvider, provider);
}
}
/**
* nma_mobile_provider_get_name:
*
* Returns: (transfer none): the name of the provider.
*/
const gchar *
nma_mobile_provider_get_name (NMAMobileProvider *provider)
{
g_return_val_if_fail (provider != NULL, NULL);
return provider->name;
}
/**
* nma_mobile_provider_get_methods:
* @provider: a #NMAMobileProvider
*
* Returns: (element-type NMGtk.MobileAccessMethod) (transfer none): the
* list of #NMAMobileAccessMethod this provider exposes.
*/
GSList *
nma_mobile_provider_get_methods (NMAMobileProvider *provider)
{
g_return_val_if_fail (provider != NULL, NULL);
return provider->methods;
}
/**
* nma_mobile_provider_get_3gpp_mcc_mnc:
* @provider: a #NMAMobileProvider
*
* Returns: (transfer none) (array zero-terminated=1) (element-type utf8): a
* list of strings with the MCC and MNC codes this provider exposes.
*/
const gchar **
nma_mobile_provider_get_3gpp_mcc_mnc (NMAMobileProvider *provider)
{
g_return_val_if_fail (provider != NULL, NULL);
return provider->mcc_mnc ? (const gchar **)provider->mcc_mnc->pdata : NULL;
}
/**
* nma_mobile_provider_get_cdma_sid:
* @provider: a #NMAMobileProvider
*
* Returns: (transfer none) (array zero-terminated=1) (element-type guint32): the
* list of CDMA SIDs this provider exposes
*/
const guint32 *
nma_mobile_provider_get_cdma_sid (NMAMobileProvider *provider)
{
g_return_val_if_fail (provider != NULL, NULL);
return provider->cdma_sid ? (const guint32 *)provider->cdma_sid->data : NULL;
}
/******************************************************************************/
/* Country Info type */
G_DEFINE_BOXED_TYPE (NMACountryInfo,
nma_country_info,
nma_country_info_ref,
nma_country_info_unref)
struct _NMACountryInfo {
volatile gint refs;
char *country_code;
char *country_name;
GSList *providers;
};
static NMACountryInfo *
country_info_new (const char *country_code,
const gchar *country_name)
{
NMACountryInfo *country_info;
country_info = g_slice_new0 (NMACountryInfo);
country_info->refs = 1;
country_info->country_code = g_strdup (country_code);
country_info->country_name = g_strdup (country_name);
return country_info;
}
NMACountryInfo *
nma_country_info_ref (NMACountryInfo *country_info)
{
g_return_val_if_fail (country_info != NULL, NULL);
g_return_val_if_fail (country_info->refs > 0, NULL);
g_atomic_int_inc (&country_info->refs);
return country_info;
}
void
nma_country_info_unref (NMACountryInfo *country_info)
{
if (g_atomic_int_dec_and_test (&country_info->refs)) {
g_free (country_info->country_code);
g_free (country_info->country_name);
g_slist_free_full (country_info->providers,
(GDestroyNotify) nma_mobile_provider_unref);
g_slice_free (NMACountryInfo, country_info);
}
}
/**
* nma_country_info_get_country_code:
*
* Returns: (transfer none): the code of the country.
*/
const gchar *
nma_country_info_get_country_code (NMACountryInfo *country_info)
{
g_return_val_if_fail (country_info != NULL, NULL);
return country_info->country_code;
}
/**
* nma_country_info_get_country_name:
*
* Returns: (transfer none): the name of the country.
*/
const gchar *
nma_country_info_get_country_name (NMACountryInfo *country_info)
{
g_return_val_if_fail (country_info != NULL, NULL);
return country_info->country_name;
}
/**
* nma_country_info_get_providers:
*
* Returns: (element-type NMGtk.MobileProvider) (transfer none): the
* list of #NMAMobileProvider this country exposes.
*/
GSList *
nma_country_info_get_providers (NMACountryInfo *country_info)
{
g_return_val_if_fail (country_info != NULL, NULL);
return country_info->providers;
}
/******************************************************************************/
/* XML Parser for iso_3166.xml */
static void
iso_3166_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer data,
GError **error)
{
int i;
const char *country_code = NULL;
const char *common_name = NULL;
const char *name = NULL;
GHashTable *table = (GHashTable *) data;
if (!strcmp (element_name, "iso_3166_entry")) {
NMACountryInfo *country_info;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "alpha_2_code"))
country_code = attribute_values[i];
else if (!strcmp (attribute_names[i], "common_name"))
common_name = attribute_values[i];
else if (!strcmp (attribute_names[i], "name"))
name = attribute_values[i];
}
if (!country_code) {
g_warning ("%s: missing mandatory 'alpha_2_code' atribute in '%s'"
" element.", __func__, element_name);
return;
}
if (!name) {
g_warning ("%s: missing mandatory 'name' atribute in '%s'"
" element.", __func__, element_name);
return;
}
country_info = country_info_new (country_code,
dgettext ("iso_3166", common_name ? common_name : name));
g_hash_table_insert (table, g_strdup (country_code), country_info);
}
}
static const GMarkupParser iso_3166_parser = {
iso_3166_parser_start_element,
NULL, /* end element */
NULL, /* text */
NULL, /* passthrough */
NULL /* error */
};
static GHashTable *
read_country_codes (const gchar *country_codes_file,
GCancellable *cancellable,
GError **error)
{
GHashTable *table = NULL;
GMarkupParseContext *ctx;
char *buf;
gsize buf_len;
/* Set domain to iso_3166 for country name translation */
bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
bind_textdomain_codeset ("iso_3166", "UTF-8");
if (!g_file_get_contents (country_codes_file, &buf, &buf_len, error)) {
g_prefix_error (error,
"Failed to load '%s' from 'iso-codes': ",
country_codes_file);
return NULL;
}
table = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)nma_country_info_unref);
ctx = g_markup_parse_context_new (&iso_3166_parser, 0, table, NULL);
if (!g_markup_parse_context_parse (ctx, buf, buf_len, error)) {
g_prefix_error (error,
"Failed to parse '%s' from 'iso-codes': ",
country_codes_file);
g_hash_table_destroy (table);
return NULL;
}
g_markup_parse_context_free (ctx);
g_free (buf);
return table;
}
/******************************************************************************/
/* XML Parser for serviceproviders.xml */
typedef enum {
PARSER_TOPLEVEL = 0,
PARSER_COUNTRY,
PARSER_PROVIDER,
PARSER_METHOD_GSM,
PARSER_METHOD_GSM_APN,
PARSER_METHOD_CDMA,
PARSER_ERROR
} MobileContextState;
typedef struct {
GHashTable *table;
char *current_country;
GSList *current_providers;
NMAMobileProvider *current_provider;
NMAMobileAccessMethod *current_method;
char *text_buffer;
MobileContextState state;
} MobileParser;
static void
provider_list_free (gpointer data)
{
GSList *list = (GSList *) data;
while (list) {
nma_mobile_provider_unref ((NMAMobileProvider *) list->data);
list = g_slist_delete_link (list, list);
}
}
static void
parser_toplevel_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
int i;
if (!strcmp (name, "serviceproviders")) {
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "format")) {
if (strcmp (attribute_values[i], "2.0")) {
g_warning ("%s: mobile broadband provider database format '%s'"
" not supported.", __func__, attribute_values[i]);
parser->state = PARSER_ERROR;
break;
}
}
}
} else if (!strcmp (name, "country")) {
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "code")) {
char *country_code;
NMACountryInfo *country_info;
country_code = g_ascii_strup (attribute_values[i], -1);
country_info = g_hash_table_lookup (parser->table, country_code);
/* Ensure we have a country provider for this country code */
if (!country_info) {
g_warning ("%s: adding providers for unknown country '%s'", __func__, country_code);
country_info = country_info_new (country_code, NULL);
g_hash_table_insert (parser->table, country_code, country_info);
}
parser->current_country = country_code;
parser->state = PARSER_COUNTRY;
break;
}
}
}
}
static void
parser_country_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "provider")) {
parser->state = PARSER_PROVIDER;
parser->current_provider = provider_new ();
}
}
static void
parser_provider_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "gsm"))
parser->state = PARSER_METHOD_GSM;
else if (!strcmp (name, "cdma")) {
parser->state = PARSER_METHOD_CDMA;
parser->current_method = access_method_new ();
}
}
static void
parser_gsm_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "network-id")) {
const char *mcc = NULL, *mnc = NULL;
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "mcc"))
mcc = attribute_values[i];
else if (!strcmp (attribute_names[i], "mnc"))
mnc = attribute_values[i];
if (mcc && strlen (mcc) && mnc && strlen (mnc)) {
gchar *mccmnc;
if (!parser->current_provider->mcc_mnc)
parser->current_provider->mcc_mnc = g_ptr_array_new_full (2, g_free);
mccmnc = g_strdup_printf ("%s%s", mcc, mnc);
g_ptr_array_add (parser->current_provider->mcc_mnc, mccmnc);
break;
}
}
} else if (!strcmp (name, "apn")) {
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "value")) {
parser->state = PARSER_METHOD_GSM_APN;
parser->current_method = access_method_new ();
parser->current_method->apn = g_strstrip (g_strdup (attribute_values[i]));
break;
}
}
}
}
static void
parser_cdma_start (MobileParser *parser,
const char *name,
const char **attribute_names,
const char **attribute_values)
{
if (!strcmp (name, "sid")) {
int i;
for (i = 0; attribute_names && attribute_names[i]; i++) {
if (!strcmp (attribute_names[i], "value")) {
guint32 tmp;
errno = 0;
tmp = (guint32) strtoul (attribute_values[i], NULL, 10);
if (errno == 0 && tmp > 0) {
if (!parser->current_provider->cdma_sid)
parser->current_provider->cdma_sid = g_array_sized_new (TRUE, FALSE, sizeof (guint32), 2);
g_array_append_val (parser->current_provider->cdma_sid, tmp);
}
break;
}
}
}
}
static void
mobile_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
if (parser->text_buffer) {
g_free (parser->text_buffer);
parser->text_buffer = NULL;
}
switch (parser->state) {
case PARSER_TOPLEVEL:
parser_toplevel_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_COUNTRY:
parser_country_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_PROVIDER:
parser_provider_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_METHOD_GSM:
parser_gsm_start (parser, element_name, attribute_names, attribute_values);
break;
case PARSER_METHOD_CDMA:
parser_cdma_start (parser, element_name, attribute_names, attribute_values);
break;
default:
break;
}
}
static void
parser_country_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "country")) {
NMACountryInfo *country_info;
country_info = g_hash_table_lookup (parser->table, parser->current_country);
g_assert (country_info);
/* Store providers for this country */
country_info->providers = parser->current_providers;
g_free (parser->current_country);
parser->current_country = NULL;
parser->current_providers = NULL;
g_free (parser->text_buffer);
parser->text_buffer = NULL;
parser->state = PARSER_TOPLEVEL;
}
}
static void
parser_provider_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "name")) {
if (!parser->current_provider->name) {
/* Use the first one. */
parser->current_provider->name = parser->text_buffer;
parser->text_buffer = NULL;
}
} else if (!strcmp (name, "provider")) {
if (parser->current_provider->mcc_mnc)
g_ptr_array_add (parser->current_provider->mcc_mnc, NULL);
parser->current_provider->methods = g_slist_reverse (parser->current_provider->methods);
parser->current_providers = g_slist_prepend (parser->current_providers, parser->current_provider);
parser->current_provider = NULL;
g_free (parser->text_buffer);
parser->text_buffer = NULL;
parser->state = PARSER_COUNTRY;
}
}
static void
parser_gsm_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "gsm")) {
g_free (parser->text_buffer);
parser->text_buffer = NULL;
parser->state = PARSER_PROVIDER;
}
}
static void
parser_gsm_apn_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "name")) {
if (!parser->current_method->name) {
/* Use the first one. */
parser->current_method->name = parser->text_buffer;
parser->text_buffer = NULL;
}
} else if (!strcmp (name, "username")) {
parser->current_method->username = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "password")) {
parser->current_method->password = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "dns")) {
if (!parser->current_method->dns)
parser->current_method->dns = g_ptr_array_new_full (2, g_free);
g_ptr_array_add (parser->current_method->dns, parser->text_buffer);
parser->text_buffer = NULL;
} else if (!strcmp (name, "gateway")) {
parser->current_method->gateway = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "apn")) {
parser->current_method->family = NMA_MOBILE_FAMILY_3GPP;
if (!parser->current_method->name)
parser->current_method->name = g_strdup (_("Default"));
if (parser->current_method->dns)
g_ptr_array_add (parser->current_method->dns, NULL);
parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
parser->current_method);
parser->current_method = NULL;
g_free (parser->text_buffer);
parser->text_buffer = NULL;
parser->state = PARSER_METHOD_GSM;
}
}
static void
parser_cdma_end (MobileParser *parser,
const char *name)
{
if (!strcmp (name, "username")) {
parser->current_method->username = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "password")) {
parser->current_method->password = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "dns")) {
if (!parser->current_method->dns)
parser->current_method->dns = g_ptr_array_new_full (2, g_free);
g_ptr_array_add (parser->current_method->dns, parser->text_buffer);
parser->text_buffer = NULL;
} else if (!strcmp (name, "gateway")) {
parser->current_method->gateway = parser->text_buffer;
parser->text_buffer = NULL;
} else if (!strcmp (name, "cdma")) {
parser->current_method->family = NMA_MOBILE_FAMILY_CDMA;
if (!parser->current_method->name)
parser->current_method->name = g_strdup (parser->current_provider->name);
if (parser->current_method->dns)
g_ptr_array_add (parser->current_method->dns, NULL);
parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
parser->current_method);
parser->current_method = NULL;
g_free (parser->text_buffer);
parser->text_buffer = NULL;
parser->state = PARSER_PROVIDER;
}
}
static void
mobile_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
switch (parser->state) {
case PARSER_COUNTRY:
parser_country_end (parser, element_name);
break;
case PARSER_PROVIDER:
parser_provider_end (parser, element_name);
break;
case PARSER_METHOD_GSM:
parser_gsm_end (parser, element_name);
break;
case PARSER_METHOD_GSM_APN:
parser_gsm_apn_end (parser, element_name);
break;
case PARSER_METHOD_CDMA:
parser_cdma_end (parser, element_name);
break;
default:
break;
}
}
static void
mobile_parser_characters (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer data,
GError **error)
{
MobileParser *parser = (MobileParser *) data;
g_free (parser->text_buffer);
parser->text_buffer = g_strdup (text);
}
static const GMarkupParser mobile_parser = {
mobile_parser_start_element,
mobile_parser_end_element,
mobile_parser_characters,
NULL, /* passthrough */
NULL /* error */
};
static gboolean
read_service_providers (GHashTable *countries,
const gchar *service_providers,
GCancellable *cancellable,
GError **error)
{
GMarkupParseContext *ctx;
GIOChannel *channel;
MobileParser parser;
char buffer[4096];
GIOStatus status;
gsize len = 0;
memset (&parser, 0, sizeof (MobileParser));
parser.table = countries;
channel = g_io_channel_new_file (service_providers, "r", error);
if (!channel) {
g_prefix_error (error,
"Could not read '%s': ",
service_providers);
return FALSE;
}
parser.state = PARSER_TOPLEVEL;
ctx = g_markup_parse_context_new (&mobile_parser, 0, &parser, NULL);
status = G_IO_STATUS_NORMAL;
while (status == G_IO_STATUS_NORMAL) {
status = g_io_channel_read_chars (channel, buffer, sizeof (buffer), &len, error);
switch (status) {
case G_IO_STATUS_NORMAL:
if (!g_markup_parse_context_parse (ctx, buffer, len, error)) {
status = G_IO_STATUS_ERROR;
g_prefix_error (error,
"Error while parsing XML at '%s': ",
service_providers);
}
break;
case G_IO_STATUS_EOF:
break;
case G_IO_STATUS_ERROR:
g_prefix_error (error,
"Error while reading '%s': ",
service_providers);
break;
case G_IO_STATUS_AGAIN:
/* FIXME: Try again a few times, but really, it never happens, right? */
break;
}
if (g_cancellable_set_error_if_cancelled (cancellable, error))
status = G_IO_STATUS_ERROR;
}
g_io_channel_unref (channel);
g_markup_parse_context_free (ctx);
if (parser.current_provider) {
g_warning ("pending current provider");
nma_mobile_provider_unref (parser.current_provider);
}
if (parser.current_providers) {
g_warning ("pending current providers");
provider_list_free (parser.current_providers);
}
g_free (parser.current_country);
g_free (parser.text_buffer);
return (status == G_IO_STATUS_EOF);
}
static GHashTable *
mobile_providers_parse_sync (const gchar *country_codes,
const gchar *service_providers,
GCancellable *cancellable,
GError **error)
{
GHashTable *countries;
/* Use default paths if none given */
if (!country_codes)
country_codes = ISO_3166_COUNTRY_CODES;
if (!service_providers)
service_providers = MOBILE_BROADBAND_PROVIDER_INFO_DATABASE;
countries = read_country_codes (country_codes,
cancellable,
error);
if (!countries)
return NULL;
if (!read_service_providers (countries,
service_providers,
cancellable,
error)) {
g_hash_table_unref (countries);
return NULL;
}
return countries;
}
/******************************************************************************/
/* Dump to stdout contents */
static void
dump_generic (NMAMobileAccessMethod *method)
{
g_print (" username: %s\n", method->username ? method->username : "");
g_print (" password: %s\n", method->password ? method->password : "");
if (method->dns) {
guint i;
const gchar **dns;
GString *dns_str;
dns = nma_mobile_access_method_get_dns (method);
dns_str = g_string_new (NULL);
for (i = 0; dns[i]; i++)
g_string_append_printf (dns_str, "%s%s", i == 0 ? "" : ", ", dns[i]);
g_print (" dns : %s\n", dns_str->str);
g_string_free (dns_str, TRUE);
}
g_print (" gateway : %s\n", method->gateway ? method->gateway : "");
}
static void
dump_cdma (NMAMobileAccessMethod *method)
{
g_print (" CDMA: %s\n", method->name);
dump_generic (method);
}
static void
dump_3gpp (NMAMobileAccessMethod *method)
{
g_print (" APN: %s (%s)\n", method->name, method->apn);
dump_generic (method);
}
static void
dump_country (gpointer key, gpointer value, gpointer user_data)
{
GSList *miter, *citer;
NMACountryInfo *country_info = value;
g_print ("Country: %s (%s)\n",
country_info->country_code,
country_info->country_name);
for (citer = country_info->providers; citer; citer = g_slist_next (citer)) {
NMAMobileProvider *provider = citer->data;
const gchar **mcc_mnc;
const guint *sid;
guint n;
g_print (" Provider: %s (%s)\n", provider->name, (const char *) key);
mcc_mnc = nma_mobile_provider_get_3gpp_mcc_mnc (provider);
if (mcc_mnc) {
for (n = 0; mcc_mnc[n]; n++)
g_print (" MCC/MNC: %s\n", mcc_mnc[n]);
}
sid = nma_mobile_provider_get_cdma_sid (provider);
if (sid) {
for (n = 0; sid[n]; n++)
g_print (" SID: %u\n", sid[n]);
}
for (miter = provider->methods; miter; miter = g_slist_next (miter)) {
NMAMobileAccessMethod *method = miter->data;
switch (method->family) {
case NMA_MOBILE_FAMILY_CDMA:
dump_cdma (method);
break;
case NMA_MOBILE_FAMILY_3GPP:
dump_3gpp (method);
break;
default:
break;
}
g_print ("\n");
}
}
}
/******************************************************************************/
/* Mobile providers database type */
static void initable_iface_init (GInitableIface *iface);
static void async_initable_iface_init (GAsyncInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (NMAMobileProvidersDatabase, nma_mobile_providers_database, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init))
enum {
PROP_0,
PROP_COUNTRY_CODES_PATH,
PROP_SERVICE_PROVIDERS_PATH,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _NMAMobileProvidersDatabasePrivate {
/* Paths to input files */
gchar *country_codes_path;
gchar *service_providers_path;
/* The HT with country code as key and NMACountryInfo as value. */
GHashTable *countries;
};
/**********************************/
/**
* nma_mobile_providers_database_get_countries:
* @self: a #NMAMobileProvidersDatabase.
*
* Returns: (element-type utf8 NMGtk.CountryInfo) (transfer none): a
* hash table where keys are country names #gchar and values are #NMACountryInfo.
*/
GHashTable *
nma_mobile_providers_database_get_countries (NMAMobileProvidersDatabase *self)
{
g_return_val_if_fail (NMA_IS_MOBILE_PROVIDERS_DATABASE (self), NULL);
/* Warn if the object hasn't been initialized */
g_return_val_if_fail (self->priv->countries != NULL, NULL);
return self->priv->countries;
}
/**
* nma_mobile_providers_database_dump:
* @self: a #NMAMobileProvidersDatabase.
*
*/
void
nma_mobile_providers_database_dump (NMAMobileProvidersDatabase *self)
{
g_return_if_fail (NMA_IS_MOBILE_PROVIDERS_DATABASE (self));
/* Warn if the object hasn't been initialized */
g_return_if_fail (self->priv->countries != NULL);
g_hash_table_foreach (self->priv->countries, dump_country, NULL);
}
/**
* nma_mobile_providers_database_lookup_country:
* @self: a #NMAMobileProvidersDatabase.
* @country_code: the country code string to look for.
*
* Returns: (transfer none): a #NMACountryInfo or %NULL if not found.
*/
NMACountryInfo *
nma_mobile_providers_database_lookup_country (NMAMobileProvidersDatabase *self,
const gchar *country_code)
{
g_return_val_if_fail (NMA_IS_MOBILE_PROVIDERS_DATABASE (self), NULL);
/* Warn if the object hasn't been initialized */
g_return_val_if_fail (self->priv->countries != NULL, NULL);
return (NMACountryInfo *) g_hash_table_lookup (self->priv->countries, country_code);
}
/**
* nma_mobile_providers_database_lookup_3gpp_mcc_mnc:
* @self: a #NMAMobileProvidersDatabase.
* @mccmnc: the MCC/MNC string to look for.
*
* Returns: (transfer none): a #NMAMobileProvider or %NULL if not found.
*/
NMAMobileProvider *
nma_mobile_providers_database_lookup_3gpp_mcc_mnc (NMAMobileProvidersDatabase *self,
const gchar *mccmnc)
{
GHashTableIter iter;
gpointer value;
GSList *piter;
NMAMobileProvider *provider_match_2mnc = NULL;
guint mccmnc_len;
g_return_val_if_fail (NMA_IS_MOBILE_PROVIDERS_DATABASE (self), NULL);
g_return_val_if_fail (mccmnc != NULL, NULL);
/* Warn if the object hasn't been initialized */
g_return_val_if_fail (self->priv->countries != NULL, NULL);
/* Expect only 5 or 6 digit MCCMNC strings */
mccmnc_len = strlen (mccmnc);
if (mccmnc_len != 5 && mccmnc_len != 6)
return NULL;
g_hash_table_iter_init (&iter, self->priv->countries);
/* Search through each country */
while (g_hash_table_iter_next (&iter, NULL, &value)) {
NMACountryInfo *country_info = value;
/* Search through each country's providers */
for (piter = nma_country_info_get_providers (country_info);
piter;
piter = g_slist_next (piter)) {
NMAMobileProvider *provider = piter->data;
const gchar **mccmnc_list;
guint i;
/* Search through MCC/MNC list */
mccmnc_list = nma_mobile_provider_get_3gpp_mcc_mnc (provider);
if (!mccmnc_list)
continue;
for (i = 0; mccmnc_list[i]; i++) {
const gchar *mccmnc_iter;
guint mccmnc_iter_len;
mccmnc_iter = mccmnc_list[i];
mccmnc_iter_len = strlen (mccmnc_iter);
/* Match both 2-digit and 3-digit MNC; prefer a
* 3-digit match if found, otherwise a 2-digit one.
*/
if (strncmp (mccmnc_iter, mccmnc, 3))
/* MCC was wrong */
continue;
/* Now we have the following match cases, examples given:
* a) input: 123/456 --> iter: 123/456 (3-digit match)
* b) input: 123/45 --> iter: 123/045 (3-digit match)
* c) input: 123/045 --> iter: 123/45 (2-digit match)
* d) input: 123/45 --> iter: 123/45 (2-digit match)
*/
if (mccmnc_iter_len == 6) {
/* Covers cases a) and b) */
if ( (mccmnc_len == 6 && !strncmp (mccmnc + 3, mccmnc_iter + 3, 3))
|| (mccmnc_len == 5 && mccmnc_iter[3] == '0' && !strncmp (mccmnc + 3, mccmnc_iter + 4, 2)))
/* 3-digit MNC match! */
return provider;
/* MNC was wrong */
continue;
}
if ( !provider_match_2mnc
&& mccmnc_iter_len == 5) {
if ( (mccmnc_len == 5 && !strncmp (mccmnc + 3, mccmnc_iter + 3, 2))
|| (mccmnc_len == 6 && mccmnc[3] == '0' && !strncmp (mccmnc + 4, mccmnc_iter + 3, 2))) {
/* Store the 2-digit MNC match, but keep looking,
* we may have a 3-digit MNC match */
provider_match_2mnc = provider;
continue;
}
/* MNC was wrong */
continue;
}
}
}
}
return provider_match_2mnc;
}
/**
* nma_mobile_providers_database_lookup_cdma_sid:
* @self: a #NMAMobileProvidersDatabase.
* @sid: the SID to look for.
*
* Returns: (transfer none): a #NMAMobileProvider, or %NULL if not found.
*/
NMAMobileProvider *
nma_mobile_providers_database_lookup_cdma_sid (NMAMobileProvidersDatabase *self,
guint32 sid)
{
GHashTableIter iter;
gpointer value;
GSList *piter;
g_return_val_if_fail (NMA_IS_MOBILE_PROVIDERS_DATABASE (self), NULL);
g_return_val_if_fail (sid > 0, NULL);
/* Warn if the object hasn't been initialized */
g_return_val_if_fail (self->priv->countries != NULL, NULL);
g_hash_table_iter_init (&iter, self->priv->countries);
/* Search through each country */
while (g_hash_table_iter_next (&iter, NULL, &value)) {
NMACountryInfo *country_info = value;
/* Search through each country's providers */
for (piter = nma_country_info_get_providers (country_info);
piter;
piter = g_slist_next (piter)) {
NMAMobileProvider *provider = piter->data;
const guint32 *sid_list;
guint i;
/* Search through CDMA SID list */
sid_list = nma_mobile_provider_get_cdma_sid (provider);
if (!sid_list)
continue;
for (i = 0; sid_list[i]; i++) {
if (sid == sid_list[i])
return provider;
}
}
}
return NULL;
}
/**********************************/
static gboolean
initable_init_sync (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
NMAMobileProvidersDatabase *self = NMA_MOBILE_PROVIDERS_DATABASE (initable);
/* Parse the files */
self->priv->countries = mobile_providers_parse_sync (self->priv->country_codes_path,
self->priv->service_providers_path,
cancellable,
error);
if (!self->priv->countries)
return FALSE;
/* All good */
return TRUE;
}
/**********************************/
/**
* nma_mobile_providers_database_new:
* @country_codes: (allow-none): Path to the country codes file.
* @service_providers: (allow-none): Path to the service providers file.
* @cancellable: (allow-none): A #GCancellable or %NULL.
* @callback: A #GAsyncReadyCallback to call when the request is satisfied.
* @user_data: User data to pass to @callback.
*
*/
void
nma_mobile_providers_database_new (const gchar *country_codes,
const gchar *service_providers,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_async_initable_new_async (NMA_TYPE_MOBILE_PROVIDERS_DATABASE,
G_PRIORITY_DEFAULT,
cancellable,
callback,
user_data,
"country-codes", country_codes,
"service-providers", service_providers,
NULL);
}
/**
* nma_mobile_providers_database_new_finish:
* @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to nma_mobile_providers_database_new().
* @error: Return location for error or %NULL.
*
* Returns: (transfer full) (type NMAMobileProvidersDatabase): The constructed object or %NULL if @error is set.
*/
NMAMobileProvidersDatabase *
nma_mobile_providers_database_new_finish (GAsyncResult *res,
GError **error)
{
GObject *initable;
GObject *out;
initable = g_async_result_get_source_object (res);
out = g_async_initable_new_finish (G_ASYNC_INITABLE (initable), res, error);
g_object_unref (initable);
return out ? NMA_MOBILE_PROVIDERS_DATABASE (out) : NULL;
}
/**
* nma_mobile_providers_database_new_sync:
* @country_codes: (allow-none): Path to the country codes file.
* @service_providers: (allow-none): Path to the service providers file.
* @cancellable: (allow-none): A #GCancellable or %NULL.
* @error: Return location for error or %NULL.
*
* Returns: (transfer full) (type NMAMobileProvidersDatabase): The constructed object or %NULL if @error is set.
*/
NMAMobileProvidersDatabase *
nma_mobile_providers_database_new_sync (const gchar *country_codes,
const gchar *service_providers,
GCancellable *cancellable,
GError **error)
{
GObject *out;
out = g_initable_new (NMA_TYPE_MOBILE_PROVIDERS_DATABASE,
cancellable,
error,
"country-codes", country_codes,
"service-providers", service_providers,
NULL);
return out ? NMA_MOBILE_PROVIDERS_DATABASE (out) : NULL;
}
/**********************************/
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NMAMobileProvidersDatabase *self = NMA_MOBILE_PROVIDERS_DATABASE (object);
switch (prop_id) {
case PROP_COUNTRY_CODES_PATH:
self->priv->country_codes_path = g_value_dup_string (value);
break;
case PROP_SERVICE_PROVIDERS_PATH:
self->priv->service_providers_path = g_value_dup_string (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)
{
NMAMobileProvidersDatabase *self = NMA_MOBILE_PROVIDERS_DATABASE (object);
switch (prop_id) {
case PROP_COUNTRY_CODES_PATH:
g_value_set_string (value, self->priv->country_codes_path);
break;
case PROP_SERVICE_PROVIDERS_PATH:
g_value_set_string (value, self->priv->service_providers_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
nma_mobile_providers_database_init (NMAMobileProvidersDatabase *self)
{
/* Setup private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
NMA_TYPE_MOBILE_PROVIDERS_DATABASE,
NMAMobileProvidersDatabasePrivate);
}
static void
finalize (GObject *object)
{
NMAMobileProvidersDatabase *self = NMA_MOBILE_PROVIDERS_DATABASE (object);
g_free (self->priv->country_codes_path);
g_free (self->priv->service_providers_path);
if (self->priv->countries)
g_hash_table_unref (self->priv->countries);
G_OBJECT_CLASS (nma_mobile_providers_database_parent_class)->finalize (object);
}
static void
initable_iface_init (GInitableIface *iface)
{
iface->init = initable_init_sync;
}
static void
async_initable_iface_init (GAsyncInitableIface *iface)
{
/* Just use defaults (run sync init() in a thread) */
}
static void
nma_mobile_providers_database_class_init (NMAMobileProvidersDatabaseClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (NMAMobileProvidersDatabasePrivate));
/* Virtual methods */
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
properties[PROP_COUNTRY_CODES_PATH] =
g_param_spec_string ("country-codes",
"Country Codes",
"Path to the country codes file",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_COUNTRY_CODES_PATH, properties[PROP_COUNTRY_CODES_PATH]);
properties[PROP_SERVICE_PROVIDERS_PATH] =
g_param_spec_string ("service-providers",
"Service Providers",
"Path to the service providers file",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_SERVICE_PROVIDERS_PATH, properties[PROP_SERVICE_PROVIDERS_PATH]);
}
/******************************************************************************/
/* Utils */
/**
* nma_mobile_providers_split_3gpp_mcc_mnc:
* @mccmnc: input MCCMNC string.
* @mcc: (out) (transfer full): the MCC.
* @mnc: (out) (transfer full): the MNC.
*
* Splits the input MCCMNC string into separate MCC and MNC strings.
*
* Returns: %TRUE if correctly split and @mcc and @mnc are set; %FALSE otherwise.
*/
gboolean
nma_mobile_providers_split_3gpp_mcc_mnc (const gchar *mccmnc,
gchar **mcc,
gchar **mnc)
{
gint len;
g_return_val_if_fail (mccmnc != NULL, FALSE);
g_return_val_if_fail (mcc != NULL, FALSE);
g_return_val_if_fail (mnc != NULL, FALSE);
len = strlen (mccmnc);
if (len != 5 && len != 6)
return FALSE;
/* MCCMNC is all digits */
while (len > 0) {
if (!g_ascii_isdigit (mccmnc[--len]))
return FALSE;
}
*mcc = g_strndup (mccmnc, 3);
*mnc = g_strdup (mccmnc + 3);
return TRUE;
}