/*
 * sr.c: ipv6 segment routing
 *
 * Copyright (c) 2013 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.
 */

/**
 * @file
 * @brief Segment Routing main functions
 *
 */
#include <vnet/vnet.h>
#include <vnet/sr/sr.h>
#include <vnet/fib/ip6_fib.h>
#include <vnet/dpo/dpo.h>

#include <openssl/hmac.h>

ip6_sr_main_t sr_main;
static vlib_node_registration_t sr_local_node;

/**
 * @brief Dynamically added SR DPO type
 */
static dpo_type_t sr_dpo_type;

/**
 * @brief Use passed HMAC key in ip6_sr_header_t in OpenSSL HMAC routines
 *
 * @param sm ip6_sr_main_t *
 * @param ip ip6_header_t *
 * @param sr ip6_sr_header_t *
 */
void
sr_fix_hmac (ip6_sr_main_t * sm, ip6_header_t * ip, ip6_sr_header_t * sr)
{
  u32 key_index;
  static u8 *keybuf;
  u8 *copy_target;
  int first_segment;
  ip6_address_t *addrp;
  int i;
  ip6_sr_hmac_key_t *hmac_key;
  u32 sig_len;

  key_index = sr->hmac_key;

  /* No signature? Pass... */
  if (key_index == 0)
    return;

  /* We don't know about this key? Fail... */
  if (key_index >= vec_len (sm->hmac_keys))
    return;

  hmac_key = sm->hmac_keys + key_index;

  vec_reset_length (keybuf);

  /* pkt ip6 src address */
  vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
  clib_memcpy (copy_target, ip->src_address.as_u8, sizeof (ip6_address_t));

  /* first segment */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0] = sr->first_segment;

  /* octet w/ bit 0 = "clean" flag */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0]
    = (sr->flags & clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP))
    ? 0x80 : 0;

  /* hmac key id */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0] = sr->hmac_key;

  first_segment = sr->first_segment;

  addrp = sr->segments;

  /* segments */
  for (i = 0; i <= first_segment; i++)
    {
      vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
      clib_memcpy (copy_target, addrp->as_u8, sizeof (ip6_address_t));
      addrp++;
    }

  addrp++;

  HMAC_CTX_init (sm->hmac_ctx);
  if (!HMAC_Init (sm->hmac_ctx, hmac_key->shared_secret,
		  vec_len (hmac_key->shared_secret), sm->md))
    clib_warning ("barf1");
  if (!HMAC_Update (sm->hmac_ctx, keybuf, vec_len (keybuf)))
    clib_warning ("barf2");
  if (!HMAC_Final (sm->hmac_ctx, (unsigned char *) addrp, &sig_len))
    clib_warning ("barf3");
  HMAC_CTX_cleanup (sm->hmac_ctx);
}

/**
 * @brief Format function for decoding various SR flags
 *
 * @param s u8 * - formatted string
 * @param args va_list * - u16 flags
 *
 * @return formatted output string u8 *
 */
u8 *
format_ip6_sr_header_flags (u8 * s, va_list * args)
{
  u16 flags = (u16) va_arg (*args, int);
  u8 pl_flag;
  int bswap_needed = va_arg (*args, int);
  int i;

  if (bswap_needed)
    flags = clib_host_to_net_u16 (flags);

  if (flags & IP6_SR_HEADER_FLAG_CLEANUP)
    s = format (s, "cleanup ");

  if (flags & IP6_SR_HEADER_FLAG_PROTECTED)
    s = format (s, "reroute ");

  s = format (s, "pl: ");
  for (i = 1; i <= 4; i++)
    {
      pl_flag = ip6_sr_policy_list_flags (flags, i);
      s = format (s, "[%d] ", i);

      switch (pl_flag)
	{
	case IP6_SR_HEADER_FLAG_PL_ELT_NOT_PRESENT:
	  s = format (s, "NotPr ");
	  break;
	case IP6_SR_HEADER_FLAG_PL_ELT_INGRESS_PE:
	  s = format (s, "InPE ");
	  break;
	case IP6_SR_HEADER_FLAG_PL_ELT_EGRESS_PE:
	  s = format (s, "EgPE ");
	  break;

	case IP6_SR_HEADER_FLAG_PL_ELT_ORIG_SRC_ADDR:
	  s = format (s, "OrgSrc ");
	  break;
	}
    }
  return s;
}

/**
 * @brief Format function for decoding ip6_sr_header_t
 *
 * @param s u8 * - formatted string
 * @param args va_list * - ip6_sr_header_t
 *
 * @return formatted output string u8 *
 */
u8 *
format_ip6_sr_header (u8 * s, va_list * args)
{
  ip6_sr_header_t *h = va_arg (*args, ip6_sr_header_t *);
  ip6_address_t placeholder_addr =
    { {254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
       254, 254}
  };
  int print_hmac = va_arg (*args, int);
  int i, pl_index, max_segs;
  int flags_host_byte_order = clib_net_to_host_u16 (h->flags);

  s = format (s, "next proto %d, len %d, type %d",
	      h->protocol, (h->length << 3) + 8, h->type);
  s = format (s, "\n      segs left %d, first_segment %d, hmac key %d",
	      h->segments_left, h->first_segment, h->hmac_key);
  s = format (s, "\n      flags %U", format_ip6_sr_header_flags,
	      flags_host_byte_order, 0 /* bswap needed */ );

  /*
   * Header length is in 8-byte units (minus one), so
   * divide by 2 to ascertain the number of ip6 addresses in the
   * segment list
   */
  max_segs = (h->length >> 1);

  if (!print_hmac && h->hmac_key)
    max_segs -= 2;

  s = format (s, "\n  Segments (in processing order):");

  for (i = h->first_segment; i >= 1; i--)
    s = format (s, "\n  %U", format_ip6_address, h->segments + i);
  if (ip6_address_is_equal (&placeholder_addr, h->segments))
    s = format (s, "\n  (empty placeholder)");
  else
    s = format (s, "\n  %U", format_ip6_address, h->segments);

  s = format (s, "\n  Policy List:");

  pl_index = 1;			/* to match the RFC text */
  for (i = (h->first_segment + 1); i < max_segs; i++, pl_index++)
    {
      char *tag;
      char *tags[] = { " ", "InPE: ", "EgPE: ", "OrgSrc: " };

      tag = tags[0];
      if (pl_index >= 1 && pl_index <= 4)
	{
	  int this_pl_flag = ip6_sr_policy_list_flags
	    (flags_host_byte_order, pl_index);
	  tag = tags[this_pl_flag];
	}

      s = format (s, "\n  %s%U", tag, format_ip6_address, h->segments + i);
    }

  return s;
}

/**
 * @brief Format function for decoding ip6_sr_header_t with length
 *
 * @param s u8 * - formatted string
 * @param args va_list * - ip6_header_t + ip6_sr_header_t
 *
 * @return formatted output string u8 *
 */
u8 *
format_ip6_sr_header_with_length (u8 * s, va_list * args)
{
  ip6_header_t *h = va_arg (*args, ip6_header_t *);
  u32 max_header_bytes = va_arg (*args, u32);
  uword header_bytes;

  header_bytes = sizeof (h[0]) + sizeof (ip6_sr_header_t);
  if (max_header_bytes != 0 && header_bytes > max_header_bytes)
    return format (s, "ip6_sr header truncated");

  s = format (s, "IP6: %U\n", format_ip6_header, h, max_header_bytes);
  s =
    format (s, "SR: %U\n", format_ip6_sr_header, (ip6_sr_header_t *) (h + 1),
	    0 /* print_hmac */ , max_header_bytes);
  return s;
}

/**
 * @brief Defined valid next nodes
 * @note Cannot call replicate yet without DPDK
*/
#if DPDK > 0
#define foreach_sr_rewrite_next                 \
_(ERROR, "error-drop")                          \
_(IP6_LOOKUP, "ip6-lookup")                     \
_(SR_LOCAL, "sr-local")                         \
_(SR_REPLICATE,"sr-replicate")
#else
#define foreach_sr_rewrite_next                 \
_(ERROR, "error-drop")                          \
_(IP6_LOOKUP, "ip6-lookup")                     \
_(SR_LOCAL, "sr-local")
#endif /* DPDK */

/**
 * @brief Struct for defined valid next nodes
*/
typedef enum
{
#define _(s,n) SR_REWRITE_NEXT_##s,
  foreach_sr_rewrite_next
#undef _
    SR_REWRITE_N_NEXT,
} sr_rewrite_next_t;

/**
 * @brief Struct for data for SR rewrite packet trace
 */
typedef struct
{
  ip6_address_t src, dst;
  u16 length;
  u32 next_index;
  u32 tunnel_index;
  u8 sr[256];
} sr_rewrite_trace_t;

/**
 * @brief Error strings for SR rewrite
 */
static char *sr_rewrite_error_strings[] = {
#define sr_error(n,s) s,
#include "sr_error.def"
#undef sr_error
};

/**
 * @brief Struct for SR rewrite error strings
 */
typedef enum
{
#define sr_error(n,s) SR_REWRITE_ERROR_##n,
#include "sr_error.def"
#undef sr_error
  SR_REWRITE_N_ERROR,
} sr_rewrite_error_t;


/**
 * @brief Format function for SR rewrite trace.
 */
u8 *
format_sr_rewrite_trace (u8 * s, va_list * args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
  sr_rewrite_trace_t *t = va_arg (*args, sr_rewrite_trace_t *);
  ip6_sr_main_t *sm = &sr_main;
  ip6_sr_tunnel_t *tun = pool_elt_at_index (sm->tunnels, t->tunnel_index);
  ip6_fib_t *rx_fib, *tx_fib;

  rx_fib = ip6_fib_get (tun->rx_fib_index);
  tx_fib = ip6_fib_get (tun->tx_fib_index);

  s = format
    (s, "SR-REWRITE: next %s ip6 src %U dst %U len %u\n"
     "           rx-fib-id %d tx-fib-id %d\n%U",
     (t->next_index == SR_REWRITE_NEXT_SR_LOCAL)
     ? "sr-local" : "ip6-lookup",
     format_ip6_address, &t->src,
     format_ip6_address, &t->dst, t->length,
     rx_fib->table_id, tx_fib->table_id,
     format_ip6_sr_header, t->sr, 0 /* print_hmac */ );
  return s;
}

/**
 * @brief Main processing dual-loop for Segment Routing Rewrite
 * @node sr-rewrite
 *
 * @param vm vlib_main_t *
 * @param node vlib_node_runtime_t *
 * @param from_frame vlib_frame_t *
 *
 * @return from_frame->n_vectors uword
 */
static uword
sr_rewrite (vlib_main_t * vm,
	    vlib_node_runtime_t * node, vlib_frame_t * from_frame)
{
  u32 n_left_from, next_index, *from, *to_next;
  ip6_sr_main_t *sm = &sr_main;
  u32 (*sr_local_cb) (vlib_main_t *, vlib_node_runtime_t *,
		      vlib_buffer_t *, ip6_header_t *, ip6_sr_header_t *);
  sr_local_cb = sm->sr_local_cb;

  from = vlib_frame_vector_args (from_frame);
  n_left_from = from_frame->n_vectors;

  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

      /* Note 2x loop disabled */
      while (0 && n_left_from >= 4 && n_left_to_next >= 2)
	{
	  u32 bi0, bi1;
	  vlib_buffer_t *b0, *b1;
	  ip6_header_t *ip0, *ip1;
	  ip6_sr_header_t *sr0, *sr1;
	  ip6_sr_tunnel_t *t0, *t1;
	  u32 next0 = SR_REWRITE_NEXT_IP6_LOOKUP;
	  u32 next1 = SR_REWRITE_NEXT_IP6_LOOKUP;
	  u16 new_l0 = 0;
	  u16 new_l1 = 0;

	  /* Prefetch next iteration. */
	  {
	    vlib_buffer_t *p2, *p3;

	    p2 = vlib_get_buffer (vm, from[2]);
	    p3 = vlib_get_buffer (vm, from[3]);

	    vlib_prefetch_buffer_header (p2, LOAD);
	    vlib_prefetch_buffer_header (p3, LOAD);
	  }

	  bi0 = from[0];
	  bi1 = from[1];
	  to_next[0] = bi0;
	  to_next[1] = bi1;
	  from += 2;
	  to_next += 2;
	  n_left_to_next -= 2;
	  n_left_from -= 2;

	  b0 = vlib_get_buffer (vm, bi0);
	  b1 = vlib_get_buffer (vm, bi1);

	  /*
	   * $$$ parse through header(s) to pick the point
	   * where we punch in the SR extention header
	   */
	  t0 =
	    pool_elt_at_index (sm->tunnels,
			       vnet_buffer (b0)->ip.adj_index[VLIB_TX]);
	  t1 =
	    pool_elt_at_index (sm->tunnels,
			       vnet_buffer (b1)->ip.adj_index[VLIB_TX]);

	  ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
		  >= ((word) vec_len (t0->rewrite)) + b0->current_data);
	  ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
		  >= ((word) vec_len (t1->rewrite)) + b1->current_data);

	  vnet_buffer (b0)->sw_if_index[VLIB_TX] = t0->tx_fib_index;
	  vnet_buffer (b1)->sw_if_index[VLIB_TX] = t1->tx_fib_index;

	  ip0 = vlib_buffer_get_current (b0);
	  ip1 = vlib_buffer_get_current (b1);
