/* packet-vpp.c
 * 
 * Routines for the disassembly of fd.io vpp project 
 * dispatch captures
 *
 * Copyright (c) 2018 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.
 * 
 * This version is not to be upstreamed as-is, since it hooks up the
 * vpp dissector to WTAP_ENCAP_USER13, a test encap type.
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/to_str.h>
#include <epan/in_cksum.h>
#include <epan/nlpid.h>
#include <epan/etypes.h>
#include <stdio.h>
#include <wsutil/ws_printf.h>

void proto_register_vpp(void);
void proto_reg_handoff_vpp(void);

static int proto_vpp = -1;
static int proto_vpp_metadata = -1;
static int proto_vpp_opaque = -1;
static int proto_vpp_opaque2 = -1;
static int proto_vpp_trace = -1;
static int hf_vpp_nodename = -1;
static int hf_vpp_metadata = -1;
static int hf_vpp_buffer_index = -1;
static int hf_vpp_buffer_opaque = -1;
static int hf_vpp_buffer_opaque2 = -1;
static int hf_vpp_buffer_trace = -1;

static gint ett_vpp = -1;
static gint ett_vpp_opaque = -1;
static gint ett_vpp_opaque2 = -1;
static gint ett_vpp_metadata = -1;
static gint ett_vpp_trace = -1;

static dissector_handle_t vpp_dissector_handle;
static dissector_handle_t vpp_opaque_dissector_handle;
static dissector_handle_t vpp_opaque2_dissector_handle;
static dissector_handle_t vpp_metadata_dissector_handle;
static dissector_handle_t vpp_trace_dissector_handle;

typedef enum
  {
    VLIB_NODE_PROTO_HINT_NONE = 0,
    VLIB_NODE_PROTO_HINT_ETHERNET,
    VLIB_NODE_PROTO_HINT_IP4,
    VLIB_NODE_PROTO_HINT_IP6,
    VLIB_NODE_PROTO_HINT_TCP,
    VLIB_NODE_PROTO_HINT_UDP,
    VLIB_NODE_N_PROTO_HINTS,
  } vlib_node_proto_hint_t;

static dissector_handle_t next_dissectors[VLIB_NODE_N_PROTO_HINTS];

/* List of next dissectors hints that we know about */
#define foreach_next_dissector                  \
_(VLIB_NODE_PROTO_HINT_ETHERNET, eth_maybefcs)  \
_(VLIB_NODE_PROTO_HINT_IP4, ip)                 \
_(VLIB_NODE_PROTO_HINT_IP6, ipv6)               \
_(VLIB_NODE_PROTO_HINT_TCP, tcp)                \
_(VLIB_NODE_PROTO_HINT_UDP, udp)

static void
add_multi_line_string_to_tree(proto_tree *tree, tvbuff_t *tvb, gint start,
  gint len, int hf)
{
    gint next;
    int  line_len;
    int  data_len;

    while (len > 0) {
        line_len = tvb_find_line_end(tvb, start, len, &next, FALSE);
        data_len = next - start;
        proto_tree_add_string(tree, hf, tvb, start, data_len, 
                              tvb_format_stringzpad(tvb, start, line_len));
        start += data_len;
        len   -= data_len;
    }
}

static int
dissect_vpp_metadata (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, 
                    void* data _U_)
{
    int         offset   = 0;
    proto_item *ti;
    proto_tree *metadata_tree;
    gint metadata_string_length;
    
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "VPP-Metadata");
    col_clear(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_vpp_metadata, tvb, offset, -1, ENC_NA);
    metadata_tree = proto_item_add_subtree(ti, ett_vpp_metadata);

    /* How long is the metadata string? */
    metadata_string_length = tvb_strsize (tvb, offset);
    
    add_multi_line_string_to_tree (metadata_tree, tvb, 0,
                                   metadata_string_length, 
                                   hf_vpp_metadata);
    return tvb_captured_length(tvb);
}

static int
dissect_vpp_trace (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, 
                    void* data _U_)
{
    int         offset   = 0;
    proto_item *ti;
    proto_tree *trace_tree;
    gint trace_string_length;
    
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "VPP-Trace");
    col_clear(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_vpp_trace, tvb, offset, -1, ENC_NA);
    trace_tree = proto_item_add_subtree(ti, ett_vpp_trace);

    /* How long is the trace string? */
    trace_string_length = tvb_strsize (tvb, offset);
    
    add_multi_line_string_to_tree (trace_tree, tvb, 0,
                                   trace_string_length, 
                                   hf_vpp_buffer_trace);
    return tvb_captured_length(tvb);
}


static int
dissect_vpp_opaque (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, 
                    void* data _U_)
{
    int         offset   = 0;
    proto_item *ti;
    proto_tree *opaque_tree;
    gint opaque_string_length;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "VPP-Opaque");
    col_clear(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_vpp_opaque, tvb, offset, -1, ENC_NA);
    opaque_tree = proto_item_add_subtree(ti, ett_vpp_opaque);

    opaque_string_length = tvb_strsize (tvb, offset); 
    add_multi_line_string_to_tree (opaque_tree, tvb, 0, opaque_string_length, 
                                   hf_vpp_buffer_opaque);

    return tvb_captured_length(tvb);
}

static int
dissect_vpp_opaque2 (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, 
                    void* data _U_)
{
    int         offset   = 0;
    proto_item *ti;
    proto_tree *opaque2_tree;
    gint opaque2_string_length;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "VPP-Opaque2");
    col_clear(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_vpp_opaque2, tvb, offset, -1, ENC_NA);
    opaque2_tree = proto_item_add_subtree(ti, ett_vpp_opaque2);

    opaque2_string_length = tvb_strsize (tvb, offset); 
    add_multi_line_string_to_tree (opaque2_tree, tvb, 0, opaque2_string_length, 
                                   hf_vpp_buffer_opaque2);

    return tvb_captured_length(tvb);
}


static int
dissect_vpp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    proto_item *ti;
    proto_tree *vpp_tree;
    tvbuff_t *metadata_tvb, *opaque_tvb, *opaque2_tvb, *eth_tvb, *trace_tvb;
    int         offset   = 0;
    guint8 major_version, minor_version, string_count, protocol_hint;
    guint8 *name;
    guint len;
    guint8 maybe_protocol_id; 
    dissector_handle_t use_this_dissector;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "VPP");
    col_clear(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_vpp, tvb, offset, -1, ENC_NA);
    vpp_tree = proto_item_add_subtree(ti, ett_vpp);

    major_version = tvb_get_guint8 (tvb, offset);
    offset++;

    minor_version = tvb_get_guint8 (tvb, offset);
    offset++;

    if (major_version != 1 || minor_version != 0)
        ws_debug_printf ("WARNING: version mismatch (%d, %d)",
                         major_version, minor_version);

    /* Number of counted strings in this trace record */
    string_count = tvb_get_guint8 (tvb, offset);
    offset++;

    /* 
     * Hint: protocol which should be at b->data[b->current_data]
     * It will be a while before vpp sends useful hints for every
     * possible node, see heuristic below
     */
    protocol_hint = tvb_get_guint8 (tvb, offset);
    offset++;

    /* Buffer Index */
    proto_tree_add_item(vpp_tree, hf_vpp_buffer_index, tvb,
                        offset, 4, ENC_BIG_ENDIAN);
    offset += 4;

    /* Nodename */
    len = tvb_strsize (tvb, offset); 
    name = tvb_get_string_enc (wmem_packet_scope(), tvb, offset, len,
                               ENC_ASCII);
    proto_tree_add_string (tree, hf_vpp_nodename, tvb, offset, len, name);
    offset += len;

    /* Metadata */
    len = tvb_strsize (tvb, offset);
    metadata_tvb = tvb_new_subset_remaining (tvb, offset);
    call_dissector (vpp_metadata_dissector_handle, metadata_tvb, pinfo, tree);
    offset += len;

    /* Opaque */
    len = tvb_strsize (tvb, offset);
    opaque_tvb = tvb_new_subset_remaining (tvb, offset);
    call_dissector (vpp_opaque_dissector_handle, opaque_tvb, pinfo, tree);
    offset += len;

    /* Opaque2 */
    len = tvb_strsize (tvb, offset);
    opaque2_tvb = tvb_new_subset_remaining (tvb, offset);
    call_dissector (vpp_opaque2_dissector_handle, opaque2_tvb, pinfo, tree);
    offset += len;

    /* Trace, if present */
    if (string_count > 4)
    {
        len = tvb_strsize (tvb, offset);
        trace_tvb = tvb_new_subset_remaining (tvb, offset);
        call_dissector (vpp_trace_dissector_handle, trace_tvb, pinfo, tree);
        offset += len;
    }

    eth_tvb = tvb_new_subset_remaining (tvb, offset);
    
    /* 
     * Delegate the rest of the packet dissection to the per-node
     * next dissector in the foreach_node_to_dissector_pair list
     *
     * Failing that, pretend its an ethernet packet
     */ 
    if (protocol_hint >= array_length(next_dissectors)) {
        ws_debug_printf ("protocol_hint %d out of range (max %d)",
                         (int) protocol_hint, 
                         (int) array_length(next_dissectors));
        protocol_hint = 0;
    }
    /* See setup for hint == 0 below */
    use_this_dissector = next_dissectors [protocol_hint];
    if (protocol_hint == 0) {
        maybe_protocol_id = tvb_get_guint8 (tvb, offset);
            
        switch (maybe_protocol_id) {
        case 0x45:
            use_this_dissector = next_dissectors[VLIB_NODE_PROTO_HINT_IP4];
            break;
        case 0x60:
            use_this_dissector = next_dissectors[VLIB_NODE_PROTO_HINT_IP6];
            break;
        default:
            break;
        }
    }
    call_dissector (use_this_dissector, eth_tvb, pinfo, tree);
    return tvb_captured_length(tvb);
}

void
proto_register_vpp(void)
{
  static hf_register_info vpp_hf[] = {
      { &hf_vpp_buffer_index,
        { "BufferIndex", "vpp.BufferIndex",  FT_UINT32, BASE_HEX, NULL, 0x0,
          NULL, HFILL },
      },
      { &hf_vpp_nodename,
        { "NodeName", "vpp.NodeName",  FT_STRINGZ, BASE_NONE, NULL, 0x0,
          NULL, HFILL },
      },
  };

  static hf_register_info metadata_hf[] = {
      { &hf_vpp_metadata,
        { "Metadata", "vpp.metadata",  
          FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL },
      },
  };

  static hf_register_info opaque_hf[] = {
      { &hf_vpp_buffer_opaque,
        { "Opaque", "vpp.opaque",  
          FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL },
      },
  };

  static hf_register_info opaque2_hf[] = {
      { &hf_vpp_buffer_opaque2,
        { "Opaque2", "vpp.opaque2",  
          FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL },
      },
  };

  static hf_register_info trace_hf[] = {
      { &hf_vpp_buffer_trace,
        { "Trace", "vpp.trace",  FT_STRINGZ, BASE_NONE, NULL, 0x0,
          NULL, HFILL },
      },
  };

  static gint *vpp_ett[] = {
    &ett_vpp,
  };
  static gint *ett_metadata[] = {
    &ett_vpp_metadata,
  };
  static gint *ett_opaque[] = {
    &ett_vpp_opaque,
  };
  static gint *ett_opaque2[] = {
    &ett_vpp_opaque2,
  };
  static gint *ett_trace[] = {
    &ett_vpp_trace,
  };

  proto_vpp = proto_register_protocol("VPP Dispatch Trace", "VPP", "vpp");
  proto_register_field_array(proto_vpp, vpp_hf, array_length(vpp_hf));
  proto_register_subtree_array (vpp_ett, array_length(vpp_ett));
  register_dissector("vpp", dissect_vpp, proto_vpp);

  proto_vpp_metadata = proto_register_protocol("VPP Buffer Metadata", 
                                               "VPP-Metadata", 
                                               "vpp-metadata");
  proto_register_field_array(proto_vpp_metadata, metadata_hf, 
                             array_length(metadata_hf));
  proto_register_subtree_array (ett_metadata, array_length(ett_metadata));
  register_dissector("vppMetadata", dissect_vpp_metadata, proto_vpp_metadata);

  proto_vpp_opaque = proto_register_protocol("VPP Buffer Opaque", "VPP-Opaque", 
                                             "vpp-opaque");
  proto_register_field_array(proto_vpp_opaque, opaque_hf, 
                             array_length(opaque_hf));
  proto_register_subtree_array (ett_opaque, array_length(ett_opaque));
  register_dissector("vppOpaque", dissect_vpp_opaque, proto_vpp_opaque);

  proto_vpp_opaque2 = proto_register_protocol("VPP Buffer Opaque2", "VPP-Opaque2", 
                                             "vpp-opaque2");
  proto_register_field_array(proto_vpp_opaque2, opaque2_hf, 
                             array_length(opaque2_hf));
  proto_register_subtree_array (ett_opaque2, array_length(ett_opaque2));
  register_dissector("vppOpaque2", dissect_vpp_opaque2, proto_vpp_opaque2);


  proto_vpp_trace = proto_register_protocol("VPP Buffer Trace", "VPP-Trace", 
                                             "vpp-trace");
  proto_register_field_array(proto_vpp_trace, trace_hf, 
                             array_length(trace_hf));
  proto_register_subtree_array (ett_trace, array_length(ett_trace));
  register_dissector("vppTrace", dissect_vpp_trace, proto_vpp_trace);
  
#define _(idx,dname) next_dissectors[idx] = find_dissector (#dname);
  foreach_next_dissector;
#undef _

  /* if all else fails, dissect data as if ethernet MAC */
  next_dissectors[VLIB_NODE_PROTO_HINT_NONE] = 
      next_dissectors [VLIB_NODE_PROTO_HINT_ETHERNET];
}

void
proto_reg_handoff_vpp(void)
{
    vpp_dissector_handle = find_dissector("vpp");
    vpp_metadata_dissector_handle = find_dissector("vppMetadata");
    vpp_opaque_dissector_handle = find_dissector("vppOpaque");
    vpp_opaque2_dissector_handle = find_dissector("vppOpaque2");
    vpp_trace_dissector_handle = find_dissector("vppTrace");
    dissector_add_uint("wtap_encap", WTAP_ENCAP_USER13, vpp_dissector_handle);
}

/*
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */