/*
 * 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_ioam_export_h__
#define __included_ioam_export_h__

#include <vnet/vnet.h>
#include <vnet/ip/ip.h>
#include <vnet/ip/ip_packet.h>
#include <vnet/ip/ip4_packet.h>
#include <vnet/ip/ip6_packet.h>
#include <vnet/ip/udp.h>
#include <vnet/flow/ipfix_packet.h>

#include <vppinfra/pool.h>
#include <vppinfra/hash.h>
#include <vppinfra/error.h>
#include <vppinfra/elog.h>

#include <vlib/threads.h>

typedef struct ioam_export_buffer {
  /* Allocated buffer */
  u32 buffer_index;
  u64 touched_at;
  u8 records_in_this_buffer;
} ioam_export_buffer_t;


typedef struct {
  /* API message ID base */
  u16 msg_id_base;

  /* TODO: to support multiple collectors all this has to be grouped and create a vector here*/
  u8 *record_header;
  u32 sequence_number;
  u32 domain_id;

  /* ipfix collector, our ip address */
  ip4_address_t ipfix_collector;
  ip4_address_t src_address;

  /* Pool of ioam_export_buffer_t */
  ioam_export_buffer_t *buffer_pool;
  /* Vector of per thread ioam_export_buffer_t to buffer pool index */
  u32 *buffer_per_thread;
  /* Lock per thread to swap buffers between worker and timer process*/
  volatile u32 **lockp;

  /* time scale transform*/
  u32 unix_time_0;
  f64 vlib_time_0;

  /* convenience */
  vlib_main_t * vlib_main;
  vnet_main_t * vnet_main;
  ethernet_main_t * ethernet_main;
  u32 ip4_lookup_node_index;

  uword my_hbh_slot;
  u32 export_process_node_index;
} ioam_export_main_t;

ioam_export_main_t ioam_export_main;

vlib_node_registration_t export_node;

#define DEFAULT_EXPORT_SIZE (3 * CLIB_CACHE_LINE_BYTES)
/*
 *  Number of records in a buffer
 * ~(MTU (1500) - [ip hdr(40) + UDP(8) + ipfix (24)]) / DEFAULT_EXPORT_SIZE
 */
#define DEFAULT_EXPORT_RECORDS 7

always_inline ioam_export_buffer_t *ioam_export_get_my_buffer(u32 thread_id)
{
  ioam_export_main_t *em = &ioam_export_main;

  if (vec_len(em->buffer_per_thread) > thread_id)
    return(pool_elt_at_index(em->buffer_pool, em->buffer_per_thread[thread_id]));
  return(0);
}

inline static int ioam_export_buffer_add_header (vlib_buffer_t *b0)
{
  ioam_export_main_t *em = &ioam_export_main;
  clib_memcpy(b0->data, em->record_header, vec_len(em->record_header));
  b0->current_data = 0;
  b0->current_length = vec_len(em->record_header);
  b0->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
  return(1);
}

inline static int ioam_export_init_buffer (vlib_main_t *vm,
					   ioam_export_buffer_t *eb)
{
  vlib_buffer_t *b = 0;

  if (!eb)
    return(-1);
  /* TODO: Perhaps buffer init from template here */
  if (vlib_buffer_alloc (vm, &(eb->buffer_index), 1) != 1)
    return(-2);
  eb->records_in_this_buffer = 0;
  eb->touched_at = vlib_time_now(vm);
  b = vlib_get_buffer(vm, eb->buffer_index);
  (void) ioam_export_buffer_add_header(b);
  vnet_buffer(b)->sw_if_index[VLIB_RX] = 0;
  vnet_buffer(b)->sw_if_index[VLIB_TX] = ~0;
  return(1);
}

