/* packet-srt.c
 * Routines for Secure Reliable Transport Protocol dissection
 * Copyright (c) 2018 Haivision Systems Inc. <info@srtalliance.org>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * SRT is an open source video transport protocol and technology stack
 * that optimizes streaming performance across unpredictable networks
 * with secure streams and easy firewall traversal, bringing the best
 * quality live video over the worst networks.
 *
 * Internet draft:
 * https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01
 *
 * Open-source implementation:
 * https://github.com/Haivision/srt
 */

#include <config.h>

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/conversation.h>
#include <wsutil/str_util.h>
#include <wsutil/inet_addr.h>
#include <wsutil/str_util.h>

/* Prototypes */
void proto_reg_handoff_srt(void);
void proto_register_srt(void);

/* Initialize the protocol */
static int proto_srt = -1;
static int hf_srt_iscontrol = -1;
static int hf_srt_type = -1;
static int hf_srt_exttype = -1;
static int hf_srt_exttype_none = -1;
static int hf_srt_seqno = -1;
static int hf_srt_ack_seqno = -1;
static int hf_srt_ackno = -1;
static int hf_srt_msgno = -1;
static int hf_srt_msgno_pb = -1;
static int hf_srt_msgno_inorder = -1;
static int hf_srt_msgno_enctypes = -1;
static int hf_srt_msgno_rexmit = -1;
static int hf_srt_timestamp = -1;
static int hf_srt_id = -1;
static int hf_srt_addinfo = -1;
static int hf_srt_rtt = -1;
static int hf_srt_rttvar = -1;
static int hf_srt_bufavail = -1;
static int hf_srt_rate = -1;
static int hf_srt_bandwidth = -1;
static int hf_srt_rcvrate = -1;

/* SRT Handshake */
static int hf_srt_handshake_version = -1;
static int hf_srt_handshake_type_v4 = -1;
static int hf_srt_handshake_enc_field_v5 = -1;
static int hf_srt_handshake_ext_field_v5 = -1;
static int hf_srt_handshake_ext_field_v5_flag_hsreq = -1;
static int hf_srt_handshake_ext_field_v5_flag_kmreq = -1;
static int hf_srt_handshake_ext_field_v5_flag_config = -1;
static int hf_srt_handshake_isn = -1;
static int hf_srt_handshake_mtu = -1;
static int hf_srt_handshake_flow_window = -1;
static int hf_srt_handshake_reqtype = -1;
static int hf_srt_handshake_failure_type = -1;
static int hf_srt_handshake_id = -1;
static int hf_srt_handshake_cookie = -1;
static int hf_srt_handshake_peerip = -1;
/* SRT Handshake Extension */
static int hf_srt_handshake_ext_version = -1;
static int hf_srt_handshake_ext_flags = -1;
static int hf_srt_handshake_ext_flag_tsbpd_snd = -1;
static int hf_srt_handshake_ext_flag_tsbpd_rcv = -1;
static int hf_srt_handshake_ext_flag_haicrypt = -1;
static int hf_srt_handshake_ext_flag_tlpkt_drop = -1;
static int hf_srt_handshake_ext_flag_nak_report = -1;
static int hf_srt_handshake_ext_flag_rexmit = -1;
static int hf_srt_handshake_ext_flag_stream = -1;

static int hf_srt_srths_blocktype = -1;
static int hf_srt_srths_blocklen = -1;
static int hf_srt_srths_agent_latency = -1; // TSBPD delay
static int hf_srt_srths_peer_latency = -1; // TSBPD delay
static int hf_srt_srtkm_msg = -1;
static int hf_srt_srtkm_error = -1;
static int hf_srt_srths_sid = -1;
static int hf_srt_srths_congestcontrol = -1;

static gint ett_srt = -1;
static gint ett_srt_handshake_ext_flags = -1;
static gint ett_srt_handshake_ext_field_flags = -1;

static expert_field ei_srt_nak_seqno = EI_INIT;
static expert_field ei_srt_hs_ext_hsreq_len = EI_INIT;
static expert_field ei_srt_hs_ext_type = EI_INIT;

static dissector_handle_t srt_udp_handle;


/* This defines the firstmost bit of the packet, so it can stay this way. */
#define SRT_TYPE_DATA            0
#define SRT_TYPE_CONTROL        1
#define SRT_CONTROL_MASK (~0x80000000)

#define SRT_LOSS_SEQUENCE_FIRST 0x80000000
#define SRT_LOSS_SEQUENCE_MASK  (~SRT_LOSS_SEQUENCE_FIRST)

enum UDTSockType
{
    SRT_UNDEFINED = 0, /* initial trap representation */
    SRT_STREAM = 1,
    SRT_DGRAM = 2,
    SRT_MAGIC_CODE = 0x4A17
};

/* Handshake Extended Field Flags */
#define SRT_OPT_FIELD_LEN            32
#define SRT_OPT_TSBPDSND        (1 << 0)
#define SRT_OPT_TSBPDRCV        (1 << 1)
#define SRT_OPT_HAICRYPT        (1 << 2)
#define SRT_OPT_TLPKTDROP       (1 << 3)
#define SRT_OPT_NAKREPORT       (1 << 4)
#define SRT_OPT_REXMITFLG       (1 << 5)
#define SRT_OPT_STREAM          (1 << 6)


/* Extended Handshake Flags */
#define SRT_HS_V5_EXT_FIELD_LEN                16
#define SRT_HS_V5_EXT_FIELD_HSREQ         (1 << 0)
#define SRT_HS_V5_EXT_FIELD_KMREQ         (1 << 1)
#define SRT_HS_V5_EXT_FIELD_CONFIG        (1 << 2)
#define SRT_HS_V5_EXT_FIELD_MAGIC   SRT_MAGIC_CODE

/* Message number field and single bit flags */
#define SRT_MSGNO_FF_FIRST_B (2 << (32-2))
#define SRT_MSGNO_FF_LAST_B (1 << (32-2))
#define SRT_MSGNO_FF_MASK (SRT_MSGNO_FF_FIRST_B | SRT_MSGNO_FF_LAST_B)

