/* peektagged.c
 * Routines for opening files in what Savvius (formerly WildPackets) calls
 * the tagged file format in the description of their "PeekRdr Sample
 * Application" (C++ source code to read their capture files, downloading
 * of which requires a maintenance contract, so it's not free as in beer
 * and probably not as in speech, either).
 *
 * As that description says, it's used by AiroPeek and AiroPeek NX 2.0
 * and later, EtherPeek 6.0 and later, EtherPeek NX 3.0 and later,
 * EtherPeek VX 1.0 and later, GigaPeek NX 1.0 and later, Omni3 1.0
 * and later (both OmniPeek and the Remote Engine), and WANPeek NX
 * 1.0 and later.  They also say it'll be used by future Savvius
 * products.
 *
 * Wiretap Library
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"
#include "peektagged.h"

#include <string.h>
#include <stdlib.h>
#include "wtap_module.h"
#include "file_wrappers.h"
#include <wsutil/802_11-utils.h>
#include <wsutil/array.h>
#include <wsutil/strtoi.h>
#include <wsutil/pint.h>
#include <wsutil/str_util.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>

void register_peektagged(void);

/* CREDITS
 *
 * This file decoder could not have been written without examining
 * http://www.varsanofiev.com/inside/airopeekv9.htm, the help from
 * Martin Regner and Guy Harris, and the etherpeek.c file (as it
 * was called before renaming it to peekclassic.c).
 */

/*
 * Section header.
 *
 * A Peek tagged file consists of multiple sections, each of which begins
 * with a header in the following format.
 *
 * The section ID is a 4-character string saying what type of section
 * it is.  The section length is a little-endian field giving the
 * length of the section, in bytes, including the section header
 * itself.  The other field of the section header is a little-endian
 * constant that always appears to be 0x00000200.
 *
 * Files we've seen have the following sections, in order:
 *
 * "\177vers" - version information.  The contents are XML, giving
 * the file format version and application version information.
 *
 * "sess" - capture session information.  The contents are XML, giving
 * various information about the capture session.
 *
 * "cpid" - capture ID.  The contents are XML, giving a capture ID UUID
 * and Index. The same UUID appears in the "sess" section, so it's not
 * clear what this adds. In at least one file this is *before* the "sess"
 * section, and in other files after it.
 *
 * "pkts" - captured packets.  The contents are binary records, one for
 * each packet, with the record being a list of tagged values followed
 * by the raw packet data.
 */
typedef struct peektagged_section_header {
        char     section_id[4];         /* string identifying the section */
        uint32_t section_len;           /* little-endian section length */
        uint32_t section_const;         /* little-endian 0x00000200 */
} peektagged_section_header_t;

/* Size of the "header" sections */
/* XXX - What is the maximum practical size here? If this increases much
 * more, we'll have to allocate on the heap instead of stack.
 */
#define MAX_SECTION_SIZE    16384
#define SECTION_CONST_VALUE 0x00000200
/*
 * Network subtype values.
 *
 * XXX - do different network subtype values for 802.11 indicate different
 * network adapter types, with some adapters supplying the FCS and others
 * not supplying the FCS?
 */
#define PEEKTAGGED_NST_ETHERNET         0
#define PEEKTAGGED_NST_802_11           1       /* 802.11 with 0's at the end */
#define PEEKTAGGED_NST_802_11_2         2       /* 802.11 with 0's at the end */
#define PEEKTAGGED_NST_802_11_WITH_FCS  3       /* 802.11 with FCS at the end */

/* tags for fields in packet header */
#define TAG_PEEKTAGGED_LENGTH                   0x0000
#define TAG_PEEKTAGGED_TIMESTAMP_LOWER          0x0001
#define TAG_PEEKTAGGED_TIMESTAMP_UPPER          0x0002
#define TAG_PEEKTAGGED_FLAGS_AND_STATUS         0x0003  /* upper 24 bits unused? */
#define TAG_PEEKTAGGED_CHANNEL                  0x0004
#define TAG_PEEKTAGGED_DATA_RATE_OR_MCS_INDEX   0x0005
#define TAG_PEEKTAGGED_SIGNAL_PERC              0x0006
#define TAG_PEEKTAGGED_SIGNAL_DBM               0x0007
#define TAG_PEEKTAGGED_NOISE_PERC               0x0008
#define TAG_PEEKTAGGED_NOISE_DBM                0x0009
#define TAG_PEEKTAGGED_UNKNOWN_0x000A           0x000A
#define TAG_PEEKTAGGED_CENTER_FREQUENCY         0x000D  /* Frequency */
#define TAG_PEEKTAGGED_UNKNOWN_0x000E           0x000E  /* "Band"? */
#define TAG_PEEKTAGGED_UNKNOWN_0x000F           0x000F  /* antenna 2 signal dBm? */
#define TAG_PEEKTAGGED_UNKNOWN_0x0010           0x0010  /* antenna 3 signal dBm? */
#define TAG_PEEKTAGGED_UNKNOWN_0x0011           0x0011  /* antenna 4 signal dBm? */
#define TAG_PEEKTAGGED_UNKNOWN_0x0012           0x0012  /* antenna 2 noise dBm? */
#define TAG_PEEKTAGGED_UNKNOWN_0x0013           0x0013  /* antenna 3 noise dBm? */
#define TAG_PEEKTAGGED_UNKNOWN_0x0014           0x0014  /* antenna 4 noise dBm? */
#define TAG_PEEKTAGGED_EXT_FLAGS                0x0015  /* Extended flags for 802.11n and beyond */

