/*
 * Copyright (c) 2020 Cisco and/or its affiliates.
 * Copyright (c) 2020 Doc.ai and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <wireguard/wireguard.h>
#include <wireguard/wireguard_key.h>
#include <wireguard/wireguard_peer.h>
#include <wireguard/wireguard_if.h>

static clib_error_t *
wg_if_create_cli (vlib_main_t * vm,
		  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  wg_main_t *wmp = &wg_main;
  unformat_input_t _line_input, *line_input = &_line_input;
  u8 private_key[NOISE_PUBLIC_KEY_LEN + 1];
  u32 instance, sw_if_index;
  ip_address_t src_ip;
  clib_error_t *error;
  u8 *private_key_64;
  u32 port, generate_key = 0;
  int rv;

  error = NULL;
  instance = sw_if_index = ~0;
  private_key_64 = 0;
  port = 0;

  wg_feature_init (wmp);

  if (unformat_user (input, unformat_line_input, line_input))
    {
      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
	{
	  if (unformat (line_input, "instance %d", &instance))
	    ;
	  else if (unformat (line_input, "private-key %s", &private_key_64))
	    {
	      if (!(key_from_base64 (private_key_64,
				     NOISE_KEY_LEN_BASE64, private_key)))
		{
		  error = clib_error_return (0, "Error parsing private key");
		  break;
		}
	    }
	  else if (unformat (line_input, "listen-port %d", &port))
	    ;
	  else if (unformat (line_input, "port %d", &port))
	    ;
	  else if (unformat (line_input, "generate-key"))
	    generate_key = 1;
	  else
	    if (unformat (line_input, "src %U", unformat_ip_address, &src_ip))
	    ;
	  else
	    {
	      error = clib_error_return (0, "unknown input: %U",
					 format_unformat_error, line_input);
	      break;
	    }
	}

      unformat_free (line_input);

      if (error)
	return error;
    }

  if (generate_key)
    curve25519_gen_secret (private_key);

  rv = wg_if_create (instance, private_key, port, &src_ip, &sw_if_index);

  if (rv)
    return clib_error_return (0, "wireguard interface create failed");

  vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, vnet_get_main (),
		   sw_if_index);
  return 0;
}

/*?
 * Create a Wireguard interface.
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_if_create_command, static) = {
  .path = "wireguard create",
  .short_help = "wireguard create listen-port <port> "
    "private-key <key> src <IP> [generate-key]",
  .function = wg_if_create_cli,
};
/* *INDENT-ON* */

static clib_error_t *
wg_if_delete_cli (vlib_main_t * vm,
		  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  wg_main_t *wmp = &wg_main;
  vnet_main_t *vnm;
  u32 sw_if_index;
  int rv;

  wg_feature_init (wmp);

  vnm = vnet_get_main ();
  sw_if_index = ~0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat
	  (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
	;
      else
	break;
    }

  if (~0 != sw_if_index)
    {
      rv = wg_if_delete (sw_if_index);

      if (rv)
	return clib_error_return (0, "wireguard interface delete failed");
    }
  else
    return clib_error_return (0, "no such interface: %U",
			      format_unformat_error, input);

  return 0;
}

/*?
 * Delete a Wireguard interface.
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_if_delete_command, static) = {
  .path = "wireguard delete",
  .short_help = "wireguard delete <interface>",
  .function = wg_if_delete_cli,
};
/* *INDENT-ON* */


static clib_error_t *
wg_peer_add_command_fn (vlib_main_t * vm,
			unformat_input_t * input, vlib_cli_command_t * cmd)
{
  vnet_main_t *vnm = vnet_get_main ();
  wg_main_t *wmp = &wg_main;
  clib_error_t *error = NULL;
  unformat_input_t _line_input, *line_input = &_line_input;

  u8 *public_key_64 = 0;
  u8 public_key[NOISE_PUBLIC_KEY_LEN + 1];
  fib_prefix_t allowed_ip, *allowed_ips = NULL;
  ip_prefix_t pfx;
  ip_address_t ip = ip_address_initializer;
  u32 portDst = 0, table_id = 0;
  u32 persistent_keepalive = 0;
  u32 tun_sw_if_index = ~0;
  u32 peer_index;
  int rv;

  if (!unformat_user (input, unformat_line_input, line_input))
    return 0;

  wg_feature_init (wmp);

  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (line_input, "public-key %s", &public_key_64))
	{
	  if (!(key_from_base64 (public_key_64,
				 NOISE_KEY_LEN_BASE64, public_key)))
	    {
	      error = clib_error_return (0, "Error parsing private key");
	      goto done;
	    }
	}
      else if (unformat (line_input, "endpoint %U", unformat_ip_address, &ip))
	;
      else if (unformat (line_input, "table-id %d", &table_id))
	;
      else if (unformat (line_input, "dst-port %d", &portDst))
	;
      else if (unformat (line_input, "persistent-keepalive %d",
			 &persistent_keepalive))
	;
      else if (unformat (line_input, "allowed-ip %U",
			 unformat_ip_prefix, &pfx))
	{
	  ip_prefix_to_fib_prefix (&pfx, &allowed_ip);
	  vec_add1 (allowed_ips, allowed_ip);
	}
      else if (unformat (line_input, "%U",
			 unformat_vnet_sw_interface, vnm, &tun_sw_if_index))
	;
      else
	{
	  error = clib_error_return (0, "Input error");
	  goto done;
	}
    }

  if (0 == vec_len (allowed_ips))
    {
      error = clib_error_return (0, "Allowed IPs are not specified");
      goto done;
    }

  rv = wg_peer_add (tun_sw_if_index, public_key, table_id, &ip_addr_46 (&ip),
		    allowed_ips, portDst, persistent_keepalive, &peer_index);

  switch (rv)
    {
    case VNET_API_ERROR_KEY_LENGTH:
      error = clib_error_return (0, "Error parsing public key");
      break;
    case VNET_API_ERROR_ENTRY_ALREADY_EXISTS:
      error = clib_error_return (0, "Peer already exist");
      break;
    case VNET_API_ERROR_INVALID_SW_IF_INDEX:
      error = clib_error_return (0, "Tunnel is not specified");
      break;
    case VNET_API_ERROR_LIMIT_EXCEEDED:
      error = clib_error_return (0, "Max peers limit");
      break;
    case VNET_API_ERROR_INIT_FAILED:
      error = clib_error_return (0, "wireguard device parameters is not set");
      break;
    case VNET_API_ERROR_INVALID_PROTOCOL:
      error = clib_error_return (0, "ipv6 not supported yet");
      break;
    }