#if DPDK > 0			/* Cannot call replication node yet without DPDK */
	  /* add a replication node */
	  if (PREDICT_FALSE (t0->policy_index != ~0))
	    {
	      vnet_buffer (b0)->ip.save_protocol = t0->policy_index;
	      next0 = SR_REWRITE_NEXT_SR_REPLICATE;
	      sr0 = (ip6_sr_header_t *) (t0->rewrite);
	      goto processnext;
	    }
#endif /* DPDK */

	  /*
	   * SR-unaware service chaining case: pkt coming back from
	   * service has the original dst address, and will already
	   * have an SR header. If so, send it to sr-local
	   */
	  if (PREDICT_FALSE (ip0->protocol == IPPROTO_IPV6_ROUTE))
	    {
	      vlib_buffer_advance (b0, sizeof (ip0));
	      sr0 = (ip6_sr_header_t *) (ip0 + 1);
	      new_l0 = clib_net_to_host_u16 (ip0->payload_length);
	      next0 = SR_REWRITE_NEXT_SR_LOCAL;
	    }
	  else
	    {
	      u32 len_bytes = sizeof (ip6_header_t);
	      u8 next_hdr = ip0->protocol;

	      /* HBH must immediately follow ipv6 header */
	      if (PREDICT_FALSE
		  (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
		  len_bytes +=
		    ip6_ext_header_len ((ip6_ext_header_t *) ext_hdr);
		  /* Ignoring the sr_local for now, if RH follows HBH here */
		  next_hdr = ext_hdr->next_hdr;
		  ext_hdr->next_hdr = IPPROTO_IPV6_ROUTE;
		}
	      else
		{
		  ip0->protocol = IPPROTO_IPV6_ROUTE;	/* routing extension header */
		}
	      /*
	       * Copy data before the punch-in point left by the
	       * required amount. Assume (for the moment) that only
	       * the main packet header needs to be copied.
	       */
	      clib_memcpy (((u8 *) ip0) - vec_len (t0->rewrite),
			   ip0, len_bytes);
	      vlib_buffer_advance (b0, -(word) vec_len (t0->rewrite));
	      ip0 = vlib_buffer_get_current (b0);
	      sr0 = (ip6_sr_header_t *) ((u8 *) ip0 + len_bytes);
	      /* $$$ tune */
	      clib_memcpy (sr0, t0->rewrite, vec_len (t0->rewrite));

	      /* Fix the next header chain */
	      sr0->protocol = next_hdr;

	      new_l0 = clib_net_to_host_u16 (ip0->payload_length) +
		vec_len (t0->rewrite);
	      ip0->payload_length = clib_host_to_net_u16 (new_l0);

	      /* Copy dst address into the DA slot in the segment list */
	      clib_memcpy (sr0->segments, ip0->dst_address.as_u64,
			   sizeof (ip6_address_t));
	      /* Rewrite the ip6 dst address with the first hop */
	      clib_memcpy (ip0->dst_address.as_u64, t0->first_hop.as_u64,
			   sizeof (ip6_address_t));

	      sr_fix_hmac (sm, ip0, sr0);

	      next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
		next0;

	      /*
	       * Ignore "do not rewrite" shtik in this path
	       */
	      if (PREDICT_FALSE (next0 & 0x80000000))
		{
		  next0 ^= 0xFFFFFFFF;
		  if (PREDICT_FALSE (next0 == SR_REWRITE_NEXT_ERROR))
		    b0->error = node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
		}
	    }
#if DPDK > 0			/* Cannot call replication node yet without DPDK */
	processnext:
	  /* add a replication node */
	  if (PREDICT_FALSE (t1->policy_index != ~0))
	    {
	      vnet_buffer (b1)->ip.save_protocol = t1->policy_index;
	      next1 = SR_REWRITE_NEXT_SR_REPLICATE;
	      sr1 = (ip6_sr_header_t *) (t1->rewrite);
	      goto trace00;
	    }
#endif /* DPDK */
	  if (PREDICT_FALSE (ip1->protocol == IPPROTO_IPV6_ROUTE))
	    {
	      vlib_buffer_advance (b1, sizeof (ip1));
	      sr1 = (ip6_sr_header_t *) (ip1 + 1);
	      new_l1 = clib_net_to_host_u16 (ip1->payload_length);
	      next1 = SR_REWRITE_NEXT_SR_LOCAL;
	    }
	  else
	    {
	      u32 len_bytes = sizeof (ip6_header_t);
	      u8 next_hdr = ip1->protocol;

	      /* HBH must immediately follow ipv6 header */
	      if (PREDICT_FALSE
		  (ip1->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip1);
		  len_bytes +=
		    ip6_ext_header_len ((ip6_ext_header_t *) ext_hdr);
		  /* Ignoring the sr_local for now, if RH follows HBH here */
		  next_hdr = ext_hdr->next_hdr;
		  ext_hdr->next_hdr = IPPROTO_IPV6_ROUTE;
		}
	      else
		{
		  ip1->protocol = IPPROTO_IPV6_ROUTE;
		}
	      /*
	       * Copy data before the punch-in point left by the
	       * required amount. Assume (for the moment) that only
	       * the main packet header needs to be copied.
	       */
	      clib_memcpy (((u8 *) ip1) - vec_len (t1->rewrite),
			   ip1, len_bytes);
	      vlib_buffer_advance (b1, -(word) vec_len (t1->rewrite));
	      ip1 = vlib_buffer_get_current (b1);
	      sr1 = (ip6_sr_header_t *) ((u8 *) ip1 + len_bytes);
	      clib_memcpy (sr1, t1->rewrite, vec_len (t1->rewrite));

	      sr1->protocol = next_hdr;
	      new_l1 = clib_net_to_host_u16 (ip1->payload_length) +
		vec_len (t1->rewrite);
	      ip1->payload_length = clib_host_to_net_u16 (new_l1);

	      /* Copy dst address into the DA slot in the segment list */
	      clib_memcpy (sr1->segments, ip1->dst_address.as_u64,
			   sizeof (ip6_address_t));
	      /* Rewrite the ip6 dst address with the first hop */
	      clib_memcpy (ip1->dst_address.as_u64, t1->first_hop.as_u64,
			   sizeof (ip6_address_t));

	      sr_fix_hmac (sm, ip1, sr1);

	      next1 = sr_local_cb ? sr_local_cb (vm, node, b1, ip1, sr1) :
		next1;

	      /*
	       * Ignore "do not rewrite" shtik in this path
	       */
	      if (PREDICT_FALSE (next1 & 0x80000000))
		{
		  next1 ^= 0xFFFFFFFF;
		  if (PREDICT_FALSE (next1 == SR_REWRITE_NEXT_ERROR))
		    b1->error = node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
		}
	    }
#if DPDK > 0			/* Cannot run replicate without DPDK and only replicate uses this label */
	trace00:
#endif /* DPDK */

	  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_rewrite_trace_t *tr = vlib_add_trace (vm, node,
						       b0, sizeof (*tr));
	      tr->tunnel_index = t0 - sm->tunnels;
	      clib_memcpy (tr->src.as_u8, ip0->src_address.as_u8,
			   sizeof (tr->src.as_u8));
	      clib_memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
			   sizeof (tr->dst.as_u8));
	      tr->length = new_l0;
	      tr->next_index = next0;
	      if (sr0)
		clib_memcpy (tr->sr, sr0, sizeof (tr->sr));
	    }
	  if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_rewrite_trace_t *tr = vlib_add_trace (vm, node,
						       b1, sizeof (*tr));
	      tr->tunnel_index = t1 - sm->tunnels;
	      clib_memcpy (tr->src.as_u8, ip1->src_address.as_u8,
			   sizeof (tr->src.as_u8));
	      clib_memcpy (tr->dst.as_u8, ip1->dst_address.as_u8,
			   sizeof (tr->dst.as_u8));
	      tr->length = new_l1;
	      tr->next_index = next1;
	      if (sr1)
		clib_memcpy (tr->sr, sr1, sizeof (tr->sr));
	    }
	  vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, bi1, next0, next1);
	}

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  u32 bi0;
	  vlib_buffer_t *b0;
	  ip6_header_t *ip0 = 0;
	  ip6_sr_header_t *sr0 = 0;
	  ip6_sr_tunnel_t *t0;
	  u32 next0 = SR_REWRITE_NEXT_IP6_LOOKUP;
	  u16 new_l0 = 0;

	  bi0 = from[0];
	  to_next[0] = bi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  b0 = vlib_get_buffer (vm, bi0);


	  /*
	   * $$$ parse through header(s) to pick the point
	   * where we punch in the SR extention header
	   */
	  t0 =
	    pool_elt_at_index (sm->tunnels,
			       vnet_buffer (b0)->ip.adj_index[VLIB_TX]);
#if DPDK > 0			/* Cannot call replication node yet without DPDK */
	  /* add a replication node */
	  if (PREDICT_FALSE (t0->policy_index != ~0))
	    {
	      vnet_buffer (b0)->ip.save_protocol = t0->policy_index;
	      next0 = SR_REWRITE_NEXT_SR_REPLICATE;
	      sr0 = (ip6_sr_header_t *) (t0->rewrite);
	      goto trace0;
	    }
#endif /* DPDK */

	  ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
		  >= ((word) vec_len (t0->rewrite)) + b0->current_data);

	  vnet_buffer (b0)->sw_if_index[VLIB_TX] = t0->tx_fib_index;

	  ip0 = vlib_buffer_get_current (b0);

	  /*
	   * SR-unaware service chaining case: pkt coming back from
	   * service has the original dst address, and will already
	   * have an SR header. If so, send it to sr-local
	   */
	  if (PREDICT_FALSE (ip0->protocol == IPPROTO_IPV6_ROUTE))
	    {
	      vlib_buffer_advance (b0, sizeof (ip0));
	      sr0 = (ip6_sr_header_t *) (ip0 + 1);
	      new_l0 = clib_net_to_host_u16 (ip0->payload_length);
	      next0 = SR_REWRITE_NEXT_SR_LOCAL;
	    }
	  else
	    {
	      u32 len_bytes = sizeof (ip6_header_t);
	      u8 next_hdr = ip0->protocol;

	      /* HBH must immediately follow ipv6 header */
	      if (PREDICT_FALSE
		  (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
		  len_bytes +=
		    ip6_ext_header_len ((ip6_ext_header_t *) ext_hdr);
		  next_hdr = ext_hdr->next_hdr;
		  ext_hdr->next_hdr = IPPROTO_IPV6_ROUTE;
		  /* Ignoring the sr_local for now, if RH follows HBH here */
		}
	      else
		{
		  ip0->protocol = IPPROTO_IPV6_ROUTE;	/* routing extension header */
		}
	      /*
	       * Copy data before the punch-in point left by the
	       * required amount. Assume (for the moment) that only
	       * the main packet header needs to be copied.
	       */
	      clib_memcpy (((u8 *) ip0) - vec_len (t0->rewrite),
			   ip0, len_bytes);
	      vlib_buffer_advance (b0, -(word) vec_len (t0->rewrite));
	      ip0 = vlib_buffer_get_current (b0);
	      sr0 = (ip6_sr_header_t *) ((u8 *) ip0 + len_bytes);
	      /* $$$ tune */
	      clib_memcpy (sr0, t0->rewrite, vec_len (t0->rewrite));

	      /* Fix the next header chain */
	      sr0->protocol = next_hdr;
	      new_l0 = clib_net_to_host_u16 (ip0->payload_length) +
		vec_len (t0->rewrite);
	      ip0->payload_length = clib_host_to_net_u16 (new_l0);

	      /* Copy dst address into the DA slot in the segment list */
	      clib_memcpy (sr0->segments, ip0->dst_address.as_u64,
			   sizeof (ip6_address_t));
	      /* Rewrite the ip6 dst address with the first hop */
	      clib_memcpy (ip0->dst_address.as_u64, t0->first_hop.as_u64,
			   sizeof (ip6_address_t));

	      sr_fix_hmac (sm, ip0, sr0);

	      next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
		next0;

	      /*
	       * Ignore "do not rewrite" shtik in this path
	       */
	      if (PREDICT_FALSE (next0 & 0x80000000))
		{
		  next0 ^= 0xFFFFFFFF;
		  if (PREDICT_FALSE (next0 == SR_REWRITE_NEXT_ERROR))
		    b0->error = node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
		}
	    }
#if DPDK > 0			/* Cannot run replicate without DPDK and only replicate uses this label */
	trace0:
#endif /* DPDK */

	  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_rewrite_trace_t *tr = vlib_add_trace (vm, node,
						       b0, sizeof (*tr));
	      tr->tunnel_index = t0 - sm->tunnels;
	      if (ip0)
		{
		  memcpy (tr->src.as_u8, ip0->src_address.as_u8,
			  sizeof (tr->src.as_u8));
		  memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
			  sizeof (tr->dst.as_u8));
		}
	      tr->length = new_l0;
	      tr->next_index = next0;
	      if (sr0)
		clib_memcpy (tr->sr, sr0, sizeof (tr->sr));
	    }
	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, next0);
	}
      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }
  return from_frame->n_vectors;
}

/* *INDENT-OFF* */
VLIB_REGISTER_NODE (sr_rewrite_node) = {
  .function = sr_rewrite,
  .name = "sr-rewrite",
  /* Takes a vector of packets. */
  .vector_size = sizeof (u32),
  .format_trace = format_sr_rewrite_trace,
  .format_buffer = format_ip6_sr_header_with_length,

  .n_errors = SR_REWRITE_N_ERROR,
  .error_strings = sr_rewrite_error_strings,

  .runtime_data_bytes = 0,

  .n_next_nodes = SR_REWRITE_N_NEXT,
  .next_nodes = {
#define _(s,n) [SR_REWRITE_NEXT_##s] = n,
    foreach_sr_rewrite_next
#undef _
  },
};

VLIB_NODE_FUNCTION_MULTIARCH (sr_rewrite_node, sr_rewrite)
/* *INDENT-ON* */

static int
ip6_delete_route_no_next_hop (ip6_address_t * dst_address_arg,
			      u32 dst_address_length, u32 rx_table_id)
{
  fib_prefix_t pfx = {
    .fp_len = dst_address_length,
    .fp_proto = FIB_PROTOCOL_IP6,
    .fp_addr = {
		.ip6 = *dst_address_arg,
		}
  };

  fib_table_entry_delete (fib_table_id_find_fib_index (FIB_PROTOCOL_IP6,
						       rx_table_id),
			  &pfx, FIB_SOURCE_SR);

  return 0;
}

/**
 * @brief Find or add if not found - HMAC shared secret
 *
 * @param sm ip6_sr_main_t *
 * @param secret u8 *
 * @param indexp u32 *
 *
 * @return ip6_sr_hmac_key_t *
 */
static ip6_sr_hmac_key_t *
find_or_add_shared_secret (ip6_sr_main_t * sm, u8 * secret, u32 * indexp)
{
  uword *p;
  ip6_sr_hmac_key_t *key = 0;
  int i;

  p = hash_get_mem (sm->hmac_key_by_shared_secret, secret);

  if (p)
    {
      key = vec_elt_at_index (sm->hmac_keys, p[0]);
      if (indexp)
	*indexp = p[0];
      return (key);
    }

  /* Specific key ID? */
  if (indexp && *indexp)
    {
      vec_validate (sm->hmac_keys, *indexp);
      key = sm->hmac_keys + *indexp;
    }
  else
    {
      for (i = 0; i < vec_len (sm->hmac_keys); i++)
	{
	  if (sm->hmac_keys[i].shared_secret == 0)
	    {
	      key = sm->hmac_keys + i;
	      goto found;
	    }
	}
      vec_validate (sm->hmac_keys, i);
      key = sm->hmac_keys + i;
    found:
      ;
    }

  key->shared_secret = vec_dup (secret);

  hash_set_mem (sm->hmac_key_by_shared_secret, key->shared_secret,
		key - sm->hmac_keys);

  if (indexp)
    *indexp = key - sm->hmac_keys;
  return (key);
}

/**
 * @brief Add or Delete a Segment Routing tunnel.
 *
 * @param a ip6_sr_add_del_tunnel_args_t *
 *
 * @return retval int
 */
int
ip6_sr_add_del_tunnel (ip6_sr_add_del_tunnel_args_t * a)
{
  ip6_main_t *im = &ip6_main;
  ip6_sr_tunnel_key_t key;
  ip6_sr_tunnel_t *t;
  uword *p, *n;
  ip6_sr_header_t *h = 0;
  u32 header_length;
  ip6_address_t *addrp, *this_address;
  ip6_sr_main_t *sm = &sr_main;
  u8 *key_copy;
  u32 rx_fib_index, tx_fib_index;
  u32 hmac_key_index_u32;
  u8 hmac_key_index = 0;
  ip6_sr_policy_t *pt;
  int i;
  dpo_id_t dpo = DPO_INVALID;

  /* Make sure that the rx FIB exists */
  p = hash_get (im->fib_index_by_table_id, a->rx_table_id);

  if (p == 0)
    return -3;

  /* remember the FIB index */
  rx_fib_index = p[0];

  /* Make sure that the supplied FIB exists */
  p = hash_get (im->fib_index_by_table_id, a->tx_table_id);

  if (p == 0)
    return -4;

  /* remember the FIB index */
  tx_fib_index = p[0];

  clib_memcpy (key.src.as_u8, a->src_address->as_u8, sizeof (key.src));
  clib_memcpy (key.dst.as_u8, a->dst_address->as_u8, sizeof (key.dst));

  /* When adding a tunnel:
   * - If a "name" is given, it must not exist.
   * - The "key" is always checked, and must not exist.
   * When deleting a tunnel:
   * - If the "name" is given, and it exists, then use it.
   * - If the "name" is not given, use the "key".
   * - If the "name" and the "key" are given, then both must point to the same
   *   thing.
   */

  /* Lookup the key */
  p = hash_get_mem (sm->tunnel_index_by_key, &key);

  /* If the name is given, look it up */
  if (a->name)
    n = hash_get_mem (sm->tunnel_index_by_name, a->name);
  else
    n = 0;

  /* validate key/name parameters */
  if (!a->is_del)		/* adding a tunnel */
    {
      if (a->name && n)		/* name given & exists already */
	return -1;
      if (p)			/* key exists already */
	return -1;
    }
  else				/* deleting a tunnel */
    {
      if (!p)			/* key doesn't exist */
	return -2;
      if (a->name && !n)	/* name given & it doesn't exist */
	return -2;

      if (n)			/* name given & found */
	{
	  if (n[0] != p[0])	/* name and key do not point to the same thing */
	    return -2;
	}
    }


  if (a->is_del)		/* delete the tunnel */
    {
      hash_pair_t *hp;

      /* Delete existing tunnel */
      t = pool_elt_at_index (sm->tunnels, p[0]);

      ip6_delete_route_no_next_hop (&t->key.dst, t->dst_mask_width,
				    a->rx_table_id);
      vec_free (t->rewrite);
      /* Remove tunnel from any policy if associated */
      if (t->policy_index != ~0)
	{
	  pt = pool_elt_at_index (sm->policies, t->policy_index);
	  for (i = 0; i < vec_len (pt->tunnel_indices); i++)
	    {
	      if (pt->tunnel_indices[i] == t - sm->tunnels)
		{
		  vec_delete (pt->tunnel_indices, 1, i);
		  goto found;
		}
	    }
	  clib_warning ("Tunnel index %d not found in policy_index %d",
			t - sm->tunnels, pt - sm->policies);
	found:
	  /* If this is last tunnel in the  policy, clean up the policy too */
	  if (vec_len (pt->tunnel_indices) == 0)
	    {
	      hash_unset_mem (sm->policy_index_by_policy_name, pt->name);
	      vec_free (pt->name);
	      pool_put (sm->policies, pt);
	    }
	}

      /* Clean up the tunnel by name */
      if (t->name)
	{
	  hash_unset_mem (sm->tunnel_index_by_name, t->name);
	  vec_free (t->name);
	}
      pool_put (sm->tunnels, t);
      hp = hash_get_pair (sm->tunnel_index_by_key, &key);
      key_copy = (void *) (hp->key);
      hash_unset_mem (sm->tunnel_index_by_key, &key);
      vec_free (key_copy);
      return 0;
    }

  /* create a new tunnel */
  pool_get (sm->tunnels, t);
  memset (t, 0, sizeof (*t));
  t->policy_index = ~0;

  clib_memcpy (&t->key, &key, sizeof (t->key));
  t->dst_mask_width = a->dst_mask_width;
  t->rx_fib_index = rx_fib_index;
  t->tx_fib_index = tx_fib_index;

  if (!vec_len (a->segments))
    /* there must be at least one segment... */
    return -4;

  /* The first specified hop goes right into the dst address */
  clib_memcpy (&t->first_hop, &a->segments[0], sizeof (ip6_address_t));

  /*
   * Create the sr header rewrite string
   * The list of segments needs an extra slot for the ultimate destination
   * which is taken from the packet we add the SRH to.
   */
  header_length = sizeof (*h) +
    sizeof (ip6_address_t) * (vec_len (a->segments) + 1 + vec_len (a->tags));

  if (a->shared_secret)
    {
      /* Allocate a new key slot if we don't find the secret key */
      hmac_key_index_u32 = 0;
      (void) find_or_add_shared_secret (sm, a->shared_secret,
					&hmac_key_index_u32);

      /* Hey Vinz Clortho: Gozzer is pissed.. you're out of keys! */
      if (hmac_key_index_u32 >= 256)
	return -5;
      hmac_key_index = hmac_key_index_u32;
      header_length += SHA256_DIGEST_LENGTH;
    }

  vec_validate (t->rewrite, header_length - 1);

  h = (ip6_sr_header_t *) t->rewrite;

  h->protocol = 0xFF;		/* we don't know yet */

  h->length = (header_length / 8) - 1;
  h->type = ROUTING_HEADER_TYPE_SR;

  /* first_segment and segments_left need to have the index of the last
   * element in the list; a->segments has one element less than ends up
   * in the header (it does not have the DA in it), so vec_len(a->segments)
   * is the value we want.
   */
  h->first_segment = h->segments_left = vec_len (a->segments);

  if (a->shared_secret)
    h->hmac_key = hmac_key_index & 0xFF;

  h->flags = a->flags_net_byte_order;

  /* Paint on the segment list, in reverse.
   * This is offset by one to leave room at the start for the ultimate
   * destination.
   */
  addrp = h->segments + vec_len (a->segments);

  vec_foreach (this_address, a->segments)
  {
    clib_memcpy (addrp->as_u8, this_address->as_u8, sizeof (ip6_address_t));
    addrp--;
  }

  /*
   * Since the ultimate destination address is not yet known, set that slot
   * to a value we will instantly recognize as bogus.
   */
  memset (h->segments, 0xfe, sizeof (ip6_address_t));

  /* Paint on the tag list, not reversed */
  addrp = h->segments + vec_len (a->segments);

  vec_foreach (this_address, a->tags)
  {
    clib_memcpy (addrp->as_u8, this_address->as_u8, sizeof (ip6_address_t));
    addrp++;
  }

  key_copy = vec_new (ip6_sr_tunnel_key_t, 1);
  clib_memcpy (key_copy, &key, sizeof (ip6_sr_tunnel_key_t));
  hash_set_mem (sm->tunnel_index_by_key, key_copy, t - sm->tunnels);

  /*
   * Stick the tunnel index into the rewrite header.
   *
   * Unfortunately, inserting an SR header according to the various
   * RFC's requires parsing through the ip6 header, perhaps consing a
   * buffer onto the head of the vlib_buffer_t, etc. We don't use the
   * normal reverse bcopy rewrite code.
   *
   * We don't handle ugly RFC-related cases yet, but I'm sure PL will complain
   * at some point...
   */
  dpo_set (&dpo, sr_dpo_type, DPO_PROTO_IP6, t - sm->tunnels);

  fib_prefix_t pfx = {
    .fp_proto = FIB_PROTOCOL_IP6,
    .fp_len = a->dst_mask_width,
    .fp_addr = {
		.ip6 = *a->dst_address,
		}
  };
  fib_table_entry_special_dpo_add (rx_fib_index,
				   &pfx,
				   FIB_SOURCE_SR,
				   FIB_ENTRY_FLAG_EXCLUSIVE, &dpo);
  dpo_reset (&dpo);

  if (a->policy_name)
    {
      p = hash_get_mem (sm->policy_index_by_policy_name, a->policy_name);
      if (p)
	{
	  pt = pool_elt_at_index (sm->policies, p[0]);
	}
      else			/* no policy, lets create one */
	{
	  pool_get (sm->policies, pt);
	  memset (pt, 0, sizeof (*pt));
	  pt->name = format (0, "%s%c", a->policy_name, 0);
	  hash_set_mem (sm->policy_index_by_policy_name, pt->name,
			pt - sm->policies);
	  p = hash_get_mem (sm->policy_index_by_policy_name, a->policy_name);
	}
      vec_add1 (pt->tunnel_indices, t - sm->tunnels);
      if (p == 0)
	clib_warning ("p is NULL!");
      t->policy_index = p ? p[0] : ~0;	/* equiv. to (pt - sm->policies) */
    }

  if (a->name)
    {
      t->name = format (0, "%s%c", a->name, 0);
      hash_set_mem (sm->tunnel_index_by_name, t->name, t - sm->tunnels);
    }

  return 0;
}

