/*
 * cwmp.c - icwmp Main file
 *
 * Copyright (C) 2021-2022, IOPSYS Software Solutions AB.
 *
 *	  Author Mohamed Kallel <mohamed.kallel@pivasoftware.com>
 *	  Author Ahmed Zribi <ahmed.zribi@pivasoftware.com>
 *	  Author Omar Kallel <omar.kallel@pivasoftware.com>
 *
 * See LICENSE file for license related information.
 *
 */

#include <math.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/file.h>
#include <sys/socket.h>

#include "common.h"
#include "ssl_utils.h"
#include "xml.h"
#include "notifications.h"
#include "event.h"
#include "uci_utils.h"
#include "log.h"
#include "session.h"
#include "diagnostic.h"
#include "http.h"
#include "rpc.h"
#include "config.h"
#include "backupSession.h"
#include "ubus_utils.h"
#include "digauth.h"
#include "upload.h"
#include "download.h"
#include "sched_inform.h"
#include "datamodel_interface.h"
#include "cwmp_du_state.h"
#include "heartbeat.h"
#include "cwmp_http.h"

struct list_head intf_reset_list;
struct list_head du_uuid_list;
struct list_head force_inform_list;

static bool interface_reset_req(char *param_name, char *value)
{
	if (param_name == NULL || value == NULL)
		return false;

	char reg_exp[100] = {0};
	snprintf(reg_exp, sizeof(reg_exp), "^(%s|%s)[0-9]+\\.Reset$", DM_IP_INTERFACE_PATH, DM_PPP_INTERFACE_PATH);

	if (match_reg_exp(reg_exp, param_name) == false)
		return false;

	if (CWMP_STRCMP(value, "1") != 0 && CWMP_STRCMP(value, "true") != 0)
		return false;

	return true;
}

void set_interface_reset_request(char *param_name, char *value)
{
	char *inst_path = NULL;

	if (param_name == NULL || value == NULL)
		return;

	if (CWMP_OK != instantiate_param_name(param_name, &inst_path))
		return;

	if (!CWMP_STRLEN(inst_path))
		return;

	if (interface_reset_req(inst_path, value) == false) {
		FREE(inst_path);
		return;
	}

	// Store the interface path to handle after session end
	int len = 0;
	char *pos = strrchr(inst_path, '.');
	if (pos == NULL) {
		FREE(inst_path);
		return;
	}

	len = pos - inst_path + 2;
	if (len <= 0) {
		FREE(inst_path);
		return;
	}

	intf_reset_node *node = (intf_reset_node *)malloc(sizeof(intf_reset_node));
	if (node == NULL) {
		CWMP_LOG(ERROR, "Out of memory");
		FREE(inst_path);
		return;
	}

	CWMP_MEMSET(node, 0, sizeof(intf_reset_node));
	snprintf(node->path, len, "%s", inst_path);
	INIT_LIST_HEAD(&node->list);
	list_add_tail(&node->list, &intf_reset_list);
	FREE(inst_path);
}

