From 508e5d19f8512c6074a9c8ba2bf83a6880f0c87b Mon Sep 17 00:00:00 2001
From: Yang Gu <yang.gu@intel.com>
Date: Mon, 12 Oct 2009 17:54:57 +0800
Subject: [PATCH] Support preferred network

---
 Makefile.am                 |    6 +-
 drivers/atmodem/atmodem.h   |    2 +
 include/dbus.h              |    1 +
 include/preferred-network.h |   42 ++++
 plugins/phonesim.c          |    6 +
 src/ofono.h                 |    1 +
 src/preferred-network.c     |  487 +++++++++++++++++++++++++++++++++++++++++++
 src/simutil.h               |    2 +
 8 files changed, 545 insertions(+), 2 deletions(-)
 create mode 100644 include/preferred-network.h
 create mode 100644 src/preferred-network.c

diff --git a/Makefile.am b/Makefile.am
index cf84bf7..002ffea 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,7 +10,8 @@ include_HEADERS = include/log.h include/plugin.h include/history.h \
 			include/phonebook.h include/ssn.h include/ussd.h \
 			include/sms.h include/sim.h include/message-waiting.h \
 			include/netreg.h include/voicecall.h include/devinfo.h \
-			include/cbs.h include/call-volume.h
+			include/cbs.h include/call-volume.h \
+			include/preferred-network.h
 
 nodist_include_HEADERS = include/version.h
 
@@ -165,7 +166,8 @@ src_ofonod_SOURCES = $(gdbus_sources) $(builtin_sources) \
 			src/ssn.c src/call-barring.c src/sim.c \
 			src/phonebook.c src/history.c src/message-waiting.c \
 			src/simutil.h src/simutil.c src/storage.h \
-			src/storage.c src/cbs.c src/watch.c src/call-volume.c
+			src/storage.c src/cbs.c src/watch.c src/call-volume.c \
+			src/preferred-network.c
 
 src_ofonod_LDADD = $(builtin_libadd) \
 			@GLIB_LIBS@ @GTHREAD_LIBS@ @DBUS_LIBS@ -ldl
diff --git a/drivers/atmodem/atmodem.h b/drivers/atmodem/atmodem.h
index 8c61073..867672b 100644
--- a/drivers/atmodem/atmodem.h
+++ b/drivers/atmodem/atmodem.h
@@ -62,3 +62,5 @@ extern void at_cbs_exit();
 
 extern void at_call_volume_init();
 extern void at_call_volume_exit();