/**
 * @brief no-op lock function.
 * The lifetime of the SR entry is managed by the control plane
 */
static void
sr_dpo_lock (dpo_id_t * dpo)
{
}

/**
 * @brief no-op unlock function.
 * The lifetime of the SR entry is managed by the control plane
 */
static void
sr_dpo_unlock (dpo_id_t * dpo)
{
}

u8 *
format_sr_dpo (u8 * s, va_list * args)
{
  index_t index = va_arg (*args, index_t);
  CLIB_UNUSED (u32 indent) = va_arg (*args, u32);

  return (format (s, "SR: tunnel:[%d]", index));
}

const static dpo_vft_t sr_vft = {
  .dv_lock = sr_dpo_lock,
  .dv_unlock = sr_dpo_unlock,
  .dv_format = format_sr_dpo,
};

const static char *const sr_ip6_nodes[] = {
  "sr-rewrite",
  NULL,
};

const static char *const *const sr_nodes[DPO_PROTO_NUM] = {
  [DPO_PROTO_IP6] = sr_ip6_nodes,
};

/**
 * @brief CLI parser for Add or Delete a Segment Routing tunnel.
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
sr_add_del_tunnel_command_fn (vlib_main_t * vm,
			      unformat_input_t * input,
			      vlib_cli_command_t * cmd)
{
  int is_del = 0;
  ip6_address_t src_address;
  int src_address_set = 0;
  ip6_address_t dst_address;
  u32 dst_mask_width;
  int dst_address_set = 0;
  u16 flags = 0;
  u8 *shared_secret = 0;
  u8 *name = 0;
  u8 *policy_name = 0;
  u32 rx_table_id = 0;
  u32 tx_table_id = 0;
  ip6_address_t *segments = 0;
  ip6_address_t *this_seg;
  ip6_address_t *tags = 0;
  ip6_address_t *this_tag;
  ip6_sr_add_del_tunnel_args_t _a, *a = &_a;
  ip6_address_t next_address, tag;
  int pl_index;
  int rv;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "del"))
	is_del = 1;
      else if (unformat (input, "rx-fib-id %d", &rx_table_id))
	;
      else if (unformat (input, "tx-fib-id %d", &tx_table_id))
	;
      else if (unformat (input, "src %U", unformat_ip6_address, &src_address))
	src_address_set = 1;
      else if (unformat (input, "name %s", &name))
	;
      else if (unformat (input, "policy %s", &policy_name))
	;
      else if (unformat (input, "dst %U/%d",
			 unformat_ip6_address, &dst_address, &dst_mask_width))
	dst_address_set = 1;
      else if (unformat (input, "next %U", unformat_ip6_address,
			 &next_address))
	{
	  vec_add2 (segments, this_seg, 1);
	  clib_memcpy (this_seg->as_u8, next_address.as_u8,
		       sizeof (*this_seg));
	}
      else if (unformat (input, "tag %U", unformat_ip6_address, &tag))
	{
	  vec_add2 (tags, this_tag, 1);
	  clib_memcpy (this_tag->as_u8, tag.as_u8, sizeof (*this_tag));
	}
      else if (unformat (input, "clean"))
	flags |= IP6_SR_HEADER_FLAG_CLEANUP;
      else if (unformat (input, "protected"))
	flags |= IP6_SR_HEADER_FLAG_PROTECTED;
      else if (unformat (input, "key %s", &shared_secret))
	/* Do not include the trailing NULL byte. Guaranteed interop issue */
	_vec_len (shared_secret) -= 1;
      else if (unformat (input, "InPE %d", &pl_index))
	{
	  if (pl_index <= 0 || pl_index > 4)
	    {
	    pl_index_range_error:
	      return clib_error_return
		(0, "Policy List Element Index %d out of range (1-4)",
		 pl_index);

	    }
	  flags |= IP6_SR_HEADER_FLAG_PL_ELT_INGRESS_PE
	    << ip6_sr_policy_list_shift_from_index (pl_index);
	}
      else if (unformat (input, "EgPE %d", &pl_index))
	{
	  if (pl_index <= 0 || pl_index > 4)
	    goto pl_index_range_error;
	  flags |= IP6_SR_HEADER_FLAG_PL_ELT_EGRESS_PE
	    << ip6_sr_policy_list_shift_from_index (pl_index);
	}
      else if (unformat (input, "OrgSrc %d", &pl_index))
	{
	  if (pl_index <= 0 || pl_index > 4)
	    goto pl_index_range_error;
	  flags |= IP6_SR_HEADER_FLAG_PL_ELT_ORIG_SRC_ADDR
	    << ip6_sr_policy_list_shift_from_index (pl_index);
	}
      else
	break;
    }

  if (!src_address_set)
    return clib_error_return (0, "src address required");

  if (!dst_address_set)
    return clib_error_return (0, "dst address required");

  if (!segments)
    return clib_error_return (0, "at least one sr segment required");

  memset (a, 0, sizeof (*a));
  a->src_address = &src_address;
  a->dst_address = &dst_address;
  a->dst_mask_width = dst_mask_width;
  a->segments = segments;
  a->tags = tags;
  a->flags_net_byte_order = clib_host_to_net_u16 (flags);
  a->is_del = is_del;
  a->rx_table_id = rx_table_id;
  a->tx_table_id = tx_table_id;
  a->shared_secret = shared_secret;

  if (vec_len (name))
    a->name = name;
  else
    a->name = 0;

  if (vec_len (policy_name))
    a->policy_name = policy_name;
  else
    a->policy_name = 0;

  rv = ip6_sr_add_del_tunnel (a);

  vec_free (segments);
  vec_free (tags);
  vec_free (shared_secret);

  switch (rv)
    {
    case 0:
      break;

    case -1:
      return clib_error_return (0, "SR tunnel src %U dst %U already exists",
				format_ip6_address, &src_address,
				format_ip6_address, &dst_address);

    case -2:
      return clib_error_return (0, "SR tunnel src %U dst %U does not exist",
				format_ip6_address, &src_address,
				format_ip6_address, &dst_address);

    case -3:
      return clib_error_return (0, "FIB table %d does not exist",
				rx_table_id);

    case -4:
      return clib_error_return (0, "At least one segment is required");

    default:
      return clib_error_return (0, "BUG: ip6_sr_add_del_tunnel returns %d",
				rv);
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (sr_tunnel_command, static) = {
    .path = "sr tunnel",
    .short_help =
      "sr tunnel [del] [name <name>] src <addr> dst <addr> [next <addr>] "
      "[clean] [reroute] [key <secret>] [policy <policy_name>]"
      "[rx-fib-id <fib_id>] [tx-fib-id <fib_id>]",
    .function = sr_add_del_tunnel_command_fn,
};
/* *INDENT-ON* */

/**
 * @brief Display Segment Routing tunnel
 *
 * @param vm vlib_main_t *
 * @param t ip6_sr_tunnel_t *
 *
 */
void
ip6_sr_tunnel_display (vlib_main_t * vm, ip6_sr_tunnel_t * t)
{
  ip6_sr_main_t *sm = &sr_main;
  ip6_fib_t *rx_fib, *tx_fib;
  ip6_sr_policy_t *pt;

  rx_fib = ip6_fib_get (t->rx_fib_index);
  tx_fib = ip6_fib_get (t->tx_fib_index);

  if (t->name)
    vlib_cli_output (vm, "sr tunnel name: %s", (char *) t->name);

  vlib_cli_output (vm, "src %U dst %U first hop %U",
		   format_ip6_address, &t->key.src,
		   format_ip6_address, &t->key.dst,
		   format_ip6_address, &t->first_hop);
  vlib_cli_output (vm, "    rx-fib-id %d tx-fib-id %d",
		   rx_fib->table_id, tx_fib->table_id);
  vlib_cli_output (vm, "  sr: %U", format_ip6_sr_header, t->rewrite,
		   0 /* print_hmac */ );

  if (t->policy_index != ~0)
    {
      pt = pool_elt_at_index (sm->policies, t->policy_index);
      vlib_cli_output (vm, "sr policy: %s", (char *) pt->name);
    }
  vlib_cli_output (vm, "-------");

  return;
}

/**
 * @brief CLI Parser for Display Segment Routing tunnel
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
show_sr_tunnel_fn (vlib_main_t * vm,
		   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  static ip6_sr_tunnel_t **tunnels;
  ip6_sr_tunnel_t *t;
  ip6_sr_main_t *sm = &sr_main;
  int i;
  uword *p = 0;
  u8 *name = 0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "name %s", &name))
	{
	  p = hash_get_mem (sm->tunnel_index_by_name, name);
	  if (!p)
	    vlib_cli_output (vm, "No SR tunnel with name: %s. Showing all.",
			     name);
	}
      else
	break;
    }

  vec_reset_length (tunnels);

  if (!p)			/* Either name parm not passed or no tunnel with that name found, show all */
    {
      /* *INDENT-OFF* */
  pool_foreach (t, sm->tunnels,
  ({
    vec_add1 (tunnels, t);
  }));
  /* *INDENT-ON* */
    }
  else				/* Just show the one tunnel by name */
    vec_add1 (tunnels, &sm->tunnels[p[0]]);

  if (vec_len (tunnels) == 0)
    vlib_cli_output (vm, "No SR tunnels configured");

  for (i = 0; i < vec_len (tunnels); i++)
    {
      t = tunnels[i];
      ip6_sr_tunnel_display (vm, t);
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_sr_tunnel_command, static) = {
    .path = "show sr tunnel",
    .short_help = "show sr tunnel [name <sr-tunnel-name>]",
    .function = show_sr_tunnel_fn,
};
/* *INDENT-ON* */

/**
 * @brief Add or Delete a Segment Routing policy
 *
 * @param a ip6_sr_add_del_policy_args_t *
 *
 * @return retval int
 */
int
ip6_sr_add_del_policy (ip6_sr_add_del_policy_args_t * a)
{
  ip6_sr_main_t *sm = &sr_main;
  uword *p;
  ip6_sr_tunnel_t *t = 0;
  ip6_sr_policy_t *policy;
  u32 *tunnel_indices = 0;
  int i;



  if (a->is_del)
    {
      p = hash_get_mem (sm->policy_index_by_policy_name, a->name);
      if (!p)
	return -6;		/* policy name not found */

      policy = pool_elt_at_index (sm->policies, p[0]);

      vec_foreach_index (i, policy->tunnel_indices)
      {
	t = pool_elt_at_index (sm->tunnels, policy->tunnel_indices[i]);
	t->policy_index = ~0;
      }
      hash_unset_mem (sm->policy_index_by_policy_name, a->name);
      pool_put (sm->policies, policy);
      return 0;
    }


  if (!vec_len (a->tunnel_names))
    return -3;			/*tunnel name is required case */

  vec_reset_length (tunnel_indices);
  /* Check tunnel names, add tunnel_index to policy */
  for (i = 0; i < vec_len (a->tunnel_names); i++)
    {
      p = hash_get_mem (sm->tunnel_index_by_name, a->tunnel_names[i]);
      if (!p)
	return -4;		/* tunnel name not found case */

      t = pool_elt_at_index (sm->tunnels, p[0]);
      /*
         No need to check t==0. -3 condition above ensures name
       */
      if (t->policy_index != ~0)
	return -5;		/* tunnel name already associated with a policy */

      /* Add to tunnel indicies */
      vec_add1 (tunnel_indices, p[0]);
    }

  /* Add policy to ip6_sr_main_t */
  pool_get (sm->policies, policy);
  policy->name = a->name;
  policy->tunnel_indices = tunnel_indices;
  hash_set_mem (sm->policy_index_by_policy_name, policy->name,
		policy - sm->policies);

  /* Yes, this could be construed as overkill but the last thing you should do is set
     the policy_index on the tunnel after everything is set in ip6_sr_main_t.
     If this is deemed overly cautious, could set this in the vec_len(tunnel_names) loop.
   */
  for (i = 0; i < vec_len (policy->tunnel_indices); i++)
    {
      t = pool_elt_at_index (sm->tunnels, policy->tunnel_indices[i]);
      t->policy_index = policy - sm->policies;
    }

  return 0;
}

/**
 * @brief CLI Parser for Add or Delete a Segment Routing policy
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
sr_add_del_policy_command_fn (vlib_main_t * vm,
			      unformat_input_t * input,
			      vlib_cli_command_t * cmd)
{
  int is_del = 0;
  u8 **tunnel_names = 0;
  u8 *tunnel_name = 0;
  u8 *name = 0;
  ip6_sr_add_del_policy_args_t _a, *a = &_a;
  int rv;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "del"))
	is_del = 1;
      else if (unformat (input, "name %s", &name))
	;
      else if (unformat (input, "tunnel %s", &tunnel_name))
	{
	  if (tunnel_name)
	    {
	      vec_add1 (tunnel_names, tunnel_name);
	      tunnel_name = 0;
	    }
	}
      else
	break;
    }

  if (!name)
    return clib_error_return (0, "name of SR policy required");


  memset (a, 0, sizeof (*a));

  a->is_del = is_del;
  a->name = name;
  a->tunnel_names = tunnel_names;

  rv = ip6_sr_add_del_policy (a);

  vec_free (tunnel_names);

  switch (rv)
    {
    case 0:
      break;

    case -3:
      return clib_error_return (0,
				"tunnel name to associate to SR policy is required");

    case -4:
      return clib_error_return (0, "tunnel name not found");

    case -5:
      return clib_error_return (0, "tunnel already associated with policy");

    case -6:
      return clib_error_return (0, "policy name %s not found", name);

    case -7:
      return clib_error_return (0, "TODO: deleting policy name %s", name);

    default:
      return clib_error_return (0, "BUG: ip6_sr_add_del_policy returns %d",
				rv);

    }
  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (sr_policy_command, static) = {
    .path = "sr policy",
    .short_help =
    "sr policy [del] name <policy-name> tunnel <sr-tunnel-name> [tunnel <sr-tunnel-name>]*",
    .function = sr_add_del_policy_command_fn,
};
/* *INDENT-ON* */

/**
 * @brief CLI Parser for Displaying Segment Routing policy
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
show_sr_policy_fn (vlib_main_t * vm,
		   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  static ip6_sr_policy_t **policies;
  ip6_sr_policy_t *policy;
  ip6_sr_tunnel_t *t;
  ip6_sr_main_t *sm = &sr_main;
  int i, j;
  uword *p = 0;
  u8 *name = 0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "name %s", &name))
	{
	  p = hash_get_mem (sm->policy_index_by_policy_name, name);
	  if (!p)
	    vlib_cli_output (vm,
			     "policy with name %s not found. Showing all.",
			     name);
	}
      else
	break;
    }

  vec_reset_length (policies);

  if (!p)			/* Either name parm not passed or no policy with that name found, show all */
    {
      /* *INDENT-OFF* */
  pool_foreach (policy, sm->policies,
  ({
    vec_add1 (policies, policy);
  }));
  /* *INDENT-ON* */
    }
  else				/* Just show the one policy by name and a summary of tunnel names */
    {
      policy = pool_elt_at_index (sm->policies, p[0]);
      vec_add1 (policies, policy);
    }

  if (vec_len (policies) == 0)
    vlib_cli_output (vm, "No SR policies configured");

  for (i = 0; i < vec_len (policies); i++)
    {
      policy = policies[i];

      if (policy->name)
	vlib_cli_output (vm, "SR policy name: %s", (char *) policy->name);
      for (j = 0; j < vec_len (policy->tunnel_indices); j++)
	{
	  t = pool_elt_at_index (sm->tunnels, policy->tunnel_indices[j]);
	  ip6_sr_tunnel_display (vm, t);
	}
    }

  return 0;

}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_sr_policy_command, static) = {
    .path = "show sr policy",
    .short_help = "show sr policy [name <sr-policy-name>]",
    .function = show_sr_policy_fn,
};
/* *INDENT-ON* */

