/*
 * Copyright (c) 2016 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.
 */
#ifndef __included_ip6_hop_by_hop_ioam_h__
#define __included_ip6_hop_by_hop_ioam_h__

#include <vnet/ip/ip6_hop_by_hop_packet.h>
#include <vnet/ip/ip.h>


#define MAX_IP6_HBH_OPTION      256

/* To determine whether a node is decap MS bit is set */
#define IOAM_DECAP_BIT 0x80000000

#define IOAM_DEAP_ENABLED(opaque_data) (opaque_data & IOAM_DECAP_BIT)

#define IOAM_SET_DECAP(opaque_data) \
    (opaque_data |= IOAM_DECAP_BIT)

#define IOAM_MASK_DECAP_BIT(x) (x & ~IOAM_DECAP_BIT)

/*
 * Stores the run time flow data of hbh options
 */
typedef struct
{
  u32 ctx[MAX_IP6_HBH_OPTION];
  u8 flow_name[64];
} flow_data_t;

typedef struct
{
  u8 next_index_by_protocol[256];
} ip6_local_hop_by_hop_runtime_t;

typedef struct
{
  /* The current rewrite we're using */
  u8 *rewrite;

  /* Trace data processing callback */
  void *ioam_end_of_path_cb;
  /* Configuration data */
  /* Adjacency */
  ip6_address_t adj;
#define IOAM_HBYH_ADD  0
#define IOAM_HBYH_MOD  1
#define IOAM_HBYH_POP  2
  u8 ioam_flag;
  /* time scale transform. Joy. */
  u32 unix_time_0;
  f64 vlib_time_0;


  /* Trace option */
  u8 has_trace_option;

  /* Pot option */
  u8 has_pot_option;

  /* Per Packet Counter option */
  u8 has_seqno_option;

  /* Enabling analyis of iOAM data on decap node */
  u8 has_analyse_option;

  /* Array of function pointers to ADD and POP HBH option handling routines */
  u8 options_size[MAX_IP6_HBH_OPTION];
  int (*add_options[MAX_IP6_HBH_OPTION]) (u8 * rewrite_string,
					  u8 * rewrite_size);
  int (*pop_options[MAX_IP6_HBH_OPTION]) (vlib_buffer_t * b,
					  ip6_header_t * ip,
					  ip6_hop_by_hop_option_t * opt);
  int (*get_sizeof_options[MAX_IP6_HBH_OPTION]) (u32 * rewrite_size);
  int (*config_handler[MAX_IP6_HBH_OPTION]) (void *data, u8 disable);

  /* Array of function pointers to handle hbh options being used with classifier */
    u32 (*flow_handler[MAX_IP6_HBH_OPTION]) (u32 flow_ctx, u8 add);
  flow_data_t *flows;

  ip6_local_hop_by_hop_runtime_t *ip6_local_hbh_runtime;

  /* convenience */
  vlib_main_t *vlib_main;
  vnet_main_t *vnet_main;
} ip6_hop_by_hop_ioam_main_t;

extern ip6_hop_by_hop_ioam_main_t ip6_hop_by_hop_ioam_main;

extern clib_error_t *ip6_ioam_enable (int has_trace_option,
				      int has_pot_option,
				      int has_seqno_option,
				      int has_analyse_option);

extern int ip6_ioam_set_destination (ip6_address_t * addr, u32 mask_width,
				     u32 vrf_id, int is_add, int is_pop,
				     int is_none);

extern clib_error_t *clear_ioam_rewrite_fn (void);

static inline u8
is_zero_ip4_address (ip4_address_t * a)
{
  return (a->as_u32 == 0);
}

static inline void
copy_ip6_address (ip6_address_t * dst, ip6_address_t * src)
{
  dst->as_u64[0] = src->as_u64[0];
  dst->as_u64[1] = src->as_u64[1];
}

static inline void
set_zero_ip6_address (ip6_address_t * a)
{
  a->as_u64[0] = 0;
  a->as_u64[1] = 0;
}

static inline u8
cmp_ip6_address (ip6_address_t * a1, ip6_address_t * a2)
{
  return ((a1->as_u64[0] == a2->as_u64[0])
	  && (a1->as_u64[1] == a2->as_u64[1]));
}