enum PacketBoundary
{
    PB_SUBSEQUENT = 0,
    /* 01: last packet of a message */
    PB_LAST = 1,
    /* 10: first packet of a message */
    PB_FIRST = 2,
    /* 11: solo message packet */
    PB_SOLO = 3,
};


#define SRT_MSGNO_INORDER (1 << (32-3))        /* 0x20000000 */

#define SRT_MSGNO_ENCTYPE (3 << (32-5))        /* 0x18000000 */

#define SRT_MSGNO_EK_NONE 0
#define SRT_MSGNO_EK_EVEN 1
#define SRT_MSGNO_EK_ODD  2

#define SRT_MSGNO_REXMIT  (1 << (32-6))        /* 0x04000000 */

/* Rest of the bits are for message sequence number */
#define SRT_MSGNO_MSGNO_MASK 0x03ffffff


/* The message types used by UDT protocol. This is a part of UDT
 * protocol and should never be changed.
 */
enum UDTMessageType
{
    UMSG_HANDSHAKE = 0, // Connection Handshake. Control: see @a CHandShake.
    UMSG_KEEPALIVE = 1, // Keep-alive.
    UMSG_ACK = 2, // Acknowledgement. Control: past-the-end sequence number up to which packets have been received.
    UMSG_LOSSREPORT = 3, // Negative Acknowledgement (NACK). Control: Loss list.
    UMSG_CGWARNING = 4, // Congestion warning.
    UMSG_SHUTDOWN = 5, // Shutdown.
    UMSG_ACKACK = 6, // Acknowledgement of Acknowledgement. Add info: The ACK sequence number
    UMSG_DROPREQ = 7, // Message Drop Request. Add info: Message ID. Control Info: (first, last) number of the message.
    UMSG_PEERERROR = 8, // Signal from the Peer side. Add info: Error code.
    /* ... add extra code types here */
    UMSG_END_OF_TYPES,
    UMSG_EXT = 0x7FFF // For the use of user-defined control packets.
};

// Adapted constants
#define SRT_CMD_HSREQ       1
#define SRT_CMD_HSRSP       2
#define SRT_CMD_KMREQ       3
#define SRT_CMD_KMRSP       4
#define SRT_CMD_SID         5
#define SRT_CMD_CONGESTCTRL 6

enum SrtDataStruct
{
    SRT_HS_VERSION = 0,
    SRT_HS_FLAGS,
    SRT_HS_EXTRAS,

    // Keep it always last
    SRT_HS__SIZE
};


enum UDTRequestType
{
    URQ_AGREEMENT     =   -2,
    URQ_CONCLUSION    =   -1,
    URQ_WAVEAHAND     =    0,
    URQ_INDUCTION     =    1,

    URQ_FAILURE_TYPES = 1000
};


enum SRT_KM_STATE
{
    SRT_KM_S_UNSECURED = 0,      ///< No encryption
    SRT_KM_S_SECURING  = 1,      ///< Stream encrypted, exchanging Keying Material
    SRT_KM_S_SECURED   = 2,      ///< Stream encrypted, keying Material exchanged, decrypting ok.
    SRT_KM_S_NOSECRET  = 3,      ///< Stream encrypted and no secret to decrypt Keying Material
    SRT_KM_S_BADSECRET = 4       ///< Stream encrypted and wrong secret, cannot decrypt Keying Material
};

static const value_string srt_ctrlmsg_types[] = {
    {UMSG_HANDSHAKE,  "UMSG_HANDSHAKE"},
    {UMSG_KEEPALIVE,  "UMSG_KEEPALIVE"},
    {UMSG_ACK,        "UMSG_ACK"},
    {UMSG_LOSSREPORT, "UMSG_LOSSREPORT"},
    {UMSG_CGWARNING,  "UMSG_CGWARNING"},
    {UMSG_SHUTDOWN,   "UMSG_SHUTDOWN"},
    {UMSG_ACKACK,     "UMSG_ACKACK"},
    {UMSG_DROPREQ,    "UMSG_DROPREQ"},
    {UMSG_PEERERROR,  "UMSG_PEERERROR"},
    {UMSG_EXT,        "UMSG_EXT"},

    {0, NULL},
};

static const value_string srt_ctrlmsg_exttypes[] = {
    {SRT_CMD_HSREQ,       "SRT_CMD_HSREQ"},
    {SRT_CMD_HSRSP,       "SRT_CMD_HSRSP"},
    {SRT_CMD_KMREQ,       "SRT_CMD_KMREQ"},
    {SRT_CMD_KMRSP,       "SRT_CMD_KMRSP"},
    {SRT_CMD_SID,         "SRT_CMD_SID"},
    {SRT_CMD_CONGESTCTRL, "SRT_CMD_CONGESTCTRL"},

    { 0, NULL },
};

static const value_string srt_hsv4_socket_types[] = {
    {SRT_STREAM,          "SRT_STREAM"},
    {SRT_DGRAM,           "SRT_DGRAM"},
    {0, NULL},
};


static const value_string srt_handshake_enc_field[] = {
    {0, "PBKEYLEN not advertised"},
    {2, "AES-128" },
    {3, "AES-192" },
    {4, "AES-256" },
    {0, NULL},
};


static const true_false_string srt_packet_types = {
    "CONTROL",  /* 1 */
    "DATA"      /* 0 */
};

static const value_string srt_pb_types[] = {
    {PB_SUBSEQUENT, "PB_SUBSEQUENT"},
    {PB_LAST,       "PB_LAST"},
    {PB_FIRST,      "PB_FIRST"},
    {PB_SOLO,       "PB_SOLO"},
    {0, NULL},
};

static const value_string srt_msgno_enctypes[] = {
    {SRT_MSGNO_EK_NONE, "Not encrypted"},
    {SRT_MSGNO_EK_EVEN, "Encrypted ith even key"},
    {SRT_MSGNO_EK_ODD,  "Encrypted with odd key"},
    {0, NULL},
};