/**
 * @brief Add or Delete a mapping of IP6 multicast address
 * to Segment Routing policy.
 *
 * @param a ip6_sr_add_del_multicastmap_args_t *
 *
 * @return retval int
 */
int
ip6_sr_add_del_multicastmap (ip6_sr_add_del_multicastmap_args_t * a)
{
  uword *p;
  ip6_sr_tunnel_t *t;
  ip6_sr_main_t *sm = &sr_main;
  ip6_sr_policy_t *pt;

  if (a->is_del)
    {
      /* clean up the adjacency */
      p =
	hash_get_mem (sm->policy_index_by_multicast_address,
		      a->multicast_address);
    }
  else
    {
      /* Get our policy by policy_name */
      p = hash_get_mem (sm->policy_index_by_policy_name, a->policy_name);

    }
  if (!p)
    return -1;

  pt = pool_elt_at_index (sm->policies, p[0]);

  /*
     Get the first tunnel associated with policy populate the fib adjacency.
     From there, since this tunnel will have it's policy_index != ~0 it will
     be the trigger in the dual_loop to pull up the policy and make a copy-rewrite
     for each tunnel in the policy
   */

  t = pool_elt_at_index (sm->tunnels, pt->tunnel_indices[0]);

  /*
   * Stick the tunnel index into the rewrite header.
   *
   * Unfortunately, inserting an SR header according to the various
   * RFC's requires parsing through the ip6 header, perhaps consing a
   * buffer onto the head of the vlib_buffer_t, etc. We don't use the
   * normal reverse bcopy rewrite code.
   *
   * We don't handle ugly RFC-related cases yet, but I'm sure PL will complain
   * at some point...
   */
  dpo_id_t dpo = DPO_INVALID;

  dpo_set (&dpo, sr_dpo_type, DPO_PROTO_IP6, t - sm->tunnels);

  /* Construct a FIB entry for multicast using the rx/tx fib from the first tunnel */
  fib_prefix_t pfx = {
    .fp_proto = FIB_PROTOCOL_IP6,
    .fp_len = 128,
    .fp_addr = {
		.ip6 = *a->multicast_address,
		}
  };
  fib_table_entry_special_dpo_add (t->rx_fib_index,
				   &pfx,
				   FIB_SOURCE_SR,
				   FIB_ENTRY_FLAG_EXCLUSIVE, &dpo);
  dpo_reset (&dpo);

  u8 *mcast_copy = 0;
  mcast_copy = vec_new (ip6_address_t, 1);
  memcpy (mcast_copy, a->multicast_address, sizeof (ip6_address_t));

  if (a->is_del)
    {
      hash_unset_mem (sm->policy_index_by_multicast_address, mcast_copy);
      vec_free (mcast_copy);
      return 0;
    }
  /* else */

  hash_set_mem (sm->policy_index_by_multicast_address, mcast_copy,
		pt - sm->policies);


  return 0;
}

/**
 * @brief CLI Parser for Adding or Delete a mapping of IP6 multicast address
 * to Segment Routing policy.
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
sr_add_del_multicast_map_command_fn (vlib_main_t * vm,
				     unformat_input_t * input,
				     vlib_cli_command_t * cmd)
{
  int is_del = 0;
  ip6_address_t multicast_address;
  u8 *policy_name = 0;
  int multicast_address_set = 0;
  ip6_sr_add_del_multicastmap_args_t _a, *a = &_a;
  int rv;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "del"))
	is_del = 1;
      else
	if (unformat
	    (input, "address %U", unformat_ip6_address, &multicast_address))
	multicast_address_set = 1;
      else if (unformat (input, "sr-policy %s", &policy_name))
	;
      else
	break;
    }

  if (!is_del && !policy_name)
    return clib_error_return (0, "name of sr policy required");

  if (!multicast_address_set)
    return clib_error_return (0, "multicast address required");

  memset (a, 0, sizeof (*a));

  a->is_del = is_del;
  a->multicast_address = &multicast_address;
  a->policy_name = policy_name;

#if DPDK > 0			/*Cannot call replicate or configure multicast map yet without DPDK */
  rv = ip6_sr_add_del_multicastmap (a);
#else
  return clib_error_return (0,
			    "cannot use multicast replicate spray case without DPDK installed");
#endif /* DPDK */

  switch (rv)
    {
    case 0:
      break;
    case -1:
      return clib_error_return (0, "no policy with name: %s", policy_name);

    case -2:
      return clib_error_return (0, "multicast map someting ");

    case -3:
      return clib_error_return (0,
				"tunnel name to associate to SR policy is required");

    case -7:
      return clib_error_return (0, "TODO: deleting policy name %s",
				policy_name);

    default:
      return clib_error_return (0, "BUG: ip6_sr_add_del_policy returns %d",
				rv);

    }
  return 0;

}


/* *INDENT-OFF* */
VLIB_CLI_COMMAND (sr_multicast_map_command, static) = {
    .path = "sr multicast-map",
    .short_help =
    "sr multicast-map address <multicast-ip6-address> sr-policy <sr-policy-name> [del]",
    .function = sr_add_del_multicast_map_command_fn,
};
/* *INDENT-ON* */

/**
 * @brief CLI Parser for Displaying a mapping of IP6 multicast address
 * to Segment Routing policy.
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
show_sr_multicast_map_fn (vlib_main_t * vm,
			  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip6_sr_main_t *sm = &sr_main;
  u8 *key = 0;
  u32 value;
  ip6_address_t multicast_address;
  ip6_sr_policy_t *pt;

  /* pull all entries from the hash table into vector for display */

  /* *INDENT-OFF* */
  hash_foreach_mem (key, value, sm->policy_index_by_multicast_address,
  ({
    if (!key)
	vlib_cli_output (vm, "no multicast maps configured");
    else
      {
	multicast_address = *((ip6_address_t *)key);
	pt = pool_elt_at_index (sm->policies, value);
	if (pt)
	  {
	    vlib_cli_output (vm, "address: %U policy: %s",
			     format_ip6_address, &multicast_address,
			     pt->name);
	  }
	else
	  vlib_cli_output (vm, "BUG: policy not found for address: %U with policy index %d",
			     format_ip6_address, &multicast_address,
			     value);

      }

  }));
  /* *INDENT-ON* */

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_sr_multicast_map_command, static) = {
    .path = "show sr multicast-map",
    .short_help = "show sr multicast-map",
    .function = show_sr_multicast_map_fn,
};
/* *INDENT-ON* */


#define foreach_sr_fix_dst_addr_next            \
_(DROP, "error-drop")

/**
 * @brief Struct for valid next-nodes for SR fix destination address node
 */
typedef enum
{
#define _(s,n) SR_FIX_DST_ADDR_NEXT_##s,
  foreach_sr_fix_dst_addr_next
#undef _
    SR_FIX_DST_ADDR_N_NEXT,
} sr_fix_dst_addr_next_t;

/**
 * @brief Error strings for SR Fix Destination rewrite
 */
static char *sr_fix_dst_error_strings[] = {
#define sr_fix_dst_error(n,s) s,
#include "sr_fix_dst_error.def"
#undef sr_fix_dst_error
};

/**
 * @brief Struct for errors for SR Fix Destination rewrite
 */
typedef enum
{
#define sr_fix_dst_error(n,s) SR_FIX_DST_ERROR_##n,
#include "sr_fix_dst_error.def"
#undef sr_fix_dst_error
  SR_FIX_DST_N_ERROR,
} sr_fix_dst_error_t;

/**
 * @brief Information for fix address trace
 */
typedef struct
{
  ip6_address_t src, dst;
  u32 next_index;
  u32 adj_index;
  u8 sr[256];
} sr_fix_addr_trace_t;

/**
 * @brief Formatter for fix address trace
 */
u8 *
format_sr_fix_addr_trace (u8 * s, va_list * args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
  sr_fix_addr_trace_t *t = va_arg (*args, sr_fix_addr_trace_t *);
  vnet_hw_interface_t *hi = 0;
  ip_adjacency_t *adj;
  ip6_main_t *im = &ip6_main;
  ip_lookup_main_t *lm = &im->lookup_main;
  vnet_main_t *vnm = vnet_get_main ();

  if (t->adj_index != ~0)
    {
      adj = ip_get_adjacency (lm, t->adj_index);
      hi = vnet_get_sup_hw_interface (vnm, adj->rewrite_header.sw_if_index);
    }

  s = format (s, "SR-FIX_ADDR: next %s ip6 src %U dst %U\n",
	      (t->next_index == SR_FIX_DST_ADDR_NEXT_DROP)
	      ? "drop" : "output",
	      format_ip6_address, &t->src, format_ip6_address, &t->dst);
  if (t->next_index != SR_FIX_DST_ADDR_NEXT_DROP)
    {
      s =
	format (s, "%U\n", format_ip6_sr_header, t->sr, 1 /* print_hmac */ );
      s =
	format (s, "   output via %s",
		hi ? (char *) (hi->name) : "Invalid adj");
    }
  return s;
}

