/*
 *------------------------------------------------------------------
 * api_fuzz_test.c - Binary API fuzz hook
 *
 * Copyright (c) 2020 Cisco 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 <vppinfra/format.h>
#include <vppinfra/byte_order.h>
#include <vppinfra/error.h>
#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
#include <vlibapi/api.h>

static u32 fuzz_seed = 0xdeaddabe;
static u16 fuzz_first;
static u16 fuzz_cli_first, fuzz_cli_last;

extern void (*vl_msg_api_fuzz_hook) (u16, void *);

static void
fuzz_hook (u16 id, void *the_msg)
{
  /*
   * Fuzz (aka screw up) this message? Leave connection establishment
   * messages alone as well as CLI messages.
   */
  if ((id > fuzz_first) && !(id >= fuzz_cli_first && id < fuzz_cli_last))
    {
      msgbuf_t *mb;
      u8 *limit, *start;

      mb = (msgbuf_t *) (((u8 *) the_msg) - offsetof (msgbuf_t, data));

      limit = (u8 *) (mb->data + ntohl (mb->data_len));

      /*
       * Leave the first 14 octets alone, aka msg_id, client_index,
       * context, sw_if_index
       */

      start = ((u8 *) the_msg) + 14;

      for (; start < limit; start++)
	*start ^= (random_u32 (&fuzz_seed) & 0xFF);
    }
}

static void
default_fuzz_config (void)
{
  fuzz_first = vl_msg_api_get_msg_index
    ((u8 *) "memclnt_keepalive_reply_e8d4e804");
  fuzz_cli_first = vl_msg_api_get_msg_index ((u8 *) "cli_23bfbfff");
  fuzz_cli_last = vl_msg_api_get_msg_index
    ((u8 *) "cli_inband_reply_05879051");
}

static clib_error_t *
test_api_fuzz_command_fn (vlib_main_t * vm,
			  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  u32 tmp;

  default_fuzz_config ();

  if (fuzz_first == 0xFFFF)
    {
      vlib_cli_output (vm, "Couldn't find 'memclnt_keepalive_reply' ID");
      vlib_cli_output
	(vm, "Manual setting required, use 'show api message table'");
    }

  if (fuzz_cli_first == 0xFFFF)
    {
      vlib_cli_output (vm, "Couldn't find 'cli' ID");
      vlib_cli_output
	(vm, "Manual setting required, use 'show api message table'");
    }

  if (fuzz_cli_last == 0xFFFF)
    {
      vlib_cli_output (vm, "Couldn't find 'cli_inband_reply' ID");
      vlib_cli_output
	(vm, "Manual setting required, use 'show api message table'");
    }

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "seed %d", &fuzz_seed))
	;
      else if (unformat (input, "disable") | unformat (input, "off"))
	fuzz_first = ~0;
      else if (unformat (input, "fuzz-first %d", &tmp))
	fuzz_first = (u16) tmp;
      else if (unformat (input, "fuzz-cli-first %d", &tmp))
	fuzz_cli_first = (u16) tmp;
      else if (unformat (input, "fuzz-cli-last %d", &tmp))
	fuzz_cli_last = (u16) tmp;
      else
	break;
    }

  if (fuzz_first == 0xFFFF)
    {
      vl_msg_api_fuzz_hook = 0;
      return clib_error_return (0, "fuzz_first is ~0, fuzzing disabled");
    }
  vl_msg_api_fuzz_hook = fuzz_hook;

  vlib_cli_output (vm, "Fuzzing enabled: first %d, skip cli range %d - %d",
		   (u32) fuzz_first, (u32) fuzz_cli_first,
		   (u32) fuzz_cli_last);

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_api_fuzz, static) = {
   .path = "test api fuzz",
   .short_help = "test api fuzz [disable][seed nnn]\n"
   "           [fuzz-first nn][fuzz-cli-first nn][fuzz-cli-last nn]",
   .function = test_api_fuzz_command_fn,
  };
/* *INDENT-ON* */

static u8 main_loop_enter_enable_api_fuzz;

static clib_error_t *
api_fuzz_config (vlib_main_t * vm, unformat_input_t * input)
{
  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "off")
	  || unformat (input, "disable") || unformat (input, "no"))
	;			/* ok, no action */
      else if (unformat (input, "on")
	       || unformat (input, "enable") || unformat (input, "yes"))
	main_loop_enter_enable_api_fuzz = 1;
      else
	return clib_error_return (0, "unknown input '%U'",
				  format_unformat_error, input);
    }
  return 0;
}

VLIB_CONFIG_FUNCTION (api_fuzz_config, "api-fuzz");

static clib_error_t *
api_fuzz_api_init (vlib_main_t * vm)
{
  /* Are we supposed to fuzz API messages? */
  if (main_loop_enter_enable_api_fuzz == 0)
    return 0;

  default_fuzz_config ();

  if (fuzz_first == 0xFFFF)
    {
      return clib_error_return
	(0, "Couldn't find 'memclnt_keepalive_reply' ID");
    }
  /* Turn on fuzzing */
  vl_msg_api_fuzz_hook = fuzz_hook;
  return 0;
}

VLIB_API_INIT_FUNCTION (api_fuzz_api_init);

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