/*
 * notifications.c - Manage CWMP Notifications
 *
 * Copyright (C) 2022, IOPSYS Software Solutions AB.
 *
 *	  Author Omar Kallel <omar.kallel@pivasoftware.com>
 *
 * See LICENSE file for license related information.
 *
 */

#include <netdb.h>
#include <fcntl.h>
#include <json-c/json.h>

#include "notifications.h"
#include "uci_utils.h"
#include "datamodel_interface.h"
#include "ssl_utils.h"
#include "log.h"
#include "event.h"
#include "xml.h"
#include "cwmp_event.h"

#define UPSTREAM_STABILITY_CHECK_TIMESPAN 5  // In seconds
#define MANAGEABLE_DEVICES_NBRE "Device.ManagementServer.ManageableDeviceNumberOfEntries"
LIST_HEAD(list_value_change);

LIST_HEAD(list_lw_value_change);
LIST_HEAD(list_param_obj_notify);

static int cr_url_retry = 3;

static void update_notify_file_line(FILE *notify_file, char *param_name, char *param_type, char *param_value, int notification);
static void create_list_param_leaf_notify(struct list_head *, void (*fp)(FILE *, char *, char *, char *, int), FILE*);
static void send_active_value_change(void);
static void add_list_value_change(const char *param_name, const char *param_data, const char *param_type);
static void add_lw_list_value_change(char *param_name, char *param_data, char *param_type);
static void cwmp_lwnotification(void);
static void periodic_check_notifiy(struct uloop_timeout *timeout  __attribute__((unused)));

struct uloop_timeout check_notify_timer = { .cb = periodic_check_notifiy };

char *supported_notification_types[7] = {"disabled" , "passive", "active", "passive_lw", "passive_passive_lw", "active_lw", "passive_active_lw"};

char *default_active_notifications_parameters[] = {
	"Device.ManagementServer.ConnectionRequestURL",
	"Device.ManagementServer.ConnReqJabberID",
	"Device.GatewayInfo.ManufacturerOUI",
	"Device.GatewayInfo.ProductClass",
	"Device.GatewayInfo.SerialNumber",
	"Device.SoftwareModules.ExecutionUnit.*.Status",
};

char *forced_notifications_parameters[] = {
	"Device.DeviceInfo.SoftwareVersion",
	"Device.DeviceInfo.ProvisioningCode"
};

/*
 * Common functions
 */
static bool parameter_is_subobject_of_parameter(const char *parent, const char *child)
{
	if (child == NULL) {
		CWMP_LOG(WARNING, "notifications %s: child is null", __FUNCTION__);
		return false;
	}
	if (parent == NULL)
		parent = "Device.";

	if (strcmp(parent, child) == 0)
		return false;
	if (strncmp(parent, child, strlen(parent)) == 0)
		return true;
	return false;
}

int check_parameter_forced_notification(const char *parameter)
{
	int i;

	if (parameter == NULL) {
		CWMP_LOG(WARNING, "notifications %s: parameter is null", __FUNCTION__);
		return 0;
	}

	for (i = 0; i < (int)ARRAY_SIZE(forced_notifications_parameters); i++) {
		if (strcmp(forced_notifications_parameters[i], parameter) == 0)
			return 2;
	}

	return 0;
}

static char *check_valid_parameter_path(const char *parameter_name)
{
	char *error = NULL;
	LIST_HEAD(parameters_list);

	/*check if parameter name is valid parameter path*/
	error = cwmp_validate_parameter_name(parameter_name, false, &parameters_list);

	if (error && CWMP_STRCMP(error, "9003") == 0)
		error = cwmp_get_parameter_values(parameter_name, &parameters_list);

	cwmp_free_all_dm_parameter_list(&parameters_list);

	return error;
}

/*
 * SetParameterAttributes
 */

// Add parameter_name to the suitable notifications list
static int add_uci_option_notification(const char *parameter_name, int type)
{
	char notif_path[BUF_SIZE_256] = {0};
	int ret;

	set_uci_path_value(ICWMPD_CONFIG, "cwmp_notifications.notifications", "notifications");
	snprintf(notif_path, BUF_SIZE_256, "cwmp_notifications.notifications.%s", supported_notification_types[type]);

	ret = set_uci_list_value(ICWMPD_CONFIG, notif_path, parameter_name);
	if (ret != UCI_OK)
		return -1;

	return ret;
}

static bool check_parent_with_different_notification(const char *parameter_name, int notification)
{
	int i;
	bool ret = false;

	for (i = 0; i < 7; i++) {
		char notif_path[BUF_SIZE_256] = {0};
		struct cwmp_dm_parameter *param_iter = NULL;
		LIST_HEAD(local_notify_list);

		snprintf(notif_path, BUF_SIZE_256, "cwmp_notifications.notifications.%s", supported_notification_types[i]);
		get_uci_dm_list(ICWMPD_CONFIG, notif_path, &local_notify_list, i);
		list_for_each_entry(param_iter, &local_notify_list, list) {
			if (CWMP_STRLEN(param_iter->name) == 0)
				continue;
			if (parameter_is_subobject_of_parameter(param_iter->name, parameter_name)) {
				ret = true;
				break;
			}
		}
		cwmp_free_all_dm_parameter_list(&local_notify_list);
		if (ret == true) {
			break;
		}
	}
	return ret;
}

static bool update_notifications_list(const char *parameter_name, int notification)
{
	int i;
	bool update_ret = true;

	if (parameter_name == NULL)
		parameter_name = "Device.";

	
	// Parse all possible lists of notifications one by one
	for (i = 0; i < 7; i++) {
		char notif_path[BUF_SIZE_256] = {0};
		struct cwmp_dm_parameter *param_iter = NULL;
		LIST_HEAD(local_notify_list);

		snprintf(notif_path, BUF_SIZE_256, "cwmp_notifications.notifications.%s", supported_notification_types[i]);
		get_uci_dm_list(ICWMPD_CONFIG, notif_path, &local_notify_list, i);
		list_for_each_entry(param_iter, &local_notify_list, list) {
			if (CWMP_STRLEN(param_iter->name) == 0)
				continue;

			if ((CWMP_STRCMP(parameter_name, param_iter->name) == 0 && (i != notification)) || parameter_is_subobject_of_parameter(parameter_name, param_iter->name))
				del_uci_list_value(ICWMPD_CONFIG, notif_path, param_iter->name);
			if ((CWMP_STRCMP(parameter_name, param_iter->name) == 0 || parameter_is_subobject_of_parameter(param_iter->name, parameter_name) ) && (i == notification))
				update_ret = false;
		}
		cwmp_free_all_dm_parameter_list(&local_notify_list);
	}

	if (update_ret && notification == 0 && !check_parent_with_different_notification(parameter_name, 0))
		update_ret = false;
	return update_ret;
}

char *cwmp_set_parameter_attributes(const char *parameter_name, int notification)
{
       char *error = NULL;
 
       if (parameter_name == NULL)
               parameter_name = "Device.";

       /*Check if the parameter name is present in TR-181 datamodel*/
       error = check_valid_parameter_path(parameter_name);
       if (error != NULL)
               return error;

       /*Mustn't set notifications for forced notifications parameter*/
       if (check_parameter_forced_notification(parameter_name))
               return "9009";

       /*checks if the notifications lists need to be updated*/
       if (update_notifications_list(parameter_name, notification) == true)
               add_uci_option_notification(parameter_name, notification);

       return NULL;
}

int cwmp_set_parameter_attributes_list(struct list_head *parameters_list)
{
	struct cwmp_dm_parameter *param_iter = NULL;
	int ret = CWMP_OK;

	list_for_each_entry (param_iter, parameters_list, list) {
		char *parameter_name = param_iter->name;
		int notif_type = param_iter->notification;
		char *error = NULL;

		error = cwmp_set_parameter_attributes(parameter_name, notif_type);
		if (error != NULL) {
			CWMP_LOG(ERROR, "Invalid/forced parameter %s, skipped %s", parameter_name, error);
			continue;
		}
	}

	return ret;
}

/*
 * GetPrameterAttributes
 */
static int get_parameter_family_notifications(const char *parameter_name, struct list_head *childs_notifications) {

	int i, notif_ret = 0;

	if (parameter_name == NULL)
		parameter_name = "Device.";

	for (i = 0; i < 7; i++) {
		char *parent_param = NULL;
		char notif_path[BUF_SIZE_256] = {0};
		LIST_HEAD(local_notify_list);
		struct cwmp_dm_parameter *param_iter = NULL;

		snprintf(notif_path, BUF_SIZE_256, "cwmp_notifications.notifications.%s", supported_notification_types[i]);
		get_uci_dm_list(ICWMPD_CONFIG, notif_path, &local_notify_list, i);
		list_for_each_entry(param_iter, &local_notify_list, list) {
			if (CWMP_STRLEN(param_iter->name) == 0)
				continue;

			if (parameter_is_subobject_of_parameter(parameter_name, param_iter->name)) {
				add_dm_parameter_to_list(childs_notifications, param_iter->name, "", "", i, false);
			}

			// cppcheck-suppress knownConditionTrueFalse
			if (parameter_is_subobject_of_parameter(param_iter->name, parameter_name) && (parent_param == NULL || parameter_is_subobject_of_parameter(parent_param, param_iter->name))) {

				parent_param = CWMP_STRDUP(param_iter->name);
				notif_ret = i;
			}
			if (CWMP_STRCMP(parameter_name, param_iter->name) == 0)
				notif_ret = i;
		}
		FREE(parent_param);
		cwmp_free_all_dm_parameter_list(&local_notify_list);
	}

	return notif_ret;
}

int get_parameter_leaf_notification_from_childs_list(char *parameter_name, struct list_head *childs_list)
{
	char *parent = NULL;
	int ret_notif = -1;
	struct cwmp_dm_parameter *param_value = NULL;
	if (childs_list == NULL)
		return -1;
	list_for_each_entry (param_value, childs_list, list) {
		if (CWMP_STRCMP(param_value->name, parameter_name) == 0) {
			ret_notif = param_value->notification;
			break;
		}
		if (parameter_is_subobject_of_parameter(param_value->name, parameter_name) && ( parent == NULL || parameter_is_subobject_of_parameter(parent, param_value->name))) {
			parent = param_value->name;
			ret_notif = param_value->notification;
		}
	}
	return ret_notif;
}

char *cwmp_get_parameter_attributes(const char *parameter_name, struct list_head *parameters_list)
{
	char *error = NULL;

	if (parameter_name == NULL || parameters_list == NULL) {
		CWMP_LOG(ERROR, "notifications %s: childs_list is null", __FUNCTION__);
		return NULL;
	}

	error = check_valid_parameter_path(parameter_name);
	if (error != NULL)
		return error;

	LIST_HEAD(childs_notifs);
	int notification = get_parameter_family_notifications(parameter_name, &childs_notifs);
	LIST_HEAD(params_list);
	error = cwmp_get_parameter_values(parameter_name, &params_list);
	if (error != NULL) {
		cwmp_free_all_dm_parameter_list(&childs_notifs);
		return error;
	}
	struct cwmp_dm_parameter *param_value = NULL;
	list_for_each_entry (param_value, &params_list, list) {
		int notif_leaf;
		notif_leaf = check_parameter_forced_notification(param_value->name);
		if (notif_leaf > 0) {
			add_dm_parameter_to_list(parameters_list, param_value->name, "", "", notif_leaf, false);
			continue;
		}
		notif_leaf = get_parameter_leaf_notification_from_childs_list(param_value->name, &childs_notifs);
		if (notif_leaf == -1) { //param_value is not among childs_notifs
			add_dm_parameter_to_list(parameters_list, param_value->name, "", "", notification, false);
		}
		else { //param_value is among childs_notifs
			add_dm_parameter_to_list(parameters_list, param_value->name, "", "", notif_leaf, false);
		}
	}
	cwmp_free_all_dm_parameter_list(&childs_notifs);
	cwmp_free_all_dm_parameter_list(&params_list);
	return NULL;
}

/*
 * Update notify file
 */
static bool parameter_is_other_notif_object_child(const char *parent, const char *parameter)
{
	struct list_head list_iter, *list_ptr;
	list_iter.next = list_param_obj_notify.next;
	list_iter.prev = list_param_obj_notify.prev;

	if (parent == NULL)
		parent = "Device.";
	if (parameter == NULL)
		parameter = "Device.";
	while (list_iter.prev != &list_param_obj_notify) {
		struct cwmp_dm_parameter *dm_parameter;
		if (list_iter.prev == NULL)
			continue;

		dm_parameter = list_entry(list_iter.prev, struct cwmp_dm_parameter, list);
		list_ptr = list_iter.prev;
		list_iter.prev = list_ptr->prev;
		list_iter.next = list_ptr->next;
		if (dm_parameter->name == NULL)
			continue;
		if (CWMP_STRCMP(parent, dm_parameter->name) == 0)
			continue;
		if (CWMP_STRNCMP(parent, dm_parameter->name, strlen(parent)) == 0 && CWMP_STRNCMP(parameter, dm_parameter->name, strlen(dm_parameter->name)) == 0)
			return true;
	}
	return false;
}