+extern void at_prefnet_init();
+extern void at_prefnet_exit();
diff --git a/include/dbus.h b/include/dbus.h
index 5a42119..252c4eb 100644
--- a/include/dbus.h
+++ b/include/dbus.h
@@ -37,6 +37,7 @@ extern "C" {
 #define OFONO_CALL_METER_INTERFACE "org.ofono.CallMeter"
 #define OFONO_PHONEBOOK_INTERFACE "org.ofono.Phonebook"
 #define OFONO_CALL_SETTINGS_INTERFACE "org.ofono.CallSettings"
+#define OFONO_PREFNET_INTERFACE "org.ofono.PreferredNetwork"
 
 /* Essentially a{sv} */
 #define OFONO_PROPERTIES_ARRAY_SIGNATURE DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING \
diff --git a/include/preferred-network.h b/include/preferred-network.h
new file mode 100644
index 0000000..d44446c
--- /dev/null
+++ b/include/preferred-network.h
@@ -0,0 +1,42 @@
+/*
+ *
+ *  oFono - Open Telephony stack for Linux
+ *
+ *  Copyright (C) 2008-2009  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
+ *
+ */
+
+#ifndef __OFONO_PREFNET_H
+#define __OFONO_PREFNET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <ofono/types.h>
+
+struct ofono_prefnet;
+
+struct ofono_prefnet *ofono_prefnet_create(struct ofono_modem *modem);
+
+void ofono_prefnet_register(struct ofono_prefnet *pn);
+void ofono_prefnet_remove(struct ofono_prefnet *pn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/plugins/phonesim.c b/plugins/phonesim.c
index 02ce443..a212743 100644
--- a/plugins/phonesim.c
+++ b/plugins/phonesim.c
@@ -54,6 +54,7 @@
 #include <ofono/ssn.h>
 #include <ofono/ussd.h>
 #include <ofono/voicecall.h>
+#include <ofono/preferred-network.h>
 
 #include <drivers/atmodem/vendor.h>
 
@@ -261,6 +262,7 @@ static void phonesim_post_sim(struct ofono_modem *modem)
 {
 	struct phonesim_data *data = ofono_modem_get_data(modem);
 	struct ofono_message_waiting *mw;
+	struct ofono_prefnet *pn;
 
 	DBG("%p", modem);
 
@@ -288,6 +290,10 @@ static void phonesim_post_sim(struct ofono_modem *modem)
 	mw = ofono_message_waiting_create(modem);
 	if (mw)
 		ofono_message_waiting_register(mw);
+
+	pn = ofono_prefnet_create(modem);
+	if (pn)
+		ofono_prefnet_register(pn);
 }
 
 static struct ofono_modem_driver phonesim_driver = {
diff --git a/src/ofono.h b/src/ofono.h
index 409a9e2..b5ff190 100644
--- a/src/ofono.h
+++ b/src/ofono.h
@@ -106,6 +106,7 @@ enum ofono_atom_type {
 	OFONO_ATOM_TYPE_MESSAGE_WAITING = 13,
 	OFONO_ATOM_TYPE_CBS = 14,
 	OFONO_ATOM_TYPES_CALL_VOLUME = 15,
+	OFONO_ATOM_TYPE_PREFNET = 16,
 };
 
 enum ofono_atom_watch_condition {
diff --git a/src/preferred-network.c b/src/preferred-network.c
new file mode 100644
index 0000000..2cf084d
--- /dev/null
+++ b/src/preferred-network.c
@@ -0,0 +1,487 @@
+/*
+ * oFono - GSM Telephony Stack for Linux
+ *
+ * Copyright (C) 2008-2009 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "ofono.h"
+#include "common.h"
+#include "smsutil.h"
+#include "simutil.h"
+#include "preferred-network.h"
+
+enum ef_support {
+	EFPLMNWACT,
+	EFOPLMNWACT,
+	EF_LAST = EFOPLMNWACT,
+};
+
+struct ef {
+	unsigned char *current;
+	unsigned char *new;
+	unsigned int len;
+	unsigned int len_record;
+	char *name;
+	int can_set;
+};
+
+struct ofono_prefnet {
+	struct ofono_sim *sim;
+	struct ofono_atom *atom;
+	DBusMessage *pending;
+	struct ef ef_array[EF_LAST+1];
+};
+
+struct pn_ef {
+	struct ofono_prefnet *pn;
+	enum ef_support es;
+};
+
+static inline struct pn_ef *pn_ef_new(struct ofono_prefnet *pn,
+					enum ef_support es)
+{
+	struct pn_ef *ret;
+
+	ret = g_try_new0(struct pn_ef, 1);
+
+	if (!ret)
+		return ret;
+
+	ret->pn = pn;
+	ret->es = es;
+
+	return ret;
+}
+
+/* Extract valid plmns from the EF */
+static void ef_extract(struct ef *ef, char ***p_list)
+{
+	int i;
+	GSList *plmn_list = NULL;
+	GSList *l;
+	int len_record = ef->len_record;
+	int records = ef->len / len_record;
+
+	if (records < 1)
+		return;
+
+	for (i = 0; i < records; i++) {
+		char plmn[7];
+		unsigned char plmn_temp[3];
+
+		if ((ef->current[i*len_record] == 0xFF) &&
+			(ef->current[1+i*len_record] == 0xFF) &&
+				(ef->current[2+i*len_record] == 0xFF))
+			break;
+
+		plmn_temp[0] = ef->current[i*len_record];
+		plmn_temp[1] = ((ef->current[2+i*len_record] & 0xF) << 4) +
+					(ef->current[1+i*len_record] & 0xF);
+		plmn_temp[2] = (ef->current[2+i*len_record] >> 4) +
+					(ef->current[1+i*len_record] & 0xF0);
+		extract_bcd_number(plmn_temp, 3, plmn);
+		plmn[6] = '\0';
+
+		plmn_list = g_slist_prepend(plmn_list, g_strdup(plmn));
+	}
+
+	plmn_list = g_slist_reverse(plmn_list);
+	*p_list = g_new0(char *, g_slist_length(plmn_list) + 1);
+	l = plmn_list;
+	i = 0;
+
+	while (l) {
+		(*p_list)[i++] = l->data;
+		l = l->next;
+	}
+
+	g_slist_free(plmn_list);
+}
+
+static DBusMessage *prefnet_get_properties(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	struct ofono_prefnet *pn = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	int i;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+					OFONO_PROPERTIES_ARRAY_SIGNATURE,
+					&dict);
+
+	for (i = 0; i <= EF_LAST; i++) {
+		char **list = NULL;
+
+		ef_extract(&pn->ef_array[i], &list);
+		if (!list)
+			continue;
+
+		ofono_dbus_dict_append_array(&dict, pn->ef_array[i].name,
+					DBUS_TYPE_STRING, &list);
+		g_strfreev(list);
+	}
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static void ef_set_cb(int ok, void *data)
+{
+	struct pn_ef *pe = data;
+	struct ofono_prefnet *pn = pe->pn;
+	enum ef_support es = pe->es;
+	DBusConnection *conn = ofono_dbus_get_connection();
+	const char *path = __ofono_atom_get_path(pn->atom);
+	DBusMessage *reply;
+
+	if (!ok) {
+		reply = __ofono_error_failed(pn->pending);
+		g_free(pn->ef_array[es].new);
+	} else {
+		char **list;
+
+		reply =	dbus_message_new_method_return(pn->pending);
+		g_free(pn->ef_array[es].current);
+		pn->ef_array[es].current = pn->ef_array[es].new;
+		pn->ef_array[es].new = NULL;
+
+		ef_extract(&pn->ef_array[es], &list);
+		ofono_dbus_signal_array_property_changed(conn, path,
+							OFONO_PREFNET_INTERFACE,
+							pn->ef_array[es].name,
+							DBUS_TYPE_STRING,
+							&list);
+		g_strfreev(list);
+	}
+
+	g_free(pe);
+	__ofono_dbus_pending_reply(&pn->pending, reply);
+}
+
+static gboolean ef_set(struct ofono_prefnet *pn,
+				enum ef_support es, DBusMessage *msg)
+{
+	int len = pn->ef_array[es].len;
+	struct pn_ef *pe = pn_ef_new(pn, es);
+
+	if (pn->pending)
+		return FALSE;
+
+	pn->pending = dbus_message_ref(msg);
+
+	ofono_sim_write(pn->sim, SIM_EFPLMNWACT_FILEID,
+		ef_set_cb, OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
+		1, pn->ef_array[es].new, len, pe);
+
+	return TRUE;
+}
+
+/* Prepare EF data according to valid data in list */
+static unsigned char *ef_gen(struct ofono_prefnet *pn,
+			enum ef_support es, GSList *list)
+{
+	int i;
+	int len = pn->ef_array[es].len;
+	unsigned char *data = g_new(unsigned char, len);
+	int len_record = pn->ef_array[es].len_record;
+	int records = len / len_record;
+	GSList *l = list;
+
+	for (i = 0; i < records; i++)
+		if (l) {
+			unsigned char data_temp[6];
+
+			g_assert(strlen(l->data) == 5 || strlen(l->data) == 6);
+			encode_bcd_number(l->data, data_temp);
+			data[i*len_record] = data_temp[0];
+			data[1+i*len_record] = (data_temp[1] & 0xF) +
+							(data_temp[2] & 0xF0);
+			data[2+i*len_record] = (data_temp[1] >> 4) +
+							(data_temp[2] << 4);
+			data[3+i*len_record] = 0xFF;
+			data[4+i*len_record] = 0xFF;
+
+			l = l->next;
+		} else
+			memset(&data[i*len_record], 0xFF, len_record);
+	return data;
+}
+
+static gboolean string_in_list(const char *s, GSList *list)
+{
+	GSList *l;
+	for (l = list; l; l = l->next)
+		if (!strcmp(s, l->data))
+			return TRUE;
+	return FALSE;
+}
+
+static DBusMessage *ef_set_property(DBusMessage *msg, void *data,
+			enum ef_support es, DBusMessageIter *piter)
+{
+	struct ofono_prefnet *pn = data;
+	struct ef *pef = &pn->ef_array[es];
+	DBusMessageIter var;
+	DBusMessageIter var_elem;
+	const char *value;
+	GSList *l = NULL;
+	gboolean ok = FALSE;
+
+	if (!pef->can_set) {
+		ofono_error("Property %s can't be set!", pef->name);
+		return __ofono_error_failed(msg);
+	}
+
+	dbus_message_iter_next(piter);
+	if (dbus_message_iter_get_arg_type(piter) != DBUS_TYPE_VARIANT)
+		return __ofono_error_invalid_args(msg);
+
+	dbus_message_iter_recurse(piter, &var);
+	if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_ARRAY ||
+		dbus_message_iter_get_element_type(&var) != DBUS_TYPE_STRING)
+			return __ofono_error_invalid_args(msg);
+
+	dbus_message_iter_recurse(&var, &var_elem);
+
+	while (dbus_message_iter_get_arg_type(&var_elem) != DBUS_TYPE_INVALID) {
+		if (dbus_message_iter_get_arg_type(&var_elem) !=
+						DBUS_TYPE_STRING)
+			goto error;
+		dbus_message_iter_get_basic(&var_elem, &value);
+
+		if ((strlen(value) != 5) && (strlen(value) != 6)) {
+			ofono_error(
+			"The length of value %s to set %s is invalid!",
+			value, pef->name);
+			goto error;
+		}
+
+		if (!string_in_list(value, l))
+			l = g_slist_prepend(l, g_strdup(value));
+		else {
+			ofono_error(
+			"The value %s to set %s is duplicated!",
+			value, pef->name);
+			goto error;
+		}
+
+		dbus_message_iter_next(&var_elem);
+	}
+
+	if (!l) {
+		ofono_error("There is not valid value to set %s", pef->name);
+		return __ofono_error_failed(msg);
+	}
+
+	if (g_slist_length(l) > (pef->len / pef->len_record)) {
+		ofono_error("The number of %s exceeds the limitation!",
+								pef->name);
+		goto error;
+	}
+
+	l = g_slist_reverse(l);
+	pef->new = ef_gen(pn, es, l);
+	if (!memcmp(pef->current, pef->new, pef->len)) {
+		ofono_error("New value of %s is the same with the old!",
+								pef->name);
+		g_free(pef->new);
+		goto error;
+	}
+
+	ok = ef_set(pn, es, msg);
+	if (!ok) {
+		g_free(pef->new);
+		goto error;
+	} else
+		return NULL;
+
+error:
+	g_slist_foreach(l, (GFunc)g_free, NULL);
+	g_slist_free(l);
+	return __ofono_error_failed(msg);
+}
+
+static DBusMessage *prefnet_set_property(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct ofono_prefnet *pn = data;
+	DBusMessageIter iter;
+	const char *name;
+	int i;
+
+	if (!dbus_message_iter_init(msg, &iter))
+		return __ofono_error_invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return __ofono_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &name);
+
+	for (i = 0; i <= EF_LAST; i++)
+		if (!strcmp(name, pn->ef_array[i].name))
+			return ef_set_property(msg, data, i, &iter);
+
+	ofono_error("The property name %s is not valid!", name);
+	return __ofono_error_failed(msg);
+}
+
+static GDBusMethodTable prefnet_methods[] = {
+	{ "GetProperties",	"",	"a{sv}",	prefnet_get_properties },
+	{ "SetProperty",	"sv",	"",	prefnet_set_property,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ }
+};
+
+static GDBusSignalTable prefnet_signals[] = {
+	{ "PropertyChanged",	"sv" },
+	{ }
+};
+
+static void prefnet_unregister(struct ofono_atom *atom)
+{
+	struct ofono_prefnet *pn = __ofono_atom_get_data(atom);
+	const char *path = __ofono_atom_get_path(pn->atom);
+	DBusConnection *conn = ofono_dbus_get_connection();
+	struct ofono_modem *modem = __ofono_atom_get_modem(pn->atom);
+
+	ofono_modem_remove_interface(modem, OFONO_PREFNET_INTERFACE);
+	g_dbus_unregister_interface(conn, path, OFONO_PREFNET_INTERFACE);
+}
+
+static void prefnet_remove(struct ofono_atom *atom)
+{
+	struct ofono_prefnet *pn = __ofono_atom_get_data(atom);
+	int i;
+
+	DBG("atom: %p", atom);
+
+	if (pn == NULL)
+		return;
+
+	for (i = 0; i <= EF_LAST; i++)
+		if (pn->ef_array[i].current)
+			g_free(pn->ef_array[i].current);
+
+	g_free(pn);
+}
+
+struct ofono_prefnet *ofono_prefnet_create(struct ofono_modem *modem)
+{
+	struct ofono_prefnet *pn;
+
+	pn = g_try_new0(struct ofono_prefnet, 1);
+
+	if (pn == NULL)
+		return NULL;
+
+	pn->atom = __ofono_modem_add_atom(modem, OFONO_ATOM_TYPE_PREFNET,
+						prefnet_remove, pn);
+
+	pn->ef_array[EFPLMNWACT].name = "UserPreferredNetworks";
+	pn->ef_array[EFPLMNWACT].len_record = 5;
+	pn->ef_array[EFPLMNWACT].can_set = 1;
+	pn->ef_array[EFOPLMNWACT].name = "OperatorPreferredNetworks";
+	pn->ef_array[EFOPLMNWACT].len_record = 5;
+	pn->ef_array[EFOPLMNWACT].can_set = 0;
+
+	return pn;
+}
+
+static void ef_read_cb(int ok, int len, int record, const unsigned char *data,
+					int len_record, void *userdata)
+{
+	struct pn_ef *pe = userdata;
+	struct ofono_prefnet *pn = pe->pn;
+	enum ef_support es = pe->es;
+
+	if (!ok)
+		goto error;
+
+	pn->ef_array[es].len = len;
+	pn->ef_array[es].current = g_new(unsigned char, len);
+	memcpy(pn->ef_array[es].current, data, len);
+error:
+	g_free(pe);
+}
+
+void ofono_prefnet_register(struct ofono_prefnet *pn)
+{
+	DBusConnection *conn = ofono_dbus_get_connection();
+	const char *path = __ofono_atom_get_path(pn->atom);
+	struct ofono_modem *modem = __ofono_atom_get_modem(pn->atom);
+	struct ofono_atom *sim_atom;
+
+	if (!g_dbus_register_interface(conn, path, OFONO_PREFNET_INTERFACE,
+					prefnet_methods, prefnet_signals,
+					NULL, pn, NULL)) {
+		ofono_error("Could not create %s interface",
+				OFONO_PREFNET_INTERFACE);
+
+		return;
+	}
+
+	ofono_modem_add_interface(modem, OFONO_PREFNET_INTERFACE);
+
+	sim_atom = __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_SIM);
+
+	if (sim_atom) {
+		struct pn_ef *pe;
+
+		pn->sim = __ofono_atom_get_data(sim_atom);
+
+		pe = pn_ef_new(pn, EFPLMNWACT);
+		ofono_sim_read(pn->sim, SIM_EFPLMNWACT_FILEID,
+				OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
+				ef_read_cb, pe);
+
+		pe = pn_ef_new(pn, EFOPLMNWACT);
+		ofono_sim_read(pn->sim, SIM_EFOPLMNWACT_FILEID,
+				OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
+				ef_read_cb, pe);
+	}
+
+	__ofono_atom_register(pn->atom, prefnet_unregister);
+}
+
+void ofono_prefnet_remove(struct ofono_prefnet *pn)
+{
+	__ofono_atom_free(pn->atom);
+}
diff --git a/src/simutil.h b/src/simutil.h
index f4fbce3..c312f60 100644
--- a/src/simutil.h
+++ b/src/simutil.h
@@ -36,6 +36,8 @@ enum sim_fileid {
 	SIM_EFCBMIR_FILEID = 0x6f50,
 	SIM_EFCBMI_FILEID = 0x6f45,
 	SIM_EFCBMID_FILEID = 0x6f48,
+	SIM_EFPLMNWACT_FILEID = 0x6f60,
+	SIM_EFOPLMNWACT_FILEID = 0x6f61,
 };
 
 /* 51.011 Section 9.3 */
-- 
1.6.0.6