#define TAG_PEEKTAGGED_SLICE_LENGTH             0xffff

/*
 * Flags.
 *
 * We're assuming here that the "remote Peek" flags from bug 9586 are
 * the same as the "Peek tagged" flags.
 *
 * Are these the same as in "Peek classic"?  The first three are.
 */
#define FLAGS_CONTROL_FRAME     0x01    /* Frame is a control frame */
#define FLAGS_HAS_CRC_ERROR     0x02    /* Frame has a CRC error */
#define FLAGS_HAS_FRAME_ERROR   0x04    /* Frame has a frame error */

/*
 * Status.
 *
 * Is this in the next 8 bits of the "flags and status" field?
 */
#define STATUS_PROTECTED        0x0400  /* Frame is protected (encrypted) */
#define STATUS_DECRYPT_ERROR    0x0800  /* Error decrypting protected frame */
#define STATUS_SHORT_PREAMBLE   0x4000  /* Short preamble */

/*
 * Extended flags.
 *
 * Some determined from bug 10637, some determined from bug 9586,
 * and the ones present in both agree, so we're assuming that
 * the "remote Peek" protocol and the "Peek tagged" file format
 * use the same bits (which wouldn't be too surprising, as they
 * both come from Wildpackets).
 */
#define EXT_FLAG_20_MHZ_LOWER                   0x00000001
#define EXT_FLAG_20_MHZ_UPPER                   0x00000002
#define EXT_FLAG_40_MHZ                         0x00000004
#define EXT_FLAGS_BANDWIDTH                     0x00000007
#define EXT_FLAG_HALF_GI                        0x00000008
#define EXT_FLAG_FULL_GI                        0x00000010
#define EXT_FLAGS_GI                            0x00000018
#define EXT_FLAG_AMPDU                          0x00000020
#define EXT_FLAG_AMSDU                          0x00000040
#define EXT_FLAG_802_11ac                       0x00000080
#define EXT_FLAG_MCS_INDEX_USED                 0x00000100

/* 64-bit time in nanoseconds from the (Windows FILETIME) epoch */
typedef struct peektagged_utime {
        uint32_t upper;
        uint32_t lower;
} peektagged_utime;

typedef struct {
        bool            has_fcs;
} peektagged_t;

static int peektagged_file_type_subtype = -1;

static uint32_t
peektagged_get_file_version(xmlDocPtr doc)
{
    xmlNodePtr root_element;

    root_element = xmlDocGetRootElement(doc);
    if (root_element == NULL)
        return 0;

    if (xmlStrcmp(root_element->name, (const xmlChar*)"VersionInfo") != 0)
        return 0;

    for (xmlNodePtr cur = root_element->children; cur != NULL; cur = cur->next) {
        if (cur->type == XML_ELEMENT_NODE && xmlStrcmp(cur->name, (const xmlChar*)"FileVersion") == 0) {
            uint32_t value;
            xmlChar* str_version = xmlNodeGetContent(cur);
            if (str_version == NULL)
                return 0;

            if (!ws_strtou32((const char*)str_version, NULL, &value)) {
                xmlFree(str_version);
                return 0;
            }
            xmlFree(str_version);
            return value;
        }
    }

    // Not found
    return 0;
}