inline static void ioam_export_thread_buffer_free (void)
{
  ioam_export_main_t *em = &ioam_export_main;
  vlib_main_t *vm = em->vlib_main;
  ioam_export_buffer_t *eb = 0;
  int i;
  for (i = 0; i < vec_len(em->buffer_per_thread); i++)
    {
      eb = pool_elt_at_index(em->buffer_pool, em->buffer_per_thread[i]);
      if (eb)
	vlib_buffer_free(vm, &(eb->buffer_index), 1);
    }
  for (i = 0; i < vec_len(em->lockp); i++)
    clib_mem_free((void *) em->lockp[i]);
  vec_free(em->buffer_per_thread);
  pool_free(em->buffer_pool);
  vec_free(em->lockp);
  em->buffer_per_thread = 0;
  em->buffer_pool = 0;
  em->lockp = 0;
}

inline static int ioam_export_thread_buffer_init (vlib_main_t *vm)
{
  ioam_export_main_t *em = &ioam_export_main;
  int no_of_threads = vec_len(vlib_worker_threads);
  int i;
  ioam_export_buffer_t *eb = 0;
  vlib_node_t * ip4_lookup_node;

  pool_alloc_aligned(em->buffer_pool,
		      no_of_threads - 1,
		      CLIB_CACHE_LINE_BYTES);
  vec_validate_aligned(em->buffer_per_thread,
			no_of_threads-1,
			CLIB_CACHE_LINE_BYTES);
  vec_validate_aligned(em->lockp, no_of_threads-1,
		       CLIB_CACHE_LINE_BYTES);
  ip4_lookup_node = vlib_get_node_by_name (vm, (u8 *) "ip4-lookup");
  em->ip4_lookup_node_index = ip4_lookup_node->index;
  if (!em->buffer_per_thread || !em->buffer_pool || !em->lockp)
    {
      return(-1);
    }
  for (i=0; i < no_of_threads; i++)
    {
      eb = 0;
      pool_get_aligned(em->buffer_pool, eb, CLIB_CACHE_LINE_BYTES);
      memset(eb, 0, sizeof (*eb));
      em->buffer_per_thread[i] = eb - em->buffer_pool;
      if (ioam_export_init_buffer(vm, eb) != 1)
	{
	  ioam_export_thread_buffer_free();
	  return(-2);
	}
      em->lockp[i] = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES,
					     CLIB_CACHE_LINE_BYTES);
      memset ((void *) em->lockp[i], 0, CLIB_CACHE_LINE_BYTES);
    }
  return(1);
}

#define IPFIX_IOAM_EXPORT_ID 272

/* Used to build the rewrite */
/* data set packet */
typedef struct {
  ipfix_message_header_t h;
  ipfix_set_header_t s;
} ipfix_data_packet_t;

typedef struct {
  ip4_header_t ip4;
  udp_header_t udp;
  ipfix_data_packet_t ipfix;
} ip4_ipfix_data_packet_t;


inline static void ioam_export_header_cleanup (ip4_address_t * collector_address,
					       ip4_address_t * src_address)
{
  ioam_export_main_t *em = &ioam_export_main;
  vec_free(em->record_header);
  em->record_header = 0;
}

