/* packet-msrcp.c
 * Routines for decoding Microsoft Cluster Route Control Protocol (MSRCP)
 * Copyright 2022, Will Aftring <william.aftring@outlook.com>
 *
 * SPDX-License-Identifier: MIT
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 */


#include "config.h"
#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/conversation.h>

void proto_register_msrcp(void);
void proto_reg_handoff_msrcp(void);

static dissector_handle_t msrcp_handle;

#define MSRCP_PORT 3343
#define MSRCP_REQUEST 0
#define MSRCP_RESPONSE 1
#define MSRCP_OFFSET_TYPE 6
#define MSRCP_OFFSET_SEQ 12


static const value_string packettypenames[] = {
    {0, "REQUEST" },
    {1, "RESPONSE" },
    {0, NULL}
};

static const value_string headertypenames[] = {
    {0, "MSRCP EXTENSION NONE",},
    {1, "MSRCP IPv4 Pair"},
    {2, "MSRCP IPv6 Pair"},
    {3, "MSRCP Signature"},
    {4, "MSRCP Maximum"},
    {0, NULL}
};

typedef struct _msrcp_conv_info_t {
    wmem_tree_t* pdus;
} msrcp_conv_info_t;

typedef struct _msrcp_transaction_t {
    guint32 req_frame;
    guint32 rep_frame;
    nstime_t req_time;
    guint32 seq;
    gboolean matched;
} msrcp_transaction_t;

static int proto_msrcp = -1;
static int hf_msrcp_id = -1;
static int hf_msrcp_type = -1;
static int hf_msrcp_vers = -1;
static int hf_msrcp_reserved = -1;
static int hf_msrcp_next_header = -1;
static int hf_msrcp_len = -1;
static int hf_msrcp_seq = -1;
static int hf_msrcp_response_in = -1;
static int hf_msrcp_response_to = -1;
static int hf_msrcp_ext_header = -1;
static int hf_msrcp_ext_next_header = -1;
static int hf_msrcp_ext_len = -1;
static int hf_msrcp_ext_res = -1;

static gint ett_msrcp = -1;
static gint ett_msrcp_nxt = -1;

static expert_field ei_msrcp_no_resp = EI_INIT;

// Handles for subparsing
static dissector_handle_t eth_handle;

