/*
 * Dissector for ZPU packets (communication with X300 firmware)
 *
 * Copyright 2013-2014 Ettus Research LLC
 * Copyright 2019 Ettus Research, a National Instruments brand
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/* Format of ZPU packets is defined in x300_fw_commons.h,
 * x300_fw_comms_t.
 *
 * Reminder:
 *
 *  uint32_t flags; (ack, error, peek, poke)
 *  uint32_t sequence;
 *  uint32_t addr;
 *  uint32_t data;
 */

#include <glib.h>
#ifdef __cplusplus
extern "C" {
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <epan/packet.h>

#ifdef __cplusplus
}
#endif

#include <ctype.h>
#include <stdio.h>
#include <endian.h>

#include "../../../../host/lib/usrp/x300/x300_fw_common.h"
#include "zpu_addr_names.h"

#define LOG_HEADER  "[ZPU] "

#ifndef min
#define min(a,b)    ((a<b)?a:b)
#endif // min

const unsigned int FW_PORT = X300_FW_COMMS_UDP_PORT;

static int proto_zpu = -1;
static int hf_zpu_flags = -1;
static int hf_zpu_flags_ack = -1;
static int hf_zpu_flags_error = -1;
static int hf_zpu_flags_poke = -1;
static int hf_zpu_flags_peek = -1;
static int hf_zpu_seq = -1;
static int hf_zpu_addr = -1;
static int hf_zpu_data = -1;
static int hf_zpu_shmem_addr = -1;
static int hf_zpu_shmem_addr_name = -1;

/* Subtree handles: set by register_subtree_array */
static gint ett_zpu = -1;
static gint ett_zpu_flags = -1;
//static gint ett_zpu_shmem = -1;

/* Forward-declare the dissector functions */
void proto_register_zpu(void);
void proto_reg_handoff_zpu(void);
static int dissect_zpu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data);

/* The dissector itself */
static int dissect_zpu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    proto_item *item;
    proto_tree *zpu_tree;
    proto_item *flags_item;
    proto_tree *flags_tree;
    gint len;

    gboolean is_network;
    gint endianness;

    if (pinfo->match_uint == FW_PORT) {
        is_network = TRUE;
        endianness = ENC_BIG_ENDIAN;
    }
    else {
        is_network = FALSE;
        endianness = ENC_LITTLE_ENDIAN;
    }

    len = tvb_reported_length(tvb);

    col_append_str(pinfo->cinfo, COL_PROTOCOL, "/ZPU");
    /*col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "ZPU", tvb_format_text_wsp(tvb, 0, len));*/
    col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "ZPU");

    if (tree)
    {
        item = proto_tree_add_item(tree, proto_zpu, tvb, 0, min(16, len), ENC_NA);

	// Dissect 'flags'
        if (len >= 4)
        {
            zpu_tree = proto_item_add_subtree(item, ett_zpu);

            flags_item = proto_tree_add_item(zpu_tree, hf_zpu_flags, tvb, 0, 4, endianness);
            flags_tree = proto_item_add_subtree(flags_item, ett_zpu_flags);

            proto_tree_add_item(flags_tree, hf_zpu_flags_ack, tvb, 0, 4, ENC_NA);
            proto_tree_add_item(flags_tree, hf_zpu_flags_error, tvb, 0, 4, ENC_NA);
            proto_tree_add_item(flags_tree, hf_zpu_flags_poke, tvb, 0, 4, ENC_NA);
            proto_tree_add_item(flags_tree, hf_zpu_flags_peek, tvb, 0, 4, ENC_NA);

            // Dissect 'sequence number'
            if (len >= 8)
            {
                proto_tree_add_item(zpu_tree, hf_zpu_seq, tvb, 4, 4, ENC_NA);

                // Dissect 'address'
                if (len >= 12)
                {
                    proto_tree_add_item(zpu_tree, hf_zpu_addr, tvb, 8, 4, ENC_NA);
#if VERSION_MAJOR == 1
                    guint8 *bytes = tvb_get_string(tvb, 8, 4);
#elif VERSION_MAJOR == 2
                    guint8 *bytes = tvb_get_string(wmem_packet_scope(), tvb, 8, 4);
#elif VERSION_MAJOR == 3
                    guint8 *bytes = tvb_get_string_enc(wmem_packet_scope(), tvb, 8, 4, ENC_ASCII);
#else
#error Wireshark version not found or not compatible
#endif
                    unsigned int addr = 0;
                    memcpy(&addr, bytes, 4);
                    /* TODO proper endianness handling */
                    addr = (addr >> 24) | ((addr & 0x00FF0000) >> 8) | ((addr & 0x0000FF00) << 8) | ((addr & 0x000000FF) << 24);
                    /* TODO check the actual size of shmem instead of this constant */
                    if (addr >= X300_FW_SHMEM_BASE && addr <= X300_FW_SHMEM_BASE + 0x2000)
                    {
                        proto_item *shmem_addr_item = NULL;

                        // Convert the address to a register number (32-bit registers == 4-byte registers)
                        addr -= X300_FW_SHMEM_BASE;
                        addr /= 4;

                        shmem_addr_item = proto_tree_add_uint(zpu_tree, hf_zpu_shmem_addr, tvb, 8, 4, addr);
                        proto_item_append_text(shmem_addr_item, ", Register name: %s",
                                val_to_str(addr, X300_SHMEM_NAMES, "Unknown (0x%04x)")
                        );

                    }

                    // Dissect 'data'
                    if (len >= 16)
                    {
                        proto_tree_add_item(zpu_tree, hf_zpu_data, tvb, 12, 4, ENC_NA);
                    }
                }
            }
        }
    }
    return len;
}

void proto_register_zpu(void)
{
    static hf_register_info hf[] = {
        { &hf_zpu_flags,
            { "Flags", "zpu.flags",
                FT_UINT32, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
        },
            { &hf_zpu_flags_ack,
                { "ACK", "zpu.flags.ack",
                    FT_BOOLEAN, 32,
                    TFS(&tfs_set_notset), 0x1,
                    NULL, HFILL }
            },
            { &hf_zpu_flags_error,
                { "Error", "zpu.flags.error",
                    FT_BOOLEAN, 32,
                    TFS(&tfs_set_notset), 0x2,
                    NULL, HFILL }
            },
            { &hf_zpu_flags_poke,
                { "Poke", "zpu.flags.poke",
                    FT_BOOLEAN, 32,
                    TFS(&tfs_set_notset), 0x4,
                    NULL, HFILL }
            },
            { &hf_zpu_flags_peek,
                { "Peek", "zpu.flags.peek",
                    FT_BOOLEAN, 32,
                    TFS(&tfs_set_notset), 0x8,
                    NULL, HFILL }
            },
        { &hf_zpu_seq,
            { "Sequence ID", "zpu.seq",
                FT_UINT32, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
        },
        { &hf_zpu_addr,
            { "Address", "zpu.addr",
                FT_UINT32, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
        },
        { &hf_zpu_shmem_addr,
            { "SHMEM section", "zpu.shmem",
                FT_UINT32, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
        },
        { &hf_zpu_data,
            { "Data", "zpu.data",
                FT_UINT32, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
        },
    };

    static gint *ett[] = {
        &ett_zpu,
        &ett_zpu_flags,
        //&ett_zpu_shmem
    };

    proto_zpu = proto_register_protocol("ZPU FW", "ZPU", "zpu");
    proto_register_field_array(proto_zpu, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
    register_dissector("zpu", dissect_zpu, proto_zpu);
}

/* Handler registration */
void proto_reg_handoff_zpu(void)
{
    /* register dissector for UDP packets */
    static dissector_handle_t zpu_handle;
    zpu_handle = create_dissector_handle(dissect_zpu, proto_zpu);
    dissector_add_uint("udp.port", FW_PORT, zpu_handle);
}