static inline u8
is_zero_ip6_address (ip6_address_t * a)
{
  return ((a->as_u64[0] == 0) && (a->as_u64[1] == 0));
}

int ip6_hbh_add_register_option (u8 option,
				 u8 size,
				 int rewrite_options (u8 * rewrite_string,
						      u8 * size));
int ip6_hbh_add_unregister_option (u8 option);

int ip6_hbh_pop_register_option (u8 option,
				 int options (vlib_buffer_t * b,
					      ip6_header_t * ip,
					      ip6_hop_by_hop_option_t * opt));
int ip6_hbh_pop_unregister_option (u8 option);

int
ip6_hbh_get_sizeof_register_option (u8 option,
				    int get_sizeof_hdr_options (u32 *
								rewrite_size));

int
ip6_ioam_set_rewrite (u8 ** rwp, int has_trace_option,
		      int has_pot_option, int has_seq_no);

int
ip6_hbh_config_handler_register (u8 option,
				 int config_handler (void *data, u8 disable));

int ip6_hbh_config_handler_unregister (u8 option);

int ip6_hbh_flow_handler_register (u8 option,
				   u32 ioam_flow_handler (u32 flow_ctx,
							  u8 add));

int ip6_hbh_flow_handler_unregister (u8 option);

u8 *get_flow_name_from_flow_ctx (u32 flow_ctx);

static inline flow_data_t *
get_flow (u32 index)
{
  flow_data_t *flow = NULL;
  ip6_hop_by_hop_ioam_main_t *hm = &ip6_hop_by_hop_ioam_main;

  if (pool_is_free_index (hm->flows, index))
    return NULL;

  flow = pool_elt_at_index (hm->flows, index);
  return flow;
}

static inline u32
get_flow_data_from_flow_ctx (u32 flow_ctx, u8 option)
{
  flow_data_t *flow = NULL;
  ip6_hop_by_hop_ioam_main_t *hm = &ip6_hop_by_hop_ioam_main;
  u32 index;

  index = IOAM_MASK_DECAP_BIT (flow_ctx);
  //flow = pool_elt_at_index (hm->flows, index);
  flow = &hm->flows[index];
  return (flow->ctx[option]);
}

static inline u8
is_seqno_enabled (void)
{
  return (ip6_hop_by_hop_ioam_main.has_seqno_option);
}

int ip6_trace_profile_setup ();

static inline u32
ioam_flow_add (u8 encap, u8 * flow_name)
{
  ip6_hop_by_hop_ioam_main_t *hm = &ip6_hop_by_hop_ioam_main;
  flow_data_t *flow = 0;
  u32 index = 0;
  u8 i;

  pool_get (hm->flows, flow);
  clib_memset (flow, 0, sizeof (flow_data_t));

  index = flow - hm->flows;
  strncpy ((char *) flow->flow_name, (char *) flow_name, 31);

  if (!encap)
    IOAM_SET_DECAP (index);

  for (i = 0; i < 255; i++)
    {
      if (hm->flow_handler[i])
	flow->ctx[i] = hm->flow_handler[i] (index, 1);
    }
  return (index);
}

always_inline ip6_hop_by_hop_option_t *
ip6_hbh_get_option (ip6_hop_by_hop_header_t * hbh0, u8 option_to_search)
{
  ip6_hop_by_hop_option_t *opt0, *limit0;
  u8 type0;

  if (!hbh0)
    return NULL;

  opt0 = (ip6_hop_by_hop_option_t *) (hbh0 + 1);
  limit0 = (ip6_hop_by_hop_option_t *)
    ((u8 *) hbh0 + ((hbh0->length + 1) << 3));

  /* Scan the set of h-b-h options, process ones that we understand */
  while (opt0 < limit0)
    {
      type0 = opt0->type;
      switch (type0)
	{
	case 0:		/* Pad1 */
	  opt0 = (ip6_hop_by_hop_option_t *) ((u8 *) opt0) + 1;
	  continue;
	case 1:		/* PadN */
	  break;
	default:
	  if (type0 == option_to_search)
	    return opt0;
	  break;
	}
      opt0 =
	(ip6_hop_by_hop_option_t *) (((u8 *) opt0) + opt0->length +
				     sizeof (ip6_hop_by_hop_option_t));
    }
  return NULL;
}

#endif /* __included_ip6_hop_by_hop_ioam_h__ */

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