Merge remote-tracking branch 'cros/upstream' into 'cros/master'
BUG=none
TEST=xmllint --valid --noout serviceproviders.xml
TEST=run unit tests
diff --git a/Makefile.am b/Makefile.am
index 646d47c..a1afe21 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,8 +1,13 @@
+# 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.
+
## Process this file with automake to produce Makefile.in
-SP_XML_DB = serviceproviders.xml
+SUBDIRS = src
-dist_pkgdata_DATA = $(SP_XML_DB) serviceproviders.2.dtd
+# Install only the .bfd file, which is the only file used on Chromium OS.
+dist_pkgdata_DATA = @PROVIDERDB_BFD@
EXTRA_DIST = mobile-broadband-provider-info.pc.in
@@ -10,5 +15,7 @@
pkgconfig_DATA = mobile-broadband-provider-info.pc
check-local:
- xmllint --valid $(top_srcdir)/$(SP_XML_DB) 2>&1 > /dev/null || exit 1;
+ xmllint --valid --noout @PROVIDERDB_XML@ || exit 1;
+@PROVIDERDB_BFD@: @PROVIDERDB_XML@
+ xsltproc --nonet --output $@ @PROVIDERDB_CONVERTER@ $<
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
new file mode 100644
index 0000000..a2140cd
--- /dev/null
+++ b/PRESUBMIT.cfg
@@ -0,0 +1,5 @@
+# This config file disables some of the ChromiumOS source style checks.
+# Comment out the disable-flags for any checks you want to leave enabled.
+
+[Hook Overrides]
+tab_check: false
diff --git a/configure.ac b/configure.ac
old mode 100755
new mode 100644
index d76e88a..0429ebc
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,32 @@
AC_PREREQ(2.52)
-AC_INIT(mobile-broadband-provider-info, 20120614, dcbw@redhat.com, mobile-broadband-provider-info)
+AC_INIT([mobile-broadband-provider-info], [20120614],
+ [dcbw@redhat.com], [mobile-broadband-provider-info])
AM_INIT_AUTOMAKE([-Wall -Werror foreign no-dist-gzip dist-bzip2])
AM_MAINTAINER_MODE([enable])
+AC_PROG_CC
+AC_PROG_LIBTOOL
+
+AM_CONFIG_HEADER([config.h])
AC_CONFIG_FILES([
Makefile
+ src/Makefile
mobile-broadband-provider-info.pc
])
+
+PKG_CHECK_MODULES([glib], [glib-2.0])
+AC_SUBST(glib_CFLAGS)
+AC_SUBST(glib_LIBS)
+
+PROVIDERDB_XML="serviceproviders.xml"
+PROVIDERDB_BFD="serviceproviders.bfd"
+PROVIDERDB_CONVERTER="convert-serviceproviders.xsl"
+AC_SUBST(PROVIDERDB_XML)
+AC_SUBST(PROVIDERDB_BFD)
+AC_SUBST(PROVIDERDB_CONVERTER)
+
+AC_ARG_ENABLE(tools, AC_HELP_STRING([--enable-tools], [enable testing tools]))
+AM_CONDITIONAL(TOOLS, test "${enable_tools}" = "yes")
+
AC_OUTPUT
diff --git a/convert-serviceproviders.xsl b/convert-serviceproviders.xsl
new file mode 100644
index 0000000..e627cf0
--- /dev/null
+++ b/convert-serviceproviders.xsl
@@ -0,0 +1,107 @@
+<!--
+Connection Manager
+
+Copyright (C) 2011 The Chromium OS Authors. 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
+-->
+
+<!--
+Generate an easy-to-parse table of APN's from
+the mobile providers database
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:strip-space elements="country"/>
+
+<xsl:template match="provider">
+<xsl:if test="count(gsm[*])!=0">
+provider:<xsl:value-of select="count(name)"/>,<xsl:value-of select="count(gsm/apn)"/>,<xsl:choose>
+ <xsl:when test="@primary='true'">1</xsl:when><xsl:otherwise>0</xsl:otherwise>
+</xsl:choose>,<xsl:choose>
+ <xsl:when test="@roaming-required='true'">1</xsl:when><xsl:otherwise>0</xsl:otherwise>
+</xsl:choose>
+<xsl:apply-templates select="name"/><xsl:apply-templates select="gsm"/>
+</xsl:if>
+</xsl:template>
+
+<xsl:template match="name">
+name:<xsl:value-of select="@xml:lang"/>,<xsl:value-of select="."/>
+</xsl:template>
+
+<xsl:template match="gsm">
+networks:<xsl:apply-templates select="network-id"/>
+<xsl:apply-templates select="apn"/></xsl:template>
+
+<xsl:template match="network-id"><xsl:value-of select="@mcc"/><xsl:value-of select="@mnc"/><xsl:if test="position()!=last()">,</xsl:if></xsl:template>
+
+<xsl:template match="apn">
+apn:<xsl:value-of select="count(name)"/>,<xsl:value-of select="@value"/>,<xsl:value-of select="username"/>,<xsl:value-of select="password"/>
+<xsl:apply-templates select="name"/>
+</xsl:template>
+
+<xsl:output method="text" indent="no" encoding="utf-8"
+ omit-xml-declaration="yes"/>
+
+<xsl:template match="/serviceproviders"># Generated file -- Do not edit
+#
+# This file starts with the line
+#
+# serviceproviders:<xsl:value-of select="@format"/>
+#
+# The rest of the file is divided into country blocks, each of which
+# is of the form
+#
+# country:xx
+# <provider block 1>
+# <provider block 2>
+# . . .
+# <provider block N>
+#
+# where xx is the two character ISO country code.
+#
+# Each provider block is of the form
+#
+# provider:<# of names>,<# of APNs>,<primary>
+# name:<lang>,<name 1>
+# name:<lang>,<name 2>
+# . . .
+# networks:NNNNNN,NNNNNN,...
+# apn:<# of descriptive names>,<value>,<username>,<password>
+# name:<lang>,<desciptive name 1>
+# name:<lang>,<desciptive name 2>
+# . . .
+# apn:<# of descriptive names>,<value>,<username>,<password>
+# . . .
+#
+# where each NNNNNN is the five or six digit string constructed by
+# concatenating the three-digit MCC (Mobile Country Code) and two-
+# or three-digit MNC (Mobile Network Code), and where the <username>
+# and <password> fields may be empty. Each 'name' entry for both
+# providers and APNs, has an optional language attribute that can
+# be used to choose a localized name to display to the user, based
+# on the current locale. The language tags are in the format defined
+# by RFC 5646.
+#
+serviceproviders:<xsl:value-of select="@format"/>
+<xsl:text>
+</xsl:text>
+<xsl:for-each select="./country">country:<xsl:value-of select="@code"/>
+<xsl:apply-templates/><xsl:text>
+</xsl:text>
+</xsl:for-each>
+</xsl:template>
+</xsl:stylesheet>
+
+<!-- vim:set sw=2 sts=2 et: -->
diff --git a/serviceproviders.2.dtd b/serviceproviders.2.dtd
index a7f64ff..ee08440 100644
--- a/serviceproviders.2.dtd
+++ b/serviceproviders.2.dtd
@@ -11,6 +11,11 @@
<!-- that piggy-back on the primary provider. -->
<!ATTLIST provider primary (true|false) #IMPLIED>
+<!-- Indicates this provider requires roaming and the customer is not -->
+<!-- charged additional for roaming. This is particularly useful for -->
+<!-- some MVNOs. -->
+<!ATTLIST provider roaming-required (true|false) "false">
+
<!ELEMENT gsm (network-id*, msisdn-query*, voicemail*, balance-check*, balance-top-up*, apn*)>
<!ELEMENT apn (plan*,
usage*,
diff --git a/serviceproviders.xml b/serviceproviders.xml
index cff4e73..579eb22 100644
--- a/serviceproviders.xml
+++ b/serviceproviders.xml
@@ -295,7 +295,7 @@
<plan type="postpaid"/>
<usage type="internet"/>
<name>A1 Breitband</name>
- <username>ppp@a1plus.at</username>
+ <username>ppp@A1plus.at</username>
<password>ppp</password>
<dns>194.48.124.202</dns>
<dns>194.48.124.200</dns>
@@ -2536,8 +2536,8 @@
</apn>
</gsm>
</provider>
- <provider>
- <name>Vodafone</name>
+ <provider primary="true">
+ <name>Vodafone.de</name>
<gsm>
<network-id mcc="262" mnc="02"/>
<network-id mcc="262" mnc="04"/>
@@ -2886,7 +2886,7 @@
</apn>
</gsm>
</provider>
- <provider>
+ <provider primary="true">
<name>TDC</name>
<gsm>
<network-id mcc="238" mnc="01"/>
@@ -3359,7 +3359,7 @@
</gsm>
</provider>
<provider>
- <name>Vodafone (Airtel)</name>
+ <name>Vodafone</name>
<gsm>
<network-id mcc="214" mnc="01"/>
<network-id mcc="214" mnc="06"/>
@@ -3638,8 +3638,8 @@
<!-- Fiji -->
<country code="fj">
- <provider>
- <name>Vodafone / Kidanet</name>
+ <provider primary="true">
+ <name>Vodafone</name>
<gsm>
<network-id mcc="542" mnc="01"/>
<msisdn-query>
@@ -4055,7 +4055,7 @@
</gsm>
</provider>
<provider>
- <name>Vodafone</name>
+ <name>Vodafone UK</name>
<gsm>
<network-id mcc="234" mnc="15"/>
<msisdn-query>
@@ -4069,35 +4069,44 @@
<apn value="internet">
<plan type="postpaid"/>
<usage type="internet"/>
- <name>Contract</name>
+ <name>Contract Internet</name>
<username>web</username>
<password>web</password>
<dns>10.206.65.68</dns>
<dns>10.203.65.68</dns>
</apn>
+ <apn value="ppbundle.internet">
+ <plan type="prepaid"/>
+ <usage type="internet"/>
+ <name>PAYG Internet Bundle</name>
+ <username>web</username>
+ <password>web</password>
+ <dns>10.203.129.68</dns>
+ <dns>10.203.129.68</dns>
+ </apn>
+ <apn value="smart">
+ <plan type="prepaid"/>
+ <usage type="internet"/>
+ <name>PAYG Internet</name>
+ <username>web</username>
+ <password>web</password>
+ </apn>
+ <apn value="pp.internet">
+ <plan type="prepaid"/>
+ <usage type="internet"/>
+ <name>PAYG Internet</name>
+ <username>web</username>
+ <password>web</password>
+ </apn>
<apn value="pp.vodafone.co.uk">
<plan type="prepaid"/>
<usage type="internet"/>
- <name>Prepaid</name>
+ <name>PAYG WAP</name>
<username>web</username>
<password>web</password>
<dns>172.29.1.11</dns>
<dns>172.29.1.11</dns>
</apn>
- <apn value="ppbundle.internet">
- <plan type="postpaid"/>
- <usage type="internet"/>
- <name>TopUp and Go</name>
- <username>web</username>
- <password>web</password>
- <dns>10.203.129.68</dns>
- <dns>10.203.129.68</dns>
- </apn>
- <apn value="pp.internet">
- <plan type="postpaid"/>
- <usage type="internet"/>
- <name>TopUp and Go (older 1GB SIMs)</name>
- </apn>
</gsm>
</provider>
<provider>
@@ -4494,7 +4503,7 @@
</gsm>
</provider>
<provider>
- <name>SmarTone</name>
+ <name>SmarTone-Vodafone</name>
<gsm>
<network-id mcc="454" mnc="06"/>
<apn value="internet">
@@ -5036,6 +5045,16 @@
</apn>
</gsm>
</provider>
+ <provider roaming-required="true">
+ <name>Cubic</name>
+ <name>CUBIC</name>
+ <gsm>
+ <apn value="mtoom.com">
+ <username>mtoom</username>
+ <password>mtoom</password>
+ </apn>
+ </gsm>
+ </provider>
</country>
<!-- Israel -->
@@ -5262,6 +5281,7 @@
<network-id mcc="404" mnc="86"/>
<network-id mcc="404" mnc="88"/>
<network-id mcc="405" mnc="66"/>
+ <network-id mcc="405" mnc="67"/>
<network-id mcc="405" mnc="750"/>
<network-id mcc="405" mnc="751"/>
<network-id mcc="405" mnc="752"/>
@@ -5664,7 +5684,7 @@
<!-- Italy -->
<country code="it">
- <provider>
+ <provider primary="true">
<name>Vodafone</name>
<gsm>
<network-id mcc="222" mnc="10"/>
@@ -6069,6 +6089,14 @@
<username>web</username>
<password>web</password>
</apn>
+ <apn value="pp.internet">
+ <username>web</username>
+ <password>web</password>
+ </apn>
+ <apn value="ppbundle.internet">
+ <username>web</username>
+ <password>web</password>
+ </apn>
</gsm>
</provider>
<provider>
@@ -7566,7 +7594,7 @@
</apn>
</gsm>
</provider>
- <provider>
+ <provider primary="true">
<name>KPN NL</name>
<gsm>
<network-id mcc="204" mnc="08"/>
@@ -8674,7 +8702,7 @@
</apn>
</gsm>
</provider>
- <provider>
+ <provider primary="true">
<name>Vodafone</name>
<gsm>
<network-id mcc="268" mnc="01"/>
@@ -9321,6 +9349,16 @@
<!-- Sweden -->
<country code="se">
+ <provider>
+ <name>Teliasonera</name>
+ <gsm>
+ <network-id mcc="240" mnc="01"/>
+ <apn value="online.telia.se">
+ <plan type="postpaid"/>
+ <usage type="internet"/>
+ </apn>
+ </gsm>
+ </provider>
<provider primary="true">
<name>3</name>
<gsm>
@@ -10014,7 +10052,7 @@
</apn>
</gsm>
</provider>
- <provider>
+ <provider primary="true">
<name>Vodafone</name>
<gsm>
<network-id mcc="286" mnc="02"/><!-- mnc="0251" in Vodofone XML -->
@@ -10441,7 +10479,7 @@
<!-- United States -->
<country code="us">
- <provider>
+ <provider primary="true">
<name>AT&T</name>
<gsm>
<network-id mcc="310" mnc="038"/>
@@ -10593,15 +10631,10 @@
<cdma />
</provider>
<provider>
- <name>Verizon</name>
+ <name>Verizon Wireless</name>
<gsm>
<network-id mcc="310" mnc="995"/>
<network-id mcc="311" mnc="480"/>
- <apn value="vzwims">
- <plan type="postpaid"/>
- <usage type="internet"/>
- <name>4G LTE Contract</name>
- </apn>
<apn value="vzwinternet">
<plan type="postpaid"/>
<usage type="internet"/>
@@ -10609,11 +10642,18 @@
<dns>66.174.92.14</dns>
<dns>69.78.96.14</dns>
</apn>
+ <!-- Commented out because these are not useful to ChromeOS
+ <apn value="vzwims">
+ <plan type="postpaid"/>
+ <usage type="internet"/>
+ <name>4G LTE Contract</name>
+ </apn>
<apn value="vzwapp">
<plan type="postpaid"/>
<usage type="wap"/>
<name>4G LTE Contract</name>
</apn>
+ -->
</gsm>
<cdma>
<!-- Assignments taken from IFAST: http://www.ifast.org/files/NationalSID.htm -->
@@ -11610,5 +11650,17 @@
</provider>
</country>
+<!-- Fake country for testing. ZZ is a user-assigned element -->
+<country code="zz">
+ <provider>
+ <name>Test Network</name>
+ <gsm>
+ <network-id mcc="001" mnc="01"/>
+ <apn value="internet">
+ </apn>
+ </gsm>
+ </provider>
+</country>
+
</serviceproviders>
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..1c7b35f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,23 @@
+# 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.
+
+AM_CPPFLAGS = @glib_CFLAGS@ -I.
+
+include_HEADERS = mobile_provider.h
+COMMON_LIBS = libmobile-provider.la @glib_LIBS@
+
+lib_LTLIBRARIES = libmobile-provider.la
+libmobile_provider_la_SOURCES = mobile_provider.c
+
+if TOOLS
+bin_PROGRAMS = browsedb
+else
+noinst_PROGRAMS = browsedb
+endif
+browsedb_SOURCES = browsedb.c
+browsedb_LDADD = $(COMMON_LIBS)
+
+check_PROGRAMS = mobile_provider_unittest
+mobile_provider_unittest_SOURCES = mobile_provider_unittest.c
+mobile_provider_unittest_LDADD = $(COMMON_LIBS) -lgtest
diff --git a/src/browsedb.c b/src/browsedb.c
new file mode 100644
index 0000000..15ee05b
--- /dev/null
+++ b/src/browsedb.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "mobile_provider.h"
+
+void print_provider(struct mobile_provider *provider)
+{
+ struct mobile_apn *apn;
+ gchar *networks;
+ int i, j;
+ int num_networks;
+ int namelen, maxnamelen;
+ int userlen, maxuserlen;
+ int passlen, maxpasslen;
+
+ num_networks = g_strv_length(provider->networks);
+ printf("\"\033[1;33m%s\033[m\" in country \"\033[1;33m%s\033[m\" has "
+ "%d network ID%s and %d APN%s%s\n",
+ provider->names[0]->name,
+ provider->country,
+ num_networks,
+ num_networks == 1 ? "" : "s",
+ provider->num_apns,
+ provider->num_apns == 1 ? "" : "s",
+ provider->primary ? " [\033[1;31mprimary\033[m]" : "");
+ for (i = 1; i < provider->num_names; i++)
+ if (provider->names[i]->lang == NULL)
+ printf(" name: %s\n", provider->names[i]->name);
+ else
+ printf(" name: %s lang: %s\n",
+ provider->names[i]->name,
+ provider->names[i]->lang);
+ networks = g_strjoinv(",", provider->networks);
+ printf(" networks: %s\n", networks);
+ g_free(networks);
+ maxnamelen = -1;
+ maxuserlen = maxpasslen = strlen("<none>");
+ for (i = 0; i < provider->num_apns; i++) {
+ apn = provider->apns[i];
+ namelen = strlen(apn->value);
+ if (namelen > maxnamelen)
+ maxnamelen = namelen;
+ if (apn->username && (userlen = strlen(apn->username)) > maxuserlen)
+ maxuserlen = userlen;
+ if (apn->password && (passlen = strlen(apn->password)) > maxpasslen)
+ passlen = passlen;
+ }
+ if (maxuserlen == -1)
+ maxuserlen = strlen("<none>");
+ if (maxpasslen == -1)
+ maxpasslen = strlen("<none>");
+ for (i = 0; i < provider->num_apns; i++) {
+ apn = provider->apns[i];
+ printf(" APN %d: \033[1;32m%-*s\033[m username %-*s password %-*s\n",
+ i+1,
+ maxnamelen, apn->value,
+ maxuserlen, apn->username ? apn->username : "<none>",
+ maxpasslen, apn->password ? apn->password : "<none>");
+ for (j = 0; j < apn->num_names; j++)
+ if (apn->names[j]->lang == NULL)
+ printf(" name: %s\n", apn->names[j]->name);
+ else
+ printf(" name: %s lang; %s\n",
+ apn->names[j]->name,
+ apn->names[j]->lang);
+ }
+}
+
+void print_provider_list(struct mobile_provider *provider)
+{
+ while (provider != NULL) {
+ print_provider(provider);
+ provider = provider->next;
+ }
+ printf("---------------------------------------------------------------\n");
+}
+
+void print_network(gchar *network, struct mobile_provider *provider)
+{
+ printf("[%s]: ", network);
+ print_provider(provider);
+ printf("---------------------------------------------------------------\n");
+}
+
+int main(int argc, const char *argv[])
+{
+ char linebuf[BUFSIZ];
+ char *line;
+ struct mobile_provider *provider;
+ struct mobile_provider_db *provider_db;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
+ exit(1);
+ }
+ provider_db = mobile_provider_open_db(argv[1]);
+ if (provider_db == NULL) {
+ perror("Cannot open provider database");
+ exit(1);
+ }
+ while (1) {
+ int linelen;
+ printf("Type a network ID or provider name: ");
+ line = fgets(linebuf, sizeof(linebuf), stdin);
+ if (line == NULL || line[0] == '\n')
+ break;
+ linelen = strlen(line);
+ if (line[linelen-1] == '\n')
+ line[--linelen] = '\0';
+ if ((linelen == 5 || linelen == 6) &&
+ isdigit(line[0]) && isdigit(line[1]) && isdigit(line[linelen-1])) {
+ provider = mobile_provider_lookup_by_network(provider_db,
+ line);
+ if (provider != NULL)
+ print_provider(provider);
+ } else if (line[0] == '*') {
+ if (line[1] == 'p')
+ mobile_provider_foreach_provider(provider_db,
+ print_provider_list);
+ else if (line[1] == 'n')
+ mobile_provider_foreach_network(provider_db,
+ print_network);
+ else
+ printf("error: Unknown command \"%s\"\n", line);
+ continue;
+ } else {
+ provider = mobile_provider_lookup_by_name(provider_db,
+ line);
+ if (provider != NULL)
+ print_provider_list(provider);
+ }
+ if (provider == NULL)
+ printf("No provider found for %s\n", line);
+ }
+ mobile_provider_close_db(provider_db);
+ return 0;
+}
diff --git a/src/mobile_provider.c b/src/mobile_provider.c
new file mode 100644
index 0000000..bf1b0a9
--- /dev/null
+++ b/src/mobile_provider.c
@@ -0,0 +1,592 @@
+/*
+ * 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);
+}
diff --git a/src/mobile_provider.h b/src/mobile_provider.h
new file mode 100644
index 0000000..ee18e7a
--- /dev/null
+++ b/src/mobile_provider.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef __MOBILE_PROVIDER_H
+#define __MOBILE_PROVIDER_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct localized_name {
+ gchar *name;
+ gchar *lang;
+};
+
+struct mobile_apn {
+ gchar *value;
+ int num_names;
+ struct localized_name **names;
+ gchar *username;
+ gchar *password;
+};
+
+struct mobile_provider
+{
+ /*
+ * the "next" field links providers with
+ * same name in different countries
+ */
+ struct mobile_provider *next;
+ gchar country[3];
+ gchar **networks;
+ int num_names;
+ struct localized_name **names;
+ int num_apns;
+ struct mobile_apn **apns;
+ int refcnt;
+ gboolean primary;
+ int requires_roaming;
+};
+
+struct mobile_provider_db;
+
+typedef void (*ProviderIterFunc)(struct mobile_provider *);
+typedef void (*NetworkIterFunc)(gchar *, struct mobile_provider *);
+
+struct mobile_provider_db *mobile_provider_open_db(const gchar *pathname);
+void mobile_provider_close_db(struct mobile_provider_db *db);
+struct mobile_provider *mobile_provider_lookup_by_name(
+ const struct mobile_provider_db *db,
+ const gchar *provider_name);
+struct mobile_provider *mobile_provider_lookup_by_network(
+ const struct mobile_provider_db *db,
+ const gchar *network_id);
+struct mobile_provider *mobile_provider_lookup_best_match(
+ const struct mobile_provider_db *db,
+ const gchar *provider_name,
+ const gchar *network_id);
+const gchar *mobile_provider_get_name(struct mobile_provider *provider);
+void mobile_provider_foreach_provider(
+ const struct mobile_provider_db *db,
+ ProviderIterFunc func);
+void mobile_provider_foreach_network(
+ const struct mobile_provider_db *db,
+ NetworkIterFunc func);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MOBILE_PROVIDER_H */
diff --git a/src/mobile_provider_unittest.c b/src/mobile_provider_unittest.c
new file mode 100644
index 0000000..cf59231
--- /dev/null
+++ b/src/mobile_provider_unittest.c
@@ -0,0 +1,499 @@
+/*
+ * 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 <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mobile_provider.h"
+
+const char *testfile = "/tmp/providers_test_data";
+
+typedef struct {
+ const char *filename;
+ struct mobile_provider_db *provider_db;
+} TestFixture;
+
+static char *test_db =
+"serviceproviders:2.0\n"
+"country:at\n"
+"provider:1,2,0,0\n"
+"name:,A1/Telekom Austria\n"
+"networks:23201\n"
+"apn:1,a1.net,ppp@a1plus.at,ppp\n"
+"name:,A1 Breitband\n"
+"apn:1,aon.data,,ppp\n"
+"name:,aon (Flex, Breitband-Duo, BusinessFlex)\n"
+"provider:1,3,0,0\n"
+"name:,T-Mobile\n"
+"networks:23203\n"
+"apn:1,gprswap,t-mobile,tm\n"
+"name:,WAP\n"
+"apn:1,gprsinternet,t-mobile,tm\n"
+"name:,Internet\n"
+"apn:1,business.gprsinternet,t-mobile,tm\n"
+"name:,Business Internet\n"
+"country:ca\n"
+"provider:1,1,0,0\n"
+"name:,Vidéotron\n"
+"networks:302500,302510\n"
+"apn:1,ihvm.videotron,,\n"
+"name:,IHVM\n"
+"country:gb\n"
+"provider:1,1,0,0\n"
+"name:,T-Mobile (Great Britain)\n"
+"networks:23430\n"
+"apn:0,general.t-mobile.uk,User,mms\n"
+"provider:1,1,0,0\n"
+"name:,3\n"
+"networks:23499\n"
+"apn:0,three.uk,,\n"
+"country:ie\n"
+"provider:2,1,0,1\n"
+"name:,Cubic\n"
+"name:,CUBIC\n"
+"networks:\n"
+"apn:0,mtoom.com,mtoom,mtoom\n"
+"country:ir\n"
+"provider:1,1,0,0\n"
+"name:,همراه اول\n"
+"networks:43211\n"
+"apn:0,mcinet,,\n"
+"provider:1,1,0,0\n"
+"name:,ایرانسل\n"
+"networks:43235\n"
+"apn:0,mtnirancell,,\n"
+"country:py\n"
+"provider:1,2,0,0\n"
+"name:,Tigo\n"
+"networks:74404\n"
+"apn:1,internet.tigo.py,,\n"
+"name:,Internet\n"
+"apn:2,broadband.tigo.py,tigo,tigo\n"
+"name:,Broadband\n"
+"name:es,Banda Ancha Móvil\n"
+"country:ru\n"
+"provider:2,1,0,0\n"
+"name:,BaikalWestCom\n"
+"name:ru,БайкалВестКом\n"
+"networks:25012\n"
+"apn:0,inet.bwc.ru,bwc,bwc\n"
+"country:us\n"
+"provider:1,1,0,0\n"
+"name:,3\n"
+"networks:31099\n"
+"apn:0,three.com,,\n"
+"provider:1,4,0,0\n"
+"name:,AT&T\n"
+"networks:310038,310090,310150,310410,310560,310680\n"
+"apn:0,Broadband,,\n"
+"apn:1,wap.cingular,,\n"
+"name:,MEdia Net\n"
+"apn:1,isp.cingular,,\n"
+"name:,Data Connect\n"
+"apn:1,ISP.CINGULAR,ISPDA@CINGULARGPRS.COM,CINGULAR1\n"
+"name:,Data Connect (Accelerated)\n"
+"provider:1,4,1,0\n"
+"name:,T-Mobile\n"
+"networks:310160,310200,310210,310220,310230,310240,310250,310260,310270,"
+ "310310,310490,31058,310660,310800\n"
+"apn:1,epc.tmobile.com,,\n"
+"name:,Internet/WebConnect\n"
+"apn:1,wap.voicestream.com,,\n"
+"name:,Web2Go/t-zones\n"
+"apn:1,internet2.voicestream.com,,\n"
+"name:,Internet (old)\n"
+"apn:1,internet3.voicestream.com,,\n"
+"name:,Internet with VPN (old)\n"
+"provider:1,1,0,0\n"
+"name:,T-Mobile MVNO\n"
+"networks:310160,310200,310210,310220,310230,310240,310250,310260,310270,"
+ "310310,310490,31058,310660,310800\n"
+"apn:1,mvno.tmobile.com,,\n"
+"name:,MVNO Internet/WebConnect\n"
+;
+
+static char *test_db_ambiguous =
+"serviceproviders:2.0\n"
+"country:gb\n"
+"provider:1,1,0,0\n"
+"name:,T-Mobile (Great Britain)\n"
+"networks:23430\n"
+"apn:0,general.t-mobile.uk,User,mms\n"
+"provider:1,1,0,0\n"
+"name:,3\n"
+"networks:23499\n"
+"apn:0,three.uk,,\n"
+"country:ie\n"
+"provider:2,1,0,1\n"
+"name:,Cubic\n"
+"name:,CUBIC\n"
+"networks:\n"
+"apn:0,mtoom.com,mtoom,mtoom\n"
+"country:ir\n"
+"provider:1,1,0,0\n"
+"name:,همراه اول\n"
+"networks:43211\n"
+"apn:0,mcinet,,\n"
+"provider:1,1,0,0\n"
+"name:,ایرانسل\n"
+"networks:43235\n"
+"apn:0,mtnirancell,,\n"
+"country:py\n"
+"provider:2,1,0,1\n"
+"name:,Cubic\n"
+"name:,CUBIC\n"
+"networks:\n"
+"apn:0,moot.com,moot,moot\n"
+"provider:1,2,0,0\n"
+"name:,Tigo\n"
+"networks:74404\n"
+"apn:1,internet.tigo.py,,\n"
+"name:,Internet\n"
+"apn:2,broadband.tigo.py,tigo,tigo\n"
+"name:,Broadband\n"
+"name:es,Banda Ancha Móvil\n"
+"country:ru\n"
+"provider:2,1,0,0\n"
+"name:,BaikalWestCom\n"
+"name:ru,БайкалВестКом\n"
+"networks:25012\n"
+"apn:0,inet.bwc.ru,bwc,bwc\n"
+;
+
+extern struct mobile_provider *network_find_provider(
+ const struct mobile_provider_db *db,
+ const gchar *network_id,
+ gboolean primary);
+
+void setup_data(const char *file, const char *test_data)
+{
+ gboolean success = g_file_set_contents(file, test_data, -1, NULL);
+ g_assert(success == TRUE);
+}
+
+void test_fixture_setup(TestFixture *fixture, gconstpointer test_data)
+{
+ g_assert(fixture->provider_db == NULL);
+ if (fixture->filename == NULL) {
+ setup_data(testfile, test_data);
+ fixture->filename = testfile;
+ }
+ fixture->provider_db = mobile_provider_open_db(testfile);
+ g_assert(fixture->provider_db != NULL);
+}
+
+void test_fixture_teardown(TestFixture *fixture, gconstpointer test_data)
+{
+ g_assert(fixture->provider_db != NULL);
+ mobile_provider_close_db(fixture->provider_db);
+ fixture->provider_db = NULL;
+}
+
+void test_lookup_by_name(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db, "AT&T");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "AT&T");
+ g_assert_cmpstr(provider->country, ==, "us");
+ g_assert_cmpint(provider->num_apns, ==, 4);
+ g_assert_cmpint(provider->requires_roaming, ==, 0);
+ g_assert_cmpint(g_strv_length(provider->networks), ==, 6);
+ g_assert_cmpstr(provider->networks[2], ==, "310150");
+ g_assert_cmpstr(provider->apns[3]->value, ==, "ISP.CINGULAR");
+ g_assert_cmpstr(provider->apns[3]->username, ==, "ISPDA@CINGULARGPRS.COM");
+ g_assert_cmpstr(provider->apns[3]->password, ==, "CINGULAR1");
+}
+
+void test_lookup_by_mvno(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db,
+ "T-Mobile MVNO");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->primary, ==, 0);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "T-Mobile MVNO");
+ g_assert_cmpstr(provider->country, ==, "us");
+ g_assert_cmpint(provider->num_apns, ==, 1);
+ g_assert_cmpint(g_strv_length(provider->networks), ==, 14);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "mvno.tmobile.com");
+ g_assert(provider->apns[0]->username == NULL);
+ g_assert(provider->apns[0]->password == NULL);
+}
+
+void test_lookup_by_network(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_network(fixture->provider_db, "31026");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->primary, ==, 1);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "T-Mobile");
+ g_assert_cmpstr(provider->country, ==, "us");
+ g_assert_cmpint(provider->num_apns, ==, 4);
+ g_assert_cmpint(provider->requires_roaming, ==, 0);
+ g_assert_cmpint(g_strv_length(provider->networks), ==, 14);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "epc.tmobile.com");
+ g_assert_cmpstr(provider->apns[1]->value, ==, "wap.voicestream.com");
+ g_assert(provider->apns[0]->username == NULL);
+ g_assert(provider->apns[1]->password == NULL);
+}
+
+void test_other_network_id(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+ struct mobile_provider *provider1;
+
+ provider = mobile_provider_lookup_by_network(fixture->provider_db, "310260");
+ g_assert(provider != NULL);
+ provider1 = mobile_provider_lookup_by_network(fixture->provider_db, "310210");
+ g_assert(provider1 == provider);
+}
+
+void test_normalize_network_id(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+ struct mobile_provider *provider1;
+
+ provider = mobile_provider_lookup_by_network(fixture->provider_db, "31058");
+ g_assert(provider != NULL);
+ provider1 = mobile_provider_lookup_by_network(fixture->provider_db, "310580");
+ g_assert(provider1 == provider);
+}
+
+void test_no_network_id(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ /* In the following, the network ID argument will be ignored */
+ provider = mobile_provider_lookup_best_match(fixture->provider_db,
+ "Cubic", "11111");
+ g_assert(provider != NULL);
+ g_assert(provider->next == NULL);
+ g_assert_cmpstr(provider->country, ==, "ie");
+ g_assert_cmpint(provider->num_names, ==, 2);
+ g_assert_cmpint(provider->num_apns, ==, 1);
+ g_assert_cmpint(provider->requires_roaming, ==, 1);
+}
+
+void test_no_network_id_ambiguous(TestFixture *fixture,
+ gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ /* In the following, the network ID argument will be ignored */
+ provider = mobile_provider_lookup_best_match(fixture->provider_db,
+ "Cubic", "11111");
+ g_assert(provider == NULL);
+}
+
+void test_multi_country(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db, "T-Mobile");
+ g_assert(provider != NULL);
+ g_assert_cmpstr(provider->country, ==, "at");
+ g_assert(provider->next != NULL);
+ g_assert_cmpstr(provider->next->country, ==, "gb");
+ g_assert(provider->next->next != NULL);
+ g_assert_cmpstr(provider->next->next->country, ==, "us");
+}
+
+void test_non_ascii_names(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db, "Vidéotron");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "Vidéotron");
+ g_assert_cmpstr(provider->country, ==, "ca");
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db,
+ "همراه اول");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "همراه اول");
+ g_assert_cmpstr(provider->country, ==, "ir");
+}
+
+void test_multi_provider_names(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db,
+ "BaikalWestCom");
+ g_assert(provider != NULL);
+ g_assert_cmpstr(provider->country, ==, "ru");
+ g_assert_cmpint(provider->num_names, ==, 2);
+ g_assert_cmpstr(provider->names[0]->name, ==, "BaikalWestCom");
+ g_assert(provider->names[0]->lang == NULL);
+ g_assert_cmpstr(provider->names[1]->name, ==, "БайкалВестКом");
+ g_assert(provider->names[1]->lang != NULL);
+ g_assert_cmpstr(provider->names[1]->lang, ==, "ru");
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db,
+ "БайкалВестКом");
+ g_assert(provider != NULL);
+ g_assert_cmpstr(provider->country, ==, "ru");
+ g_assert_cmpint(provider->num_names, ==, 2);
+ g_assert_cmpstr(provider->names[0]->name, ==, "BaikalWestCom");
+}
+
+void test_multi_apn_names(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db, "Tigo");
+ g_assert(provider != NULL);
+ g_assert_cmpstr(provider->country, ==, "py");
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "Tigo");
+ g_assert(provider->names[0]->lang == NULL);
+ g_assert_cmpint(provider->num_apns, ==, 2);
+
+ g_assert_cmpint(provider->apns[0]->num_names, ==, 1);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "internet.tigo.py");
+ g_assert_cmpstr(provider->apns[0]->names[0]->name, ==, "Internet");
+ g_assert(provider->apns[0]->names[0]->lang == NULL);
+
+ g_assert_cmpint(provider->apns[1]->num_names, ==, 2);
+ g_assert_cmpstr(provider->apns[1]->value, ==, "broadband.tigo.py");
+ g_assert_cmpstr(provider->apns[1]->names[0]->name, ==, "Broadband");
+ g_assert(provider->apns[1]->names[0]->lang == NULL);
+ g_assert_cmpstr(provider->apns[1]->names[1]->name, ==, "Banda Ancha Móvil");
+ g_assert(provider->apns[1]->names[1]->lang != NULL);
+ g_assert_cmpstr(provider->apns[1]->names[1]->lang, ==, "es");
+}
+
+void test_name_with_commas(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_network(fixture->provider_db, "23201");
+ g_assert(provider != NULL);
+ g_assert_cmpint(provider->num_names, ==, 1);
+ g_assert_cmpstr(provider->names[0]->name, ==, "A1/Telekom Austria");
+ g_assert_cmpint(provider->num_apns, ==, 2);
+ g_assert_cmpint(provider->apns[1]->num_names, ==, 1);
+ g_assert_cmpstr(provider->apns[1]->names[0]->name, ==,
+ "aon (Flex, Breitband-Duo, BusinessFlex)");
+}
+
+void test_bad_provider_name(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_name(fixture->provider_db,
+ "NOSUCHPROVIDER");
+ g_assert(provider == NULL);
+}
+
+void test_bad_network_id(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ provider = mobile_provider_lookup_by_network(fixture->provider_db,
+ "000000");
+ g_assert(provider == NULL);
+}
+
+void test_lookup_best_match(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ /* Make sure we find the one in the US */
+ provider = mobile_provider_lookup_best_match(fixture->provider_db, "3",
+ "31099");
+ g_assert_cmpint(provider->num_apns, ==, 1);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "three.com");
+
+ /* Make sure we find the one in the UK */
+ provider = mobile_provider_lookup_best_match(fixture->provider_db, "3",
+ "23499");
+ g_assert_cmpint(provider->num_apns, ==, 1);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "three.uk");
+
+ /* Make sure we find the one in the UK even with a different MNC */
+ provider = mobile_provider_lookup_best_match(fixture->provider_db, "3",
+ "234878");
+ g_assert_cmpint(provider->num_apns, ==, 1);
+ g_assert_cmpstr(provider->apns[0]->value, ==, "three.uk");
+
+ /* test for NULL db */
+ provider = mobile_provider_lookup_best_match(NULL, "3", "234878");
+
+ g_assert(provider == NULL);
+}
+
+void test_find_provider(TestFixture *fixture, gconstpointer test_data)
+{
+ struct mobile_provider *provider;
+
+ /* test for NULL db */
+ provider = network_find_provider(NULL, "234878", 0);
+
+ g_assert(provider == NULL);
+
+ /* test for NULL network id */
+ provider = network_find_provider(fixture->provider_db, NULL, 0);
+
+ g_assert(provider == NULL);
+}
+
+void test_get_name (TestFixture *fixture, gconstpointer test_data)
+{
+ const gchar* name;
+
+ /* test for NULL provider */
+ name = mobile_provider_get_name(NULL);
+
+ g_assert(name == NULL);
+}
+
+int main(int argc, char *argv[])
+{
+ int result;
+
+ g_test_init(&argc, &argv, NULL);
+
+#define ADD_TEST(name, data) \
+ g_test_add("/flimflam/providerdb/" #name, TestFixture, data, \
+ test_fixture_setup, test_##name, test_fixture_teardown)
+
+ ADD_TEST(lookup_by_name, test_db);
+ ADD_TEST(lookup_by_network, test_db);
+ ADD_TEST(lookup_by_mvno, test_db);
+ ADD_TEST(other_network_id, test_db);
+ ADD_TEST(normalize_network_id, test_db);
+ ADD_TEST(no_network_id, test_db);
+ ADD_TEST(no_network_id_ambiguous, test_db_ambiguous);
+ ADD_TEST(multi_country, test_db);
+ ADD_TEST(non_ascii_names, test_db);
+ ADD_TEST(multi_provider_names, test_db);
+ ADD_TEST(multi_apn_names, test_db);
+ ADD_TEST(name_with_commas, test_db);
+ ADD_TEST(bad_provider_name, test_db);
+ ADD_TEST(bad_network_id, test_db);
+ ADD_TEST(lookup_best_match, test_db);
+ ADD_TEST(find_provider, test_db);
+ ADD_TEST(get_name, test_db);
+ ADD_TEST(find_provider, test_db);
+
+ result = g_test_run();
+
+ unlink(testfile);
+ return result;
+}