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

/* Common prefix of tcp and udp headers
 * containing only source and destination port fields */
typedef struct {
  u16 src_port, dst_port;
} tcpudp_header_t;

flow_report_classify_main_t flow_report_classify_main;

u8 * ipfix_classify_template_rewrite (flow_report_main_t * frm,
                                      flow_report_t * fr,
                                      ip4_address_t * collector_address,
                                      ip4_address_t * src_address,
                                      u16 collector_port)
{
  flow_report_classify_main_t * fcm = &flow_report_classify_main;
  vnet_classify_table_t * tblp;
  vnet_classify_main_t * vcm = &vnet_classify_main;
  u32 flow_table_index = fr->opaque.as_uword;
  u8 * ip_start;
  ip4_header_t * ip;
  ip6_header_t * ip6;
  tcpudp_header_t * tcpudp;
  udp_header_t * udp;
  ipfix_message_header_t * h;
  ipfix_set_header_t * s;
  ipfix_template_header_t * t;
  ipfix_field_specifier_t * f;
  ipfix_field_specifier_t * first_field;
  u8 * rewrite = 0;
  ip4_ipfix_template_packet_t * tp;
  i32 l3_offset = -2;  /* sizeof (ethernet_header_t) - sizeof (u32x4) */
  u32 field_count = 0;
  u32 field_index = 0;
  flow_report_stream_t * stream;
  u8 ip_version;
  u8 transport_protocol;

  stream = &frm->streams[fr->stream_index];

  ipfix_classify_table_t * table = &fcm->tables[flow_table_index];

  ip_version = table->ip_version;
  transport_protocol = table->transport_protocol;

  tblp = pool_elt_at_index (vcm->tables, table->classify_table_index);

  /* 
   * Mumble, assumes that we're not classifying on L2 or first 2 octets
   * of L3..
   */

  /* Determine field count */
  ip_start = ((u8 *)(tblp->mask)) + l3_offset;
#define _(field,mask,item,length)                                       \
  if (memcmp(&field, &mask, length) == 0)                               \
    {                                                                   \
      field_count++;                                                    \
                                                                        \
      fr->fields_to_send = clib_bitmap_set (fr->fields_to_send,         \
                                            field_index, 1);            \
    }                                                                   \
  field_index++;
  foreach_ipfix_field;
#undef _

  /* Add packetTotalCount manually */
  field_count += 1;

  /* $$$ enterprise fields, at some later date */

  /* allocate rewrite space */
  vec_validate_aligned (rewrite, 
                        sizeof (ip4_ipfix_template_packet_t) 
                        + field_count * sizeof (ipfix_field_specifier_t) - 1,
                        CLIB_CACHE_LINE_BYTES);

  tp = (ip4_ipfix_template_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);
  t = (ipfix_template_header_t *)(s+1);
  first_field = f = (ipfix_field_specifier_t *)(t+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 (stream->src_port);
  udp->dst_port = clib_host_to_net_u16 (collector_port);
  udp->length = clib_host_to_net_u16 (vec_len(rewrite) - sizeof (*ip));

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

  /* Take another trip through the mask and build the template */
  ip_start = ((u8 *)(tblp->mask)) + l3_offset;
#define _(field,mask,item,length)                               \
  if (memcmp(&field, &mask, length) == 0)                       \
    {                                                           \
      f->e_id_length = ipfix_e_id_length (0 /* enterprise */,   \
                                          item, length);        \
      f++;                                                      \
    }
  foreach_ipfix_field;
#undef _

  /* Add packetTotalCount manually */
  f->e_id_length = ipfix_e_id_length (0 /* enterprise */, packetTotalCount, 8);
  f++;

  /* Back to the template packet... */
  ip = (ip4_header_t *) &tp->ip4;
  udp = (udp_header_t *) (ip+1);
  
  ASSERT (f - first_field);
  /* Field count in this template */
  t->id_count = ipfix_id_count (fr->template_id, f - first_field);

  /* set length in octets*/
  s->set_id_length = ipfix_set_id_length (2 /* set_id */, (u8 *) f - (u8 *)s);

  /* message length in octets */
  h->version_length = version_length ((u8 *)f - (u8 *)h);

  ip->length = clib_host_to_net_u16 ((u8 *)f - (u8 *)ip);
  ip->checksum = ip4_header_checksum (ip);

  return rewrite;
}

vlib_frame_t * ipfix_classify_send_flows (flow_report_main_t * frm,
                                          flow_report_t * fr,
                                          vlib_frame_t * f,
                                          u32 * to_next,
                                          u32 node_index)
{
  flow_report_classify_main_t * fcm = &flow_report_classify_main;
  vnet_classify_main_t * vcm = &vnet_classify_main;
  u32 flow_table_index = fr->opaque.as_uword;
  vnet_classify_table_t * t;
  vnet_classify_bucket_t * b;
  vnet_classify_entry_t * v, * save_v;
  vlib_buffer_t *b0 = 0;
  u32 next_offset = 0;
  u32 record_offset = 0;
  u32 bi0 = ~0;
  int i, j, k;
  ip4_ipfix_template_packet_t * tp;
  ipfix_message_header_t * h = 0;
  ipfix_set_header_t * s = 0;
  u8 * ip_start;
  ip4_header_t * ip;
  ip6_header_t * ip6;
  tcpudp_header_t * tcpudp;
  udp_header_t * udp;
  int field_index;
  u32 records_this_buffer;
  u16 new_l0, old_l0;
  ip_csum_t sum0;
  vlib_main_t * vm = frm->vlib_main;
  flow_report_stream_t * stream;
  u8 ip_version;
  u8 transport_protocol;

  stream = &frm->streams[fr->stream_index];

  ipfix_classify_tab<style>.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */</style><div class="highlight"><pre><span></span>FROM ubuntu:18.04

RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
    inetutils-traceroute \
    ca-certificates \
    build-essential \
    git gdb sudo \
    iputils-ping \
    net-tools \
    iproute2 \
    tcpdump \
    asciidoc \
    xmlto \
    libssl-dev \
    netcat; \
    rm -rf /var/lib/apt/lists/*; \
    mv /usr/sbin/tcpdump /usr/bin/tcpdump

RUN set -eux; \
    mkdir -p {{vpp_path}}

COPY . / {{vpp_path}}/

WORKDIR {{vpp_path}}

RUN set -eux; \
    make wipe; \
    export UNATTENDED=y; \
    echo &quot;y&quot; | make install-dep; \
    rm -rf /var/lib/apt/lists/* ; \
    make build; \
    make pkg-deb; \
    rm -rf .ccache; \
    find . -type f -name &#39;*.o&#39; -delete ; \
    cd {{vpp_path}}/build-root; \
    rm vpp-api-python_*.deb; \
    tar czf vpp-package.tgz *.deb; \
    mv vpp-package.tgz {{vpp_path}}/; \
    dpkg -i *.deb ; \
    cp {{vpp_path}}/startup.conf /etc/startup.conf

WORKDIR /
 
CMD vpp -c /etc/startup.conf
</pre></div>
</code></pre></td></tr></table>
</div> <!-- class=content -->
<div id="lfcollabprojects-footer">
  <div class="gray-diagonal">
    <div class="footer-inner">
      <p>
        &copy; 2016 <a href="https://www.fd.io/">FD.io</a> a Linux Foundation
        Collaborative Project. All Rights Reserved.
      </p>
      <p>
        Linux Foundation is a registered trademark of The Linux Foundation.
        Linux is a registered
        <a
          href="http://www.linuxfoundation.org/programs/legal/trademark"
          title="Linux Mark Institute"
          >trademark</a
        >
        of Linus Torvalds.
      </p>
      <p>
        Please see our
        <a href="http://www.linuxfoundation.org/privacy">privacy policy</a> and
        <a href="http://www.linuxfoundation.org/terms">terms of use</a>
      </p>
    </div>
  </div>
</div>
</div> <!-- id=cgit -->
</body>
</html>
0);
      ip = (ip4_header_t *) &tp->ip4;
      udp = (udp_header_t *) (ip+1);
      
      sum0 = ip->checksum;
      old_l0 = ip->length;
      new_l0 = clib_host_to_net_u16 ((u16)next_offset);
      
      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));

      if (frm->udp_checksum)
        {
          /* RFC 7011 section 10.3.2. */
          udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
          if (udp->checksum == 0)
          udp->checksum = 0xffff;
        }

      ASSERT (ip->checksum == ip4_header_checksum (ip));
      
      to_next[0] = bi0;
      f->n_vectors++;
      
      b0 = 0;
      bi0 = ~0;
    }
  
  *(t->writer_lock) = 0;
  return f;
}

static clib_error_t *
ipfix_classify_table_add_del_command_fn (vlib_main_t * vm,
                                         unformat_input_t * input,
                                         vlib_cli_command_t * cmd)
{
  flow_report_classify_main_t *fcm = &flow_report_classify_main;
  flow_report_main_t *frm = &flow_report_main;
  vnet_flow_report_add_del_args_t args;
  ipfix_classify_table_t * table;
  int rv;
  int is_add = -1;
  u32 classify_table_index = ~0;
  u8 ip_version = 0;
  u8 transport_protocol = 255;
  clib_error_t * error = 0;

  if (fcm->src_port == 0)
    clib_error_return (0, "call 'set ipfix classify stream' first");

  memset (&args, 0, sizeof (args));

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) {
    if (unformat (input, "add"))
      is_add = 1;
    else if (unformat (input, "del"))
      is_add = 0;
    else if (unformat (input, "%d", &classify_table_index))
      ;
    else if (unformat (input, "ip4"))
      ip_version = 4;
    else if (unformat (input, "ip6"))
      ip_version = 6;
    else if (unformat (input, "tcp"))
      transport_protocol = 6;
    else if (unformat (input, "udp"))
      transport_protocol = 17;
    else
      return clib_error_return (0, "unknown input `%U'",
                                format_unformat_error, input);
  }

  if (is_add == -1)
    return clib_error_return (0, "expecting: add|del");
  if (classify_table_index == ~0)
    return clib_error_return (0, "classifier table not specified");
  if (ip_version == 0)
    return clib_error_return (0, "IP version not specified");

  table = 0;
  int i;
  for (i = 0; i < vec_len(fcm->tables); i++)
    if (ipfix_classify_table_index_valid(i))
      if (fcm->tables[i].classify_table_index == classify_table_index) {
        table = &fcm->tables[i];
        break;
      }

  if (is_add) {
    if (table)
      return clib_error_return (0, "Specified classifier table already used");
    table = ipfix_classify_add_table();
    table->classify_table_index = classify_table_index;
  } else {
    if (!table)
      return clib_error_return (0, "Specified classifier table not registered");
  }

  table->ip_version = ip_version;
  table->transport_protocol = transport_protocol;

  args.opaque.as_uword = table - fcm->tables;
  args.rewrite_callback = ipfix_classify_template_rewrite;
  args.flow_data_callback = ipfix_classify_send_flows;
  args.is_add = is_add;
  args.domain_id = fcm->domain_id;
  args.src_port = fcm->src_port;

  rv = vnet_flow_report_add_del (frm, &args);

  error = flow_report_add_del_error_to_clib_error(rv);

  /* If deleting, or add failed */
  if (is_add == 0 || (rv && is_add))
    ipfix_classify_delete_table (table - fcm->tables);

  return error;
}