static const true_false_string srt_msgno_rexmit = {
    "Retransmitted", /* 1 */
    "Original"      /* 0 */
};

static const value_string srt_hs_request_types[] = {
    {URQ_INDUCTION,  "URQ_INDUCTION (c/l invocation)"},
    {URQ_CONCLUSION, "URQ_CONCLUSION"},
    {URQ_WAVEAHAND,  "URQ_WAVEAHAND (rendezvous invocation)"},
    {URQ_AGREEMENT,  "URQ_AGREEMENT (rendezvous finalization)"},
    {0, NULL}
};

static const value_string srt_enc_kmstate[] = {
    {SRT_KM_S_UNSECURED, "UNSECURED"},
    {SRT_KM_S_SECURING,  "SECURING"},
    {SRT_KM_S_SECURED,   "SECURED"},
    {SRT_KM_S_NOSECRET,  "NOSECRET"},
    {SRT_KM_S_BADSECRET, "BADSECRET"},

    {0, NULL},
};


/*
 * XXX To be added later to extract correct IPv4/IPv6 address from 16 bytes of data
 * static void srt_tree_add_ipaddr( proto_tree *tree, const int hf, tvbuff_t *tvb, gint offset)
 * {
 *
 * }
 */

#define IP_BUFFER_SIZE 64

static void srt_format_ip_address(gchar* dest, size_t dest_size, const gchar* ptr)
{
    /* Initial IPv4 check.
     * The address is considered IPv4 if:
     * byte[0] and byte[3] != 0
     * bytes[4...16] == 0
     */

    ws_in4_addr ia4;
    ws_in6_addr ia6;
    guint32* p;
    int i, j;

    if (ptr[0] != 0 && ptr[3] != 0)
    {
        for (i = 4; i < 16; ++i)
        {
            if ( ptr[i] == 0 )
                continue;

            /* This is not an IP4 */
            p = (guint32*) &ia6;
            for (j = 0; j < 4; ++j)
                p[j] = g_ntohl(((guint32*)ptr)[j]);

            ws_inet_ntop6(&ia6, dest, (guint) dest_size);
            return;
        }
    }

    // There's one small problem: the contents of the handshake
    // goes in LITTLE ENDIAN. That's an initial problem of UDT.
    // The address must be inverted.

    // Here's IPv4, so invert only one l.
    ia4 = g_ntohl(*((const guint32*)ptr));

    ws_inet_ntop4(&ia4, dest, (guint) dest_size);
    return;
}


static void srt_format_hs_ext_hsreq(proto_tree* tree, tvbuff_t* tvb, int baseoff)
{
    proto_item* pi;
    guint32 version = 0;
    pi = proto_tree_add_item_ret_uint(tree, hf_srt_handshake_ext_version, tvb, baseoff, 4, ENC_BIG_ENDIAN, &version);

    const int vminor = (version >>  8) & 0xff;
    const int vmajor = (version >> 16) & 0xff;
    const int vpatch = version & 0xff;
    proto_item_append_text(pi, " (%d.%d.%d)", vmajor, vminor, vpatch);

    static int * const ext_hs_flags[] = {
        &hf_srt_handshake_ext_flag_tsbpd_snd,
        &hf_srt_handshake_ext_flag_tsbpd_rcv,
        &hf_srt_handshake_ext_flag_haicrypt,
        &hf_srt_handshake_ext_flag_tlpkt_drop,
        &hf_srt_handshake_ext_flag_nak_report,
        &hf_srt_handshake_ext_flag_rexmit,
        &hf_srt_handshake_ext_flag_stream,
        NULL
    };

    proto_tree_add_bitmask_with_flags(tree, tvb, baseoff + 4, hf_srt_handshake_ext_flags,
                                      ett_srt_handshake_ext_flags, ext_hs_flags, ENC_NA, BMT_NO_APPEND);

    proto_tree_add_item(tree, hf_srt_srths_peer_latency, tvb, baseoff+8, 2, ENC_BIG_ENDIAN);
    proto_tree_add_item(tree, hf_srt_srths_agent_latency, tvb, baseoff+10, 2, ENC_BIG_ENDIAN);
}

static void srt_format_kmx(proto_tree* tree, tvbuff_t* tvb, int baseoff, int blocklen)
{
    if (blocklen == 4)
    {
        // Error report. Format as KMX state.
        proto_tree_add_item(tree, hf_srt_srtkm_error, tvb, baseoff, 4, ENC_NA);
    }
    else
    {
        proto_tree_add_item(tree, hf_srt_srtkm_msg, tvb, baseoff, blocklen, ENC_NA);
    }
}

// Wireshark dissector doesn't have a possibility to format enum-collected flags.
static void dissect_srt_hs_ext_field(proto_tree* tree,
        tvbuff_t* tvb, int baseoff)
{
    static const gint ext_field_len = 2;

    const int bits = tvb_get_ntohs(tvb, baseoff);
    if (bits == SRT_HS_V5_EXT_FIELD_MAGIC)
    {
        proto_item* pi = proto_tree_add_item(tree, hf_srt_handshake_ext_field_v5,
                                             tvb, baseoff, ext_field_len, ENC_BIG_ENDIAN);
        proto_item_append_text(pi, ": HSv5 MAGIC");
        return;
    }

    static int * const ext_hs_ext_field_flags[] = {
        &hf_srt_handshake_ext_field_v5_flag_hsreq,
        &hf_srt_handshake_ext_field_v5_flag_kmreq,
        &hf_srt_handshake_ext_field_v5_flag_config,
        NULL
    };

    proto_tree_add_bitmask_with_flags(tree, tvb, baseoff, hf_srt_handshake_ext_field_v5,
                                      ett_srt_handshake_ext_field_flags, ext_hs_ext_field_flags, ENC_NA, BMT_NO_APPEND);

    return;
}


/*
 * UTF-8 string packed as 32 bit little endian words (what?!)
 * https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01#section-3.2.1.3
 *
 * THe spec says
 *
 *     The actual size is determined by the Extension Length field,
 *     which defines the length in four byte blocks.  If the actual
 *     payload is less than the declared length, the remaining bytes
 *     are set to zeros.
 *
 *     The content is stored as 32-bit little endian words.
 *
 * This means that the octets of the string are in the rather peculiar
 * order:
 *
 *    octet 3
 *    octet 2
 *    octet 1
 *    octet 0
 *    octet 8
 *    octet 7
 *    octet 6
 *    octet 5
 *
 * and so on, with null padding (not null termination).
 */
static void format_text_reorder_32(proto_tree* tree, tvbuff_t* tvb, int hfinfo, int baseoff, int blocklen)
{
    wmem_strbuf_t *sid = wmem_strbuf_create(wmem_packet_scope());
    const char *str;
    size_t len;
    for (int ii = 0; ii < blocklen; ii += 4)
    {
        //
        // Yes, this is fetching the 32-bit word as big-endian
        // rather than little-endian.
        //
        // However, it's then taking the low-order byte of the
        // result as the first octet, followed by the byte above
        // that, followed by the byte above that, followed by
        // the high-order byte.
        //
        // This is equivalent t fetching the 32-bit word as little-endian
        // and then taking the high-order byte of the result as the
        // first octet, etc.
        //
        // And both of those implement what's described above.
        //
        // No, I have no idea why they chose this representation for
        // strings.
        //
        const guint32 u = tvb_get_ntohl(tvb, baseoff + ii);
        wmem_strbuf_append_c(sid, 0xFF & (u >>  0));
        wmem_strbuf_append_c(sid, 0xFF & (u >>  8));
        wmem_strbuf_append_c(sid, 0xFF & (u >> 16));
        wmem_strbuf_append_c(sid, 0xFF & (u >> 24));
    }
    if (!wmem_strbuf_utf8_validate(sid, NULL))
        wmem_strbuf_utf8_make_valid(sid);
    str = wmem_strbuf_get_str(sid);
    len = wmem_strbuf_get_len(sid);
    while (len > 0 && str[len-1] == '\0')
        len--;
    proto_tree_add_string(tree, hfinfo, tvb, baseoff, blocklen,
                          format_text(wmem_packet_scope(), str, len));
}


/* Code to actually dissect the packets
 *
 */
static void
dissect_srt_control_packet(tvbuff_t *tvb, packet_info* pinfo,
                           proto_tree *tree, proto_item *srt_item)
{
    guint32 type    = 0;
    guint32 exttype = 0;

    proto_tree_add_item_ret_uint(tree, hf_srt_type, tvb, 0, 2,
                                 ENC_BIG_ENDIAN, &type);

    if ( type != UMSG_EXT )
        proto_tree_add_item(tree, hf_srt_exttype_none, tvb, 2, 2,
                            ENC_BIG_ENDIAN);
    else
        proto_tree_add_item_ret_uint(tree, hf_srt_exttype, tvb, 2, 2,
                                     ENC_BIG_ENDIAN, &exttype);

    switch (type)
    {
    case UMSG_EXT:
        col_add_fstr(pinfo->cinfo, COL_INFO, "Control/ext: %s  socket: 0x%x",
                        val_to_str(exttype, srt_ctrlmsg_exttypes,
                                   "Unknown SRT Control Type (0x%x)"),
                        tvb_get_ntohl(tvb, 12));
        break;
    case UMSG_ACK:
        col_add_fstr(pinfo->cinfo, COL_INFO, "Control: UMSG_ACK %d ackseq: %d  socket: 0x%x",
                        tvb_get_ntohl(tvb, 4),
                        tvb_get_ntohl(tvb, 16),
                        tvb_get_ntohl(tvb, 12));
        break;
    case UMSG_ACKACK:
        col_add_fstr(pinfo->cinfo, COL_INFO, "Control: UMSG_ACKACK %d socket: 0x%x",
                        tvb_get_ntohl(tvb, 4),
                        tvb_get_ntohl(tvb, 12));
        break;
    default:
        col_add_fstr(pinfo->cinfo, COL_INFO, "Control: %s  socket: 0x%x",
                        val_to_str(type, srt_ctrlmsg_types,
                                   "Unknown UDT Control Type (%x)"),
                        tvb_get_ntohl(tvb, 12));
        break;
    }

    switch (type)
    {
    case UMSG_ACK:
    case UMSG_ACKACK:
        proto_tree_add_item(tree, hf_srt_ackno, tvb, 4, 4,
                            ENC_BIG_ENDIAN);
        break;
    case UMSG_DROPREQ:
        proto_tree_add_item(tree, hf_srt_msgno, tvb, 4, 4,
                            ENC_BIG_ENDIAN);
        break;
    default:
        proto_tree_add_item(tree, hf_srt_addinfo, tvb, 4, 4,
                            ENC_BIG_ENDIAN);
        break;
    }
    proto_tree_add_item(tree, hf_srt_timestamp, tvb, 8, 4,
                        ENC_BIG_ENDIAN);
    proto_tree_add_item(tree, hf_srt_id, tvb, 12, 4,
                        ENC_BIG_ENDIAN);

    switch (type)
    {
    case UMSG_HANDSHAKE:
        {
            char ipbuf[IP_BUFFER_SIZE];

            const int version = tvb_get_ntohl(tvb, 16);
            const int final_length = tvb_reported_length(tvb);
            int baselen = 64;
            int handshake_reqtype;

            /* This contains the handshake version (currently 4 or 5) */
            proto_tree_add_item(tree, hf_srt_handshake_version, tvb,
                                16,  4, ENC_BIG_ENDIAN);

            /* Version 4 embraces both HSv4 listener URQ_INDUCTION response
             * and HSv5 caller URQ_INDUCTION request. In both these cases the
             * value is interpreted as socket type (UDT legacy). With version 5
             * the first message is the listener's URQ_INDUCTION response, where
             * the layout in the type is already the MAGIC in the lower block,
             * and ENC FLAGS in the upper block. The next caller's URQ_CONCLUSION
             * will have SRT HS Extension block flags in the lower block.
             */
            if (version == 4)
            {
                proto_tree_add_item(tree, hf_srt_handshake_type_v4, tvb,
                                    20,  4, ENC_BIG_ENDIAN);
            }
            else
            {
                /* Both the PBKEYLEN-ad and magic are used in HSv5 induction. */
                proto_tree_add_item(tree, hf_srt_handshake_enc_field_v5, tvb,
                                    20, 2, ENC_BIG_ENDIAN);

                dissect_srt_hs_ext_field(tree, tvb, 22);    /* 2 bytes */
            }

            proto_tree_add_item(tree, hf_srt_handshake_isn, tvb,
                        24,  4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_handshake_mtu, tvb,
                        28,  4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_handshake_flow_window, tvb,
                        32,  4, ENC_BIG_ENDIAN);
            handshake_reqtype = tvb_get_ntohl(tvb, 36);
            if (handshake_reqtype < URQ_FAILURE_TYPES)
            {
                proto_tree_add_item(tree, hf_srt_handshake_reqtype, tvb,
                        36,  4, ENC_BIG_ENDIAN);
            }
            else
            {
                int error_code = handshake_reqtype - URQ_FAILURE_TYPES;
                proto_tree_add_uint_format_value(tree, hf_srt_handshake_failure_type, tvb, 36, 4, handshake_reqtype,
                        "%d (%s)", error_code,
                        error_code < 1000 ? "SRT internal" :
                        error_code < 2000 ? "SRT predefined" : "user-defined");
            }

            proto_tree_add_item(tree, hf_srt_handshake_id, tvb,
                        40,  4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_handshake_cookie, tvb,
                        44,  4, ENC_BIG_ENDIAN);

            srt_format_ip_address(ipbuf, sizeof ipbuf, (const gchar *)tvb_memdup(wmem_packet_scope(), tvb, 48, 16));

            proto_tree_add_string(tree, hf_srt_handshake_peerip, tvb,
                                  48, 16, ipbuf);
            if (final_length > baselen)
            {
                /* Extract SRT handshake extension blocks
                    * and increase baselen accordingly.
                    */
                int begin = baselen;
                for (;;)
                {
                    const guint16 blockid  = tvb_get_ntohs(tvb, begin);
                    const guint16 blocklen = tvb_get_ntohs(tvb, begin + 2);

                    proto_tree_add_item(tree, hf_srt_srths_blocktype, tvb,
                                        begin, 2, ENC_BIG_ENDIAN);
                    proto_tree_add_item(tree, hf_srt_srths_blocklen, tvb,
                                        begin+2, 2, ENC_BIG_ENDIAN);

                    // Shift to the payload
                    begin += 4;

                    switch (blockid)
                    {
                    case SRT_CMD_HSREQ:
                    case SRT_CMD_HSRSP:
                        if (blocklen == 3)
                        {
                            srt_format_hs_ext_hsreq(tree, tvb, begin);
                        }
                        else
                        {
                            /* blocklen should be 3, that corresponds to (3 * 4) = 12 bytes.
                             * Otherwise the format is unknown.*/
                            proto_tree_add_expert_format(tree, pinfo, &ei_srt_hs_ext_hsreq_len,
                                    tvb, begin, 4 * blocklen, "Actual length is %u",
                                    blocklen);
                        }
                        break;

                    case SRT_CMD_KMREQ:
                    case SRT_CMD_KMRSP:
                        // Rely on the extracted blocklen
                        srt_format_kmx(tree, tvb, begin, blocklen*4);
                        break;

                    case SRT_CMD_SID:
                        format_text_reorder_32(tree, tvb, hf_srt_srths_sid, begin, 4 * blocklen);
                        break;

                    case SRT_CMD_CONGESTCTRL:
                        format_text_reorder_32(tree, tvb, hf_srt_srths_congestcontrol, begin, 4 * blocklen);
                        break;

                    default:
                        proto_tree_add_expert_format(tree, pinfo, &ei_srt_hs_ext_type,
                                    tvb, begin, 4 * blocklen, "Ext Type value is %u",
                                    blockid);
                        break;
                    }

                    /* Move the index pointer past the block and repeat. */
                    begin += blocklen * 4;

                    /* OK, once one block is done, interrupt the loop. */
                    if (begin >= final_length)
                        break;
                }

                baselen = begin;
            }

            proto_item_set_len(srt_item, baselen);
        }
        break;
    case UMSG_ACK:
        {
            guint len = tvb_reported_length(tvb);

            proto_tree_add_item(tree, hf_srt_ack_seqno, tvb, 4 * 4, 4,
                                ENC_BIG_ENDIAN);

            // Check for "Lite ACK" (size 4)
            if (len <= (4 + 1) * 4)
            {
                proto_item_set_len(srt_item, (4 + 1) * 4);
            }
            else
            {
                proto_tree_add_item(tree, hf_srt_rtt,      tvb, (4+1)*4, 4,
                                    ENC_BIG_ENDIAN);
                proto_tree_add_item(tree, hf_srt_rttvar,   tvb, (4+2)*4, 4,
                                    ENC_BIG_ENDIAN);
                proto_tree_add_item(tree, hf_srt_bufavail, tvb, (4+3)*4, 4,
                                    ENC_BIG_ENDIAN);
                /* if not a light ack, decode the rate and link capacity */

                if (len > (4 + 4) * 4)
                {
                    proto_tree_add_item(tree, hf_srt_rate, tvb, (4 + 4) * 4, 4, ENC_BIG_ENDIAN);
                    proto_tree_add_item(tree, hf_srt_bandwidth, tvb, (4 + 5) * 4, 4, ENC_BIG_ENDIAN);

                    // SRT Extra data. This can be version dependent, so
                    // test the length for each field.
                    if (len > (4 + 6) * 4)
                    {
                        proto_tree_add_item(tree, hf_srt_rcvrate, tvb, (4 + 6) * 4, 4, ENC_BIG_ENDIAN);
                        len = (4 + 7) * 4;
                    }

                    proto_item_set_len(srt_item, (gint) len);
                }
                else
                {
                    proto_item_set_len(srt_item, (4 + 4) * 4);
                }
            }
        }
        break;
    case UMSG_DROPREQ:
        {
            guint len = tvb_reported_length(tvb);
            if (len > (4 + 0) * 4)
            {
                guint lo = tvb_get_ntohl(tvb, (4 + 0) * 4);
                guint hi = tvb_get_ntohl(tvb, (4 + 1) * 4);

                proto_tree_add_expert_format(tree, pinfo, &ei_srt_nak_seqno,
                        tvb, 16, 8, "Drop sequence range: %u-%u",
                        lo, hi);
                proto_item_set_len(srt_item, (gint) len);
            }
        }
        break;
    case UMSG_LOSSREPORT:
        {
            guint len = tvb_reported_length(tvb);
            guint pos;
            guint32 val;
            guint prev = 0;
            for (pos = 16; pos < len; pos += 4)
            {
                val = tvb_get_ntohl(tvb, pos);
                if (val & SRT_LOSS_SEQUENCE_FIRST) {
                    // Remember this as a beginning range
                    prev = val;
                    continue;
                }

                // We have either a single value, or end-range here.
                if (prev & SRT_LOSS_SEQUENCE_FIRST) {
                    // Was a range. Display as range and clear the state.
                    proto_tree_add_expert_format(tree, pinfo, &ei_srt_nak_seqno,
                                    tvb, pos-4, 8, "Loss sequence range: %u-%u",
                                    (prev & SRT_LOSS_SEQUENCE_MASK), val);
                    prev = 0;
                } else {
                    // No from, so this is a freestanding loss value
                    proto_tree_add_expert_format(tree, pinfo, &ei_srt_nak_seqno,
                                    tvb, pos, 4, "Loss sequence: %u", val);
                }
            }

            // Report possible errors
            if (prev)
            {
                proto_tree_add_expert_format(tree, pinfo, &ei_srt_nak_seqno,
                                tvb, pos-4, 4, "ERROR: loss sequence range begin only: %u (%x)",
                                val & SRT_LOSS_SEQUENCE_MASK, val);
            }

            proto_item_set_len(srt_item, len);
        }
        break;

    case UMSG_EXT:
        switch (exttype)
        {
        case SRT_CMD_HSREQ:
        case SRT_CMD_HSRSP:
            srt_format_hs_ext_hsreq(tree, tvb, 16);
            break;

        case SRT_CMD_KMREQ:
        case SRT_CMD_KMRSP:
            {
                // This relies on value of HCRYPT_MSG_KM_MAX_SZ resulting from this above.
                // Too strongly dependent on devel API, so using explicit 104.
                int plen = tvb_reported_length(tvb) - 16;
                if (plen > 104)
                    plen = 104;
                srt_format_kmx(tree, tvb, 16, plen);
            }
            break;

        default:
            break;
        }
        break;

    default:
            // All other types have kinda "extra padding"
        proto_tree_add_item(tree, hf_srt_addinfo, tvb, 16, 4, ENC_BIG_ENDIAN);
        break;
    }
}


/* Code to actually dissect the packets
 *
 * @return the amount of data this dissector was able to dissect
 */
static int
dissect_srt_udp(tvbuff_t *tvb, packet_info* pinfo, proto_tree *parent_tree,
                void *data _U_)
{
    /* Other misc. local variables. */
    gboolean is_control = 0;

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

    proto_item *srt_item = proto_tree_add_item(parent_tree, proto_srt, tvb,
                                   0 /*start*/, -1 /*length*/, ENC_NA);
    proto_tree *tree = proto_item_add_subtree(srt_item, ett_srt);
    proto_tree_add_item_ret_boolean(tree, hf_srt_iscontrol, tvb, 0, 4, ENC_BIG_ENDIAN, &is_control);

    if (is_control)
    {
        dissect_srt_control_packet(tvb, pinfo, tree, srt_item);
    }
    else
    {
        /* otherwise, a data packet */
        tvbuff_t *next_tvb;

        col_add_fstr(pinfo->cinfo, COL_INFO,
                     "DATA: seqno: %u  msgno: %u  socket: 0x%x",
                     tvb_get_ntohl(tvb, 0),
                     tvb_get_ntohl(tvb, 4) & SRT_MSGNO_MSGNO_MASK,
                     tvb_get_ntohl(tvb, 12));

        if (tree)
        {
            // Sequence number
            proto_tree_add_item(tree, hf_srt_seqno, tvb, 0, 4, ENC_BIG_ENDIAN);

            proto_tree_add_item(tree, hf_srt_msgno_pb, tvb, 4, 4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_msgno_inorder, tvb, 4, 4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_msgno_enctypes, tvb, 4, 4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_msgno_rexmit, tvb, 4, 4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_msgno, tvb, 4, 4, ENC_BIG_ENDIAN);

            proto_tree_add_item(tree, hf_srt_timestamp, tvb, 8, 4, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_srt_id,        tvb, 12, 4, ENC_BIG_ENDIAN);

        }

        next_tvb = tvb_new_subset_remaining(tvb, 16);
        call_data_dissector(next_tvb, pinfo, tree);
    }

    return tvb_reported_length(tvb);
}


static gboolean
dissect_srt_heur_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    conversation_t *conv;

    /* Must have at least 24 captured bytes for heuristic check */
    if (tvb_captured_length(tvb) < 24)
        return FALSE;

    /* detect handshake control packet */
    if (tvb_get_ntohl(tvb, 0) != (0x80000000 | UMSG_HANDSHAKE))
        return FALSE;

    /* must be version 4 or 5*/
    const guint32 version = tvb_get_ntohl(tvb, 16);
    if (version != 4 && version != 5)
        return FALSE;

    /* SRT: must be DGRAM. STREAM is not supported in SRT */
    if (version == 4 && tvb_get_ntohl(tvb, 20) != SRT_DGRAM)
        return FALSE;

    conv = find_or_create_conversation(pinfo);
    conversation_set_dissector(conv, srt_udp_handle);
    dissect_srt_udp(tvb, pinfo, tree, data);

    return TRUE;
}


