/*
 * oFono - Open Source Telephony
 * Copyright (C) 2008-2016  Intel Corporation
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <ofono.h>
#include <simutil.h>

#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono/plugin.h>
#include <ofono/modem.h>
#include <ofono/sim.h>
#include <ofono/dbus.h>
#include <gdbus.h>

#define SIM_EFACL_FILEID	0x6f57

#define ALLOWED_ACCESS_POINTS_INTERFACE "org.ofono.AllowedAccessPoints"

guint modemwatch_id;
GSList *context_list;

struct allowed_apns_ctx {
	guint simwatch_id;
	guint atomwatch_id;
	struct ofono_modem *modem;
	struct ofono_sim *sim;
	struct ofono_sim_context *sim_context;
	DBusMessage *pending;
	DBusMessage *reply;
	bool registered;
};

static void context_destroy(gpointer data)
{
	struct allowed_apns_ctx *ctx = data;

	if (ctx->simwatch_id)
		ofono_sim_remove_state_watch(ctx->sim,
					ctx->simwatch_id);

	if (ctx->atomwatch_id)
		__ofono_modem_remove_atom_watch(ctx->modem,
					ctx->atomwatch_id);

	if (ctx->sim_context)
		ofono_sim_context_free(ctx->sim_context);

	g_free(ctx);
}

static void atomwatch_destroy(gpointer data)
{
	struct allowed_apns_ctx *ctx = data;

	ctx->atomwatch_id = 0;
}

static void sim_acl_read_cb(int ok, int total_length, int record,
			const unsigned char *data, int record_length,
			void *userdata)
{
	struct allowed_apns_ctx *ctx = userdata;
	DBusMessage *reply = ctx->reply;
	DBusMessageIter iter;
	DBusMessageIter array;
	struct simple_tlv_iter tlv_iter;
	char *apn;

	if (!ok) {
		reply = __ofono_error_failed(ctx->pending);
		__ofono_dbus_pending_reply(&ctx->pending, reply);
		return;
	}

	reply = dbus_message_new_method_return(ctx->pending);
	if (reply == NULL)
		return;

	dbus_message_iter_init_append(reply, &iter);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
					DBUS_TYPE_STRING_AS_STRING,
					&array);

	if (data[0] == 0)
		goto done;

	simple_tlv_iter_init(&tlv_iter, &data[1], total_length - 1);

	while (simple_tlv_iter_next(&tlv_iter)) {
		if (simple_tlv_iter_get_tag(&tlv_iter) != 0xDD)
			continue;

		apn = l_strndup(
			(char *) simple_tlv_iter_get_data(&tlv_iter),
			simple_tlv_iter_get_length(&tlv_iter));

		dbus_message_iter_append_basic(&array,
					DBUS_TYPE_STRING,
					&apn);

		l_free(apn);
	}

done:
	dbus_message_iter_close_container(&iter, &array);

	__ofono_dbus_pending_reply(&ctx->pending, reply);
}

static DBusMessage *get_allowed_apns(DBusConnection *conn,
			DBusMessage *msg, void *data)
{
	struct allowed_apns_ctx *ctx = data;

	if (ctx->pending)
		return __ofono_error_busy(msg);

	ctx->pending = dbus_message_ref(msg);

	ofono_sim_read(ctx->sim_context, SIM_EFACL_FILEID,
		OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
		sim_acl_read_cb, ctx);

	return NULL;
}

static const GDBusMethodTable allowed_apns_methods[] = {
	{ GDBUS_ASYNC_METHOD("GetAllowedAccessPoints",
			NULL, GDBUS_ARGS({ "apnlist", "as" }),
			get_allowed_apns) },
	{ }
};

static void sim_state_watch(enum ofono_sim_state new_state, void *data)
{
	struct allowed_apns_ctx *ctx = data;
	DBusConnection *conn = ofono_dbus_get_connection();

	if (new_state != OFONO_SIM_STATE_READY) {
		if (!ctx->registered)
			return;

		g_dbus_unregister_interface(conn,
				ofono_modem_get_path(ctx->modem),
				ALLOWED_ACCESS_POINTS_INTERFACE);

		ofono_modem_remove_interface(ctx->modem,
				ALLOWED_ACCESS_POINTS_INTERFACE);

		ctx->registered = false;
		return;
	}

	if (!g_dbus_register_interface(conn,
				ofono_modem_get_path(ctx->modem),
				ALLOWED_ACCESS_POINTS_INTERFACE,
				allowed_apns_methods, NULL, NULL,
				ctx, NULL)) {
		ofono_error("Cannot create %s Interface\n",
			ALLOWED_ACCESS_POINTS_INTERFACE);

		return;
	}

	ctx->registered = true;
	ofono_modem_add_interface(ctx->modem,
			ALLOWED_ACCESS_POINTS_INTERFACE);
}

static void sim_watch(struct ofono_atom *atom,
		enum ofono_atom_watch_condition cond,
		void *data)
{
	struct allowed_apns_ctx *ctx = data;

	if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) {
		if (ctx->simwatch_id) {
			sim_state_watch(OFONO_SIM_STATE_NOT_PRESENT, data);
			ofono_sim_remove_state_watch(ctx->sim, ctx->simwatch_id);
			ctx->simwatch_id = 0;
		}

		if (ctx->sim_context) {
			ofono_sim_context_free(ctx->sim_context);
			ctx->sim_context = NULL;
		}

		return;
	}

	ctx->sim = __ofono_atom_get_data(atom);

	ctx->sim_context = ofono_sim_context_create(ctx->sim);

	ctx->simwatch_id = ofono_sim_add_state_watch(ctx->sim,
						sim_state_watch,
						ctx, NULL);
}

static gint context_list_modem_compare(gconstpointer data1,
				gconstpointer data2)
{
	const struct allowed_apns_ctx *ctx = data1;
	const struct ofono_modem *modem = data2;
	return (ctx->modem == modem);
}

static void modem_watch(struct ofono_modem *modem,
		gboolean added, void *userdata)
{
	struct allowed_apns_ctx *ctx;
	GSList *l;

	if (added == FALSE) {
		l = g_slist_find_custom(context_list,
				modem, context_list_modem_compare);

		if (l) {
			ctx = l->data;
			context_destroy(ctx);
			context_list = g_slist_delete_link(context_list, l);
		}

		return;
	}

	ctx = g_try_new0(struct allowed_apns_ctx, 1);
	if (ctx == NULL)
		return;

	context_list = g_slist_prepend(context_list, ctx);

	ctx->modem = modem;

	ctx->atomwatch_id = __ofono_modem_add_atom_watch(ctx->modem,
						OFONO_ATOM_TYPE_SIM,
						sim_watch, ctx,
						atomwatch_destroy);
}

static void call_modemwatch(struct ofono_modem *modem, void *userdata)
{
	modem_watch(modem, TRUE, userdata);
}

static int allowed_apns_init(void)
{
	modemwatch_id = __ofono_modemwatch_add(modem_watch, NULL, NULL);

	__ofono_modem_foreach(call_modemwatch, NULL);

	return 0;
}

static void allowed_apns_exit(void)
{
	__ofono_modemwatch_remove(modemwatch_id);

	g_slist_free_full(context_list, context_destroy);
}

OFONO_PLUGIN_DEFINE(allowed_apns, "Plugin to read EFACL from SIM",
		VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT,
		allowed_apns_init, allowed_apns_exit)
