/* packet-sync.c
 * Routines for MBMS synchronisation protocol dissection
 * Copyright 2012, David Wei <davidwei@lavabit.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Ref 3GPP TS 25.446
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/crc6-tvb.h>

#define TYPE_0_LEN 17
#define TYPE_1_LEN 11
#define TYPE_2_LEN 12
#define TYPE_3_LEN 19

void proto_register_sync(void);
void proto_reg_handoff_sync(void);

/* Initialize the protocol and registered fields */
static int proto_sync = -1;
static int hf_sync_type = -1;
static int hf_sync_spare4 = -1;
static int hf_sync_timestamp = -1;
static int hf_sync_packet_nr = -1;
static int hf_sync_elapsed_octet_ctr = -1;
static int hf_sync_total_nr_of_packet = -1;
static int hf_sync_total_nr_of_octet = -1;
static int hf_sync_header_crc = -1;
static int hf_sync_payload_crc = -1;
static int hf_sync_length_of_packet = -1;

/* Initialize the subtree pointers */
static gint ett_sync = -1;

static expert_field ei_sync_pdu_type2 = EI_INIT;
static expert_field ei_sync_type = EI_INIT;

static dissector_handle_t sync_handle;
static dissector_handle_t ip_handle;

static const value_string sync_type_vals[] = {
    { 0, "Synchronisation frame without payload" },
    { 1, "User data with synchronisation frame for uncompressed headers" },
    { 2, "User data with synchronisation frame for compressed headers" },
    { 3, "Synchronisation frame with Length of Packets" },
    /* 4-15 reserved for future PDU type extensions */
    { 0, NULL}
};

