text: /*
   * Copyright (C) 2020 Volta Networks, Inc
   *
   * 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 Lesser General Public License
   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
   */
  
  #include "stdlib.h"
  
  #include <zebra.h>
  
  #include "memory.h"
  #include "log.h"
  #include "command.h"
  #include "prefix.h"
  #include <lib/json.h>
  
  #include "pathd.h"
  #include "pathd/path_errors.h"
  #include "pathd/path_ted.h"
  
  #ifndef VTYSH_EXTRACT_PL
  #include "pathd/path_ted_clippy.c"
  #endif
  
  static struct ls_ted *path_ted_create_ted(void);
  static void path_ted_register_vty(void);
  static void path_ted_unregister_vty(void);
  static uint32_t path_ted_start_importing_igp(const char *daemon_str);
  static uint32_t path_ted_stop_importing_igp(void);
  static enum zclient_send_status path_ted_link_state_sync(void);
  static int path_ted_timer_handler_sync(struct thread *thread);
  static int path_ted_timer_handler_refresh(struct thread *thread);
  static int path_ted_cli_debug_config_write(struct vty *vty);
  static int path_ted_cli_debug_set_all(uint32_t flags, bool set);
  
  extern struct zclient *zclient;
  
  struct ted_state ted_state_g = {};
  
  /*
   * path_path_ted public API function implementations
   */
  
  void path_ted_init(struct thread_master *master)
  {
  	ted_state_g.main = master;
  	ted_state_g.link_state_delay_interval = TIMER_RETRY_DELAY;
  	ted_state_g.segment_list_refresh_interval = TIMER_RETRY_DELAY;
  	path_ted_register_vty();
  	path_ted_segment_list_refresh();
  }
  
  uint32_t path_ted_teardown(void)
  {
  	PATH_TED_DEBUG("%s : TED [%p]", __func__, ted_state_g.ted);
  	path_ted_unregister_vty();
  	path_ted_stop_importing_igp();
  	ls_ted_del_all(ted_state_g.ted);
  	path_ted_timer_sync_cancel();
  	path_ted_timer_refresh_cancel();
  	return 0;
  }
  
  /**
   * Set all needed to receive igp data.
   *
   * @return		true if ok
   *
   */
  uint32_t path_ted_start_importing_igp(const char *daemon_str)
  {
  	uint32_t status = 0;
  
  	if (strcmp(daemon_str, "ospfv2") == 0)
  		ted_state_g.import = IMPORT_OSPFv2;
  	else if (strcmp(daemon_str, "ospfv3") == 0) {
  		ted_state_g.import = IMPORT_UNKNOWN;
  		return 1;
  	} else if (strcmp(daemon_str, "isis") == 0)
  		ted_state_g.import = IMPORT_ISIS;
  	else {
  		ted_state_g.import = IMPORT_UNKNOWN;
  		return 1;
  	}
  
  	if (ls_register(zclient, false /*client*/) != 0) {
  		PATH_TED_ERROR("%s: PATHD-TED: Unable to register Link State",
  			       __func__);
  		ted_state_g.import = IMPORT_UNKNOWN;
  		status = 1;
  	} else {
  		if (path_ted_link_state_sync() != -1) {
  			PATH_TED_DEBUG("%s: PATHD-TED: Importing %s data ON",
  				       __func__,
  				       PATH_TED_IGP_PRINT(ted_state_g.import));
  		} else {
  			PATH_TED_WARN("%s: PATHD-TED: Importing %s data OFF",
  				      __func__,
  				      PATH_TED_IGP_PRINT(ted_state_g.import));
  			ted_state_g.import = IMPORT_UNKNOWN;
  		}
  	}
  	return status;
  }
  
  /**
   * Unset all needed to receive igp data.
   *
   * @return		true if ok
   *
   */
  uint32_t path_ted_stop_importing_igp(void)
  {
  	uint32_t status = 0;
  
  	if (ted_state_g.import != IMPORT_UNKNOWN) {
  		if (ls_unregister(zclient, false /*client*/) != 0) {
  			PATH_TED_ERROR(
  				"%s: PATHD-TED: Unable to unregister Link State",
  				__func__);
  			status = 1;
  		} else {
  			ted_state_g.import = IMPORT_UNKNOWN;
  			PATH_TED_DEBUG("%s: PATHD-TED: Importing igp data OFF",
  				   __func__);
  		}
  		path_ted_timer_sync_cancel();
  	}
  	return status;
  }
  /**
   * Check for ted status
   *
   * @return		true if ok
   *
   */
  bool path_ted_is_initialized(void)
  {
  	if (ted_state_g.ted == NULL) {
  		PATH_TED_WARN("PATHD TED ls_ted not initialized");
  		return false;
  	}
  
  	return true;
  }
  
  /**
   * Creates an empty ted
   *
   * @param void
   *
   * @return		Ptr to ted or NULL
   */
  struct ls_ted *path_ted_create_ted()
  {
  	struct ls_ted *ted = ls_ted_new(TED_KEY, TED_NAME, TED_ASN);
  
  	if (ted == NULL) {
  		PATH_TED_ERROR("%s Unable to initialize TED Key [%d] ASN [%d] Name [%s]",
  			 __func__, TED_KEY, TED_ASN, TED_NAME);
  	} else {
  		PATH_TED_INFO("%s Initialize TED Key [%d] ASN [%d] Name [%s]",
  			 __func__, TED_KEY, TED_ASN, TED_NAME);
  	}
  
  	return ted;
  }
  
  uint32_t path_ted_rcvd_message(struct ls_message *msg)
  {
  	if (!path_ted_is_initialized())
  		return 1;
  
  	if (msg == NULL) {
  		PATH_TED_ERROR("%s: [rcv ted] TED received NULL message ",
  			       __func__);
  		return 1;
  	}
  
  	if (path_ted_get_current_igp(msg->data.node->adv.origin))
  		return 1;
  
  	switch (msg->type) {
  	case LS_MSG_TYPE_NODE:
  		ls_msg2vertex(ted_state_g.ted, msg, true /*hard delete*/);
  		break;
  
  	case LS_MSG_TYPE_ATTRIBUTES:
  		ls_msg2edge(ted_state_g.ted, msg, true /*ĥard delete*/);
  		break;
  
  	case LS_MSG_TYPE_PREFIX:
  		ls_msg2subnet(ted_state_g.ted, msg, true /*hard delete*/);
  		break;
  
  	default:
  		PATH_TED_DEBUG(
  			"%s: [rcv ted] TED received unknown message type [%d]",
  			__func__, msg->type);
  		break;
  	}
  	return 0;
  }
  
  uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote)
  {
  	uint32_t sid = MPLS_LABEL_NONE;
  	struct ls_edge *edge;
  	uint64_t key;
  
  	if (!path_ted_is_initialized())
  		return MPLS_LABEL_NONE;
  
  	if (!local || !remote)
  		return MPLS_LABEL_NONE;
  
  	switch (local->ipa_type) {
  	case IPADDR_V4:
  		/* We have local and remote ip */
  		/* so check all attributes in ted */
  		key = ((uint64_t)ntohl(local->ip._v4_addr.s_addr)) & 0xffffffff;
  		edge = ls_find_edge_by_key(ted_state_g.ted, key);
  		if (edge) {
  			if (edge->attributes->standard.remote.s_addr
  				    == remote->ip._v4_addr.s_addr
  			    && CHECK_FLAG(edge->attributes->flags,
  					  LS_ATTR_ADJ_SID)) {
  				sid = edge->attributes->adj_sid[0]
  					      .sid; /* from primary */
  				break;
  			}
  		}
  		break;
  	case IPADDR_V6:
  		key = (uint64_t)(local->ip._v6_addr.s6_addr32[0] & 0xffffffff)
  		      | ((uint64_t)local->ip._v6_addr.s6_addr32[1] << 32);
  		edge = ls_find_edge_by_key(ted_state_g.ted, key);
  		if (edge) {
  			if ((memcmp(&edge->attributes->standard.remote6,
  				    &remote->ip._v6_addr,
  				    sizeof(remote->ip._v6_addr))
  			     && CHECK_FLAG(edge->attributes->flags,
  					   LS_ATTR_ADJ_SID))) {
  				sid = edge->attributes->adj_sid[0]
  					      .sid; /* from primary */
  				break;
  			}
  		}
  		break;
  	case IPADDR_NONE:
  		break;
  	}
  
  	return sid;
  }
  
  uint32_t path_ted_query_type_c(struct prefix *prefix, uint8_t algo)
  {
  	uint32_t sid = MPLS_LABEL_NONE;
  	struct ls_subnet *subnet;
  
  	if (!path_ted_is_initialized())
  		return MPLS_LABEL_NONE;
  
  	if (!prefix)
  		return MPLS_LABEL_NONE;
  
  	switch (prefix->family) {
  	case AF_INET:
  	case AF_INET6:
  		subnet = ls_find_subnet(ted_state_g.ted, *prefix);
  		if (subnet) {
  			if ((CHECK_FLAG(subnet->ls_pref->flags, LS_PREF_SR))
  			    && (subnet->ls_pref->sr.algo == algo))
  				sid = subnet->ls_pref->sr.sid;
  		}
  		break;
  	default:
  		break;
  	}
  
  	return sid;
  }
  
  uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id)
  {
  	uint32_t sid = MPLS_LABEL_NONE;
  	struct ls_subnet *subnet;
  	struct listnode *lst_node;
  	struct ls_edge *edge;
  
  	if (!path_ted_is_initialized())
  		return MPLS_LABEL_NONE;
  
  	if (!prefix)
  		return MPLS_LABEL_NONE;
  
  	switch (prefix->family) {
  	case AF_INET:
  	case AF_INET6:
  		subnet = ls_find_subnet(ted_state_g.ted, *prefix);
  		if (subnet && subnet->vertex
  		    && subnet->vertex->outgoing_edges) {
  			/* from the vertex linked in subnet */
  			/* loop over outgoing edges */
  			for (ALL_LIST_ELEMENTS_RO(
  				     subnet->vertex->outgoing_edges, lst_node,
  				     edge)) {
  				/* and look for ifaceid */
  				/* so get sid of attribute */
  				if (CHECK_FLAG(edge->attributes->flags,
  					       LS_ATTR_LOCAL_ID)
  				    && edge->attributes->standard.local_id
  					       == iface_id) {
  					sid = subnet->ls_pref->sr.sid;
  					break;
  				}
  			}
  		}
  		break;
  	default:
  		break;
  	}
  
  	return sid;
  }
  
  DEFPY (debug_path_ted,
         debug_path_ted_cmd,
         "[no] debug pathd mpls-te",
         NO_STR
         DEBUG_STR
         "path debugging\n"
         "ted debugging\n")
  {
  	uint32_t mode = DEBUG_NODE2MODE(vty->node);
  	bool no_debug = (no != NULL);
  
  	DEBUG_MODE_SET(&ted_state_g.dbg, mode, !no);
  	DEBUG_FLAGS_SET(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC, !no_debug);
  	return CMD_SUCCESS;
  }
  
  /*
   * Followings are vty command functions.
   */
  /* clang-format off */
  DEFUN (path_ted_on,
         path_ted_on_cmd,
         "mpls-te on",
         NO_STR
         "Enable the TE database (TED) functionality\n")
  /* clang-format on */
  {
  
  	if (ted_state_g.enabled) {
  		PATH_TED_DEBUG("%s: PATHD-TED: Enabled ON -> ON.", __func__);
  		return CMD_SUCCESS;
  	}
  
  	ted_state_g.ted = path_ted_create_ted();
  	ted_state_g.enabled = true;
  	PATH_TED_DEBUG("%s: PATHD-TED: Enabled OFF -> ON.", __func__);
  
  	return CMD_SUCCESS;
  }
  
  /* clang-format off */
  DEFUN (no_path_ted,
         no_path_ted_cmd,
         "no mpls-te [on]",
         NO_STR
         NO_STR
         "Disable the TE Database functionality\n")
  /* clang-format on */
  {
  	if (ted_state_g.enabled) {
  		PATH_TED_DEBUG("%s: PATHD-TED: OFF -> OFF", __func__);
  		return CMD_SUCCESS;
  	}
  
  	/* Remove TED */
  	ls_ted_del_all(ted_state_g.ted);
  	ted_state_g.enabled = false;
  	PATH_TED_DEBUG("%s: PATHD-TED: ON -> OFF", __func__);
  	ted_state_g.import = IMPORT_UNKNOWN;
  	if (ls_unregister(zclient, false /*client*/) != 0) {
  		vty_out(vty, "Unable to unregister Link State\n");
  		return CMD_WARNING;
  	}
  
  	return CMD_SUCCESS;
  }
  
  /* clang-format off */
  DEFPY(path_ted_import,
         path_ted_import_cmd,
         "mpls-te import <ospfv2|ospfv3|isis>$import_daemon",
         "Enable the TE database (TED) fill with remote igp data\n"
         "import\n"
         "Origin ospfv2\n"
         "Origin ospfv3\n"
         "Origin isis\n")
  /* clang-format on */
  {
  
  	if (ted_state_g.enabled)
  		if (path_ted_start_importing_igp(import_daemon)) {
  			vty_out(vty, "Unable to start importing\n");
  			return CMD_WARNING;
  		}
  	return CMD_SUCCESS;
  }
  
  /* clang-format off */
  DEFUN (no_path_ted_import,
         no_path_ted_import_cmd,
         "no mpls-te import",
         NO_STR
         NO_STR
         "Disable the TE Database fill with remote igp data\n")
  /* clang-format on */
  {
  
  	if (ted_state_g.import) {
  		if (path_ted_stop_importing_igp()) {
  			vty_out(vty, "Unable to stop importing\n");
  			return CMD_WARNING;
  		} else {
  			PATH_TED_DEBUG(
  				"%s: PATHD-TED: Importing igp data already OFF",
  				__func__);
  		}
  	}
  	return CMD_SUCCESS;
  }
  
  /* clang-format off */
  DEFPY (show_pahtd_ted_db,
         show_pathd_ted_db_cmd,
         "show pathd ted database <verbose|json>$ver_json ",
         "show command\n"
         "pathd daemon\n"
         "traffic eng\n"
         "database\n"
         "verbose output\n"
         "Show complete received TED database\n")
  /* clang-format on */
  {
  	bool st_json = false;
  	json_object *json = NULL;
  
  	if (!ted_state_g.enabled) {
  		vty_out(vty, "PATHD TED database is not enabled\n");
  		return CMD_WARNING;
  	}
  	if (strcmp(ver_json, "json") == 0) {
  		st_json = true;
  		json = json_object_new_object();
  	}
  	/* Show the complete TED */
  	ls_show_ted(ted_state_g.ted, vty, json, !st_json);
  	if (st_json) {
  		vty_out(vty, "%s\n",
  			json_object_to_json_string_ext(
  				json, JSON_C_TO_STRING_PRETTY));
  		json_object_free(json);
  	}
  	return CMD_SUCCESS;
  }
  
  /*
   * Config Write functions
   */
  
  int path_ted_cli_debug_config_write(struct vty *vty)
  {
  	if (DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_CONF)) {
  		if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC))
  			vty_out(vty, "debug pathd mpls-te\n");
  		return 1;
  	}
  	return 0;
  }
  
  int path_ted_cli_debug_set_all(uint32_t flags, bool set)
  {
  	DEBUG_FLAGS_SET(&ted_state_g.dbg, flags, set);
  
  	/* If all modes have been turned off, don't preserve options. */
  	if (!DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_ALL))
  		DEBUG_CLEAR(&ted_state_g.dbg);
  
  	return 0;
  }
  
  /**
   * Help fn to show ted related configuration
   *
   * @param vty
   *
   * @return		Status
   */
  uint32_t path_ted_config_write(struct vty *vty)
  {
  
  	if (ted_state_g.enabled) {
  		vty_out(vty, "  mpls-te on\n");
  		switch (ted_state_g.import) {
  		case IMPORT_ISIS:
  			vty_out(vty, "  mpls-te import isis\n");
  			break;
  		case IMPORT_OSPFv2:
  			vty_out(vty, "  mpls-te import ospfv2\n");
  			break;
  		case IMPORT_OSPFv3:
  			vty_out(vty, "  mpls-te import ospfv3\n");
  			break;
  		default:
  			break;
  		}
  	}
  	return 0;
  }
  
  /**
   * Register the fn's for CLI and hook for config show
   *
   * @param void
   *
   */
  static void path_ted_register_vty(void)
  {
  	install_element(VIEW_NODE, &show_pathd_ted_db_cmd);
  	install_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd);
  	install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd);
  	install_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd);
  	install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd);
  
  	install_element(CONFIG_NODE, &debug_path_ted_cmd);
  	install_element(ENABLE_NODE, &debug_path_ted_cmd);
  
  	hook_register(nb_client_debug_config_write,
  		      path_ted_cli_debug_config_write);
  	hook_register(nb_client_debug_set_all, path_ted_cli_debug_set_all);
  }
  
  /**
   * UnRegister the fn's for CLI and hook for config show
   *
   * @param void
   *
   */
  static void path_ted_unregister_vty(void)
  {
  	uninstall_element(VIEW_NODE, &show_pathd_ted_db_cmd);
  	uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd);
  	uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd);
  	uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd);
  	uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd);
  }
  
  /**
   * Ask igp for a complete TED so far
   *
   * @param void
   *
   * @return		zclient status
   */
  enum zclient_send_status path_ted_link_state_sync(void)
  {
  	enum zclient_send_status status;
  
  	status = ls_request_sync(zclient);
  	if (status == -1) {
  		PATH_TED_ERROR(
  			"%s: PATHD-TED: Opaque error asking for TED sync ",
  			__func__);
  		return status;
  	} else {
  		PATH_TED_DEBUG("%s: PATHD-TED: Opaque asked for TED sync ",
  			       __func__);
  	}
  	thread_add_timer(ted_state_g.main, path_ted_timer_handler_sync,
  			 &ted_state_g, ted_state_g.link_state_delay_interval,
  			 &ted_state_g.t_link_state_sync);
  
  	return status;
  }
  
  /**
   * Timer cb for check link state sync
   *
   * @param thread	Current thread
   *
   * @return		status
   */
  int path_ted_timer_handler_sync(struct thread *thread)
  {
  	/* data unpacking */
  	struct ted_state *data = THREAD_ARG(thread);
  
  	assert(data != NULL);
  	/* Retry the sync */
  	return path_ted_link_state_sync();
  }
  
  /**
   * refresg segment list and create timer to keep up updated
   *
   * @param void
   *
   * @return		status
   */
  int path_ted_segment_list_refresh(void)
  {
  	int status = 0;
  
  	path_ted_timer_refresh_cancel();
  	thread_add_timer(ted_state_g.main, path_ted_timer_handler_refresh,
  			 &ted_state_g,
  			 ted_state_g.segment_list_refresh_interval,
  			 &ted_state_g.t_segment_list_refresh);
  
  	return status;
  }
  
  /**
   * Timer cb for refreshing sid in segment lists
   *
   * @param void
   *
   * @return		status
   */
  int path_ted_timer_handler_refresh(struct thread *thread)
  {
  	if (!path_ted_is_initialized())
  		return MPLS_LABEL_NONE;
  
  	PATH_TED_DEBUG("%s: PATHD-TED: Refresh sid from current TED", __func__);
  	/* data unpacking */
  	struct ted_state *data = THREAD_ARG(thread);
  
  	assert(data != NULL);
  
  	srte_policy_update_ted_sid();
  	return 0;
  }
  
  /**
   * Cancel sync timer
   *
   * @param void
   *
   * @return		void status
   */
  void path_ted_timer_sync_cancel(void)
  {
  	if (ted_state_g.t_link_state_sync != NULL) {
  		thread_cancel(&ted_state_g.t_link_state_sync);
  		ted_state_g.t_link_state_sync = NULL;
  	}
  }
  
  /**
   * Cancel refresh timer
   *
   * @param void
   *
   * @return		void status
   */
  void path_ted_timer_refresh_cancel(void)
  {
  	if (ted_state_g.t_segment_list_refresh != NULL) {
  		thread_cancel(&ted_state_g.t_segment_list_refresh);
  		ted_state_g.t_segment_list_refresh = NULL;
  	}
  }
  
  /**
   * Check which igp is configured
   *
   * @param igp who want to check against config-
   *
   * @return		status
   */
  uint32_t path_ted_get_current_igp(uint32_t igp)
  {
  	switch (igp) {
  	case ISIS_L1:
  	case ISIS_L2:
  		if (ted_state_g.import != IMPORT_ISIS) {
  			PATH_TED_ERROR(
  				"%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ",
  				__func__,
  				PATH_TED_IGP_PRINT(ted_state_g.import),
  				LS_IGP_PRINT(igp));
  			return 1;
  		}
  		break;
  	case OSPFv2:
  		if (ted_state_g.import != IMPORT_OSPFv2) {
  			PATH_TED_ERROR(
  				"%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ",
  				__func__,
  				PATH_TED_IGP_PRINT(ted_state_g.import),
  				LS_IGP_PRINT(igp));
  			return 1;
  		}
  		break;
  	case STATIC:
  		break;
  	}
  	return 0;
  }
  