static bool
peektagged_get_media_info(xmlDocPtr doc, uint32_t *mediaType, uint32_t* mediaSubType)
{
    xmlNodePtr root_element;
    bool found_media_type = false,
         found_media_subtype = false;

    root_element = xmlDocGetRootElement(doc);
    if (root_element == NULL)
        return false;

    if (xmlStrcmp(root_element->name, (const xmlChar*)"Session") != 0)
        return false;

    for (xmlNodePtr cur = root_element->children; cur != NULL; cur = cur->next) {
        if (cur->type == XML_ELEMENT_NODE && xmlStrcmp(cur->name, (const xmlChar*)"MediaType") == 0) {
            xmlChar* str_type = xmlNodeGetContent(cur);
            if (str_type != NULL) {
                if (ws_strtou32((const char*)str_type, NULL, mediaType))
                    found_media_type = true;

                xmlFree(str_type);
            }
        }
        else if (cur->type == XML_ELEMENT_NODE && xmlStrcmp(cur->name, (const xmlChar*)"MediaSubType") == 0) {
            xmlChar* str_type = xmlNodeGetContent(cur);

            if (str_type != NULL) {
                if (ws_strtou32((const char*)str_type, NULL, mediaSubType))
                    found_media_subtype = true;

                xmlFree(str_type);
            }
        }
        /* XXX - There is some other information we could parse out of here
         * that might be useful. */
    }

    return (found_media_type && found_media_subtype);
}

static bool
peektagged_skip_section(wtap* wth, peektagged_section_header_t* ap_hdr, int* err, char** err_info)
{
    uint32_t length;

    /*
     * Skip over a section.
     */
    length = GUINT32_TO_LE(ap_hdr->section_len);
    if ((length >= MAX_SECTION_SIZE) || (GUINT32_TO_LE(ap_hdr->section_const) != SECTION_CONST_VALUE))
        return false;

    /* This assumes that MAX_SECTION_SIZE < INT_MAX, which it currently is */
    if (!wtap_read_bytes(wth->fh, NULL, (int)length, err, err_info)) {
        return false;
    }

    if (!wtap_read_bytes(wth->fh, ap_hdr, (int)sizeof(*ap_hdr), err, err_info))
        return false;

    return true;
}

/*
 * Read the packet.
 *
 * XXX - we should supply the additional radio information;
 * the pseudo-header should probably be supplied in a fashion
 * similar to the radiotap radio header, so that the 802.11
 * dissector can determine which, if any, information items
 * are present.
 */