done:
  vec_free (public_key_64);
  vec_free (allowed_ips);
  unformat_free (line_input);
  return error;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_peer_add_command, static) = {
  .path = "wireguard peer add",
  .short_help =
    "wireguard peer add <wg_int> public-key <pub_key_other> "
    "endpoint <ip4_dst> allowed-ip <prefix> "
    "dst-port [port_dst] persistent-keepalive [keepalive_interval]",
  .function = wg_peer_add_command_fn,
};
/* *INDENT-ON* */

static clib_error_t *
wg_peer_remove_command_fn (vlib_main_t * vm,
			   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  wg_main_t *wmp = &wg_main;
  clib_error_t *error = NULL;
  u32 peer_index;
  int rv;

  unformat_input_t _line_input, *line_input = &_line_input;
  if (!unformat_user (input, unformat_line_input, line_input))
    return 0;

  wg_feature_init (wmp);

  if (unformat (line_input, "%d", &peer_index))
    ;
  else
    {
      error = clib_error_return (0, "Input error");
      goto done;
    }

  rv = wg_peer_remove (peer_index);

  switch (rv)
    {
    case VNET_API_ERROR_KEY_LENGTH:
      error = clib_error_return (0, "Error parsing public key");
      break;
    }

done:
  unformat_free (line_input);
  return error;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_peer_remove_command, static) =
{
  .path = "wireguard peer remove",
  .short_help = "wireguard peer remove <index>",
  .function = wg_peer_remove_command_fn,
};
/* *INDENT-ON* */

static walk_rc_t
wg_peer_show_one (index_t peeri, void *arg)
{
  vlib_cli_output (arg, "%U", format_wg_peer, peeri);

  return (WALK_CONTINUE);
}

static clib_error_t *
wg_show_peer_command_fn (vlib_main_t * vm,
			 unformat_input_t * input, vlib_cli_command_t * cmd)
{
  wg_peer_walk (wg_peer_show_one, vm);

  return NULL;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_show_peers_command, static) =
{
  .path = "show wireguard peer",
  .short_help = "show wireguard peer",
  .function = wg_show_peer_command_fn,
};
/* *INDENT-ON* */

static walk_rc_t
wg_if_show_one (index_t itfi, void *arg)
{
  vlib_cli_output (arg, "%U", format_wg_if, itfi);

  return (WALK_CONTINUE);
}

static clib_error_t *
wg_show_if_command_fn (vlib_main_t * vm,
		       unformat_input_t * input, vlib_cli_command_t * cmd)
{
  wg_main_t *wmp = &wg_main;

  wg_feature_init (wmp);

  wg_if_walk (wg_if_show_one, vm);

  return NULL;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (wg_show_itfs_command, static) =
{
  .path = "show wireguard interface",
  .short_help = "show wireguard",
  .function = wg_show_if_command_fn,
};

static clib_error_t *
wg_set_async_mode_command_fn (vlib_main_t *vm, unformat_input_t *input,
			      vlib_cli_command_t *cmd)
{
  unformat_input_t _line_input, *line_input = &_line_input;
  int async_enable = 0;

  if (!unformat_user (input, unformat_line_input, line_input))
    return 0;

  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (line_input, "on"))
	async_enable = 1;
      else if (unformat (line_input, "off"))
	async_enable = 0;
      else
	return (clib_error_return (0, "unknown input '%U'",
				   format_unformat_error, line_input));
    }

  wg_set_async_mode (async_enable);

  unformat_free (line_input);
  return (NULL);
}

VLIB_CLI_COMMAND (wg_set_async_mode_command, static) = {
  .path = "set wireguard async mode",
  .short_help = "set wireguard async mode on|off",
  .function = wg_set_async_mode_command_fn,
};

static clib_error_t *
wg_show_mode_command_fn (vlib_main_t *vm, unformat_input_t *input,
			 vlib_cli_command_t *cmd)
{
  vlib_cli_output (vm, "Wireguard mode");

#define _(v, f, s)                                                            \
  vlib_cli_output (vm, "\t%s: %s", s,                                         \
		   (wg_op_mode_is_set_##f () ? "enabled" : "disabled"));
  foreach_wg_op_mode_flags
#undef _

    return (NULL);
}

VLIB_CLI_COMMAND (wg_show_modemode_command, static) = {
  .path = "show wireguard mode",
  .short_help = "show wireguard mode",
  .function = wg_show_mode_command_fn,
};

/* *INDENT-ON* */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */