mobile_provider: Choose the primary provider in preference to non-primary providers

Look for a 'primary' attribute on a provider to know if it should be
used as the provider when there are multiple providers that match the
same PLMN.

Update unit tests for mobile_provider_lookup_best_match

BUG=chrome-os-partner:3647
TEST=Ensure that APN selection is done properly

(cherry picked from commit 2e44b115b52ace624ee6b687219bb78c3fec9705)

Change-Id: I7624a8333ae27203ead7f01683214a4cd6d79c0f
Reviewed-on: http://gerrit.chromium.org/gerrit/1611
Tested-by: Jason Glasgow <jglasgow@chromium.org>
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
diff --git a/plugins/mobile_provider.c b/plugins/mobile_provider.c
index 873c290..6bf269a 100644
--- a/plugins/mobile_provider.c
+++ b/plugins/mobile_provider.c
@@ -46,8 +46,8 @@
 	int apnindex;
 };
 
-#define WARN(fmt, arg...) \
-	syslog(LOG_WARNING, "%s line %d: " fmt, state->filename, \
+#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)
@@ -112,6 +112,13 @@
 	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;
@@ -182,6 +189,7 @@
 	gchar **pfields;
 	int num_names;
 	int num_apns;
+	int primary;
 	struct mobile_provider *provider;
 
 	state->provider = NULL;
@@ -189,7 +197,7 @@
 	state->apnindex = 0;
 	state->nameindex = 0;
 	pfields = g_strsplit(text, ",", 0);
-	if (g_strv_length(pfields) != 2) {
+	if (g_strv_length(pfields) != 3) {
 		WARN("Badly formed \"providers\" entry: \"%s\"", text);
 		g_strfreev(pfields);
 		return;
@@ -197,6 +205,7 @@
 	errno = 0;
 	num_names = strtol(pfields[0], NULL, 0);
 	num_apns = strtol(pfields[1], NULL, 0);
+	primary = strtol(pfields[2], NULL, 0);
 	if (errno != 0) {
 		WARN("Error parsing \"providers\" entry \"%s\"", text);
 		g_strfreev(pfields);
@@ -208,12 +217,24 @@
 	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);
 	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;
@@ -229,9 +250,9 @@
 		normalize_list(provider->networks);
 		listlen = g_strv_length(provider->networks);
 		for (i = 0; i < listlen; i++) {
-			g_hash_table_insert(state->network_table,
-					    provider->networks[i],
-					    provider);
+			network_add_provider(state->network_table,
+					     provider->networks[i],
+					     provider);
 		}
 	}
 }
@@ -323,8 +344,8 @@
 						    name->name, provider);
 			} else {
 				DEBUG("Chain %s (%s) to (%s)\n", name->name,
-				       provider->country,
-				       other_provider->country);
+				      provider->country,
+				      other_provider->country);
 				while (other_provider->next != NULL)
 					other_provider = other_provider->next;
 				other_provider->next = provider;
@@ -333,8 +354,8 @@
 		} else {
 			name->name = g_strdup(namefields[1]);
 			DEBUG("Additional name %s (%s) for %s\n",
-			       name->name, provider->country,
-			       provider->names[0]->name);
+			      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,
@@ -435,20 +456,46 @@
 	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);
 	}
 }
 
 
+static struct mobile_provider *network_find_provider(
+	const struct mobile_provider_db *db,
+	const gchar *network_id,
+	gboolean primary)
+{
+	GSList *list;
+
+	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) {
+	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
@@ -456,11 +503,13 @@
 		 * difficult to determine.
 		 */
 		normalize(network_id, netid, sizeof(netid));
-		provider = g_hash_table_lookup(db->network2provider, netid);
+		provider = network_find_provider(db, netid, primary);
 		if (provider != NULL)
 			return provider;
 		normalize(network_id, netid, sizeof(netid) - 1);
-		return g_hash_table_lookup(db->network2provider, netid);
+		provider = network_find_provider(db, netid, primary);
+		if (provider != NULL)
+			return provider;
 	}
 	return NULL;
 }
diff --git a/plugins/mobile_provider.h b/plugins/mobile_provider.h
index ccb10c5..ad26774 100644
--- a/plugins/mobile_provider.h
+++ b/plugins/mobile_provider.h
@@ -55,6 +55,7 @@
 	int num_apns;
 	struct mobile_apn **apns;
 	int refcnt;
+	gboolean primary;
 };
 
 struct mobile_provider_db;
diff --git a/scripts/convert-serviceproviders.xsl b/scripts/convert-serviceproviders.xsl
index b2efa5d..0364ca6 100644
--- a/scripts/convert-serviceproviders.xsl
+++ b/scripts/convert-serviceproviders.xsl
@@ -27,7 +27,10 @@
 
 <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:apply-templates select="name"/><xsl:apply-templates select="gsm"/>