/* Register the protocol with Wireshark.
 *
 * This format is required because a script is used to build the C function that
 * calls all the protocol registration.
 */
void proto_register_srt(void)
{
    expert_module_t *expert_srt;

    /* Setup list of header fields  See Section 1.5 of README.dissector for
     * details. */
    static hf_register_info hf[] = {
        {&hf_srt_iscontrol, {
            "Content", "srt.iscontrol",
            FT_BOOLEAN, 32,
            TFS(&srt_packet_types), 0x80000000, NULL, HFILL }},

        {&hf_srt_type, {
            "Msg Type", "srt.type",
            FT_UINT16, BASE_HEX,
            VALS(srt_ctrlmsg_types), 0x7fff, NULL, HFILL}},

        {&hf_srt_exttype, {
            "Extended type", "srt.exttype",
            FT_UINT16, BASE_HEX,
            VALS(srt_ctrlmsg_exttypes), 0, NULL, HFILL}},

        {&hf_srt_exttype_none, {
            "(no extended type)", "srt.exttype_none",
            FT_UINT16, BASE_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_seqno, {
            "Sequence Number", "srt.seqno",
            FT_UINT32, BASE_DEC,
            NULL, SRT_CONTROL_MASK, NULL, HFILL}},

        {&hf_srt_addinfo, {
            "(Unused)", "srt.addinfo",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_msgno, {
            "Message Number", "srt.msgno",
            FT_UINT32, BASE_DEC,
            NULL, SRT_MSGNO_MSGNO_MASK, NULL, HFILL}},

        {&hf_srt_msgno_pb, {
            "Packet Boundary", "srt.pb",
                FT_UINT32, BASE_DEC,
            VALS(srt_pb_types), SRT_MSGNO_FF_MASK, NULL, HFILL}},

        {&hf_srt_msgno_inorder, {
            "In-Order Indicator", "srt.msg.order",
            FT_UINT32, BASE_DEC,
            NULL, SRT_MSGNO_INORDER, NULL, HFILL}},

        {&hf_srt_msgno_enctypes, {
            "Encryption Status", "srt.msg.enc",
            FT_UINT32, BASE_DEC,
            VALS(srt_msgno_enctypes), SRT_MSGNO_ENCTYPE, NULL, HFILL }},

        {&hf_srt_msgno_rexmit, {
            "Sent as", "srt.msg.rexmit",
            FT_BOOLEAN, 32,
            TFS(&srt_msgno_rexmit), SRT_MSGNO_REXMIT, NULL, HFILL }},

        {&hf_srt_timestamp, {
            "Time Stamp", "srt.timestamp",
            FT_UINT32, BASE_DEC_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_id, {
            "Destination Socket ID", "srt.id",
            FT_UINT32, BASE_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_ack_seqno, {
            "ACKD_RCVLASTACK", "srt.ack_seqno",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_ackno, {
            "Ack Number", "srt.ackno",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_rtt, {
            "ACKD_RTT", "srt.rtt",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_microseconds, 0, NULL, HFILL}},

        {&hf_srt_rttvar, {
            "ACKD_RTTVAR", "srt.rttvar",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_microseconds, 0, NULL, HFILL}},

        {&hf_srt_bufavail, {
            "ACKD_BUFFERLEFT", "srt.bufavail",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_pkts, 0, NULL, HFILL}},

        {&hf_srt_rate, {
            "ACKD_RCVSPEED", "srt.rate",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_pkts_per_sec, 0, NULL, HFILL}},

        {&hf_srt_bandwidth, {
            "ACKD_BANDWIDTH", "srt.bw",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_pkts_per_sec, 0, NULL, HFILL}},

        {&hf_srt_rcvrate, {
            "ACKD_RCVRATE", "srt.rcvrate",
            FT_UINT32, BASE_DEC | BASE_UNIT_STRING,
            &units_byte_bytespsecond, 0, NULL, HFILL}},

        {&hf_srt_handshake_version, {
            "Handshake Version", "srt.hs.version",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_type_v4, {
            "(Legacy) Socket type", "srt.hs.socktype",
            FT_UINT32, BASE_DEC,
            VALS(srt_hsv4_socket_types), 0, NULL,
            HFILL}},

        {&hf_srt_handshake_enc_field_v5, {
            "Encryption Field", "srt.hs.encfield",
            FT_UINT16, BASE_HEX,
            VALS(srt_handshake_enc_field), 0, NULL,
            HFILL}},

        {&hf_srt_handshake_ext_field_v5, {
            "Extended Field", "srt.hs.extfield",
            FT_UINT16, BASE_HEX,
            NULL, 0, NULL,
            HFILL}},

        {&hf_srt_handshake_ext_field_v5_flag_hsreq, {
            "HS_EXT_FIELD_HSREQ", "srt.hs.extfield.hsreq",
            FT_BOOLEAN, SRT_HS_V5_EXT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_HS_V5_EXT_FIELD_HSREQ,
            "Handshake request",
            HFILL}},

        {&hf_srt_handshake_ext_field_v5_flag_kmreq, {
            "HS_EXT_FIELD_KMREQ", "srt.hs.extfield.kmreq",
            FT_BOOLEAN, SRT_HS_V5_EXT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_HS_V5_EXT_FIELD_KMREQ,
            "KM request",
            HFILL}},

        {&hf_srt_handshake_ext_field_v5_flag_config, {
            "HS_EXT_FIELD_CONFIG", "srt.hs.extfield.config",
            FT_BOOLEAN, SRT_HS_V5_EXT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_HS_V5_EXT_FIELD_CONFIG,
            "Handshake has configuration",
            HFILL}},

        {&hf_srt_handshake_isn, {
            "Initial Sequence Number", "srt.hs.isn",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_mtu, {
            "MTU", "srt.hs.mtu",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_flow_window, {
            "Flow Window", "srt.hs.flow_window",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_reqtype, {
            "Handshake Type", "srt.hs.reqtype",
            FT_INT32, BASE_DEC,
            VALS(srt_hs_request_types), 0, NULL, HFILL}},

        {&hf_srt_handshake_failure_type, {
            "Handshake FAILURE code", "srt.hs.failtype",
            FT_UINT32, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_id, {
            "Socket ID", "srt.hs.id",
            FT_UINT32, BASE_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_cookie, {
            "SYN Cookie", "srt.hs.cookie",
            FT_UINT32, BASE_HEX,
            NULL, 0, NULL, HFILL}},
        {&hf_srt_handshake_peerip, {
            /* FT_STRINGZ is used because the value
             * is formatted to a temporary buffer first */
            "Peer IP Address", "srt.hs.peerip",
            FT_STRINGZ, BASE_NONE,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_ext_version, {
            "SRT Version", "srt.hs.version",
            FT_UINT32, BASE_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_ext_flags, {
            /* This uses custom format by appending the flag format string,
             * while the value in hex is still printed. */
            "SRT Flags", "srt.hs.srtflags",
            FT_UINT32, BASE_HEX,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_handshake_ext_flag_tsbpd_snd, {
            "TSBPDSND", "srt.hs.srtflags.tsbpd_snd",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_TSBPDSND,
            "The party will be sending in TSBPD (Time Stamp Based Packet Delivery) mode",
            HFILL}},

        {&hf_srt_handshake_ext_flag_tsbpd_rcv, {
            "TSBPDRCV", "srt.hs.srtflags.tsbpd_rcv",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_TSBPDRCV,
            "The party expects to receive in TSBPD (Time Stamp Based Packet Delivery) mode",
            HFILL}},

        {&hf_srt_handshake_ext_flag_haicrypt, {
            "HAICRYPT", "srt.hs.srtflags.haicrypt",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_HAICRYPT,
            "The party includes haicrypt (legacy flag)",
            HFILL}},

        {&hf_srt_handshake_ext_flag_tlpkt_drop, {
            "TLPKTDROP", "srt.hs.srtflags.tlpkt_drop",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_TLPKTDROP,
            "The party will do the Too-Late Packet Drop",
            HFILL}},

        {&hf_srt_handshake_ext_flag_nak_report, {
            "NAKREPORT", "srt.hs.srtflags.nak_report",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_NAKREPORT,
            "The party will do periodic NAK reporting",
            HFILL}},

        {&hf_srt_handshake_ext_flag_rexmit, {
            "REXMITFLG", "srt.hs.srtflags.rexmit",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_REXMITFLG,
            "The party uses the REXMIT flag",
            HFILL}},

        {&hf_srt_handshake_ext_flag_stream, {
            "STREAM", "srt.hs.srtflags.stream",
            FT_BOOLEAN, SRT_OPT_FIELD_LEN, TFS(&tfs_set_notset),
            SRT_OPT_STREAM,
            "The party uses stream type transmission",
            HFILL}},

        {&hf_srt_srths_blocktype, {
            "SRT HS Extension type", "srt.hs.blocktype",
            FT_UINT16, BASE_HEX,
            VALS(srt_ctrlmsg_exttypes), 0, NULL, HFILL}},

        {&hf_srt_srths_blocklen, {
            "SRT HS Extension size (4-byte blocks)", "srt.hs.blocklen",
            FT_UINT16, BASE_DEC,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_srths_agent_latency, {
            "Latency", "srt.hs.agent_latency",
            FT_UINT16, BASE_DEC | BASE_UNIT_STRING,
            &units_milliseconds, 0, NULL, HFILL}},

        {&hf_srt_srths_peer_latency, {
            "Peer Latency", "srt.hs.peer_latency",
            FT_UINT16, BASE_DEC | BASE_UNIT_STRING,
            &units_milliseconds, 0, NULL, HFILL}},

        {&hf_srt_srtkm_msg, {
            "KMX Message (or KM State if 4 bytes)", "srt.km.msg",
            FT_BYTES, BASE_NONE,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_srtkm_error, {
            "KM State", "srt.km.error",
            FT_UINT32, BASE_DEC,
            VALS(srt_enc_kmstate), 0, NULL, HFILL}},

        {&hf_srt_srths_sid, {
            "Stream ID", "srt.hs.sid",
            FT_STRING, BASE_NONE,
            NULL, 0, NULL, HFILL}},

        {&hf_srt_srths_congestcontrol, {
            "Congestion Control Type", "srt.hs.congestctrl",
            FT_STRING, BASE_NONE,
            NULL, 0, NULL, HFILL}}
    };

    static gint *ett[] = {
        &ett_srt,
        &ett_srt_handshake_ext_flags,
        &ett_srt_handshake_ext_field_flags
    };

    static ei_register_info ei[] = {
        { &ei_srt_nak_seqno,
            { "srt.nak_seqno", PI_SEQUENCE, PI_NOTE,
              "Missing Sequence Number(s)", EXPFILL }},

        { &ei_srt_hs_ext_hsreq_len,
            { "srt.hs.ext.hsreq", PI_PROTOCOL, PI_WARN,
              "Unknown HS Ext HSREQ length", EXPFILL }},

        { &ei_srt_hs_ext_type,
            { "srt.hs.ext.type", PI_PROTOCOL, PI_WARN,
              "Unknown HS Ext Type", EXPFILL }},
    };

    proto_srt = proto_register_protocol("SRT Protocol", "SRT", "srt");
    proto_register_field_array(proto_srt, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    expert_srt = expert_register_protocol(proto_srt);
    expert_register_field_array(expert_srt, ei, array_length(ei));

    srt_udp_handle = register_dissector("srt", dissect_srt_udp, proto_srt);
}


void proto_reg_handoff_srt(void)
{
    /* register as heuristic dissector for UDP */
    heur_dissector_add("udp", dissect_srt_heur_udp, "SRT over UDP",
                       "srt_udp", proto_srt, HEURISTIC_ENABLE);

    /* Add a handle to the list of handles that *could* be used with this
       table.  That list is used by the "Decode As"/"-d" code in the UI. */
    dissector_add_for_decode_as("udp.port", srt_udp_handle);
}


/*
 * Editor modelines  -  https://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:
 */