static int
peektagged_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec,
                       int *err, char **err_info)
{
    peektagged_t *peektagged = (peektagged_t *)wth->priv;
    bool read_a_tag = false;
    uint8_t tag_value[6];
    uint16_t tag;
    bool saw_length = false;
    uint32_t length = 0;
    uint32_t sliceLength = 0;
    bool saw_timestamp_lower = false;
    bool saw_timestamp_upper = false;
    bool saw_flags_and_status = false;
    peektagged_utime timestamp;
    uint32_t flags_and_status = 0;
    uint32_t ext_flags = 0;
    bool saw_data_rate_or_mcs_index = false;
    uint32_t data_rate_or_mcs_index = 0;
    int channel;
    unsigned frequency;
    struct ieee_802_11_phdr ieee_802_11 = {0};
    unsigned i;
    int skip_len = 0;
    uint64_t t;

    timestamp.upper = 0;
    timestamp.lower = 0;
    ieee_802_11.fcs_len = -1; /* Unknown */
    ieee_802_11.decrypted = false;
    ieee_802_11.datapad = false;
    ieee_802_11.phy = PHDR_802_11_PHY_UNKNOWN;

    /* Extract the fields from the packet header */
    do {
        /* Get the tag and value.
           XXX - this assumes all values are 4 bytes long. */
        if (!wtap_read_bytes_or_eof(fh, tag_value, sizeof tag_value, err, err_info)) {
            if (*err == 0) {
                /*
                 * Short read if we've read something already;
                 * just an EOF if we haven't.
                 */
                if (read_a_tag)
                    *err = WTAP_ERR_SHORT_READ;
            }
            return -1;
        }
        read_a_tag = true;
        tag = pletohu16(&tag_value[0]);
        switch (tag) {

        case TAG_PEEKTAGGED_LENGTH:
            if (saw_length) {
                *err = WTAP_ERR_BAD_FILE;
                *err_info = g_strdup("peektagged: record has two length fields");
                return -1;
            }
            length = pletohu32(&tag_value[2]);
            saw_length = true;
            break;

        case TAG_PEEKTAGGED_TIMESTAMP_LOWER:
            if (saw_timestamp_lower) {
                *err = WTAP_ERR_BAD_FILE;
                *err_info = g_strdup("peektagged: record has two timestamp-lower fields");
                return -1;
            }
            timestamp.lower = pletohu32(&tag_value[2]);
            saw_timestamp_lower = true;
            break;

        case TAG_PEEKTAGGED_TIMESTAMP_UPPER:
            if (saw_timestamp_upper) {
                *err = WTAP_ERR_BAD_FILE;
                *err_info = g_strdup("peektagged: record has two timestamp-upper fields");
                return -1;
            }
            timestamp.upper = pletohu32(&tag_value[2]);
            saw_timestamp_upper = true;
            break;

        case TAG_PEEKTAGGED_FLAGS_AND_STATUS:
            saw_flags_and_status = true;
            flags_and_status = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_CHANNEL:
            ieee_802_11.has_channel = true;
            ieee_802_11.channel = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_DATA_RATE_OR_MCS_INDEX:
            data_rate_or_mcs_index = pletohu32(&tag_value[2]);
            saw_data_rate_or_mcs_index = true;
            break;

        case TAG_PEEKTAGGED_SIGNAL_PERC:
            ieee_802_11.has_signal_percent = true;
            ieee_802_11.signal_percent = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_SIGNAL_DBM:
            ieee_802_11.has_signal_dbm = true;
            ieee_802_11.signal_dbm = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_NOISE_PERC:
            ieee_802_11.has_noise_percent = true;
            ieee_802_11.noise_percent = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_NOISE_DBM:
            ieee_802_11.has_noise_dbm = true;
            ieee_802_11.noise_dbm = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x000A:
            /*
             * XXX - seen in some 802.11 captures.
             * Always seems to have the value 0 or 5.
             */
            break;

        case TAG_PEEKTAGGED_CENTER_FREQUENCY:
            /* XXX - also seen in an EtherPeek capture; value unknown */
            ieee_802_11.has_frequency = true;
            ieee_802_11.frequency = pletohu32(&tag_value[2]);
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x000E:
            /*
             * XXX - seen in some 802.11 captures.
             * Usually has the value 4, but, in some packets, has the
             * values 6 or 302.
             *
             * Is this the mysterious "band" field that shows up in
             * some "Peek remote" protocol captures, with values in
             * the 30x or 40x ranges?  It's not always associated
             * with the "extended flags" tag for HT/VHT information,
             * so it's probably not 11n/11ac-specific.  Values other
             * than 4 appear, in my captures, only in packets with
             * the "extended flags" tag.  302 appeared in a packet
             * with EXT_FLAG_MCS_INDEX_USED; 6 appeared in packets
             * without EXT_FLAG_MCS_INDEX_USED.
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x000F:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x0010:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x0011:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x0012:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x0013:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_UNKNOWN_0x0014:
            /*
             * XXX - seen in some 802.11 captures; dB or dBm value?
             * Multiple antennas?
             */
            break;

        case TAG_PEEKTAGGED_EXT_FLAGS:
            /*
             * We assume this is present for HT and VHT frames and absent
             * for other frames.
             */
            ext_flags = pletohu32(&tag_value[2]);
            if (ext_flags & EXT_FLAG_802_11ac) {
                ieee_802_11.phy = PHDR_802_11_PHY_11AC;
                /*
                 * XXX - this probably has only one user, so only
                 * one MCS index and only one NSS, but where's the
                 * NSS?
                 */
                for (i = 0; i < 4; i++)
                    ieee_802_11.phy_info.info_11ac.nss[i] = 0;

                switch (ext_flags & EXT_FLAGS_GI) {

                case EXT_FLAG_HALF_GI:
                    ieee_802_11.phy_info.info_11ac.has_short_gi = true;
                    ieee_802_11.phy_info.info_11ac.short_gi = 1;
                    break;

                case EXT_FLAG_FULL_GI:
                    ieee_802_11.phy_info.info_11ac.has_short_gi = true;
                    ieee_802_11.phy_info.info_11ac.short_gi = 0;
                    break;

                default:
                    /* Mutually exclusive flags set or nothing set */
                    break;
                }
            } else {
                ieee_802_11.phy = PHDR_802_11_PHY_11N;
                switch (ext_flags & EXT_FLAGS_BANDWIDTH) {

                case 0:
                    ieee_802_11.phy_info.info_11n.has_bandwidth = true;
                    ieee_802_11.phy_info.info_11n.bandwidth = PHDR_802_11_BANDWIDTH_20_MHZ;
                    break;

                case EXT_FLAG_20_MHZ_LOWER:
                    ieee_802_11.phy_info.info_11n.has_bandwidth = true;
                    ieee_802_11.phy_info.info_11n.bandwidth = PHDR_802_11_BANDWIDTH_20_20L;
                    break;

                case EXT_FLAG_20_MHZ_UPPER:
                    ieee_802_11.phy_info.info_11n.has_bandwidth = true;
                    ieee_802_11.phy_info.info_11n.bandwidth = PHDR_802_11_BANDWIDTH_20_20U;
                    break;

                case EXT_FLAG_40_MHZ:
                    ieee_802_11.phy_info.info_11n.has_bandwidth = true;
                    ieee_802_11.phy_info.info_11n.bandwidth = PHDR_802_11_BANDWIDTH_40_MHZ;
                    break;

                default:
                    /* Mutually exclusive flags set */
                    break;
                }

                switch (ext_flags & EXT_FLAGS_GI) {

                case EXT_FLAG_HALF_GI:
                    ieee_802_11.phy_info.info_11n.has_short_gi = true;
                    ieee_802_11.phy_info.info_11n.short_gi = 1;
                    break;

                case EXT_FLAG_FULL_GI:
                    ieee_802_11.phy_info.info_11n.has_short_gi = true;
                    ieee_802_11.phy_info.info_11n.short_gi = 0;
                    break;

                default:
                    /* Mutually exclusive flags set or nothing set */
                    break;
                }
            }
            break;

        case TAG_PEEKTAGGED_SLICE_LENGTH:
            sliceLength = pletohu32(&tag_value[2]);
            break;

        default:
            break;
        }
    } while (tag != TAG_PEEKTAGGED_SLICE_LENGTH);       /* last tag */

    if (!saw_length) {
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: record has no length field");
        return -1;
    }
    if (!saw_timestamp_lower) {
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: record has no timestamp-lower field");
        return -1;
    }
    if (!saw_timestamp_upper) {
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: record has no timestamp-upper field");
        return -1;
    }

    /*
     * If sliceLength is 0, force it to be the actual length of the packet.
     */
    if (sliceLength == 0)
        sliceLength = length;

    if (sliceLength > WTAP_MAX_PACKET_SIZE_STANDARD) {
        /*
         * Probably a corrupt capture file; don't blow up trying
         * to allocate space for an immensely-large packet.
         */
        *err = WTAP_ERR_BAD_FILE;
        *err_info = ws_strdup_printf("peektagged: File has %u-byte packet, bigger than maximum of %u",
            sliceLength, WTAP_MAX_PACKET_SIZE_STANDARD);
        return -1;
    }

    wtap_setup_packet_rec(rec, wth->file_encap);
    rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
    rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
    rec->rec_header.packet_header.len    = length;
    rec->rec_header.packet_header.caplen = sliceLength;
    if (saw_flags_and_status) {
        uint32_t flags = 0;
        if (flags_and_status & FLAGS_HAS_CRC_ERROR)
            flags |= PACK_FLAGS_CRC_ERROR;
        wtap_block_add_uint32_option(rec->block, OPT_PKT_FLAGS, flags);
    }

    /* calculate and fill in packet time stamp */
    t = (((uint64_t) timestamp.upper) << 32) + timestamp.lower;
    if (!filetime_ns_to_nstime(&rec->ts, t)) {
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: time stamp outside supported range");
        return -1;
    }

    switch (wth->file_encap) {

    case WTAP_ENCAP_IEEE_802_11_WITH_RADIO:
        if (saw_data_rate_or_mcs_index) {
            if (ext_flags & EXT_FLAG_MCS_INDEX_USED) {
                /*
                 * It's an MCS index.
                 *
                 * XXX - what about 11ac?
                 */
                if (!(ext_flags & EXT_FLAG_802_11ac)) {
                    ieee_802_11.phy_info.info_11n.has_mcs_index = true;
                    ieee_802_11.phy_info.info_11n.mcs_index = data_rate_or_mcs_index;
                }
            } else {
                /* It's a data rate. */
                ieee_802_11.has_data_rate = true;
                ieee_802_11.data_rate = data_rate_or_mcs_index;
                if (ieee_802_11.phy == PHDR_802_11_PHY_UNKNOWN) {
                  /*
                   * We don't know they PHY; try to guess it based
                   * on the data rate and channel/center frequency.
                   */
                  if (RATE_IS_DSSS(ieee_802_11.data_rate)) {
                    /* 11b */
                    ieee_802_11.phy = PHDR_802_11_PHY_11B;
                    if (saw_flags_and_status) {
                      ieee_802_11.phy_info.info_11b.has_short_preamble = true;
                      ieee_802_11.phy_info.info_11b.short_preamble =
                          (flags_and_status & STATUS_SHORT_PREAMBLE) ? true : false;
                    } else
                      ieee_802_11.phy_info.info_11b.has_short_preamble = false;
                  } else if (RATE_IS_OFDM(ieee_802_11.data_rate)) {
                    /* 11a or 11g, depending on the band. */
                    if (ieee_802_11.has_channel) {
                      if (CHAN_IS_BG(ieee_802_11.channel)) {
                        /* 11g */
                        ieee_802_11.phy = PHDR_802_11_PHY_11G;
                      } else {
                        /* 11a */
                        ieee_802_11.phy = PHDR_802_11_PHY_11A;
                      }
                    } else if (ieee_802_11.has_frequency) {
                      if (FREQ_IS_BG(ieee_802_11.frequency)) {
                        /* 11g */
                        ieee_802_11.phy = PHDR_802_11_PHY_11G;
                      } else {
                        /* 11a */
                        ieee_802_11.phy = PHDR_802_11_PHY_11A;
                      }
                    }
                    if (ieee_802_11.phy == PHDR_802_11_PHY_11G) {
                      /* Set 11g metadata */
                      ieee_802_11.phy_info.info_11g.has_mode = false;
                    } else if (ieee_802_11.phy == PHDR_802_11_PHY_11A) {
                      /* Set 11a metadata */
                      ieee_802_11.phy_info.info_11a.has_channel_type = false;
                      ieee_802_11.phy_info.info_11a.has_turbo_type = false;
                    }
                    /* Otherwise we don't know the PHY */
                  }
                }
            }
        }
        if (ieee_802_11.has_frequency && !ieee_802_11.has_channel) {
            /* Frequency, but no channel; try to calculate the channel. */
            channel = ieee80211_mhz_to_chan(ieee_802_11.frequency);
            if (channel != -1) {
                ieee_802_11.has_channel = true;
                ieee_802_11.channel = channel;
            }
        } else if (ieee_802_11.has_channel && !ieee_802_11.has_frequency) {
            /*
             * If it's 11 legacy DHSS, 11b, or 11g, it's 2.4 GHz,
             * so we can calculate the frequency.
             *
             * If it's 11a, it's 5 GHz, so we can calculate the
             * frequency.
             */
            switch (ieee_802_11.phy) {

            case PHDR_802_11_PHY_11_DSSS:
            case PHDR_802_11_PHY_11B:
            case PHDR_802_11_PHY_11G:
                frequency = ieee80211_chan_to_mhz(ieee_802_11.channel, true);
                break;

            case PHDR_802_11_PHY_11A:
                frequency = ieee80211_chan_to_mhz(ieee_802_11.channel, false);
                break;

            default:
                /* We don't know the band. */
                frequency = 0;
                break;
            }
            if (frequency != 0) {
                ieee_802_11.has_frequency = true;
                ieee_802_11.frequency = frequency;
            }
        }
        rec->rec_header.packet_header.pseudo_header.ieee_802_11 = ieee_802_11;
        if (peektagged->has_fcs)
            rec->rec_header.packet_header.pseudo_header.ieee_802_11.fcs_len = 4;
        else {
            if (rec->rec_header.packet_header.len < 4 || rec->rec_header.packet_header.caplen < 4) {
                *err = WTAP_ERR_BAD_FILE;
                *err_info = ws_strdup_printf("peektagged: 802.11 packet has length < 4");
                return false;
            }
            rec->rec_header.packet_header.pseudo_header.ieee_802_11.fcs_len = 0;
            rec->rec_header.packet_header.len -= 4;
            rec->rec_header.packet_header.caplen -= 4;
            skip_len = 4;
        }
        rec->rec_header.packet_header.pseudo_header.ieee_802_11.decrypted = false;
        rec->rec_header.packet_header.pseudo_header.ieee_802_11.datapad = false;
        break;

    case WTAP_ENCAP_ETHERNET:
        /*
         * The last 4 bytes appear to be 0 in the captures I've seen;
         * are there any captures where it's an FCS?
         */
        if (rec->rec_header.packet_header.len < 4 || rec->rec_header.packet_header.caplen < 4) {
            *err = WTAP_ERR_BAD_FILE;
            *err_info = ws_strdup_printf("peektagged: Ethernet packet has length < 4");
            return false;
        }
        rec->rec_header.packet_header.pseudo_header.eth.fcs_len = 0;
        rec->rec_header.packet_header.len -= 4;
        rec->rec_header.packet_header.caplen -= 4;
        skip_len = 4;
        break;
    }

    /* Read the packet data. */
    if (!wtap_read_bytes_buffer(fh, &rec->data, rec->rec_header.packet_header.caplen, err, err_info))
        return -1;

    return skip_len;
}