static int
dissect_msrcp(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data _U_)
{

    /*
        Rough Packet layout:
        Identifier:         4 bytes
        Version:            1 byte
        Reserved:           1 byte
        Type:               2 bytes
        NextHeader:         2 bytes
        Total Length:       2 bytes
        SeqNum:             4 bytes
        ExtHeader:          40 bytes
            NextHeader:     2 bytes
            Length:         2 bytes
            Reserved:       4 bytes
            SrcAddr:        16 bytes
            DstAddr:        16 bytes
    */
    guint tree_offset = 0;

    proto_tree* msrcp_tree, * nxt_tree;
    proto_item* ti, * nxt_ti;
    tvbuff_t* next_tvb;
    guint32         seq;
    guint16         type;

    // variables for our expert analysis
    conversation_t* conv = NULL;
    msrcp_conv_info_t* msrcp_info = NULL;
    msrcp_transaction_t* msrcp_trans = NULL;
    wmem_tree_key_t  key[3];

    type = tvb_get_guint8(tvb, MSRCP_OFFSET_TYPE);
    seq = tvb_get_guint32(tvb, MSRCP_OFFSET_SEQ, ENC_LITTLE_ENDIAN);

    conv = find_or_create_conversation(pinfo);
    msrcp_info = (msrcp_conv_info_t*)conversation_get_proto_data(conv, proto_msrcp);
    if (!msrcp_info)
    {
        msrcp_info = wmem_new(wmem_file_scope(), msrcp_conv_info_t);
        msrcp_info->pdus = wmem_tree_new(wmem_file_scope());
        conversation_add_proto_data(conv, proto_msrcp, msrcp_info);
    }

    key[0].length = 1;
    key[0].key = &seq;
    key[1].length = 1;
    key[1].key = &pinfo->num;
    key[2].length = 0;
    key[2].key = NULL;
    if ((type == MSRCP_REQUEST) || (type == MSRCP_RESPONSE))
    {
        if (!pinfo->fd->visited)
        {
            if (type == MSRCP_REQUEST)
            {
                msrcp_trans = wmem_new(wmem_file_scope(), msrcp_transaction_t);
                msrcp_trans->req_frame = pinfo->num;
                msrcp_trans->rep_frame = 0;
                msrcp_trans->req_time = pinfo->abs_ts;
                msrcp_trans->seq = seq;
                msrcp_trans->matched = FALSE;
                wmem_tree_insert32_array(msrcp_info->pdus, key, (void*)msrcp_trans);
            }
            else
            {
                msrcp_trans = (msrcp_transaction_t*)wmem_tree_lookup32_array_le(msrcp_info->pdus, key);
                if (msrcp_trans)
                {
                    if (msrcp_trans->seq != seq)
                    {
                        msrcp_trans = NULL;
                    }
                    else if (msrcp_trans->rep_frame == 0)
                    {
                        msrcp_trans->rep_frame = pinfo->num;
                        msrcp_trans->matched = TRUE;
                    }
                }
            }
        }
        else
        {
            msrcp_trans = (msrcp_transaction_t*)wmem_tree_lookup32_array_le(msrcp_info->pdus, key);
            if (msrcp_trans)
            {
                if (msrcp_trans->seq != seq)
                {
                    msrcp_trans = NULL;
                }
                else if ((!(type == MSRCP_RESPONSE)) && (msrcp_trans->req_frame != pinfo->num))
                {
                    msrcp_transaction_t* retrans_msrcp = wmem_new(pinfo->pool, msrcp_transaction_t);
                    retrans_msrcp->req_frame = msrcp_trans->req_frame;
                    retrans_msrcp->rep_frame = 0;
                    retrans_msrcp->req_time = pinfo->abs_ts;
                    msrcp_trans = retrans_msrcp;
                }
            }
        }
        if (!msrcp_trans)
        {
            msrcp_trans = wmem_new(pinfo->pool, msrcp_transaction_t);
            msrcp_trans->req_frame = 0;
            msrcp_trans->rep_frame = 0;
            msrcp_trans->req_time = pinfo->abs_ts;
            msrcp_trans->matched = FALSE;
        }
    }


    col_set_str(pinfo->cinfo, COL_PROTOCOL, "MSRCP");
    col_clear(pinfo->cinfo, COL_INFO);
    col_add_fstr(pinfo->cinfo, COL_INFO, "%s ID %d (0x%X)",
        val_to_str_const(type, packettypenames, "MSRCP"), seq, seq);


    ti = proto_tree_add_item(tree, proto_msrcp, tvb, 0, -1, ENC_BIG_ENDIAN);
    proto_item_append_text(ti, "Type %s",
        val_to_str_const(type, packettypenames, "MSRCP"));
    msrcp_tree = proto_item_add_subtree(ti, ett_msrcp);

    if (type == MSRCP_REQUEST || type == MSRCP_RESPONSE)
    {
        proto_item* it;
        proto_tree_add_item(msrcp_tree, hf_msrcp_id, tvb, 0, 4, ENC_BIG_ENDIAN);
        tree_offset += 4;
        proto_tree_add_item(msrcp_tree, hf_msrcp_vers, tvb, tree_offset, 1, ENC_LITTLE_ENDIAN);
        tree_offset += 1;
        proto_tree_add_item(msrcp_tree, hf_msrcp_reserved, tvb, tree_offset, 1, ENC_LITTLE_ENDIAN);
        tree_offset += 1;
        proto_tree_add_item(msrcp_tree, hf_msrcp_type, tvb, tree_offset, 2, ENC_LITTLE_ENDIAN);
        tree_offset += 2;
        proto_tree_add_item(msrcp_tree, hf_msrcp_next_header, tvb, tree_offset, 2, ENC_LITTLE_ENDIAN);
        tree_offset += 2;
        proto_tree_add_item(msrcp_tree, hf_msrcp_len, tvb, tree_offset, 2, ENC_LITTLE_ENDIAN);
        tree_offset += 2;
        it = proto_tree_add_item(msrcp_tree, hf_msrcp_seq, tvb, tree_offset, 4, ENC_LITTLE_ENDIAN);
        tree_offset += 4;

        if (msrcp_trans->matched)
        {
            if ((msrcp_trans->req_frame) && (type == MSRCP_RESPONSE))
            {
                it = proto_tree_add_uint(msrcp_tree, hf_msrcp_response_to, tvb, 0, 0, msrcp_trans->req_frame);
                proto_item_set_generated(it);

            }
            else if ((msrcp_trans->rep_frame) && (type == MSRCP_REQUEST))
            {
                it = proto_tree_add_uint(msrcp_tree, hf_msrcp_response_in, tvb, 0, 0, msrcp_trans->rep_frame);
                proto_item_set_generated(it);
            }
        }
        else
        {
            expert_add_info(pinfo, it, &ei_msrcp_no_resp);
            col_prepend_fence_fstr(pinfo->cinfo, COL_INFO, "[Missing MSRCP Response]");
        }

        nxt_ti = proto_tree_add_item(msrcp_tree, hf_msrcp_ext_header, tvb, 0, 0, ENC_ASCII);
        nxt_tree = proto_item_add_subtree(nxt_ti, ett_msrcp_nxt);
        proto_tree_add_item(nxt_tree, hf_msrcp_ext_next_header, tvb, tree_offset, 2, ENC_LITTLE_ENDIAN);
        tree_offset += 2;
        proto_tree_add_item(nxt_tree, hf_msrcp_ext_len, tvb, tree_offset, 2, ENC_LITTLE_ENDIAN);
        tree_offset += 2;
        proto_tree_add_item(nxt_tree, hf_msrcp_ext_res, tvb, tree_offset, 4, ENC_LITTLE_ENDIAN);

    }
    else
    {
        next_tvb = tvb_new_subset_remaining(tvb, 0);
        call_dissector(eth_handle, next_tvb, pinfo, msrcp_tree);
    }

    return tvb_captured_length(tvb);
}


void
proto_register_msrcp(void)
{
    expert_module_t* expert_msrcp;

    static hf_register_info hf[] = {
    { &hf_msrcp_id,
        { "MSRCP ID", "msrcp.id",
        FT_UINT32, BASE_DEC_HEX,
        NULL, 0x0,
        NULL, HFILL},
    },
    { &hf_msrcp_vers,
        { "Version", "msrcp.vers",
        FT_UINT8, BASE_DEC,
        NULL, 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_reserved,
        { "Reserved", "msrcp.reserved",
        FT_UINT8, BASE_DEC,
        NULL, 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_type,
        { "MSRCP Type", "msrcp.type",
        FT_UINT16, BASE_DEC,
        VALS(packettypenames), 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_next_header,
        { "Next Header", "msrcp.nxt_header",
        FT_UINT16, BASE_DEC,
        VALS(headertypenames), 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_len,
        { "Total Length", "msrcp.len",
        FT_UINT16, BASE_DEC,
        NULL, 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_seq,
        { "Sequence Number", "msrcp.seq",
        FT_UINT32, BASE_DEC_HEX,
        NULL, 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_response_in,
        { "Response In", "msrcp.response_in",
        FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
        "The response to this MSRCP request is in frame", HFILL}
    },
    { &hf_msrcp_response_to,
        { "Request In", "msrcp.response_to",
        FT_FRAMENUM,BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
        "This is a response to an MSRCP request in frame", HFILL}
    },
    { &hf_msrcp_ext_header,
        { "Extension Header", "msrcp.ext",
        FT_NONE, BASE_NONE,
        NULL, 0x0,
        NULL, HFILL}
    },
    { &hf_msrcp_ext_next_header,
        { "Next Header", "msrcp.ext_nxt_header",
        FT_UINT16, BASE_DEC,
        VALS(headertypenames), 0x0, NULL, HFILL}
    },
    { &hf_msrcp_ext_len,
        { "Length", "msrcp.ext_len",
        FT_UINT16, BASE_DEC,
        NULL, 0x0, NULL, HFILL}
    },
    { &hf_msrcp_ext_res,
        { "Reserved", "msrcp.nxt_res",
        FT_UINT32, BASE_HEX,
        NULL, 0x0,
        NULL, HFILL}
    },
    };

    static gint* ett[] = {
            &ett_msrcp,
            &ett_msrcp_nxt
    };

    static ei_register_info ei[] = {
        {
            &ei_msrcp_no_resp,
            { "msrcp.no_resp", PI_SEQUENCE, PI_WARN,
              "MSRCP Response not found", EXPFILL }
        }
    };

    proto_msrcp = proto_register_protocol("MSRCP Protocol", "MSRCP", "msrcp");

    proto_register_field_array(proto_msrcp, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
    expert_msrcp = expert_register_protocol(proto_msrcp);
    expert_register_field_array(expert_msrcp, ei, array_length(ei));

    msrcp_handle = register_dissector("msrcp", dissect_msrcp, proto_msrcp);
}

void
proto_reg_handoff_msrcp(void)
{
    eth_handle = find_dissector_add_dependency("eth_withoutfcs", proto_msrcp);
    dissector_add_uint("udp.port", MSRCP_PORT, msrcp_handle);
}