char* update_list_param_leaf_notify_with_sub_parameter_list(struct list_head *list_param_leaf_notify, char* parent_parameter, int parent_notification, bool parent_forced_notif, void (*update_notify_file_line_arg)(FILE *notify_file, char *param_name, char *param_type, char *param_value, int notification), FILE* notify_file_arg)
{
	struct cwmp_dm_parameter *param_iter = NULL;
	LIST_HEAD(params_list);
	char *err = cwmp_get_parameter_values(parent_parameter, &params_list);
	if (err)
		return err;
	list_for_each_entry (param_iter, &params_list, list) {
		if (parent_forced_notif || (!parameter_is_other_notif_object_child(parent_parameter, param_iter->name) && !check_parameter_forced_notification(param_iter->name))) {
			if (list_param_leaf_notify != NULL)
				add_dm_parameter_to_list(list_param_leaf_notify, param_iter->name, param_iter->value, "", parent_notification, false);
			if (notify_file_arg != NULL && update_notify_file_line_arg != NULL)
				update_notify_file_line_arg(notify_file_arg, param_iter->name, param_iter->type, param_iter->value, parent_notification);
		}
	}
	cwmp_free_all_dm_parameter_list(&params_list);
	return NULL;
}

void create_list_param_leaf_notify(struct list_head *list_param_leaf_notify, void (*update_notify_file_line_arg)(FILE *notify_file, char *param_name, char *param_type, char *param_value, int notification), FILE* notify_file_arg)
{
	struct cwmp_dm_parameter *param_iter = NULL;
	int i;

	for (i = 0; i < (int)ARRAY_SIZE(forced_notifications_parameters); i++)
		update_list_param_leaf_notify_with_sub_parameter_list(list_param_leaf_notify, forced_notifications_parameters[i], 2, true, update_notify_file_line_arg, notify_file_arg);

	list_for_each_entry (param_iter, &list_param_obj_notify, list) {
		if (param_iter->notification == 0)
			continue;
		update_list_param_leaf_notify_with_sub_parameter_list(list_param_leaf_notify, param_iter->name, param_iter->notification, false, update_notify_file_line_arg, notify_file_arg);
	}
}

static void load_notify_values(struct list_head *notify_list)
{
	if (notify_list == NULL)
		return;

	// cppcheck-suppress cert-MSC24-C
	FILE *fp = fopen(CWMP_NOTIFICATION_DB, "r");
	if (fp == NULL)
		return;

	char line[2048] = {0};

	while (fgets(line, sizeof(line), fp)) {
		const char *p_name = NULL, *p_val = NULL, *p_type = NULL;
		int p_notif = 0;

		json_object *jobj = json_tokener_parse(line);
		if (jobj == NULL)
			continue;

		json_object *p_obj = json_object_object_get(jobj, "parameter");
		json_object *v_obj = json_object_object_get(jobj, "value");
		json_object *n_obj = json_object_object_get(jobj, "notification");
		json_object *t_obj = json_object_object_get(jobj, "type");

		if (p_obj == NULL || v_obj == NULL || n_obj == NULL || t_obj == NULL) {
			json_object_put(jobj);
			jobj = NULL;
			continue;
		}

		p_name = json_object_get_string(p_obj);
		p_val = json_object_get_string(v_obj);
		p_notif = json_object_get_int(n_obj);
		p_type = json_object_get_string(t_obj);

		add_dm_parameter_to_list(notify_list, p_name, p_val, p_type, p_notif, 0);

		json_object_put(jobj);
		jobj = NULL;
	}

	fclose(fp);
}

static void apply_notify_values(struct list_head *notify_list)
{
	if (notify_list == NULL)
		return;

	if (list_empty(notify_list))
		return;

	// cppcheck-suppress cert-MSC24-C
	FILE *fp = fopen(CWMP_NOTIFICATION_DB, "w");
	if (fp == NULL)
		return;

	struct cwmp_dm_parameter *notif_value = NULL;
	list_for_each_entry(notif_value, notify_list, list) {
		update_notify_file_line(fp, notif_value->name, notif_value->type, notif_value->value, notif_value->notification);
	}

	fclose(fp);
}

void cwmp_update_notify_values(struct list_head *parameter_values_list)
{
	struct cwmp_dm_parameter *param_value = NULL, *notif_value = NULL;
	bool value_changed = false;

	LIST_HEAD(notify_list);

	load_notify_values(&notify_list);

	if (list_empty(&notify_list))
		return;

	list_for_each_entry(param_value, parameter_values_list, list) {
		if (CWMP_STRLEN(param_value->name) == 0)
			continue;

		char *inst_path = NULL;
		if (CWMP_OK != instantiate_param_name(param_value->name, &inst_path))
			continue;

		list_for_each_entry(notif_value, &notify_list, list) {
			if (CWMP_STRCMP(inst_path, notif_value->name) == 0) {
				if (CWMP_STRCMP(param_value->value, notif_value->value) != 0) {
					FREE(notif_value->value);
					notif_value->value = strdup(param_value->value);
					value_changed = true;
				}

				break;
			}
		}
		FREE(inst_path);
	}

	if (value_changed == true) {
		apply_notify_values(&notify_list);
	}

	cwmp_free_all_dm_parameter_list(&notify_list);
}

void init_list_param_notify()
{
	int i;

	for (i = 0; i < 7; i++) {
		char notif_path[BUF_SIZE_256] = {0};

		snprintf(notif_path, BUF_SIZE_256, "cwmp_notifications.notifications.%s", supported_notification_types[i]);
		get_uci_dm_list(ICWMPD_CONFIG, notif_path, &list_param_obj_notify, i);
	}
}

void clean_list_param_notify()
{
	cwmp_free_all_dm_parameter_list(&list_param_obj_notify);
}

void reinit_list_param_notify()
{
	clean_list_param_notify();
	init_list_param_notify();
}

void update_notify_file_line(FILE *notify_file, char *param_name, char *param_type, char *param_value, int notification)
{
	if (notify_file == NULL)
		return;
	if (param_name == NULL)
		return;
	struct blob_buf bbuf;
	CWMP_MEMSET(&bbuf, 0, sizeof(struct blob_buf));
	blob_buf_init(&bbuf, 0);
	blobmsg_add_string(&bbuf, "parameter", param_name);
	blobmsg_add_u32(&bbuf, "notification", notification);
	blobmsg_add_string(&bbuf, "type", param_type ? param_type : "xsd:string");
	blobmsg_add_string(&bbuf, "value", param_value ? param_value : "");
	char *notification_line = blobmsg_format_json(bbuf.head, true);
	if (notification_line != NULL) {
		fprintf(notify_file, "%s\n", notification_line);
		FREE(notification_line);
	}
	blob_buf_free(&bbuf);
}

void cwmp_update_enabled_notify_file(void)
{
	FILE *fp = NULL;

	LIST_HEAD(list_notify_params);
	remove(CWMP_NOTIFICATION_DB);

	// cppcheck-suppress cert-MSC24-C
	fp = fopen(CWMP_NOTIFICATION_DB, "a");
	if (fp == NULL)
		return;

	create_list_param_leaf_notify(NULL, update_notify_file_line, fp);
	fclose(fp);
}

/*
 * Load custom notify json file
 */
void load_custom_notify_json(void)
{
	struct blob_buf bbuf = {0};
	struct blob_attr *cur = NULL;
	struct blob_attr *custom_notify_list = NULL;
	int rem = 0;

	cwmp_ctx.custom_notify_active = false;
	if (!file_exists(cwmp_ctx.conf.custom_notify_json))
		return;

	// Check for custom notification success import marker
	if (file_exists(CWMP_NOTIFICATION_DB_PERSISTENT_MARKER) == true)
		return;

	CWMP_MEMSET(&bbuf, 0, sizeof(struct blob_buf));
	blob_buf_init(&bbuf, 0);

	// Create success marker in temp area, so that it can be in sync with backup script
	if (blobmsg_add_json_from_file(&bbuf, cwmp_ctx.conf.custom_notify_json) == false) {
		CWMP_LOG(WARNING, "The file %s is not a valid JSON file", cwmp_ctx.conf.custom_notify_json);
		blob_buf_free(&bbuf);
		creat(CWMP_NOTIFICATION_DB_TMP_MARKER, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
		return;
	}

	struct blob_attr *tb_notif[1] = {0};
	const struct blobmsg_policy p_notif[1] = {
			{ "custom_notification", BLOBMSG_TYPE_ARRAY }
	};

	blobmsg_parse(p_notif, 1, tb_notif, blobmsg_data(bbuf.head), blobmsg_len(bbuf.head));
	if (tb_notif[0] == NULL) {
		CWMP_LOG(WARNING, "The JSON file %s doesn't contain a notify parameters list", cwmp_ctx.conf.custom_notify_json);
		blob_buf_free(&bbuf);
		creat(CWMP_NOTIFICATION_DB_TMP_MARKER, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
		return;
	}

	custom_notify_list = tb_notif[0];

	const struct blobmsg_policy p[2] = {
			{ "parameter", BLOBMSG_TYPE_STRING },
			{ "notify_type", BLOBMSG_TYPE_STRING }
	};

	LIST_HEAD(notification_list_head);
	blobmsg_for_each_attr(cur, custom_notify_list, rem) {
		struct blob_attr *tb[2] = { 0, 0 };

		blobmsg_parse(p, 2, tb, blobmsg_data(cur), blobmsg_len(cur));
		if (!tb[0] || !tb[1])
			continue;

		if (!icwmp_validate_int_in_range(blobmsg_get_string(tb[1]), 0, 6)) {
			CWMP_LOG(WARNING, "Wrong notification value: %s", blobmsg_get_string(tb[1]));
			continue;
		}

		add_dm_parameter_to_list(&notification_list_head, blobmsg_get_string(tb[0]), "", "",
					 (int)strtol(blobmsg_get_string(tb[1]), NULL, 10), false);
	}
	blob_buf_free(&bbuf);
	cwmp_set_parameter_attributes_list(&notification_list_head);
	cwmp_free_all_dm_parameter_list(&notification_list_head);

	creat(CWMP_NOTIFICATION_DB_TMP_MARKER, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	cwmp_ctx.custom_notify_active = true;
}

void set_default_forced_active_parameters_notifications()
{
	LIST_HEAD(forced_list_head);
	for (size_t i = 0; i < ARRAY_SIZE(default_active_notifications_parameters); i++) {
		add_dm_parameter_to_list(&forced_list_head, default_active_notifications_parameters[i], "", "", 2, false);
	}
	cwmp_set_parameter_attributes_list(&forced_list_head);
	cwmp_free_all_dm_parameter_list(&forced_list_head);
}

/*
 * Check value change
 */
static void get_parameter_value_from_parameters_list(struct list_head *params_list, const char *parameter_name, char **value, char **type)
{
	struct cwmp_dm_parameter *param_value = NULL;

	if (params_list == NULL) {
		CWMP_LOG(ERROR, "notifications %s: params_list is null", __FUNCTION__);
		return;
	}
	if (parameter_name == NULL)
		parameter_name = "Device.";

	list_for_each_entry (param_value, params_list, list) {
		if (param_value->name == NULL)
			continue;
		if (strcmp(parameter_name, param_value->name) != 0)
			continue;

		*value = strdup(param_value->value ? param_value->value : "");
		*type = strdup(param_value->type ? param_value->type : "");
	}
}

int check_value_change(void)
{
	FILE *fp;
	char buf[1280];
	char *dm_value = NULL, *dm_type = NULL;
	int notif_ret = 0;
	struct blob_buf bbuf;

	char *parameter = NULL, *value = NULL;
	int notification = 0;

	// cppcheck-suppress cert-MSC24-C
	fp = fopen(CWMP_NOTIFICATION_DB, "r");
	if (fp == NULL)
		return notif_ret;

	LIST_HEAD(list_notify_params);
	create_list_param_leaf_notify(&list_notify_params, NULL, NULL);
	while (fgets(buf, 1280, fp) != NULL) {
		int len = strlen(buf);
		if (len)
			buf[len - 1] = '\0';

		CWMP_MEMSET(&bbuf, 0, sizeof(struct blob_buf));
		blob_buf_init(&bbuf, 0);

		if (blobmsg_add_json_from_string(&bbuf, buf) == false) {
			blob_buf_free(&bbuf);
			continue;
		}
		const struct blobmsg_policy p[4] = { { "parameter", BLOBMSG_TYPE_STRING }, { "notification", BLOBMSG_TYPE_INT32 }, { "type", BLOBMSG_TYPE_STRING }, { "value", BLOBMSG_TYPE_STRING } };

		struct blob_attr *tb[4] = { NULL, NULL, NULL, NULL };
		blobmsg_parse(p, 4, tb, blobmsg_data(bbuf.head), blobmsg_len(bbuf.head));
		parameter = blobmsg_get_string(tb[0]);
		notification = blobmsg_get_u32(tb[1]);
		//type = blobmsg_get_string(tb[2]);
		value = blobmsg_get_string(tb[3]);
		get_parameter_value_from_parameters_list(&list_notify_params, parameter, &dm_value, &dm_type);
		if (dm_value == NULL && dm_type == NULL){
			blob_buf_free(&bbuf);
			parameter = NULL;
			notification = 0;
			//type = NULL;
			value = NULL;
			continue;
		}
		if ((notification >= 1) && (dm_value != NULL) && value && (strcmp(dm_value, value) != 0)) {

			if (cwmp_ctx.conf.md_notif_limit > 0 && CWMP_STRCMP(parameter, MANAGEABLE_DEVICES_NBRE) == 0 && notification == 2) {
				unsigned int time_from_last_vc = time(NULL) - cwmp_ctx.md_value_change_last_time;
				if ((cwmp_ctx.md_value_change_last_time <= 0) || (time_from_last_vc >= cwmp_ctx.conf.md_notif_limit)) {
					cwmp_ctx.md_value_change_last_time = time(NULL);
					add_list_value_change(MANAGEABLE_DEVICES_NBRE, dm_value, dm_type);
					notif_ret |= NOTIF_ACTIVE;
				}
			} else if (notification == 1 || notification == 2) {
				add_list_value_change(parameter, dm_value, dm_type);

				if (notification == 1)
					notif_ret |= NOTIF_PASSIVE;
				if (notification == 2)
					notif_ret |= NOTIF_ACTIVE;
			} else {
				add_lw_list_value_change(parameter, dm_value, dm_type);
				notif_ret |= NOTIF_LW_ACTIVE;
			}
		}
		FREE(dm_value);
		FREE(dm_type);
		parameter = NULL;
		notification = 0;
		//type = NULL;
		value = NULL;
		blob_buf_free(&bbuf);
	}
	fclose(fp);

	if (notif_ret > 0) {
		apply_notify_values(&list_notify_params);
	}

	cwmp_free_all_dm_parameter_list(&list_notify_params);
	return notif_ret;
}

void cwmp_prepare_value_change(void)
{
	struct event_container *event_container;

	if (list_value_change.next == &(list_value_change))
		return;

	event_container = cwmp_add_event_container(EVENT_IDX_4VALUE_CHANGE, "");
	if (!event_container)
		return;

	list_splice_init(&(list_value_change), &(event_container->head_dm_parameter));
	cwmp_save_event_container(event_container);
}

void sotfware_version_value_change(struct transfer_complete *p)
{
	char *current_software_version = NULL;

	if (p == NULL) {
		CWMP_LOG(ERROR, "notifications %s: p is null", __FUNCTION__);
		return;
	}
	if (!p->old_software_version || p->old_software_version[0] == 0)
		return;

	current_software_version = cwmp_ctx.deviceid.softwareversion;
	if (p->old_software_version && current_software_version && strcmp(p->old_software_version, current_software_version) != 0)
		cwmp_add_event_container(EVENT_IDX_4VALUE_CHANGE, "");
}

void periodic_check_notifiy(struct uloop_timeout *timeout  __attribute__((unused)))
{
	int is_notify = 0;

	/* If ConnectionRequestURL is empty then reschedule the timer after 5 second for
	 * maximum of 3 times to check the upstream connection is stable, before enqueing
	 * for notification. This can be a case of DHCP lease renewal phase for e.g
	 * renew of old address is NACKED by the server (In this case interface releases its
	 * current IP and waits for a new IP from server) */
	// An empty connection url cause CDR test to break

	if (cr_url_retry) {
		struct cwmp_dm_parameter cwmp_dm_param = {0};

		if (!cwmp_get_parameter_value("Device.ManagementServer.ConnectionRequestURL", &cwmp_dm_param)) {
			uloop_timeout_set(&check_notify_timer, UPSTREAM_STABILITY_CHECK_TIMESPAN * 1000);
			cr_url_retry = cr_url_retry - 1;
			return;
		}

		if (CWMP_STRLEN(cwmp_dm_param.value) == 0) {
			uloop_timeout_set(&check_notify_timer, UPSTREAM_STABILITY_CHECK_TIMESPAN * 1000);
			cr_url_retry = cr_url_retry - 1;
			return;
		}
	}

	// restore the retry count
	cr_url_retry = 3;

	is_notify = check_value_change();
	if (is_notify & NOTIF_ACTIVE) {
		send_active_value_change();
		int last_session_interval = time(NULL) - cwmp_ctx.session->session_status.last_end_time;
		if (!cwmp_ctx.throttle_session_triggered && (cwmp_ctx.session->session_status.last_status == SESSION_SUCCESS) && (cwmp_ctx.conf.active_notif_throttle > 0)) {
			cwmp_ctx.throttle_session_triggered = true;
			if (last_session_interval < cwmp_ctx.conf.active_notif_throttle)
				trigger_cwmp_throttle_session_timer(cwmp_ctx.conf.active_notif_throttle - last_session_interval);
			else
				trigger_cwmp_throttle_session_timer(0);
		}
		else if (cwmp_ctx.conf.active_notif_throttle == 0)
			trigger_cwmp_session_timer();
	}

	if (is_notify & NOTIF_LW_ACTIVE)
		cwmp_lwnotification();

	uloop_timeout_set(&check_notify_timer, cwmp_ctx.conf.periodic_notify_interval * 1000);
}

void trigger_periodic_notify_check()
{
	uloop_timeout_cancel(&check_notify_timer);
	uloop_timeout_set(&check_notify_timer, 10);
}

void add_list_value_change(const char *param_name, const char *param_data, const char *param_type)
{
	add_dm_parameter_to_list(&list_value_change, param_name, param_data, param_type, 0, false);
}

void clean_list_value_change()
{
	cwmp_free_all_dm_parameter_list(&list_value_change);
}

void send_active_value_change(void)
{
	struct event_container *event_container;

	event_container = cwmp_add_event_container(EVENT_IDX_4VALUE_CHANGE, "");
	if (event_container == NULL)
		return;

	cwmp_save_event_container(event_container);
	return;
}

/*
 * Light Weight Notifications
 */
void add_lw_list_value_change(char *param_name, char *param_data, char *param_type) { add_dm_parameter_to_list(&list_lw_value_change, param_name, param_data, param_type, 0, false); }
static void udplw_server_param(struct addrinfo **res)
{
	struct addrinfo hints = { 0 };
	struct config *conf = &(cwmp_ctx.conf);
	char port[32];

	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	snprintf(port, sizeof(port), "%d", conf->lwn_port);
	getaddrinfo(conf->lwn_hostname, port, &hints, res);
}

char *calculate_lwnotification_cnonce()
{
	char *cnonce = calloc(33, sizeof(char));
	if (cnonce == NULL)
		return NULL;

	char *rand = generate_random_string(16);
	if (rand == NULL) {
		free(cnonce);
		return NULL;
	}

	snprintf(cnonce, 33, "%s", rand);
	free(rand);
	return cnonce;
}

static void send_udp_message(struct addrinfo *servaddr, char *msg)
{
	int fd;

	if (msg == NULL) {
		CWMP_LOG(ERROR, "notifications %s: msg is null", __FUNCTION__);
		return;
	}
	fd = socket(servaddr->ai_family, SOCK_DGRAM, 0);

	if (fd >= 0) {
		sendto(fd, msg, strlen(msg), 0, servaddr->ai_addr, servaddr->ai_addrlen);
		close(fd);
	}
}

void del_list_lw_notify(struct cwmp_dm_parameter *dm_parameter)
{
	list_del(&dm_parameter->list);
	free(dm_parameter->name);
	free(dm_parameter);
}

static void free_all_list_lw_notify()
{
	struct cwmp_dm_parameter *dm_parameter = NULL, *node;

	list_for_each_entry_safe(dm_parameter, node, &list_lw_value_change, list) {
		del_list_lw_notify(dm_parameter);
	}
}

void cwmp_lwnotification()
{
	char msg[1024], *msg_out = NULL;
	char signature[41];
	struct addrinfo *servaddr;
	struct config *conf;
	conf = &(cwmp_ctx.conf);

	udplw_server_param(&servaddr);
	xml_prepare_lwnotification_message(&msg_out);
	if (msg_out == NULL) {
		CWMP_LOG(ERROR, "%s: msg_out is null", __FUNCTION__);
		return;
	}
	message_compute_signature(msg_out, signature, sizeof(signature));
	snprintf(msg, sizeof(msg), "%s \n %s: %s \n %s: %s \n %s: %zu\n %s: %s\n\n%s", "POST /HTTPS/1.1", "HOST", conf->lwn_hostname, "Content-Type", "test/xml; charset=utf-8", "Content-Lenght", strlen(msg_out), "Signature", signature, msg_out);

	send_udp_message(servaddr, msg);
	free_all_list_lw_notify();
	//freeaddrinfo(servaddr); //To check
	FREE(msg_out);
}