+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:apply-templates select="name"/><xsl:apply-templates select="gsm"/>
 </xsl:if>
 </xsl:template>
 
@@ -68,7 +71,7 @@
 #
 # Each provider block is of the form
 #
-#   provider:&lt;# of names&gt;,&lt;# of APNs&gt;
+#   provider:&lt;# of names&gt;,&lt;# of APNs&gt;,&lt;primary&gt;
 #   name:&lt;lang&gt;,&lt;name 1&gt;
 #   name:&lt;lang&gt;,&lt;name 2&gt;
 #   . . .
diff --git a/tools/providerdb_test.c b/tools/providerdb_test.c
index 2ff872f..0dc87f4 100644
--- a/tools/providerdb_test.c
+++ b/tools/providerdb_test.c
@@ -38,14 +38,14 @@
 static char *test_data =
 "serviceproviders:2.0\n"
 "country:at\n"
-"provider:1,2\n"
+"provider:1,2,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\n"
+"provider:1,3,0\n"
 "name:,T-Mobile\n"
 "networks:23203\n"
 "apn:1,gprswap,t-mobile,tm\n"
@@ -55,27 +55,31 @@
 "apn:1,business.gprsinternet,t-mobile,tm\n"
 "name:,Business Internet\n"
 "country:ca\n"
-"provider:1,1\n"
+"provider:1,1,0\n"
 "name:,Vidéotron\n"
 "networks:302500,302510\n"
 "apn:1,ihvm.videotron,,\n"
 "name:,IHVM\n"
 "country:gb\n"
-"provider:1,1\n"
+"provider:1,1,0\n"
 "name:,T-Mobile (Great Britain)\n"
 "networks:23430\n"
 "apn:0,general.t-mobile.uk,User,mms\n"
+"provider:1,1,0\n"
+"name:,3\n"
+"networks:23499\n"
+"apn:0,three.uk,,\n"
 "country:ir\n"
-"provider:1,1\n"
+"provider:1,1,0\n"
 "name:,همراه اول\n"
 "networks:43211\n"
 "apn:0,mcinet,,\n"
-"provider:1,1\n"
+"provider:1,1,0\n"
 "name:,ایرانسل\n"
 "networks:43235\n"
 "apn:0,mtnirancell,,\n"
 "country:py\n"
-"provider:1,2\n"
+"provider:1,2,0\n"
 "name:,Tigo\n"
 "networks:74404\n"
 "apn:1,internet.tigo.py,,\n"
@@ -84,13 +88,17 @@
 "name:,Broadband\n"
 "name:es,Banda Ancha Móvil\n"
 "country:ru\n"
-"provider:2,1\n"
+"provider:2,1,0\n"
 "name:,BaikalWestCom\n"
 "name:ru,БайкалВестКом\n"
 "networks:25012\n"
 "apn:0,inet.bwc.ru,bwc,bwc\n"
 "country:us\n"
-"provider:1,4\n"
+"provider:1,1,0\n"
+"name:,3\n"
+"networks:31099\n"
+"apn:0,three.com,,\n"
+"provider:1,4,0\n"
 "name:,AT&T\n"
 "networks:310038,310090,310150,310410,310560,310680\n"
 "apn:0,Broadband,,\n"
@@ -100,7 +108,7 @@
 "name:,Data Connect\n"
 "apn:1,ISP.CINGULAR,ISPDA@CINGULARGPRS.COM,CINGULAR1\n"
 "name:,Data Connect (Accelerated)\n"
-"provider:1,4\n"
+"provider:1,4,1\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"
@@ -111,6 +119,11 @@
 "name:,Internet (old)\n"
 "apn:1,internet3.voicestream.com,,\n"
 "name:,Internet with VPN (old)\n"
+"provider:1,1,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"
 ;
 
 void setup_data(const char *file)
@@ -154,12 +167,30 @@
 	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");
@@ -304,6 +335,29 @@
 	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");
+}
+
 int main(int argc, char *argv[])
 {
 	int result;
@@ -316,6 +370,7 @@
 
 	ADD_TEST(lookup_by_name);
 	ADD_TEST(lookup_by_network);
+	ADD_TEST(lookup_by_mvno);
 	ADD_TEST(other_network_id);
 	ADD_TEST(normalize_network_id);
 	ADD_TEST(multi_country);
@@ -325,6 +380,7 @@
 	ADD_TEST(name_with_commas);
 	ADD_TEST(bad_provider_name);
 	ADD_TEST(bad_network_id);
+	ADD_TEST(lookup_best_match);
 
 	result = g_test_run();