Hi Oleg, On 08/30/2011 07:56 AM, Oleg Zhurakivskyy wrote: > --- > plugins/mobile-broadband-provider-info.c | 695 ++++++++++++++++++++++++++++++ > 1 files changed, 695 insertions(+), 0 deletions(-) > create mode 100644 plugins/mobile-broadband-provider-info.c > > diff --git a/plugins/mobile-broadband-provider-info.c b/plugins/mobile-broadband-provider-info.c > new file mode 100644 > index 0000000..f8c898e > --- /dev/null > +++ b/plugins/mobile-broadband-provider-info.c > @@ -0,0 +1,695 @@ > +/* > + * > + * oFono - Open Source Telephony > + * > + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * 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. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > + * > + */ > + > +#ifdef HAVE_CONFIG_H > +#include > +#endif > + > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#define OFONO_API_SUBJECT_TO_CHANGE > +#include > +#include > +#include > +#include > +#include > + > +#define MAX_APS 4 > + > +#define _(x) case x: return (#x) > + > +enum ap_type_configured_flags { > + AP_TYPE_CONFIGURED_FLAG_INTERNET = 0x1, > + AP_TYPE_CONFIGURED_FLAG_MMS = 0x2, > + AP_TYPE_CONFIGURED_FLAG_WAP = 0x4, > +}; > + > +enum element_type { > + ELEMENT_PROVIDER, > + ELEMENT_SPN, > + ELEMENT_NETWORK_ID, > + ELEMENT_GSM, > + ELEMENT_APN, > + ELEMENT_USAGE, > + ELEMENT_PLAN, > + ELEMENT_NAME, > + ELEMENT_USERNAME, > + ELEMENT_PASSWORD, > + ELEMENT_NONE, > +}; > + > +struct provider_info { > + struct ofono_gprs_provision_data settings[MAX_APS]; > + struct ofono_gprs_provision_data *ap; > + int ap_count; > + char *spn; > + const char *match_mcc; > + const char *match_mnc; > + const char *match_spn; > + enum element_type element; > + gboolean match_found; > + gboolean ap_ignore; > + gboolean provisioning_fail; I'm not particularly happy with this approach, can't we simply bail out when we detect this condition? Perhaps we should just invent our own GError and report it when this condition occurs in e.g. start_element / end_element functions. > + guint8 ap_type_configured; > +}; > + > +static struct provider_info provider_info; > + > +static const char *element_type_name(enum element_type element) > +{ > + switch (element) { > + _(ELEMENT_PROVIDER); > + _(ELEMENT_SPN); > + _(ELEMENT_NETWORK_ID); > + _(ELEMENT_GSM); > + _(ELEMENT_APN); > + _(ELEMENT_USAGE); > + _(ELEMENT_PLAN); > + _(ELEMENT_NAME); > + _(ELEMENT_USERNAME); > + _(ELEMENT_PASSWORD); > + _(ELEMENT_NONE); > + } > + > + return "ELEMENT_"; > +} > + > +static const char *ap_type_name(enum ofono_gprs_context_type ap_type) > +{ > + switch (ap_type) { > + _(OFONO_GPRS_CONTEXT_TYPE_ANY); > + _(OFONO_GPRS_CONTEXT_TYPE_INTERNET); > + _(OFONO_GPRS_CONTEXT_TYPE_MMS); > + _(OFONO_GPRS_CONTEXT_TYPE_WAP); > + _(OFONO_GPRS_CONTEXT_TYPE_IMS); > + } > + > + return "OFONO_GPRS_CONTEXT_TYPE_"; > +} > + > +static enum element_type element_type(const gchar *element) > +{ > + switch (element[0]) { > + case 'a': > + if (g_str_equal(element, "apn") == TRUE) > + return ELEMENT_APN; > + break; > + case 'g': > + if (g_str_equal(element, "gsm") == TRUE) > + return ELEMENT_GSM; > + break; > + case 'n': > + if (g_str_equal(element, "name") == TRUE) > + return ELEMENT_NAME; > + if (g_str_equal(element, "network-id") == TRUE) > + return ELEMENT_NETWORK_ID; > + break; > + case 'p': > + if (g_str_equal(element, "provider") == TRUE) > + return ELEMENT_PROVIDER; > + if (g_str_equal(element, "plan") == TRUE) > + return ELEMENT_PLAN; > + if (g_str_equal(element, "password") == TRUE) > + return ELEMENT_PASSWORD; > + break; > + case 'u': > + if (g_str_equal(element, "usage") == TRUE) > + return ELEMENT_USAGE; > + if (g_str_equal(element, "username") == TRUE) > + return ELEMENT_USERNAME; > + } > + > + return ELEMENT_NONE; > +} > + I like this optimization, but have you considered using g_markup_parse_context_push / pop to simplify the logic even more? > +static void element_network_id_parse(struct provider_info *data, > + const gchar **attribute_names, > + const gchar **attribute_values) > +{ > + const char *mcc = NULL, *mnc = NULL; > + int i; > + > + for (i = 0; attribute_names[i]; i++) { > + if (g_str_equal(attribute_names[i], "mcc") == TRUE) > + mcc = attribute_values[i]; > + if (g_str_equal(attribute_names[i], "mnc") == TRUE) > + mnc = attribute_values[i]; > + } > + > + if (g_strcmp0(mcc, data->match_mcc) == 0 && > + g_strcmp0(mnc, data->match_mnc) == 0) { > + if (data->match_spn == NULL) { > + data->match_found = TRUE; > + return; > + } > + > + if (g_strcmp0(data->spn, data->match_spn) == 0) > + data->match_found = TRUE; > + } I just double checked serviceproviders.2.dtd and can't even find an entry for the SPN. I mentioned this before, but you can't treat the provider name the same as the SPN. The xml database is simply not setup this way and the results you will get will be wrong. Can we please drop this check for now? > +} > + > +static struct ofono_gprs_provision_data *ap_new(struct provider_info *data) > +{ > + if (data->ap_count < MAX_APS) { > + struct ofono_gprs_provision_data *ap = > + data->settings + data->ap_count++; > + > + DBG("%p", ap); > + > + return ap; > + > + } > + > + return NULL; > +} > + > +static void ap_value_free(char **value) > +{ > + g_free(*value); > + > + *value = NULL; > +} > + > +static void ap_delete(struct provider_info *data) > +{ > + struct ofono_gprs_provision_data *ap = data->ap; > + > + DBG("%p", ap); > + > + ap_value_free(&ap->name); > + > + ap_value_free(&ap->apn); > + > + ap_value_free(&ap->username); > + > + ap_value_free(&ap->password); > + > + ap->type = OFONO_GPRS_CONTEXT_TYPE_ANY; > + > + data->ap_count--; > + > + data->ap--; > +} > + > +static void element_apn_parse(struct provider_info *data, > + const gchar **attribute_names, > + const gchar **attribute_values) > +{ > + int i; > + > + data->ap = ap_new(data); > + if (data->ap == NULL) { > + DBG("MAX_APS %d reached, end parsing", MAX_APS); > + data->match_found = FALSE; > + return; > + } > + > + for (i = 0; attribute_names[i]; i++) > + if (g_str_equal(attribute_names[i], "value") == TRUE) { > + data->ap->apn = g_strdup(attribute_values[i]); > + break; > + } > +} > + > +static void element_usage_parse(struct provider_info *data, > + const gchar **attribute_names, > + const gchar **attribute_values) > +{ > + struct ofono_gprs_provision_data *ap = data->ap; > + const char *usage = NULL; > + int i; > + > + for (i = 0; attribute_names[i]; i++) { > + if (g_str_equal(attribute_names[i], "type") == TRUE) > + usage = attribute_values[i]; > + } > + > + if (usage == NULL) { > + DBG("AP '%s', no type, AP ignored", ap->apn); > + data->ap_ignore = TRUE; > + return; > + } > + > + if (g_str_equal(usage, "internet") == TRUE) > + ap->type = OFONO_GPRS_CONTEXT_TYPE_INTERNET; > + else if (g_str_equal(usage, "mms") == TRUE) > + ap->type = OFONO_GPRS_CONTEXT_TYPE_MMS; > + else if (g_str_equal(usage, "wap") == TRUE) > + ap->type = OFONO_GPRS_CONTEXT_TYPE_WAP; > +} > + > +static void element_plan_parse(struct provider_info *data, > + const gchar **attribute_names, > + const gchar **attribute_values) > +{ > + int i; > + > + for (i = 0; attribute_names[i]; i++) > + if (g_str_equal(attribute_names[i], "type") == TRUE) { > + if (attribute_values[i] == NULL) { > + DBG("AP '%s', plan is not present, AP ignored", > + data->ap->apn); > + break; > + } > + > + if (g_str_equal(attribute_values[i], > + "postpaid") == TRUE || > + g_str_equal(attribute_values[i], > + "prepaid") == TRUE) > + return; > + > + DBG("AP '%s', plan '%s' is not supported, AP ignored", > + data->ap->apn, attribute_values[i]); > + break; > + } > + > + data->ap_ignore = TRUE; > +} > + > +static void element_start_parse(GMarkupParseContext *context, > + const gchar *element_name, > + const gchar **attribute_names, > + const gchar **attribute_values, > + gpointer user_data, GError **error) > +{ > + struct provider_info *data = user_data; > + enum element_type prev = data->element; > + > + data->element = element_type(element_name); > + > + if (prev == ELEMENT_PROVIDER && data->element == ELEMENT_NAME) { > + data->element = ELEMENT_SPN; > + g_free(data->spn); > + data->spn = NULL; > + data->match_found = TRUE; > + return; > + } > + > + if (data->element == ELEMENT_NETWORK_ID) > + element_network_id_parse(data, attribute_names, > + attribute_values); > + > + if (data->match_found == FALSE || data->ap_ignore == TRUE) > + return; > + > + DBG("%s: '%s'", element_type_name(data->element), element_name); > + > + switch (data->element) { > + case ELEMENT_APN: > + element_apn_parse(data, attribute_names, attribute_values); > + break; > + case ELEMENT_USAGE: > + element_usage_parse(data, attribute_names, attribute_values); > + break; > + case ELEMENT_PLAN: > + element_plan_parse(data, attribute_names, attribute_values); > + break; > + default: > + break; > + } > +} > + > +static gchar *body_text_parse(const gchar *text, gsize text_len) > +{ > + gchar *body = g_strndup(text, text_len); > + gchar *print = NULL; > + int i; > + > + for (i = 0; body && body[i]; i++) { > + if (g_ascii_isprint(body[i])) { > + print = g_strescape(body, NULL); > + break; > + } > + } > + > + g_free(body); > + > + return print; What in the world is this doing? Since the body can contain UTF8 characters, I don't think you should be escaping anything here. If you're trying to validate utf8, then you might need to do something else. > +} > + > +static char *body_to_hex(const gchar *text, gsize text_len) > +{ > + char *hex; > + guint i, j; > + > + hex = g_try_malloc(text_len * 2 + 1); > + if (hex == NULL) > + return NULL; > + > + for (i = 0, j = 0; i < text_len; i++, j+=2) > + g_snprintf(hex + j, 3, "%02x", (unsigned char)text[i]); > + > + hex[j] = 0; > + > + return hex; > +} > + With the above comment in mind, I don't see the need for this at all... > +static void element_body_parse(GMarkupParseContext *context, > + const gchar *text, gsize text_len, > + gpointer user_data, GError **error) > +{ > + struct provider_info *data = user_data; > + struct ofono_gprs_provision_data *ap = data->ap; > + gchar *body; > + > + if (data->match_found == FALSE || text_len == 0 || > + data->ap_ignore == TRUE) > + return; > + > + body = body_text_parse(text, text_len); > + if (body == NULL) { > + if (data->element == ELEMENT_SPN) { > + char *hex = body_to_hex(text, text_len); > + ofono_warn("Can't parse SPN body, len: %lu hex: %s", > + text_len, hex); > + g_free(hex); > + data->match_found = FALSE; > + } > + return; You do realize you can set the GError **error argument here and the parser will bail out properly ;) > + } > + > + if (data->element == ELEMENT_SPN) { > + data->spn = g_strdup(body); > + data->match_found = FALSE; > + g_free(body); > + return; > + } > + > + DBG("%s: '%s'", element_type_name(data->element), body); > + > + switch (data->element) { > + case ELEMENT_USERNAME: > + ap->username = g_strdup(body); > + break; > + case ELEMENT_PASSWORD: > + ap->password = g_strdup(body); > + break; > + case ELEMENT_NAME: > + ap->name = g_strdup(body); > + break; > + default: > + break; > + } > + > + g_free(body); > +} > + > +static gboolean ap_type_is_configured(struct provider_info *data, > + struct ofono_gprs_provision_data *ap) > +{ > + switch (ap->type) { > + case OFONO_GPRS_CONTEXT_TYPE_INTERNET: > + if (data->ap_type_configured & > + AP_TYPE_CONFIGURED_FLAG_INTERNET) > + return TRUE; > + break; > + case OFONO_GPRS_CONTEXT_TYPE_MMS: > + if (data->ap_type_configured & AP_TYPE_CONFIGURED_FLAG_MMS) > + return TRUE; > + break; > + case OFONO_GPRS_CONTEXT_TYPE_WAP: > + if (data->ap_type_configured & AP_TYPE_CONFIGURED_FLAG_WAP) > + return TRUE; > + break; > + default: > + break; > + } > + > + return FALSE; > +} > + > +static gboolean ap_type_valid(struct ofono_gprs_provision_data *ap) > +{ > + switch (ap->type) { > + case OFONO_GPRS_CONTEXT_TYPE_INTERNET: > + case OFONO_GPRS_CONTEXT_TYPE_MMS: > + case OFONO_GPRS_CONTEXT_TYPE_WAP: > + return TRUE; > + default: > + break; > + } > + > + return FALSE; > +} > + > +static void ap_type_mark_configured(struct provider_info *data, > + struct ofono_gprs_provision_data *ap) > +{ > + switch (ap->type) { > + case OFONO_GPRS_CONTEXT_TYPE_INTERNET: > + data->ap_type_configured |= AP_TYPE_CONFIGURED_FLAG_INTERNET; > + break; > + case OFONO_GPRS_CONTEXT_TYPE_MMS: > + data->ap_type_configured |= AP_TYPE_CONFIGURED_FLAG_MMS; > + break; > + case OFONO_GPRS_CONTEXT_TYPE_WAP: > + data->ap_type_configured |= AP_TYPE_CONFIGURED_FLAG_WAP; > + break; > + default: > + break; > + } > +} > + > +static gboolean ap_ignore(struct provider_info *data, > + struct ofono_gprs_provision_data *ap) > +{ > + if (data->ap_ignore == TRUE) > + return TRUE; > + > + if (ap_type_valid(ap) == FALSE) { > + DBG("AP '%s', type isn't supported or present, AP ignored", > + ap->apn); > + return TRUE; > + } > + > + if (ap_type_is_configured(data, ap) == TRUE) { > + DBG("AP '%s', multiple settings for type '%s' found, " > + "fail provisioning", > + ap->apn, ap_type_name(ap->type)); > + data->provisioning_fail = TRUE; > + return TRUE; > + } > + > + return FALSE; > +} > + > +static void element_end_parse(GMarkupParseContext *context, > + const gchar *element_name, > + gpointer user_data, GError **error) > +{ > + struct provider_info *data = user_data; > + struct ofono_gprs_provision_data *ap = data->ap; > + enum element_type element; > + > + if (data->match_found == FALSE) > + return; > + > + element = element_type(element_name); > + > + if (data->ap_ignore == FALSE) > + DBG("%s: '%s'", element_type_name(element), element_name); > + > + switch (element) { > + case ELEMENT_APN: > + if (ap_ignore(data, ap)) { > + ap_delete(data); > + if (data->provisioning_fail == TRUE) > + data->match_found = FALSE; > + } else > + ap_type_mark_configured(data, ap); > + > + data->ap_ignore = FALSE; > + break; > + case ELEMENT_GSM: > + data->match_found = FALSE; > + break; > + default: > + break; > + } > +} > + > +static void parser_error(GMarkupParseContext *context, > + GError *error, gpointer user_data) > +{ > + ofono_error("Error parsing %s: %s", PROVIDER_DATABASE, error->message); > +} > + > +static const GMarkupParser parser = { > + element_start_parse, > + element_end_parse, > + element_body_parse, > + NULL, > + parser_error, > +}; > + > +static void parse_database(const char *data, ssize_t size, > + struct provider_info *provider_info) > +{ > + GMarkupParseContext *context; > + gboolean result; > + > + provider_info->match_found = FALSE; > + > + context = g_markup_parse_context_new(&parser, > + G_MARKUP_TREAT_CDATA_AS_TEXT, > + provider_info, NULL); > + > + result = g_markup_parse_context_parse(context, data, size, NULL); > + if (result == TRUE) > + result = g_markup_parse_context_end_parse(context, NULL); > + > + g_markup_parse_context_free(context); > +} > + > +static void provider_info_aps_free(struct provider_info *data) > +{ > + while (data->ap_count) > + ap_delete(data); > +} > + > +static void provider_info_free(struct provider_info *data) > +{ > + g_free(data->spn); > + > + data->spn = NULL; > +} > + > +static int provider_info_lookup(struct provider_info *data, const char *mcc, > + const char *mnc, const char *spn) > +{ > + struct stat st; > + char *map; > + int fd; > + > + fd = open(PROVIDER_DATABASE, O_RDONLY); > + if (fd < 0) { > + ofono_error("Error: open(%s): %s", PROVIDER_DATABASE, > + strerror(errno)); > + return -errno; > + } > + > + if (fstat(fd, &st) < 0) { > + ofono_error("Error: fstat(%s): %s", PROVIDER_DATABASE, > + strerror(errno)); > + close(fd); > + return -errno; > + } > + > + map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); > + if (map == MAP_FAILED) { > + ofono_error("Error: mmap(%s): %s", PROVIDER_DATABASE, > + strerror(errno)); > + close(fd); > + return -errno; > + } > + > + memset(data, 0, sizeof(struct provider_info)); > + > + data->match_mcc = mcc; > + data->match_mnc = mnc; > + data->match_spn = spn; > + > + parse_database(map, st.st_size, data); > + > + munmap(map, st.st_size); > + > + close(fd); > + > + return data->ap_count; > +} > + > +static int mobile_broadband_provider_info_get(const char *mcc, > + const char *mnc, const char *spn, > + struct ofono_gprs_provision_data **settings, > + int *count) > +{ > + struct ofono_gprs_provision_data *aps; > + int ap_count; > + int i; > + > + DBG("Provisioning for MCC %s, MNC %s, SPN '%s'", mcc, mnc, spn); > + > + ap_count = provider_info_lookup(&provider_info, mcc, mnc, spn); > + if (ap_count <= 0 || provider_info.provisioning_fail == TRUE) { > + provider_info_aps_free(&provider_info); > + provider_info_free(&provider_info); > + return -ENOENT; > + } > + > + DBG("Found %d APs", ap_count); > + > + aps = g_try_malloc_n(ap_count, > + sizeof(struct ofono_gprs_provision_data)); > + if (aps == NULL) { > + ofono_error("Error provisioning APNs: memory exhausted"); > + provider_info_aps_free(&provider_info); > + provider_info_free(&provider_info); > + return -ENOMEM; > + } > + > + memcpy(aps, provider_info.settings, > + sizeof(struct ofono_gprs_provision_data) * ap_count); > + > + *settings = aps; > + *count = ap_count; > + > + for (i = 0; i < ap_count; aps++, i++) { > + DBG("Name: '%s'", aps->name); > + DBG("APN: '%s'", aps->apn); > + DBG("Type: %s", ap_type_name(aps->type)); > + DBG("Username: '%s'", aps->username); > + DBG("Password: '%s'", aps->password); > + } > + > + provider_info_free(&provider_info); You call provider_info_aps_free and provider_info_free in the error conditions above, but only provider_info_free here. Is there a reason? > + > + return 0; > +} > + > +static struct ofono_gprs_provision_driver provision_driver = { > + .name = "Mobile Broadband Provider Info", > + .get_settings = mobile_broadband_provider_info_get > +}; > + > +static int mobile_broadband_provider_info_init(void) > +{ > + return ofono_gprs_provision_driver_register(&provision_driver); > +} > + > +static void mobile_broadband_provider_info_exit(void) > +{ > + ofono_gprs_provision_driver_unregister(&provision_driver); > +} > + > +OFONO_PLUGIN_DEFINE(mobile_broadband_provider_info, > + "Mobile Broadband Provider Info Plugin", > + VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT, > + mobile_broadband_provider_info_init, > + mobile_broadband_provider_info_exit) Regards, -Denis