static bool peektagged_read(wtap *wth, wtap_rec *rec,
    int *err, char **err_info, int64_t *data_offset)
{
    int skip_len;

    *data_offset = file_tell(wth->fh);

    /* Read the packet. */
    skip_len = peektagged_read_packet(wth, wth->fh, rec, err, err_info);
    if (skip_len == -1)
        return false;

    if (skip_len != 0) {
        /* Skip extra junk at the end of the packet data. */
        if (!wtap_read_bytes(wth->fh, NULL, skip_len, err, err_info))
            return false;
    }

    return true;
}

static bool
peektagged_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec,
    int *err, char **err_info)
{
    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
        return false;

    /* Read the packet. */
    if (peektagged_read_packet(wth, wth->random_fh, rec, err, err_info) == -1) {
        if (*err == 0)
            *err = WTAP_ERR_SHORT_READ;
        return false;
    }
    return true;
}

wtap_open_return_val peektagged_open(wtap* wth, int* err, char** err_info)
{
    peektagged_section_header_t ap_hdr;
    xmlDocPtr doc;
    uint32_t length;
    uint32_t fileVersion, mediaType = 0, mediaSubType = 0;
    uint8_t sectionData[MAX_SECTION_SIZE];
    const int peektagged_encap[] = {
        WTAP_ENCAP_ETHERNET,
        WTAP_ENCAP_IEEE_802_11_WITH_RADIO,
        WTAP_ENCAP_IEEE_802_11_WITH_RADIO,
        WTAP_ENCAP_IEEE_802_11_WITH_RADIO
    };
#define NUM_PEEKTAGGED_ENCAPS array_length(peektagged_encap)
    peektagged_t* peektagged;

    if (!wtap_read_bytes(wth->fh, &ap_hdr, (int)sizeof(ap_hdr), err, err_info)) {
        if (*err != WTAP_ERR_SHORT_READ)
            return WTAP_OPEN_ERROR;
        return WTAP_OPEN_NOT_MINE;
    }

    if (memcmp(ap_hdr.section_id, "\177ver", sizeof(ap_hdr.section_id)) != 0)
        return WTAP_OPEN_NOT_MINE;      /* doesn't begin with a "\177ver" section */

    length = GUINT32_TO_LE(ap_hdr.section_len);
    if ((length >= MAX_SECTION_SIZE) || (GUINT32_TO_LE(ap_hdr.section_const) != SECTION_CONST_VALUE))
        return WTAP_OPEN_NOT_MINE;

    if (!wtap_read_bytes(wth->fh, sectionData, (int)length, err, err_info)) {
        if (*err != WTAP_ERR_SHORT_READ)
            return WTAP_OPEN_ERROR;
        return WTAP_OPEN_NOT_MINE;
    }
    sectionData[length] = 0;

    /* Now section data can be parsed into a proper structure */
    doc = xmlParseMemory((const char*)sectionData, (int)length);
    if (doc == NULL)
        return WTAP_OPEN_NOT_MINE;

    fileVersion = peektagged_get_file_version(doc);
    xmlFreeDoc(doc);
    if (fileVersion == 0) {
        /* Something when wrong trying to retrieve file version, so
           consider this not a Peek tagged file */
        return WTAP_OPEN_NOT_MINE;
    }

    /* If we got this far, we assume it's a Peek tagged file. */
    if (fileVersion != 9) {
        /* We only support version 9. */
        *err = WTAP_ERR_UNSUPPORTED;
        *err_info = ws_strdup_printf("peektagged: version %u unsupported",
            fileVersion);
        return WTAP_OPEN_ERROR;
    }

    /*
     * Now check for a "sess" section and fail if we don't see it.
     * Then we should get the length of the "sess" section, check
     * that it's followed by a little-endian 0x00000200, and then,
     * when reading the XML, make sure we don't go past the end of
     * that section, and skip to the end of the section when
     * we have the file version (and possibly check to make sure all
     * tags are properly opened and closed).
     */

    if (!wtap_read_bytes(wth->fh, &ap_hdr, (int)sizeof(ap_hdr), err, err_info))
        return WTAP_OPEN_ERROR;

    /*
     * If we see an "cpid" section, which appears to be optional, skip over it.
     */
    if (memcmp(ap_hdr.section_id, "cpid", sizeof(ap_hdr.section_id)) == 0) {
        if (!peektagged_skip_section(wth, &ap_hdr, err, err_info)) {
            if (*err && *err != WTAP_ERR_SHORT_READ)
                return WTAP_OPEN_ERROR;
            return WTAP_OPEN_NOT_MINE;
        }
    }

    if (memcmp(ap_hdr.section_id, "sess", sizeof(ap_hdr.section_id)) != 0) {
        *err = WTAP_ERR_UNSUPPORTED;
        char *section_name = format_text_wsp(NULL, (char *)ap_hdr.section_id, sizeof(ap_hdr.section_id));
        *err_info = ws_strdup_printf("peektagged: Unknown section ID 0x%08x (\"%s\")", pntohu32(ap_hdr.section_id), section_name);
        wmem_free(NULL, section_name);
        return WTAP_OPEN_ERROR;
    }

    length = GUINT32_TO_LE(ap_hdr.section_len);
    if ((length >= MAX_SECTION_SIZE) || (GUINT32_TO_LE(ap_hdr.section_const) != SECTION_CONST_VALUE))
        return WTAP_OPEN_NOT_MINE;

    if (!wtap_read_bytes(wth->fh, sectionData, (int)length, err, err_info)) {
        if (*err != WTAP_ERR_SHORT_READ)
            return WTAP_OPEN_ERROR;
        return WTAP_OPEN_NOT_MINE;
    }
    sectionData[length] = 0;

    /* Now section data can be parsed into a proper structure */
    doc = xmlParseMemory((const char*)sectionData, (int)length);
    if (doc == NULL) {
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: session section XML couldn't be parsed");
        return WTAP_OPEN_ERROR;
    }

    if (!peektagged_get_media_info(doc, &mediaType, &mediaSubType)) {
        xmlFreeDoc(doc);
        *err = WTAP_ERR_BAD_FILE;
        *err_info = g_strdup("peektagged: session media tag(s) not found");
        return WTAP_OPEN_ERROR;
    }

    xmlFreeDoc(doc);

    if (mediaSubType >= NUM_PEEKTAGGED_ENCAPS
        || peektagged_encap[mediaSubType] == WTAP_ENCAP_UNKNOWN) {
        *err = WTAP_ERR_UNSUPPORTED;
        *err_info = ws_strdup_printf("peektagged: network type %u unknown or unsupported",
            mediaSubType);
        return WTAP_OPEN_ERROR;
    }

    /*
     * Now check for a "pkts" section and fail if we don't see it.
     */
    if (!wtap_read_bytes(wth->fh, &ap_hdr, (int)sizeof(ap_hdr), err, err_info))
        return WTAP_OPEN_ERROR;

    /*
     * If we see an "cpid" section, which appears to be optional, skip over it.
     */
    if (memcmp(ap_hdr.section_id, "cpid", sizeof(ap_hdr.section_id)) == 0) {
        if (!peektagged_skip_section(wth, &ap_hdr, err, err_info)) {
            if (*err && *err != WTAP_ERR_SHORT_READ)
                return WTAP_OPEN_ERROR;
            return WTAP_OPEN_NOT_MINE;
        }
    }

    if (memcmp(ap_hdr.section_id, "pkts", sizeof(ap_hdr.section_id)) != 0) {
        *err = WTAP_ERR_UNSUPPORTED;
        char *section_name = format_text_wsp(NULL, (char *)ap_hdr.section_id, sizeof(ap_hdr.section_id));
        *err_info = ws_strdup_printf("peektagged: Unknown section ID 0x%08x (\"%s\")", pntohu32(ap_hdr.section_id), section_name);
        wmem_free(NULL, section_name);
        return WTAP_OPEN_ERROR;
    }

    /*
     * This is an Peek tagged file.
     */
    wth->file_type_subtype = peektagged_file_type_subtype;
    wth->file_encap = peektagged_encap[mediaSubType];
    wth->subtype_read = peektagged_read;
    wth->subtype_seek_read = peektagged_seek_read;
    wth->file_tsprec = WTAP_TSPREC_NSEC;

    peektagged = g_new(peektagged_t, 1);
    wth->priv = (void*)peektagged;
    switch (mediaSubType) {

    case PEEKTAGGED_NST_ETHERNET:
    case PEEKTAGGED_NST_802_11:
    case PEEKTAGGED_NST_802_11_2:
        peektagged->has_fcs = false;
        break;

    case PEEKTAGGED_NST_802_11_WITH_FCS:
        peektagged->has_fcs = true;
        break;
    }

    wth->snapshot_length = 0; /* not available in header */

    /*
     * Add an IDB; we don't know how many interfaces were involved,
     * so we just say one interface, about which we only know
     * the link-layer type, snapshot length, and time stamp
     * resolution.
     */
    wtap_add_generated_idb(wth);

    return WTAP_OPEN_MINE;
}

static const struct supported_block_type peektagged_blocks_supported[] = {
    /*
     * We support packet blocks, with no comments or other options.
     */
    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
};

static const struct file_type_subtype_info peektagged_info = {
    "Savvius tagged", "peektagged", "pkt", "tpc;apc;wpz",
    false, BLOCKS_SUPPORTED(peektagged_blocks_supported),
    NULL, NULL, NULL
};

void register_peektagged(void)
{
    peektagged_file_type_subtype = wtap_register_file_type_subtype(&peektagged_info);

    /*
     * Register name for backwards compatibility with the
     * wtap_filetypes table in Lua.
     */
    wtap_register_backwards_compatibility_lua_name("PEEKTAGGED",
                                                   peektagged_file_type_subtype);
}

/*
 * 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:
 */