static int
dissect_sync(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    proto_item *ti, *item, *type_item;
    proto_tree *sync_tree;
    guint8      type, spare;
    guint16     packet_nr, packet_len1, packet_len2;
    guint32     timestamp, total_nr_of_packet;
    int         offset = 0;
    tvbuff_t   *next_tvb;

    type  = tvb_get_guint8(tvb, offset) >> 4;
    spare = tvb_get_guint8(tvb, offset) & 0x0F;

    /* Heuristics to check if packet is really MBMS sync */
#if 0
    if ( type > 3 )
        return 0;

    if ( type == 0 && tvb_captured_length(tvb) < 18) {
        return 0;
    } else if ( type == 1 && tvb_captured_length(tvb) < 11 ) {
        return 0;
    } else if ( type == 3 && tvb_captured_length(tvb) < 19 ) {
        return 0;
    }

    if ( (type != 2) && (spare != 0) )
        return 0;
#endif

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "SYNC");
    col_set_str(pinfo->cinfo, COL_INFO, "MBMS synchronisation protocol");

    /* Ugly, but necessary to get the correct length for type 3 */
    packet_nr = tvb_get_ntohs(tvb, offset+3) + 1;

        /* The length varies depending on PDU type */
        switch (type) {
            case 0:
                ti = proto_tree_add_item(tree, proto_sync, tvb, 0, TYPE_0_LEN, ENC_NA);
                break;
            case 1:
                ti = proto_tree_add_item(tree, proto_sync, tvb, 0, TYPE_1_LEN, ENC_NA);
                break;
            case 2:
                ti = proto_tree_add_item(tree, proto_sync, tvb, 0, TYPE_2_LEN + (spare & 0x01 ? 40 : 20), ENC_NA);
                break;
            case 3:
                ti = proto_tree_add_item(tree, proto_sync, tvb, 0,
                                         TYPE_3_LEN + (gint16)(packet_nr % 2 == 0 ?
                                                               1.5*packet_nr : 1.5*(packet_nr-1)+2),
                                         ENC_NA);
                break;
            default:
                ti = proto_tree_add_item(tree, proto_sync, tvb, 0, -1, ENC_NA);
                break;
        }

        sync_tree = proto_item_add_subtree(ti, ett_sync);

        /* Octet 1 - PDU Type */
        type_item = proto_tree_add_item(sync_tree, hf_sync_type, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(sync_tree, hf_sync_spare4, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;

        /* Octet 2 - Time Stamp */
        timestamp = tvb_get_ntohs(tvb, offset) * 10;
        proto_tree_add_uint(sync_tree, hf_sync_timestamp, tvb, offset, 2, timestamp);
        offset += 2;

        /* Octet 4 - Packet Number */
        proto_tree_add_uint(sync_tree, hf_sync_packet_nr, tvb, offset, 2, packet_nr);
        offset += 2;

        /* Octet 6 - Elapsed Octet Counter */
        proto_tree_add_item(sync_tree, hf_sync_elapsed_octet_ctr, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;

        switch (type) {
            case 0:
                /* SYNC PDU Type 0 */
                proto_tree_add_item(sync_tree, hf_sync_total_nr_of_packet, tvb, offset, 3, ENC_BIG_ENDIAN);
                offset += 3;
                proto_tree_add_item(sync_tree, hf_sync_total_nr_of_octet, tvb, offset, 5, ENC_BIG_ENDIAN);
                offset += 5;
                proto_tree_add_item(sync_tree, hf_sync_header_crc, tvb, offset, 1, ENC_BIG_ENDIAN);
                offset++;
                break;
            case 1:
                /* SYNC PDU Type 1 */
                /* XXX - Calculate the CRC and check against this value? */
                item = proto_tree_add_item(sync_tree, hf_sync_header_crc, tvb, offset, 1, ENC_BIG_ENDIAN);
                proto_tree_add_item(sync_tree, hf_sync_payload_crc, tvb, offset, 2, ENC_BIG_ENDIAN);
                proto_item_append_text(item, " [Calculated CRC 0x%x]",
                                        crc6_compute_tvb(tvb, offset));
                offset += 2;

                /* XXX - The payload may not always be present? */
                next_tvb = tvb_new_subset_remaining(tvb, offset);
                /* XXX - The payload may not always be IP? */
                call_dissector(ip_handle, next_tvb, pinfo, tree);
                break;
            case 2:
                /* SYNC PDU Type 2 */
                expert_add_info(pinfo, ti, &ei_sync_pdu_type2);
                break;
            case 3:
                /* SYNC PDU Type 3 */
                total_nr_of_packet = tvb_get_ntoh24(tvb, offset);
                proto_tree_add_item(sync_tree, hf_sync_total_nr_of_packet, tvb, offset, 3, ENC_BIG_ENDIAN);
                offset += 3;
                proto_tree_add_item(sync_tree, hf_sync_total_nr_of_octet, tvb, offset, 5, ENC_BIG_ENDIAN);
                offset += 5;
                proto_tree_add_item(sync_tree, hf_sync_header_crc, tvb, offset, 1, ENC_BIG_ENDIAN);
                proto_tree_add_item(sync_tree, hf_sync_payload_crc, tvb, offset, 2, ENC_BIG_ENDIAN);
                offset += 2;

                if (offset < (gint)tvb_reported_length(tvb)) {
                    int i;

                    if (total_nr_of_packet != 0 && packet_nr % 2 == 0) {
                        /* Even number of packets */
                        for (i = 1; i < packet_nr; i+=2, offset+=3) {
                            packet_len1 = tvb_get_bits16(tvb, offset*8,    12, ENC_BIG_ENDIAN);
                            packet_len2 = tvb_get_bits16(tvb, offset*8+12, 12, ENC_BIG_ENDIAN);
                            proto_tree_add_uint_format(sync_tree, hf_sync_length_of_packet, tvb, offset,   2, packet_len1, "Length of Packet %u : %hu", i,   packet_len1);
                            proto_tree_add_uint_format(sync_tree, hf_sync_length_of_packet, tvb, offset+1, 2, packet_len2, "Length of Packet %u : %hu", i+1, packet_len2);
                        }
                    } else {
                        /* Odd number of packets */
                        for (i = 1; i < packet_nr; i+=2, offset+=3) {
                            packet_len1 = tvb_get_bits16(tvb, offset*8,    12, ENC_BIG_ENDIAN);
                            packet_len2 = tvb_get_bits16(tvb, offset*8+12, 12, ENC_BIG_ENDIAN);
                            proto_tree_add_uint_format(sync_tree, hf_sync_length_of_packet, tvb, offset,   2, packet_len1, "Length of Packet %u : %hu", i,   packet_len1);
                            proto_tree_add_uint_format(sync_tree, hf_sync_length_of_packet, tvb, offset+1, 2, packet_len2, "Length of Packet %u : %hu", i+1, packet_len2);
                        }
                        packet_len1 = tvb_get_bits16(tvb, offset*8, 12, ENC_BIG_ENDIAN);
                        proto_tree_add_uint_format(sync_tree, hf_sync_length_of_packet, tvb, offset, 2, packet_len1, "Length of Packet %u : %hu", packet_nr, packet_len1);
                        offset++;
                        proto_tree_add_item(sync_tree, hf_sync_spare4, tvb, offset, 1, ENC_BIG_ENDIAN);
                    }

                }
                break;
            default:
                expert_add_info(pinfo, type_item, &ei_sync_type);
                break;
        }

    return tvb_captured_length(tvb);

}

void
proto_register_sync(void)
{
    static hf_register_info hf_sync[] = {
        { &hf_sync_type,
            { "PDU Type", "sync.type",
            FT_UINT8, BASE_DEC, VALS(sync_type_vals), 0xF0,
            NULL, HFILL }
        },
        { &hf_sync_spare4,
            { "Spare", "sync.spare",
            FT_UINT8, BASE_DEC, NULL, 0x0F,
            NULL, HFILL }
        },
        { &hf_sync_timestamp,
            { "Timestamp", "sync.timestamp",
            FT_UINT16, BASE_DEC|BASE_UNIT_STRING, &units_milliseconds, 0x0,
            "Relative time value for the starting time of a synchronisation sequence within the synchronisation period.", HFILL }
        },
        { &hf_sync_packet_nr,
            { "Packet Number", "sync.packet_nr",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Number of elapsed SYNC PDUs cumulatively within the synchronisation sequence.", HFILL }
        },
        { &hf_sync_elapsed_octet_ctr,
            { "Elapsed Octet Counter", "sync.elapsed_octet_ctr",
            FT_UINT32, BASE_DEC, NULL, 0x0,
            "Number of elapsed cumulative octets cumulatively within one synchronisation sequence.", HFILL }
        },
        { &hf_sync_total_nr_of_packet,
            { "Total Number of Packet", "sync.total_nr_of_packet",
            FT_UINT24, BASE_DEC, NULL, 0x0,
            "Cumulatively the number of the packets for the MBMS service within one synchronisation period.", HFILL }
        },
        { &hf_sync_total_nr_of_octet,
            { "Total Number of Octet", "sync.total_nr_of_octet",
            FT_UINT64, BASE_DEC, NULL, 0x0,
            "Cumulatively the number of the octets for the MBMS service within one synchronisation period.", HFILL }
        },
        { &hf_sync_header_crc,
            { "Header CRC", "sync.header_crc",
            FT_UINT8, BASE_HEX, NULL, 0xFC,
            NULL, HFILL }
        },
        { &hf_sync_payload_crc,
            { "Payload CRC", "sync.payload_crc",
            FT_UINT16, BASE_HEX, NULL, 0x03FF,
            NULL, HFILL }
        },
        { &hf_sync_length_of_packet,
            { "Length of Packet", "sync.length_of_packet",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
    };

    static gint *ett_sync_array[] = {
        &ett_sync
    };

    static ei_register_info ei[] = {
        { &ei_sync_pdu_type2, { "sync.pdu_type2", PI_UNDECODED, PI_WARN, "SYNC PDU type 2 unsupported", EXPFILL }},
        { &ei_sync_type, { "sync.type.unknown", PI_PROTOCOL, PI_WARN, "Unknown SYNC PDU type", EXPFILL }},
    };

    expert_module_t* expert_sync;

    proto_sync = proto_register_protocol("MBMS synchronisation protocol", "SYNC", "sync");

    proto_register_field_array(proto_sync, hf_sync, array_length(hf_sync));
    proto_register_subtree_array(ett_sync_array, array_length(ett_sync_array));
    expert_sync = expert_register_protocol(proto_sync);
    expert_register_field_array(expert_sync, ei, array_length(ei));

    sync_handle = register_dissector("sync", dissect_sync, proto_sync);
}

void
proto_reg_handoff_sync(void)
{
    ip_handle   = find_dissector_add_dependency("ip", proto_sync);

    dissector_add_for_decode_as_with_preference("udp.port", sync_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:
 */