static int create_cwmp_temporary_files(void)
{
	if (!file_exists(VARSTATE_CONFIG"/icwmp")) {
		creat(VARSTATE_CONFIG"/icwmp", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	}

	set_uci_path_value(VARSTATE_CONFIG, "icwmp.acs", "acs");
	set_uci_path_value(VARSTATE_CONFIG, "icwmp.cpe", "cpe");

	if (!file_exists(CWMP_NOTIFICATIONS_PACKAGE)) {
		creat(CWMP_NOTIFICATIONS_PACKAGE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	}

	set_uci_path_value("/etc/icwmpd", "cwmp_notifications.notifications", "notifications");

	if (!folder_exists("/var/run/icwmpd")) {
		if (mkdir("/var/run/icwmpd", S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
			CWMP_LOG(INFO, "Not able to create the folder /var/run/icwmpd");
			return CWMP_GEN_ERR;
		}
	}

	return CWMP_OK;
}

static void wait_for_time_sync(void)
{
	struct cwmp_dm_parameter cwmp_dm_param = {0};

	if (cwmp_ctx.conf.insecure_enable) {
		CWMP_LOG(INFO, "Insecure enabled, skipping wait for time sync");
		return;
	}

	if (CWMP_STRNCMP(cwmp_ctx.conf.acs_url, "http://", 7) == 0) {
		CWMP_LOG(INFO, "ACS url is http, no TLS, skipping wait for time sync");
		return;
	}

	int loop_count = (cwmp_ctx.conf.clock_sync_timeout / 2);

	if (loop_count == 0) {
		CWMP_LOG(INFO, "Wait for time sync is disabled, cwmp.cpe.clock_sync_timeout=%d", cwmp_ctx.conf.clock_sync_timeout);
		return;
	}

	while (loop_count) {
		memset(&cwmp_dm_param, 0, sizeof(struct cwmp_dm_parameter));

		if (!cwmp_get_parameter_value("Device.Time.Status", &cwmp_dm_param)) {
			CWMP_LOG(ERROR, "Failed to get Device.Time.Status ...");
			return;
		}

		if (CWMP_STRCMP(cwmp_dm_param.value, "Disabled") == 0) {
			CWMP_LOG(INFO, "Time.Status is Disabled, no need to wait");
			return;
		}

		if (CWMP_STRCMP(cwmp_dm_param.value, "Synchronized") != 0) {
			loop_count--;
			CWMP_LOG(INFO, "Clock status [%s], checking again %d", cwmp_dm_param.value, loop_count);
			sleep(2);
			continue;
		}

		CWMP_LOG(INFO, "Clock is synchronized, ready to send inform");
		return;
	}

	CWMP_LOG(ERROR, "Clock not Synchronized in %d sec, last status %s", cwmp_ctx.conf.clock_sync_timeout, cwmp_dm_param.value);
	return;
}

static int cwmp_init(void)
{
	cwmp_ctx.curr_delay_reboot = -1;
	cwmp_ctx.curr_schedule_reboot = 0;

	CWMP_LOG(INFO, "STARTING ICWMP with PID :%d", getpid());

	/* Only One instance should run*/
	// cppcheck-suppress cert-MSC24-C
	cwmp_ctx.pid_file = fopen("/var/run/icwmpd.pid", "w+");
	if (!cwmp_ctx.pid_file) {
		CWMP_LOG(ERROR, "%s", "Could not open pid file");
		exit(EXIT_FAILURE);
	}

	fcntl(fileno(cwmp_ctx.pid_file), F_SETFD, fcntl(fileno(cwmp_ctx.pid_file), F_GETFD) | FD_CLOEXEC);
	int rc = flock(fileno(cwmp_ctx.pid_file), LOCK_EX | LOCK_NB);
	if (rc) {
		if (EWOULDBLOCK != errno) {
			const char *piderr = "PID file creation failed: Quit the daemon!";
			fprintf(stderr, "%s\n", piderr);
			CWMP_LOG(ERROR, "%s", piderr);
			exit(EXIT_FAILURE);
		} else
			exit(EXIT_SUCCESS);
	}

	/* Write the pid */
	// cppcheck-suppress cert-MSC24-C
	rewind(cwmp_ctx.pid_file);
	ftruncate(fileno(cwmp_ctx.pid_file), 0);
	fprintf(cwmp_ctx.pid_file, "%d\n", getpid());
	fflush(cwmp_ctx.pid_file);
	fsync(fileno(cwmp_ctx.pid_file));

	if (CWMP_OK != create_cwmp_temporary_files()) {
		return CWMP_GEN_ERR;
	}

	CWMP_LOG(DEBUG, "Loading icwmpd configuration");
	get_preinit_config();
	cwmp_config_load();
	CWMP_LOG(DEBUG, "Successfully load icwmpd configuration");

	wait_for_time_sync();

	cwmp_ctx.prev_periodic_enable = cwmp_ctx.conf.periodic_enable;
	cwmp_ctx.prev_periodic_interval = cwmp_ctx.conf.period;
	cwmp_ctx.prev_periodic_time = cwmp_ctx.conf.time;
	cwmp_ctx.prev_heartbeat_enable = cwmp_ctx.conf.heart_beat_enable;
	cwmp_ctx.prev_heartbeat_interval = cwmp_ctx.conf.heartbeat_interval;
	cwmp_ctx.prev_heartbeat_time = cwmp_ctx.conf.heart_time;

	set_cpe_port_conf(cwmp_ctx.conf.connection_request_port);
	cwmp_get_deviceid();

	/* Load default force inform parameters */
	CWMP_MEMSET(&force_inform_list, 0, sizeof(struct list_head));
	INIT_LIST_HEAD(&force_inform_list);
	load_default_forced_inform();

	/* Load custom notify and force inform parameters */
	load_forced_inform_json();
	load_custom_notify_json();
	set_default_forced_active_parameters_notifications();
	init_list_param_notify();

	create_cwmp_session_structure();
	get_nonce_key();

	CWMP_MEMSET(&intf_reset_list, 0, sizeof(struct list_head));
	INIT_LIST_HEAD(&intf_reset_list);

	CWMP_MEMSET(&du_uuid_list, 0, sizeof(struct list_head));
	INIT_LIST_HEAD(&du_uuid_list);

	cwmp_ctx.start_time = time(NULL);

	return CWMP_OK;
}

static void cwmp_free()
{
	http_server_stop();
	FREE(nonce_key);
	clean_list_param_notify();
	bkp_tree_clean();
	icwmp_uloop_ubus_exit();
	icwmp_cleanmem();
	rpc_exit();
	clean_cwmp_session_structure();
	icwmp_free_critical_services();
	CWMP_LOG(INFO, "EXIT ICWMP");
	closelog();
}

void cwmp_exit()
{
	cwmp_stop = true;

	if (cwmp_ctx.session->session_status.last_status == SESSION_RUNNING)
		http_set_timeout();

	uloop_timeout_cancel(&retry_session_timer);
	uloop_timeout_cancel(&periodic_session_timer);
	uloop_timeout_cancel(&session_timer);
	uloop_timeout_cancel(&heartbeat_session_timer);
	clean_autonomous_complpolicy();
	clean_interface_update();
#ifdef ICWMP_ENABLE_SMM_SUPPORT
	clean_du_uuid_list();
#endif
	clean_force_inform_list();
	FREE(cwmp_ctx.ev);
	FREE(cwmp_ctx.intf_ev);
	uloop_end();
	shutdown(cwmp_ctx.cr_socket_desc, SHUT_RDWR);
	FREE(global_session_event);

	if (cwmp_ctx.pid_file)
		fclose(cwmp_ctx.pid_file);

	/* Free all memory allocation */
	cwmp_free();
}

int main(int argc, char **argv)
{
	int error = 0;

	CWMP_MEMSET(&cwmp_ctx, 0, sizeof(struct cwmp));
	openlog("cwmp", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);

	error = icwmp_connect_ubus();
	if (error)
		return error;

	error = wait_for_bbf_object();
	if (error) {
		icwmp_free_ubus();
		return error;
	}

	if ((error = global_env_init(argc, argv, &(cwmp_ctx.env)))) {
		icwmp_free_ubus();
		return error;
	}

	if ((error = cwmp_init())) {
		icwmp_free_ubus();
		return error;
	}

	cwmp_initiate_get_DM();

	if ((error = cwmp_init_backup_session(NULL, ALL))) {
		icwmp_free_ubus();
		return error;
	}

	if ((error = cwmp_root_cause_events())) {
		icwmp_free_ubus();
		return error;
	}

	icwmp_http_server_init();

	uloop_init();

	if ((error = icwmp_uloop_ubus_register())) {
		icwmp_free_ubus();
		return error;
	}

	if ((error = initiate_autonomous_complpolicy())) {
		icwmp_uloop_ubus_exit();
		return error;
	}

	if ((error = initiate_interface_update())) {
		icwmp_uloop_ubus_exit();
		return error;
	}

	icwmp_init_critical_services();

	trigger_cwmp_session_timer();

	intiate_heartbeat_procedures();

	initiate_cwmp_periodic_session_feature();

	http_server_start();

	apply_allowed_cr_ip_port();
	cwmp_ctx.cr_ip_port_change = false;

	uloop_run();
	uloop_done();

	cwmp_exit();

	return CWMP_OK;
}