/**
 * @brief Fix SR destination address - dual-loop
 *
 * @node sr-fix-dst-addr
 * @param vm vlib_main_t *
 * @param node vlib_node_runtime_t *
 * @param from_frame vlib_frame_t *
 *
 * @return from_frame->n_vectors uword
 */
static uword
sr_fix_dst_addr (vlib_main_t * vm,
		 vlib_node_runtime_t * node, vlib_frame_t * from_frame)
{
  u32 n_left_from, next_index, *from, *to_next;
  ip6_main_t *im = &ip6_main;
  ip_lookup_main_t *lm = &im->lookup_main;

  from = vlib_frame_vector_args (from_frame);
  n_left_from = from_frame->n_vectors;

  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

#if 0
      while (0 && n_left_from >= 4 && n_left_to_next >= 2)
	{
	  u32 bi0, bi1;
	  __attribute__ ((unused)) vlib_buffer_t *b0, *b1;
	  u32 next0 = SR_FIX_DST_ADDR_NEXT_DROP;
	  u32 next1 = SR_FIX_DST_ADDR_NEXT_DROP;

	  /* Prefetch next iteration. */
	  {
	    vlib_buffer_t *p2, *p3;

	    p2 = vlib_get_buffer (vm, from[2]);
	    p3 = vlib_get_buffer (vm, from[3]);

	    vlib_prefetch_buffer_header (p2, LOAD);
	    vlib_prefetch_buffer_header (p3, LOAD);
	  }

	  bi0 = from[0];
	  bi1 = from[1];
	  to_next[0] = bi0;
	  to_next[1] = bi1;
	  from += 2;
	  to_next += 2;
	  n_left_to_next -= 2;
	  n_left_from -= 2;

	  b0 = vlib_get_buffer (vm, bi0);
	  b1 = vlib_get_buffer (vm, bi1);


	  vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, bi1, next0, next1);
	}
#endif

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  u32 bi0;
	  vlib_buffer_t *b0;
	  ip6_header_t *ip0;
	  ip_adjacency_t *adj0;
	  ip6_sr_header_t *sr0;
	  u32 next0 = SR_FIX_DST_ADDR_NEXT_DROP;
	  ip6_address_t *new_dst0;
	  ethernet_header_t *eh0;

	  bi0 = from[0];
	  to_next[0] = bi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  b0 = vlib_get_buffer (vm, bi0);

	  adj0 =
	    ip_get_adjacency (lm, vnet_buffer (b0)->ip.adj_index[VLIB_TX]);
	  next0 = adj0->mcast_group_index;

	  /* We should be pointing at an Ethernet header... */
	  eh0 = vlib_buffer_get_current (b0);
	  ip0 = (ip6_header_t *) (eh0 + 1);
	  sr0 = (ip6_sr_header_t *) (ip0 + 1);

	  /* We'd better find an SR header... */
	  if (PREDICT_FALSE (ip0->protocol != IPPROTO_IPV6_ROUTE))
	    {
	      b0->error = node->errors[SR_FIX_DST_ERROR_NO_SR_HEADER];
	      goto do_trace0;
	    }
	  else
	    {
	      /*
	       * We get here from sr_rewrite or sr_local, with
	       * sr->segments_left pointing at the (copy of the original) dst
	       * address. Use it, then increment sr0->segments_left.
	       */

	      /* Out of segments? Turf the packet */
	      if (PREDICT_FALSE (sr0->segments_left == 0))
		{
		  b0->error = node->errors[SR_FIX_DST_ERROR_NO_MORE_SEGMENTS];
		  goto do_trace0;
		}

	      /*
	       * Rewrite the packet with the original dst address
	       * We assume that the last segment (in processing order) contains
	       * the original dst address. The list is reversed, so sr0->segments
	       * contains the original dst address.
	       */
	      new_dst0 = sr0->segments;
	      ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
	      ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];
	    }

	do_trace0:

	  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_fix_addr_trace_t *t = vlib_add_trace (vm, node,
						       b0, sizeof (*t));
	      t->next_index = next0;
	      t->adj_index = ~0;

	      if (next0 != SR_FIX_DST_ADDR_NEXT_DROP)
		{
		  t->adj_index = vnet_buffer (b0)->ip.adj_index[VLIB_TX];
		  clib_memcpy (t->src.as_u8, ip0->src_address.as_u8,
			       sizeof (t->src.as_u8));
		  clib_memcpy (t->dst.as_u8, ip0->dst_address.as_u8,
			       sizeof (t->dst.as_u8));
		  clib_memcpy (t->sr, sr0, sizeof (t->sr));
		}
	    }

	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, next0);
	}

      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }
  return from_frame->n_vectors;
}


/* *INDENT-OFF* */
VLIB_REGISTER_NODE (sr_fix_dst_addr_node) = {
  .function = sr_fix_dst_addr,
  .name = "sr-fix-dst-addr",
  /* Takes a vector of packets. */
  .vector_size = sizeof (u32),
  .format_trace = format_sr_fix_addr_trace,
  .format_buffer = format_ip6_sr_header_with_length,

  .runtime_data_bytes = 0,

  .n_errors = SR_FIX_DST_N_ERROR,
  .error_strings = sr_fix_dst_error_strings,

  .n_next_nodes = SR_FIX_DST_ADDR_N_NEXT,
  .next_nodes = {
#define _(s,n) [SR_FIX_DST_ADDR_NEXT_##s] = n,
    foreach_sr_fix_dst_addr_next
#undef _
  },
};

VLIB_NODE_FUNCTION_MULTIARCH (sr_fix_dst_addr_node, sr_fix_dst_addr)
/* *INDENT-ON* */

static clib_error_t *
sr_init (vlib_main_t * vm)
{
  ip6_sr_main_t *sm = &sr_main;
  clib_error_t *error = 0;
  vlib_node_t *ip6_lookup_node, *ip6_rewrite_node;

  if ((error = vlib_call_init_function (vm, ip_main_init)))
    return error;

  if ((error = vlib_call_init_function (vm, ip6_lookup_init)))
    return error;

  sm->vlib_main = vm;
  sm->vnet_main = vnet_get_main ();

  vec_validate (sm->hmac_keys, 0);
  sm->hmac_keys[0].shared_secret = (u8 *) 0xdeadbeef;

  sm->tunnel_index_by_key =
    hash_create_mem (0, sizeof (ip6_sr_tunnel_key_t), sizeof (uword));

  sm->tunnel_index_by_name = hash_create_string (0, sizeof (uword));

  sm->policy_index_by_policy_name = hash_create_string (0, sizeof (uword));

  sm->policy_index_by_multicast_address =
    hash_create_mem (0, sizeof (ip6_address_t), sizeof (uword));

  sm->hmac_key_by_shared_secret = hash_create_string (0, sizeof (uword));

  ip6_register_protocol (IPPROTO_IPV6_ROUTE, sr_local_node.index);

  ip6_lookup_node = vlib_get_node_by_name (vm, (u8 *) "ip6-lookup");
  ASSERT (ip6_lookup_node);

  ip6_rewrite_node = vlib_get_node_by_name (vm, (u8 *) "ip6-rewrite");
  ASSERT (ip6_rewrite_node);

#if DPDK > 0			/* Cannot run replicate without DPDK */
  /* Add a disposition to sr_replicate for the sr multicast replicate node */
  sm->ip6_lookup_sr_replicate_index =
    vlib_node_add_next (vm, ip6_lookup_node->index, sr_replicate_node.index);
#endif /* DPDK */

  /* Add a disposition to ip6_rewrite for the sr dst address hack node */
  sm->ip6_rewrite_sr_next_index =
    vlib_node_add_next (vm, ip6_rewrite_node->index,
			sr_fix_dst_addr_node.index);

  OpenSSL_add_all_digests ();

  sm->md = (void *) EVP_get_digestbyname ("sha1");
  sm->hmac_ctx = clib_mem_alloc (sizeof (HMAC_CTX));

  sr_dpo_type = dpo_register_new_type (&sr_vft, sr_nodes);

  return error;
}

VLIB_INIT_FUNCTION (sr_init);

/**
 * @brief Definition of next-nodes for SR local
 */
#define foreach_sr_local_next                   \
  _ (ERROR, "error-drop")                       \
  _ (IP6_LOOKUP, "ip6-lookup")

/**
 * @brief Struct for definition of next-nodes for SR local
 */
typedef enum
{
#define _(s,n) SR_LOCAL_NEXT_##s,
  foreach_sr_local_next
#undef _
    SR_LOCAL_N_NEXT,
} sr_local_next_t;

/**
 * @brief Struct for packet trace of SR local
 */
typedef struct
{
  u8 next_index;
  u8 sr_valid;
  ip6_address_t src, dst;
  u16 length;
  u8 sr[256];
} sr_local_trace_t;

/**
 * @brief Definition of SR local error-strings
 */
static char *sr_local_error_strings[] = {
#define sr_error(n,s) s,
#include "sr_error.def"
#undef sr_error
};

/**
 * @brief Struct for definition of SR local error-strings
 */
typedef enum
{
#define sr_error(n,s) SR_LOCAL_ERROR_##n,
#include "sr_error.def"
#undef sr_error
  SR_LOCAL_N_ERROR,
} sr_local_error_t;

/**
 * @brief Format SR local trace
 *
 * @param s u8 *
 * @param args va_list *
 *
 * @return s u8 *
 */
u8 *
format_sr_local_trace (u8 * s, va_list * args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
  sr_local_trace_t *t = va_arg (*args, sr_local_trace_t *);

  s = format (s, "SR-LOCAL: src %U dst %U len %u next_index %d",
	      format_ip6_address, &t->src,
	      format_ip6_address, &t->dst, t->length, t->next_index);
  if (t->sr_valid)
    s =
      format (s, "\n  %U", format_ip6_sr_header, t->sr, 1 /* print_hmac */ );
  else
    s = format (s, "\n  popped SR header");

  return s;
}


/* $$$$ fixme: smp, don't copy data, cache input, output (maybe) */
/**
 * @brief Validate the SR HMAC
 *
 * @param sm ip6_sr_main_t *
 * @param ip ip6_header_t *
 * @param sr ip6_sr_header_t *
 *
 * @return retval int
 */
static int
sr_validate_hmac (ip6_sr_main_t * sm, ip6_header_t * ip, ip6_sr_header_t * sr)
{
  u32 key_index;
  static u8 *keybuf;
  u8 *copy_target;
  int first_segment;
  ip6_address_t *addrp;
  int i;
  ip6_sr_hmac_key_t *hmac_key;
  static u8 *signature;
  u32 sig_len;

  key_index = sr->hmac_key;

  /* No signature? Pass... */
  if (key_index == 0)
    return 0;

  /* We don't know about this key? Fail... */
  if (key_index >= vec_len (sm->hmac_keys))
    return 1;

  vec_validate (signature, SHA256_DIGEST_LENGTH - 1);

  hmac_key = sm->hmac_keys + key_index;

  vec_reset_length (keybuf);

  /* pkt ip6 src address */
  vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
  clib_memcpy (copy_target, ip->src_address.as_u8, sizeof (ip6_address_t));

  /* last segment */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0] = sr->first_segment;

  /* octet w/ bit 0 = "clean" flag */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0]
    = (sr->flags & clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP))
    ? 0x80 : 0;

  /* hmac key id */
  vec_add2 (keybuf, copy_target, 1);
  copy_target[0] = sr->hmac_key;

  first_segment = sr->first_segment;

  addrp = sr->segments;

  /* segments */
  for (i = 0; i <= first_segment; i++)
    {
      vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
      clib_memcpy (copy_target, addrp->as_u8, sizeof (ip6_address_t));
      addrp++;
    }

  if (sm->is_debug)
    clib_warning ("verify key index %d keybuf: %U", key_index,
		  format_hex_bytes, keybuf, vec_len (keybuf));

  /* shared secret */

  /* SHA1 is shorter than SHA-256 */
  memset (signature, 0, vec_len (signature));

  HMAC_CTX_init (sm->hmac_ctx);
  if (!HMAC_Init (sm->hmac_ctx, hmac_key->shared_secret,
		  vec_len (hmac_key->shared_secret), sm->md))
    clib_warning ("barf1");
  if (!HMAC_Update (sm->hmac_ctx, keybuf, vec_len (keybuf)))
    clib_warning ("barf2");
  if (!HMAC_Final (sm->hmac_ctx, signature, &sig_len))
    clib_warning ("barf3");
  HMAC_CTX_cleanup (sm->hmac_ctx);

  if (sm->is_debug)
    clib_warning ("computed signature len %d, value %U", sig_len,
		  format_hex_bytes, signature, vec_len (signature));

  /* Point at the SHA signature in the packet */
  addrp++;
  if (sm->is_debug)
    clib_warning ("read signature %U", format_hex_bytes, addrp,
		  SHA256_DIGEST_LENGTH);

  return memcmp (signature, addrp, SHA256_DIGEST_LENGTH);
}