VLIB_CLI_COMMAND (ipfix_classify_table_add_del_command, static) = {
  .path = "ipfix classify table",
  .short_help = "ipfix classify table add|del <table-index>",
  .function = ipfix_classify_table_add_del_command_fn,
};

static clib_error_t *
set_ipfix_classify_stream_command_fn (vlib_main_t * vm,
                                      unformat_input_t * input,
                                      vlib_cli_command_t * cmd)
{
  flow_report_classify_main_t *fcm = &flow_report_classify_main;
  flow_report_main_t *frm = &flow_report_main;
  u32 domain_id = 1;
  u32 src_port = UDP_DST_PORT_ipfix;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) {
    if (unformat (input, "domain %d", &domain_id))
      ;
    else if (unformat (input, "src-port %d", &src_port))
      ;
    else
      return clib_error_return (0, "unknown input `%U'",
                                format_unformat_error, input);
  }

  if (fcm->src_port != 0 &&
      (fcm->domain_id != domain_id ||
       fcm->src_port != (u16)src_port)) {
    int rv = vnet_stream_change (frm, fcm->domain_id, fcm->src_port,
                                 domain_id, (u16)src_port);
    ASSERT (rv == 0);
  }

  fcm->domain_id = domain_id;
  fcm->src_port = (u16)src_port;

  return 0;
}

VLIB_CLI_COMMAND (set_ipfix_classify_stream_command, static) = {
  .path = "set ipfix classify stream",
  .short_help = "set ipfix classify stream"
                "[domain <domain-id>] [src-port <src-port>]",
  .function = set_ipfix_classify_stream_command_fn,
};

static clib_error_t *
flow_report_classify_init (vlib_main_t *vm)
{
  clib_error_t * error;

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

  return 0;
}

VLIB_INIT_FUNCTION (flow_report_classify_init);