inline static int ioam_export_header_create (ip4_address_t * collector_address,
					     ip4_address_t * src_address)
{
  ioam_export_main_t *em = &ioam_export_main;
  ip4_header_t * ip;
  udp_header_t * udp;
  ipfix_message_header_t * h;
  ipfix_set_header_t * s;
  u8 * rewrite = 0;
  ip4_ipfix_data_packet_t * tp;


  /* allocate rewrite space */
  vec_validate_aligned (rewrite,
                        sizeof (ip4_ipfix_data_packet_t) - 1,
                        CLIB_CACHE_LINE_BYTES);

  tp = (ip4_ipfix_data_packet_t *) rewrite;
  ip = (ip4_header_t *) &tp->ip4;
  udp = (udp_header_t *) (ip+1);
  h = (ipfix_message_header_t *)(udp+1);
  s = (ipfix_set_header_t *)(h+1);

  ip->ip_version_and_header_length = 0x45;
  ip->ttl = 254;
  ip->protocol = IP_PROTOCOL_UDP;
  ip->src_address.as_u32 = src_address->as_u32;
  ip->dst_address.as_u32 = collector_address->as_u32;
  udp->src_port = clib_host_to_net_u16 (4939 /* $$FIXME */);
  udp->dst_port = clib_host_to_net_u16 (4939);
  /* FIXUP: UDP length */
  udp->length = clib_host_to_net_u16 (vec_len(rewrite) +
    (DEFAULT_EXPORT_RECORDS * DEFAULT_EXPORT_SIZE) - sizeof (*ip));

  /* FIXUP: message header export_time */
  /* FIXUP: message header sequence_number */
  h->domain_id = clib_host_to_net_u32 (em->domain_id);

  /*FIXUP: Setid length in octets if records exported are not default*/
  s->set_id_length = ipfix_set_id_length (IPFIX_IOAM_EXPORT_ID,
    (sizeof(*s) + (DEFAULT_EXPORT_RECORDS * DEFAULT_EXPORT_SIZE)));

  /* FIXUP: h version and length length in octets if records exported are not default */
  h->version_length = version_length (sizeof(*h)+
    (sizeof(*s) + (DEFAULT_EXPORT_RECORDS * DEFAULT_EXPORT_SIZE)));

  /* FIXUP: ip length if records exported are not default */
  /* FIXUP: ip checksum if records exported are not default */
  ip->length = clib_host_to_net_u16 (vec_len(rewrite) +
    (DEFAULT_EXPORT_RECORDS * DEFAULT_EXPORT_SIZE));
  ip->checksum = ip4_header_checksum (ip);
  _vec_len(rewrite) = sizeof(ip4_ipfix_data_packet_t);
  em->record_header = rewrite;
  return(1);
}

inline static int ioam_export_send_buffer (vlib_main_t *vm,
    ioam_export_buffer_t *eb)
{
  ioam_export_main_t *em = &ioam_export_main;
  ip4_header_t * ip;
  udp_header_t * udp;
  ipfix_message_header_t * h;
  ipfix_set_header_t * s;
  ip4_ipfix_data_packet_t * tp;
  vlib_buffer_t *b0;
  u16 new_l0, old_l0;
  ip_csum_t sum0;
  vlib_frame_t * nf = 0;
  u32 * to_next;

  b0 = vlib_get_buffer(vm, eb->buffer_index);
  tp = vlib_buffer_get_current (b0);
  ip = (ip4_header_t *) &tp->ip4;
  udp = (udp_header_t *) (ip+1);
  h = (ipfix_message_header_t *)(udp+1);
  s = (ipfix_set_header_t *)(h+1);

  /* FIXUP: message header export_time */
  h->export_time = clib_host_to_net_u32((u32)
    (((f64)em->unix_time_0) +
    (vlib_time_now(em->vlib_main) - em->vlib_time_0)));

  /* FIXUP: message header sequence_number */
  h->sequence_number = clib_host_to_net_u32 (em->sequence_number++);

  /* FIXUP: lengths if different from default */
  if (PREDICT_FALSE(eb->records_in_this_buffer != DEFAULT_EXPORT_RECORDS)) {
     s->set_id_length = ipfix_set_id_length (IPFIX_IOAM_EXPORT_ID /* set_id */,
                                            b0->current_length -
                                            (sizeof (*ip) + sizeof (*udp) +
                                            sizeof (*h)));
     h->version_length = version_length (b0->current_length -
                                        (sizeof (*ip) + sizeof (*udp)));
     sum0 = ip->checksum;
     old_l0 = ip->length;
     new_l0 = clib_host_to_net_u16 ((u16)b0->current_length);
     sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
                            length /* changed member */);
     ip->checksum = ip_csum_fold (sum0);
     ip->length = new_l0;
     udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
  }

  /* Enqueue pkts to ip4-lookup */

  nf = vlib_get_frame_to_node (vm, em->ip4_lookup_node_index);
  nf->n_vectors = 0;
  to_next = vlib_frame_vector_args (nf);
  nf->n_vectors = 1;
  to_next[0] = eb->buffer_index;
  vlib_put_frame_to_node(vm, em->ip4_lookup_node_index, nf);
  return(1);

}

#endif /* __included_ioam_export_h__ */