/**
 * @brief SR local node
 * @node sr-local
 *
 * @param vm vlib_main_t *
 * @param node vlib_node_runtime_t *
 * @param from_frame vlib_frame_t *
 *
 * @return from_frame->n_vectors uword
 */
static uword
sr_local (vlib_main_t * vm,
	  vlib_node_runtime_t * node, vlib_frame_t * from_frame)
{
  u32 n_left_from, next_index, *from, *to_next;
  ip6_sr_main_t *sm = &sr_main;
  u32 (*sr_local_cb) (vlib_main_t *, vlib_node_runtime_t *,
		      vlib_buffer_t *, ip6_header_t *, ip6_sr_header_t *);
  sr_local_cb = sm->sr_local_cb;

  from = vlib_frame_vector_args (from_frame);
  n_left_from = from_frame->n_vectors;

  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

      while (n_left_from >= 4 && n_left_to_next >= 2)
	{
	  u32 bi0, bi1;
	  vlib_buffer_t *b0, *b1;
	  ip6_header_t *ip0, *ip1;
	  ip6_sr_header_t *sr0, *sr1;
	  ip6_address_t *new_dst0, *new_dst1;
	  u32 next0 = SR_LOCAL_NEXT_IP6_LOOKUP;
	  u32 next1 = SR_LOCAL_NEXT_IP6_LOOKUP;

	  /* Prefetch next iteration. */
	  {
	    vlib_buffer_t *p2, *p3;

	    p2 = vlib_get_buffer (vm, from[2]);
	    p3 = vlib_get_buffer (vm, from[3]);

	    vlib_prefetch_buffer_header (p2, LOAD);
	    vlib_prefetch_buffer_header (p3, LOAD);

	    CLIB_PREFETCH (p2->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);
	    CLIB_PREFETCH (p3->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);
	  }

	  bi0 = from[0];
	  bi1 = from[1];
	  to_next[0] = bi0;
	  to_next[1] = bi1;
	  from += 2;
	  to_next += 2;
	  n_left_to_next -= 2;
	  n_left_from -= 2;


	  b0 = vlib_get_buffer (vm, bi0);
	  ip0 = vlib_buffer_get_current (b0);
	  sr0 = (ip6_sr_header_t *) (ip0 + 1);
	  if (PREDICT_FALSE
	      (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
	    {
	      ip6_hop_by_hop_ext_t *ext_hdr =
		(ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
	      sr0 =
		(ip6_sr_header_t *) ip6_ext_next_header ((ip6_ext_header_t *)
							 ext_hdr);
	    }

	  if (PREDICT_FALSE (sr0->type != ROUTING_HEADER_TYPE_SR))
	    {
	      next0 = SR_LOCAL_NEXT_ERROR;
	      b0->error =
		node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
	      goto do_trace0;
	    }

	  /* Out of segments? Turf the packet */
	  if (PREDICT_FALSE (sr0->segments_left == 0))
	    {
	      next0 = SR_LOCAL_NEXT_ERROR;
	      b0->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
	      goto do_trace0;
	    }

	  if (PREDICT_FALSE (sm->validate_hmac))
	    {
	      if (sr_validate_hmac (sm, ip0, sr0))
		{
		  next0 = SR_LOCAL_NEXT_ERROR;
		  b0->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
		  goto do_trace0;
		}
	    }

	  next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) : next0;

	  /*
	   * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx
	   */
	  if (PREDICT_FALSE (next0 & 0x80000000))
	    {
	      next0 ^= 0xFFFFFFFF;
	      if (PREDICT_FALSE (next0 == SR_LOCAL_NEXT_ERROR))
		b0->error = node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
	    }
	  else
	    {
	      u32 segment_index0;

	      segment_index0 = sr0->segments_left - 1;

	      /* Rewrite the packet */
	      new_dst0 = (ip6_address_t *) (sr0->segments + segment_index0);
	      ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
	      ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];

	      if (PREDICT_TRUE (sr0->segments_left > 0))
		sr0->segments_left -= 1;
	    }

	  /* End of the path. Clean up the SR header, or not */
	  if (PREDICT_FALSE
	      (sr0->segments_left == 0 &&
	       (sr0->flags &
		clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP))))
	    {
	      u64 *copy_dst0, *copy_src0;
	      u16 new_l0;
	      u32 copy_len_u64s0 = 0;
	      int i;

	      /*
	       * Copy the ip6 header right by the (real) length of the
	       * sr header.
	       */
	      if (PREDICT_FALSE
		  (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
		  copy_len_u64s0 =
		    (((ip6_ext_header_t *) ext_hdr)->n_data_u64s) + 1;
		  ext_hdr->next_hdr = sr0->protocol;
		}
	      else
		{
		  ip0->protocol = sr0->protocol;
		}
	      vlib_buffer_advance (b0, (sr0->length + 1) * 8);

	      new_l0 = clib_net_to_host_u16 (ip0->payload_length) -
		(sr0->length + 1) * 8;
	      ip0->payload_length = clib_host_to_net_u16 (new_l0);

	      copy_src0 = (u64 *) ip0;
	      copy_dst0 = copy_src0 + (sr0->length + 1);

	      copy_dst0[4 + copy_len_u64s0] = copy_src0[4 + copy_len_u64s0];
	      copy_dst0[3 + copy_len_u64s0] = copy_src0[3 + copy_len_u64s0];
	      copy_dst0[2 + copy_len_u64s0] = copy_src0[2 + copy_len_u64s0];
	      copy_dst0[1 + copy_len_u64s0] = copy_src0[1 + copy_len_u64s0];
	      copy_dst0[0 + copy_len_u64s0] = copy_src0[0 + copy_len_u64s0];

	      for (i = copy_len_u64s0 - 1; i >= 0; i--)
		{
		  copy_dst0[i] = copy_src0[i];
		}

	      sr0 = 0;
	    }

	do_trace0:
	  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_local_trace_t *tr = vlib_add_trace (vm, node,
						     b0, sizeof (*tr));
	      clib_memcpy (tr->src.as_u8, ip0->src_address.as_u8,
			   sizeof (tr->src.as_u8));
	      clib_memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
			   sizeof (tr->dst.as_u8));
	      tr->length = vlib_buffer_length_in_chain (vm, b0);
	      tr->next_index = next0;
	      tr->sr_valid = sr0 != 0;
	      if (tr->sr_valid)
		clib_memcpy (tr->sr, sr0, sizeof (tr->sr));
	    }

	  b1 = vlib_get_buffer (vm, bi1);
	  ip1 = vlib_buffer_get_current (b1);
	  sr1 = (ip6_sr_header_t *) (ip1 + 1);
	  if (PREDICT_FALSE
	      (ip1->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
	    {

	      ip6_hop_by_hop_ext_t *ext_hdr =
		(ip6_hop_by_hop_ext_t *) ip6_next_header (ip1);
	      sr1 =
		(ip6_sr_header_t *) ip6_ext_next_header ((ip6_ext_header_t *)
							 ext_hdr);
	    }

	  if (PREDICT_FALSE (sr1->type != ROUTING_HEADER_TYPE_SR))
	    {
	      next1 = SR_LOCAL_NEXT_ERROR;
	      b1->error =
		node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
	      goto do_trace1;
	    }

	  /* Out of segments? Turf the packet */
	  if (PREDICT_FALSE (sr1->segments_left == 0))
	    {
	      next1 = SR_LOCAL_NEXT_ERROR;
	      b1->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
	      goto do_trace1;
	    }

	  if (PREDICT_FALSE (sm->validate_hmac))
	    {
	      if (sr_validate_hmac (sm, ip1, sr1))
		{
		  next1 = SR_LOCAL_NEXT_ERROR;
		  b1->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
		  goto do_trace1;
		}
	    }

	  next1 = sr_local_cb ? sr_local_cb (vm, node, b1, ip1, sr1) : next1;

	  /*
	   * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx
	   */
	  if (PREDICT_FALSE (next1 & 0x80000000))
	    {
	      next1 ^= 0xFFFFFFFF;
	      if (PREDICT_FALSE (next1 == SR_LOCAL_NEXT_ERROR))
		b1->error = node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
	    }
	  else
	    {
	      u32 segment_index1;

	      segment_index1 = sr1->segments_left - 1;

	      /* Rewrite the packet */
	      new_dst1 = (ip6_address_t *) (sr1->segments + segment_index1);
	      ip1->dst_address.as_u64[0] = new_dst1->as_u64[0];
	      ip1->dst_address.as_u64[1] = new_dst1->as_u64[1];

	      if (PREDICT_TRUE (sr1->segments_left > 0))
		sr1->segments_left -= 1;
	    }

	  /* End of the path. Clean up the SR header, or not */
	  if (PREDICT_FALSE
	      (sr1->segments_left == 0 &&
	       (sr1->flags &
		clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP))))
	    {
	      u64 *copy_dst1, *copy_src1;
	      u16 new_l1;
	      u32 copy_len_u64s1 = 0;
	      int i;

	      /*
	       * Copy the ip6 header right by the (real) length of the
	       * sr header.
	       */
	      if (PREDICT_FALSE
		  (ip1->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip1);
		  copy_len_u64s1 =
		    (((ip6_ext_header_t *) ext_hdr)->n_data_u64s) + 1;
		  ext_hdr->next_hdr = sr1->protocol;
		}
	      else
		{
		  ip1->protocol = sr1->protocol;
		}
	      vlib_buffer_advance (b1, (sr1->length + 1) * 8);

	      new_l1 = clib_net_to_host_u16 (ip1->payload_length) -
		(sr1->length + 1) * 8;
	      ip1->payload_length = clib_host_to_net_u16 (new_l1);

	      copy_src1 = (u64 *) ip1;
	      copy_dst1 = copy_src1 + (sr1->length + 1);

	      copy_dst1[4 + copy_len_u64s1] = copy_src1[4 + copy_len_u64s1];
	      copy_dst1[3 + copy_len_u64s1] = copy_src1[3 + copy_len_u64s1];
	      copy_dst1[2 + copy_len_u64s1] = copy_src1[2 + copy_len_u64s1];
	      copy_dst1[1 + copy_len_u64s1] = copy_src1[1 + copy_len_u64s1];
	      copy_dst1[0 + copy_len_u64s1] = copy_src1[0 + copy_len_u64s1];

	      for (i = copy_len_u64s1 - 1; i >= 0; i--)
		{
		  copy_dst1[i] = copy_src1[i];
		}

	      sr1 = 0;
	    }

	do_trace1:
	  if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_local_trace_t *tr = vlib_add_trace (vm, node,
						     b1, sizeof (*tr));
	      clib_memcpy (tr->src.as_u8, ip1->src_address.as_u8,
			   sizeof (tr->src.as_u8));
	      clib_memcpy (tr->dst.as_u8, ip1->dst_address.as_u8,
			   sizeof (tr->dst.as_u8));
	      tr->length = vlib_buffer_length_in_chain (vm, b1);
	      tr->next_index = next1;
	      tr->sr_valid = sr1 != 0;
	      if (tr->sr_valid)
		clib_memcpy (tr->sr, sr1, sizeof (tr->sr));
	    }

	  vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, bi1, next0, next1);
	}

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  u32 bi0;
	  vlib_buffer_t *b0;
	  ip6_header_t *ip0 = 0;
	  ip6_sr_header_t *sr0;
	  ip6_address_t *new_dst0;
	  u32 next0 = SR_LOCAL_NEXT_IP6_LOOKUP;

	  bi0 = from[0];
	  to_next[0] = bi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  b0 = vlib_get_buffer (vm, bi0);
	  ip0 = vlib_buffer_get_current (b0);
	  sr0 = (ip6_sr_header_t *) (ip0 + 1);

	  if (PREDICT_FALSE
	      (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
	    {
	      ip6_hop_by_hop_ext_t *ext_hdr =
		(ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
	      sr0 =
		(ip6_sr_header_t *) ip6_ext_next_header ((ip6_ext_header_t *)
							 ext_hdr);
	    }
	  if (PREDICT_FALSE (sr0->type != ROUTING_HEADER_TYPE_SR))
	    {
	      next0 = SR_LOCAL_NEXT_ERROR;
	      b0->error =
		node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
	      goto do_trace;
	    }

	  /* Out of segments? Turf the packet */
	  if (PREDICT_FALSE (sr0->segments_left == 0))
	    {
	      next0 = SR_LOCAL_NEXT_ERROR;
	      b0->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
	      goto do_trace;
	    }

	  if (PREDICT_FALSE (sm->validate_hmac))
	    {
	      if (sr_validate_hmac (sm, ip0, sr0))
		{
		  next0 = SR_LOCAL_NEXT_ERROR;
		  b0->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
		  goto do_trace;
		}
	    }

	  next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) : next0;

	  /*
	   * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx
	   */
	  if (PREDICT_FALSE (next0 & 0x80000000))
	    {
	      next0 ^= 0xFFFFFFFF;
	      if (PREDICT_FALSE (next0 == SR_LOCAL_NEXT_ERROR))
		b0->error = node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
	    }
	  else
	    {
	      u32 segment_index0;

	      segment_index0 = sr0->segments_left - 1;

	      /* Rewrite the packet */
	      new_dst0 = (ip6_address_t *) (sr0->segments + segment_index0);
	      ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
	      ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];

	      if (PREDICT_TRUE (sr0->segments_left > 0))
		sr0->segments_left -= 1;
	    }

	  /* End of the path. Clean up the SR header, or not */
	  if (PREDICT_FALSE
	      (sr0->segments_left == 0 &&
	       (sr0->flags &
		clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP))))
	    {
	      u64 *copy_dst0, *copy_src0;
	      u16 new_l0;
	      u32 copy_len_u64s0 = 0;
	      int i;

	      /*
	       * Copy the ip6 header right by the (real) length of the
	       * sr header.
	       */
	      if (PREDICT_FALSE
		  (ip0->protocol == IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS))
		{
		  ip6_hop_by_hop_ext_t *ext_hdr =
		    (ip6_hop_by_hop_ext_t *) ip6_next_header (ip0);
		  copy_len_u64s0 =
		    (((ip6_ext_header_t *) ext_hdr)->n_data_u64s) + 1;
		  ext_hdr->next_hdr = sr0->protocol;
		}
	      else
		{
		  ip0->protocol = sr0->protocol;
		}

	      vlib_buffer_advance (b0, (sr0->length + 1) * 8);

	      new_l0 = clib_net_to_host_u16 (ip0->payload_length) -
		(sr0->length + 1) * 8;
	      ip0->payload_length = clib_host_to_net_u16 (new_l0);

	      copy_src0 = (u64 *) ip0;
	      copy_dst0 = copy_src0 + (sr0->length + 1);
	      copy_dst0[4 + copy_len_u64s0] = copy_src0[4 + copy_len_u64s0];
	      copy_dst0[3 + copy_len_u64s0] = copy_src0[3 + copy_len_u64s0];
	      copy_dst0[2 + copy_len_u64s0] = copy_src0[2 + copy_len_u64s0];
	      copy_dst0[1 + copy_len_u64s0] = copy_src0[1 + copy_len_u64s0];
	      copy_dst0[0 + copy_len_u64s0] = copy_src0[0 + copy_len_u64s0];

	      for (i = copy_len_u64s0 - 1; i >= 0; i--)
		{
		  copy_dst0[i] = copy_src0[i];
		}

	      sr0 = 0;
	    }

	do_trace:
	  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
	    {
	      sr_local_trace_t *tr = vlib_add_trace (vm, node,
						     b0, sizeof (*tr));
	      clib_memcpy (tr->src.as_u8, ip0->src_address.as_u8,
			   sizeof (tr->src.as_u8));
	      clib_memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
			   sizeof (tr->dst.as_u8));
	      tr->length = vlib_buffer_length_in_chain (vm, b0);
	      tr->next_index = next0;
	      tr->sr_valid = sr0 != 0;
	      if (tr->sr_valid)
		clib_memcpy (tr->sr, sr0, sizeof (tr->sr));
	    }

	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, next0);
	}

      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }
  vlib_node_increment_counter (vm, sr_local_node.index,
			       SR_LOCAL_ERROR_PKTS_PROCESSED,
			       from_frame->n_vectors);
  return from_frame->n_vectors;
}

