| /* |
| * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <glib.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| |
| #include "mobile_provider.h" |
| |
| struct mobile_provider_db { |
| GHashTable *name2provider; |
| GHashTable *network2provider; |
| }; |
| |
| struct parser_state { |
| const gchar *filename; |
| int linenum; |
| GHashTable *name_table; |
| GHashTable *network_table; |
| gchar *country; |
| struct mobile_provider *provider; |
| struct mobile_apn *apn; |
| int nameindex; |
| int apnindex; |
| }; |
| |
| #define WARN(fmt, arg...) \ |
| syslog(LOG_WARNING, "%s line %d: " fmt, state->filename, \ |
| state->linenum, ## arg) |
| #ifdef DEBUG |
| #define DEBUG(fmt, arg...) printf(fmt, ## arg) |
| #else |
| #define DEBUG(fmt, arg...) |
| #endif |
| |
| #define NORMAL_NETWORK_ID_LEN (6) |
| |
| /************ Utility functions ************/ |
| |
| /* |
| * Normalize a network ID by appending a '0' to the end if |
| * the network ID consists of 5 digits. |output| is assumed |
| * to refer to a buffer of at least 7 bytes. |
| */ |
| static void normalize(const gchar *input, gchar *output, size_t size) |
| { |
| int len = strlen(input); |
| if (len > size - 1) |
| len = size - 1; |
| strncpy(output, input, len); |
| if (len == NORMAL_NETWORK_ID_LEN-1) { |
| output[len] = '0'; |
| output[len+1] = '\0'; |
| } else { |
| output[len] = '\0'; |
| } |
| } |
| |
| /* |
| * Normalize a list of network IDs. Each id that is changed |
| * replaces the original string in the list. |
| */ |
| static void normalize_list(gchar **netids) |
| { |
| int listlen = g_strv_length(netids); |
| int i; |
| |
| for (i = 0; i < listlen; i++) { |
| gchar *id = netids[i]; |
| if (strlen(netids[i])) { |
| netids[i] = g_malloc0(NORMAL_NETWORK_ID_LEN+1); |
| normalize(id, netids[i], NORMAL_NETWORK_ID_LEN+1); |
| g_free(id); |
| } |
| } |
| } |
| |
| /* |
| * Extract a provider name from a string that may be of the form |
| * "<provider-name> (<extra-text>)". Modifies its argument. |
| */ |
| static char *extract_name(gchar *text) |
| { |
| char *paren = strchr(text, '('); |
| |
| if (paren != NULL) { |
| *paren = '\0'; |
| g_strchomp(text); |
| } |
| return g_strdup(text); |
| } |
| |
| static void free_network_providers(gpointer key, gpointer value, |
| gpointer user_data) |
| { |
| GSList *list = (GSList *)value; |
| g_slist_free(list); |
| } |
| |
| static void free_provider(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct mobile_provider *provider = (struct mobile_provider *)value; |
| struct mobile_provider *next = provider->next; |
| int i, j; |
| |
| /* |
| * A provider may be in the hash table under multiple names. |
| * Free the structure only when the last name is removed. |
| */ |
| while (provider != NULL) { |
| DEBUG("free_provider: %s / %s (%s)\n", |
| key == NULL ? "***" : (char *)key, |
| provider->names[0]->name, provider->country); |
| next = provider->next; |
| if (--provider->refcnt != 0) { |
| DEBUG("Not freeing %s / %s, refcnt = %d\n", |
| key == NULL ? "***" : (char *)key, |
| provider->names[0]->name, |
| provider->refcnt); |
| break; |
| } |
| for (i = 0; i < provider->num_apns; i++) { |
| for (j = 0; j < provider->apns[i]->num_names; j++) { |
| g_free(provider->apns[i]->names[j]->name); |
| g_free(provider->apns[i]->names[j]); |
| } |
| g_free(provider->apns[i]->names); |
| g_free(provider->apns[i]->value); |
| g_free(provider->apns[i]->username); |
| g_free(provider->apns[i]->password); |
| g_free(provider->apns[i]); |
| } |
| g_free(provider->apns); |
| for (i = 0; i < provider->num_names; i++) { |
| g_free(provider->names[i]->name); |
| g_free(provider->names[i]->lang); |
| g_free(provider->names[i]); |
| } |
| g_free(provider->names); |
| g_strfreev(provider->networks); |
| g_free(provider); |
| /* |
| * key is invalid after provider is freed |
| */ |
| key = NULL; |
| provider = next; |
| } |
| } |
| |
| static void provider_iter_shim(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct mobile_provider *provider = (struct mobile_provider *)value; |
| ProviderIterFunc func = (ProviderIterFunc)user_data; |
| func(provider); |
| } |
| |
| static void network_iter_shim(gpointer key, gpointer value, gpointer user_data) |
| { |
| gchar *network = (gchar *)key; |
| struct mobile_provider *provider = (struct mobile_provider *)value; |
| NetworkIterFunc func = (NetworkIterFunc)user_data; |
| func(network, provider); |
| } |
| |
| static void handle_provider(struct parser_state *state, gchar *text) |
| { |
| gchar **pfields; |
| int num_names; |
| int num_apns; |
| int primary; |
| int roaming; |
| struct mobile_provider *provider; |
| |
| state->provider = NULL; |
| state->apn = NULL; |
| state->apnindex = 0; |
| state->nameindex = 0; |
| pfields = g_strsplit(text, ",", 0); |
| if (g_strv_length(pfields) != 4) { |
| WARN("Badly formed \"providers\" entry: \"%s\"", text); |
| g_strfreev(pfields); |
| return; |
| } |
| errno = 0; |
| num_names = strtol(pfields[0], NULL, 0); |
| num_apns = strtol(pfields[1], NULL, 0); |
| primary = strtol(pfields[2], NULL, 0); |
| roaming = strtol(pfields[3], NULL, 0); |
| if (errno != 0) { |
| WARN("Error parsing \"providers\" entry \"%s\"", text); |
| g_strfreev(pfields); |
| return; |
| } |
| provider = g_new0(struct mobile_provider, 1); |
| strncpy(provider->country, state->country, sizeof(provider->country)-1); |
| provider->num_names = num_names; |
| if (num_names != 0) |
| provider->names = g_new0(struct localized_name *, num_names); |
| provider->num_apns = num_apns; |
| provider->primary = primary != 0; |
| if (num_apns != 0) |
| provider->apns = g_new0(struct mobile_apn *, num_apns); |
| provider->requires_roaming = roaming; |
| state->provider = provider; |
| g_strfreev(pfields); |
| } |
| |
| static void network_add_provider(GHashTable *network_table, |
| gchar *network_id, |
| struct mobile_provider *provider) |
| { |
| GSList *list; |
| |
| list = g_hash_table_lookup(network_table, network_id); |
| list = g_slist_prepend(list, provider); |
| g_hash_table_insert(network_table, network_id, list); |
| } |
| |
| static void handle_networks(struct parser_state *state, gchar *text) |
| { |
| int listlen; |
| int i; |
| struct mobile_provider *provider; |
| |
| if (state->provider == NULL) |
| return; |
| |
| provider = state->provider; |
| provider->networks = g_strsplit(text, ",", 0); |
| if (provider->networks != NULL) { |
| normalize_list(provider->networks); |
| listlen = g_strv_length(provider->networks); |
| for (i = 0; i < listlen; i++) { |
| network_add_provider(state->network_table, |
| provider->networks[i], |
| provider); |
| } |
| } |
| } |
| |
| static void handle_apn(struct parser_state *state, gchar *text) |
| { |
| gchar **apnfields; |
| struct mobile_apn *apn; |
| struct mobile_provider *provider; |
| int num_names; |
| |
| if (state->provider == NULL) |
| return; |
| |
| state->apn = NULL; |
| provider = state->provider; |
| state->nameindex = 0; |
| apnfields = g_strsplit(text, ",", 0); |
| if (g_strv_length(apnfields) != 4) { |
| WARN("Badly formed \"apn\" entry: \"%s\"", text); |
| g_strfreev(apnfields); |
| return; |
| } |
| errno = 0; |
| num_names = strtol(apnfields[0], NULL, 0); |
| if (errno != 0) { |
| WARN("Error parsing \"apn\" entry \"%s\"", text); |
| g_strfreev(apnfields); |
| return; |
| } |
| apn = g_new0(struct mobile_apn, 1); |
| apn->num_names = num_names; |
| apn->value = g_strdup(apnfields[1]); |
| if (strlen(apnfields[2]) != 0) |
| apn->username = g_strdup(apnfields[2]); |
| if (strlen(apnfields[3]) != 0) |
| apn->password = g_strdup(apnfields[3]); |
| provider->apns[state->apnindex++] = apn; |
| state->apn = apn; |
| g_strfreev(apnfields); |
| } |
| |
| static void handle_name(struct parser_state *state, gchar *text) |
| { |
| gchar **namefields; |
| struct localized_name *name = NULL; |
| struct mobile_provider *provider; |
| struct mobile_provider *other_provider; |
| struct mobile_apn *apn; |
| |
| if (state->provider == NULL && state->apn == NULL) |
| return; |
| |
| provider = state->provider; |
| apn = state->apn; |
| namefields = g_strsplit(text, ",", 2); |
| if (g_strv_length(namefields) != 2) { |
| WARN("Badly formed \"name\" entry: \"%s\"", text); |
| g_strfreev(namefields); |
| return; |
| } |
| name = g_new0(struct localized_name, 1); |
| if (strlen(namefields[0]) != 0) |
| name->lang = g_strdup(namefields[0]); |
| |
| if (apn != NULL) { |
| if (apn->names == NULL) |
| apn->names = g_new0(struct localized_name *, |
| apn->num_names); |
| name->name = g_strdup(namefields[1]); |
| apn->names[state->nameindex++] = name; |
| } else if (provider != NULL) { |
| /* |
| * If this is the first name encountered for the provider, |
| * put the provider in the hash table under this name. |
| */ |
| if (state->nameindex == 0) { |
| name->name = extract_name(namefields[1]); |
| /* |
| * If a provider already exists under this name, but |
| * for a different country, then chain the new provider |
| * onto the end of a linked list. |
| */ |
| other_provider = g_hash_table_lookup(state->name_table, |
| name->name); |
| if (other_provider == NULL) { |
| DEBUG("Add new %s (%s)\n", name->name, provider->country); |
| g_hash_table_insert(state->name_table, |
| name->name, provider); |
| } else { |
| DEBUG("Chain %s (%s) to (%s)\n", name->name, |
| provider->country, |
| other_provider->country); |
| while (other_provider->next != NULL) |
| other_provider = other_provider->next; |
| other_provider->next = provider; |
| } |
| ++provider->refcnt; |
| } else { |
| name->name = g_strdup(namefields[1]); |
| DEBUG("Additional name %s (%s) for %s\n", |
| name->name, provider->country, |
| provider->names[0]->name); |
| if (g_hash_table_lookup(state->name_table, |
| name->name) == NULL) { |
| g_hash_table_insert(state->name_table, |
| name->name, provider); |
| ++provider->refcnt; |
| } |
| } |
| provider->names[state->nameindex++] = name; |
| } |
| g_strfreev(namefields); |
| } |
| |
| /*********** Exported functions ***********/ |
| |
| struct mobile_provider_db *mobile_provider_open_db(const gchar *pathname) |
| { |
| char linebuf[BUFSIZ]; |
| char *line; |
| size_t linelen; |
| gchar **fields; |
| FILE *dbfile; |
| struct mobile_provider_db *db = NULL; |
| int firstline = 1; |
| const gchar *fname; |
| struct parser_state *state; |
| |
| dbfile = fopen(pathname, "r"); |
| if (dbfile == NULL) |
| return NULL; |
| |
| fname = strrchr(pathname, '/'); |
| if (fname == NULL) |
| fname = pathname; |
| else |
| ++fname; |
| state = g_new0(struct parser_state, 1); |
| state->filename = fname; |
| while ((line = fgets(linebuf, sizeof(linebuf), dbfile)) != NULL) { |
| ++state->linenum; |
| linelen = strlen(line); |
| if (linelen <= 1 || line[0] == '#') |
| continue; |
| if (line[linelen-1] == '\n') |
| line[linelen-1] = '\0'; |
| fields = g_strsplit(line, ":", 2); |
| if (g_strv_length(fields) != 2) { |
| WARN("Badly formed line: \"%s\"", line); |
| g_strfreev(fields); |
| continue; |
| } |
| /* Do a basic check to see whether we have a valid file */ |
| if (firstline) { |
| char *errmsg = NULL; |
| if (strcmp(fields[0], "serviceproviders") != 0) |
| errmsg = "File does not begin with \"serviceproviders\" entry"; |
| else if (strcmp(fields[1], "2.0") != 0) |
| errmsg = "Unrecognized serviceproviders format"; |
| if (errmsg != NULL) { |
| WARN("%s", errmsg); |
| g_strfreev(fields); |
| g_free(state); |
| errno = ENOSYS; |
| fclose(dbfile); |
| return NULL; |
| } |
| firstline = 0; |
| state->name_table = g_hash_table_new(g_str_hash, g_str_equal); |
| state->network_table = g_hash_table_new(g_str_hash, g_str_equal); |
| } else if (strcmp(fields[0], "country") == 0) { |
| g_free(state->country); |
| state->country = g_strdup(fields[1]); |
| } else if (strcmp(fields[0], "provider") == 0) { |
| handle_provider(state, fields[1]); |
| } else if (strcmp(fields[0], "networks") == 0) { |
| handle_networks(state, fields[1]); |
| } else if (strcmp(fields[0], "apn") == 0) { |
| handle_apn(state, fields[1]); |
| } else if (strcmp(fields[0], "name") == 0) { |
| handle_name(state, fields[1]); |
| } |
| g_strfreev(fields); |
| } |
| fclose(dbfile); |
| if (state->country == NULL && state->provider == NULL) { |
| g_hash_table_destroy(state->name_table); |
| g_hash_table_destroy(state->network_table); |
| errno = ENOMSG; |
| } else { |
| g_free(state->country); |
| db = g_new0(struct mobile_provider_db, 1); |
| db->name2provider = state->name_table; |
| db->network2provider = state->network_table; |
| } |
| g_free(state); |
| return db; |
| } |
| |
| void mobile_provider_close_db(struct mobile_provider_db *db) |
| { |
| if (db != NULL) { |
| g_hash_table_foreach(db->name2provider, free_provider, NULL); |
| g_hash_table_destroy(db->name2provider); |
| g_hash_table_foreach(db->network2provider, free_network_providers, NULL); |
| g_hash_table_destroy(db->network2provider); |
| g_free(db); |
| } |
| } |
| |
| |
| struct mobile_provider *network_find_provider( |
| const struct mobile_provider_db *db, |
| const gchar *network_id, |
| gboolean primary) |
| { |
| GSList *list; |
| |
| if (db == NULL || network_id == NULL) |
| return NULL; |
| |
| list = g_hash_table_lookup(db->network2provider, network_id); |
| for( ; list != NULL; list = list->next) { |
| struct mobile_provider *provider = list->data; |
| if (provider->primary == primary) |
| return provider; |
| } |
| return NULL; |
| } |
| |
| struct mobile_provider *mobile_provider_lookup_by_network( |
| const struct mobile_provider_db *db, |
| const gchar *network_id) |
| { |
| char netid[NORMAL_NETWORK_ID_LEN+1]; |
| struct mobile_provider *provider; |
| int primary; |
| |
| if (db == NULL || network_id == NULL) |
| return NULL; |
| |
| /* |
| * First try to find a match of a primary provider, if that |
| * fails, return the first matching non-primary provider. |
| */ |
| for (primary = 1; primary >= 0; primary--) { |
| |
| /* |
| * Try the lookup of the MCC/MNC pair with 6 digits |
| * and then 5 digits, to account for the vagaries of |
| * whether the MNC is 2 or 3 digits, which is |
| * difficult to determine. |
| */ |
| normalize(network_id, netid, sizeof(netid)); |
| provider = network_find_provider(db, netid, primary); |
| if (provider != NULL) |
| return provider; |
| normalize(network_id, netid, sizeof(netid) - 1); |
| provider = network_find_provider(db, netid, primary); |
| if (provider != NULL) |
| return provider; |
| } |
| return NULL; |
| } |
| |
| struct mobile_provider *mobile_provider_lookup_by_name( |
| const struct mobile_provider_db *db, |
| const gchar *provider_name) |
| { |
| if (db != NULL && provider_name != NULL && strlen(provider_name) != 0) |
| return g_hash_table_lookup(db->name2provider, provider_name); |
| return NULL; |
| } |
| |
| struct mobile_provider *mobile_provider_lookup_best_match( |
| const struct mobile_provider_db *db, |
| const gchar *provider_name, |
| const gchar *network_id) |
| { |
| struct mobile_provider *provider = NULL; |
| |
| if (db == NULL) |
| return NULL; |
| |
| if (provider_name != NULL) |
| provider = g_hash_table_lookup(db->name2provider, |
| provider_name); |
| /* |
| * We have a linked list of providers with the given name, but |
| * they may not be in the right country. Pick one with a |
| * matching MCC. If there are no networks listed for the provider, |
| * and there are no other providers with the same name, then return |
| * the result without doing any further checks. |
| */ |
| if (provider != NULL) { |
| /* |
| * If the provider has no networks listed, then return it |
| * if it is unambiguous, i.e., there's only one entry for |
| * that provider name. |
| */ |
| if (provider->networks == NULL || provider->networks[0] == NULL) { |
| if (provider->next == NULL) |
| return provider; |
| else { |
| syslog(LOG_WARNING, |
| "%s: Provider \"%s\" in country \"%s\" has no networks, " |
| "but has multiple entries.", |
| __func__, provider_name, provider->country); |
| return NULL; |
| } |
| } else { |
| for ( ; provider != NULL ; provider = provider->next) { |
| gchar **networks; |
| for (networks = provider->networks; |
| *networks != NULL; |
| networks++) { |
| if (strncmp(network_id, *networks, 3) == 0) |
| break; |
| } |
| if (*networks != NULL) |
| break; |
| } |
| } |
| } |
| if (provider ==NULL) |
| provider = mobile_provider_lookup_by_network(db, network_id); |
| return provider; |
| } |
| |
| |
| const gchar *mobile_provider_get_name(struct mobile_provider *provider) |
| { |
| if (provider != NULL && provider->num_names != 0) |
| return provider->names[0]->name; |
| return NULL; |
| } |
| |
| void mobile_provider_foreach_provider(const struct mobile_provider_db *db, |
| ProviderIterFunc func) |
| { |
| g_hash_table_foreach(db->name2provider, provider_iter_shim, func); |
| } |
| |
| void mobile_provider_foreach_network(const struct mobile_provider_db *db, |
| NetworkIterFunc func) |
| { |
| g_hash_table_foreach(db->network2provider, network_iter_shim, func); |
| } |