/* *INDENT-OFF* */
VLIB_REGISTER_NODE (sr_local_node, static) = {
  .function = sr_local,
  .name = "sr-local",
  /* Takes a vector of packets. */
  .vector_size = sizeof (u32),
  .format_trace = format_sr_local_trace,

  .runtime_data_bytes = 0,

  .n_errors = SR_LOCAL_N_ERROR,
  .error_strings = sr_local_error_strings,

  .n_next_nodes = SR_LOCAL_N_NEXT,
  .next_nodes = {
#define _(s,n) [SR_LOCAL_NEXT_##s] = n,
    foreach_sr_local_next
#undef _
  },
};

VLIB_NODE_FUNCTION_MULTIARCH (sr_local_node, sr_local)
/* *INDENT-ON* */

ip6_sr_main_t *
sr_get_main (vlib_main_t * vm)
{
  vlib_call_init_function (vm, sr_init);
  ASSERT (sr_local_node.index);
  return &sr_main;
}

/**
 * @brief CLI parser for SR fix destination rewrite node
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
set_ip6_sr_rewrite_fn (vlib_main_t * vm,
		       unformat_input_t * input, vlib_cli_command_t * cmd)
{
  fib_prefix_t pfx = {
    .fp_proto = FIB_PROTOCOL_IP6,
    .fp_len = 128,
  };
  u32 fib_index = 0;
  u32 fib_id = 0;
  u32 adj_index;
  ip_adjacency_t *adj;
  vnet_hw_interface_t *hi;
  u32 sw_if_index;
  ip6_sr_main_t *sm = &sr_main;
  vnet_main_t *vnm = vnet_get_main ();
  fib_node_index_t fei;

  if (!unformat (input, "%U", unformat_ip6_address, &pfx.fp_addr.ip6))
    return clib_error_return (0, "ip6 address missing in '%U'",
			      format_unformat_error, input);

  if (unformat (input, "rx-table-id %d", &fib_id))
    {
      fib_index = fib_table_id_find_fib_index (FIB_PROTOCOL_IP6, fib_id);
      if (fib_index == ~0)
	return clib_error_return (0, "fib-id %d not found", fib_id);
    }

  fei = fib_table_lookup_exact_match (fib_index, &pfx);

  if (FIB_NODE_INDEX_INVALID == fei)
    return clib_error_return (0, "no match for %U",
			      format_ip6_address, &pfx.fp_addr.ip6);

  adj_index = fib_entry_get_adj_for_source (fei, FIB_SOURCE_SR);

  if (ADJ_INDEX_INVALID == adj_index)
    return clib_error_return (0, "%U not SR sourced",
			      format_ip6_address, &pfx.fp_addr.ip6);

  adj = adj_get (adj_index);

  if (adj->lookup_next_index != IP_LOOKUP_NEXT_REWRITE)
    return clib_error_return (0, "%U unresolved (not a rewrite adj)",
			      format_ip6_address, &pfx.fp_addr.ip6);

  adj->rewrite_header.next_index = sm->ip6_rewrite_sr_next_index;

  sw_if_index = adj->rewrite_header.sw_if_index;
  hi = vnet_get_sup_hw_interface (vnm, sw_if_index);
  adj->rewrite_header.node_index = sr_fix_dst_addr_node.index;

  /* $$$$$ hack... steal the mcast group index */
  adj->mcast_group_index =
    vlib_node_add_next (vm, sr_fix_dst_addr_node.index,
			hi->output_node_index);

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (set_ip6_sr_rewrite, static) = {
    .path = "set ip6 sr rewrite",
    .short_help = "set ip6 sr rewrite <ip6-address> [fib-id <id>]",
    .function = set_ip6_sr_rewrite_fn,
};
/* *INDENT-ON* */

/**
 * @brief Register a callback routine to set next0 in sr_local
 *
 * @param cb void *
 */
void
vnet_register_sr_app_callback (void *cb)
{
  ip6_sr_main_t *sm = &sr_main;

  sm->sr_local_cb = cb;
}

/**
 * @brief Test routine for validation of HMAC
 */
static clib_error_t *
test_sr_hmac_validate_fn (vlib_main_t * vm,
			  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip6_sr_main_t *sm = &sr_main;

  if (unformat (input, "validate on"))
    sm->validate_hmac = 1;
  else if (unformat (input, "chunk-offset off"))
    sm->validate_hmac = 0;
  else
    return clib_error_return (0, "expected validate on|off in '%U'",
			      format_unformat_error, input);

  vlib_cli_output (vm, "hmac signature validation %s",
		   sm->validate_hmac ? "on" : "off");
  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_sr_hmac_validate, static) = {
    .path = "test sr hmac",
    .short_help = "test sr hmac validate [on|off]",
    .function = test_sr_hmac_validate_fn,
};
/* *INDENT-ON* */

/**
 * @brief Add or Delete HMAC key
 *
 * @param sm ip6_sr_main_t *
 * @param key_id u32
 * @param shared_secret u8 *
 * @param is_del u8
 *
 * @return retval i32
 */
// $$$ fixme shouldn't return i32
i32
sr_hmac_add_del_key (ip6_sr_main_t * sm, u32 key_id, u8 * shared_secret,
		     u8 is_del)
{
  u32 index;
  ip6_sr_hmac_key_t *key;

  if (is_del == 0)
    {
      /* Specific key in use? Fail. */
      if (key_id && vec_len (sm->hmac_keys) > key_id
	  && sm->hmac_keys[key_id].shared_secret)
	return -2;

      index = key_id;
      key = find_or_add_shared_secret (sm, shared_secret, &index);
      ASSERT (index == key_id);
      return 0;
    }

  /* delete */

  if (key_id)			/* delete by key ID */
    {
      if (vec_len (sm->hmac_keys) <= key_id)
	return -3;

      key = sm->hmac_keys + key_id;

      hash_unset_mem (sm->hmac_key_by_shared_secret, key->shared_secret);
      vec_free (key->shared_secret);
      return 0;
    }

  index = 0;
  key = find_or_add_shared_secret (sm, shared_secret, &index);
  hash_unset_mem (sm->hmac_key_by_shared_secret, key->shared_secret);
  vec_free (key->shared_secret);
  return 0;
}


static clib_error_t *
sr_hmac_add_del_key_fn (vlib_main_t * vm,
			unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip6_sr_main_t *sm = &sr_main;
  u8 is_del = 0;
  u32 key_id = 0;
  u8 key_id_set = 0;
  u8 *shared_secret = 0;
  i32 rv;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "del"))
	is_del = 1;
      else if (unformat (input, "id %d", &key_id))
	key_id_set = 1;
      else if (unformat (input, "key %s", &shared_secret))
	{
	  /* Do not include the trailing NULL byte. Guaranteed interop issue */
	  _vec_len (shared_secret) -= 1;
	}
      else
	break;
    }

  if (is_del == 0 && shared_secret == 0)
    return clib_error_return (0, "shared secret must be set to add a key");

  if (shared_secret == 0 && key_id_set == 0)
    return clib_error_return (0, "shared secret and key id both unset");

  rv = sr_hmac_add_del_key (sm, key_id, shared_secret, is_del);

  vec_free (shared_secret);

  switch (rv)
    {
    case 0:
      break;

    default:
      return clib_error_return (0, "sr_hmac_add_del_key returned %d", rv);
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (sr_hmac, static) = {
    .path = "sr hmac",
    .short_help = "sr hmac [del] id <nn> key <str>",
    .function = sr_hmac_add_del_key_fn,
};
/* *INDENT-ON* */

/**
 * @brief CLI parser for show HMAC key shared secrets
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
show_sr_hmac_fn (vlib_main_t * vm,
		 unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip6_sr_main_t *sm = &sr_main;
  int i;

  for (i = 1; i < vec_len (sm->hmac_keys); i++)
    {
      if (sm->hmac_keys[i].shared_secret)
	vlib_cli_output (vm, "[%d]: %v", i, sm->hmac_keys[i].shared_secret);
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_sr_hmac, static) = {
    .path = "show sr hmac",
    .short_help = "show sr hmac",
    .function = show_sr_hmac_fn,
};
/* *INDENT-ON* */

/**
 * @brief Test for SR debug flag
 *
 * @param vm vlib_main_t *
 * @param input unformat_input_t *
 * @param cmd vlib_cli_command_t *
 *
 * @return error clib_error_t *
 */
static clib_error_t *
test_sr_debug_fn (vlib_main_t * vm,
		  unformat_input_t * input, vlib_cli_command_t * cmd)
{
  ip6_sr_main_t *sm = &sr_main;

  if (unformat (input, "on"))
    sm->is_debug = 1;
  else if (unformat (input, "off"))
    sm->is_debug = 0;
  else
    return clib_error_return (0, "expected on|off in '%U'",
			      format_unformat_error, input);

  vlib_cli_output (vm, "debug trace now %s", sm->is_debug ? "on" : "off");

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_sr_debug, static) = {
    .path = "test sr debug",
    .short_help = "test sr debug on|off",
    .function = test_sr_debug_fn,
};
/* *INDENT-ON* */

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