/*
 * Copyright (c) 2009 University of Washington
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author:  Craig Dowell (craigdo@ee.washington.edu)
 */

#ifndef PCAP_FILE_H
#define PCAP_FILE_H

#include "ns3/ptr.h"

#include <fstream>
#include <stdint.h>
#include <string>

namespace ns3
{

class Packet;
class Header;

/**
 * @brief A class representing a pcap file
 *
 * A class representing a pcap file.  This allows easy creation, writing and
 * reading of files composed of stored packets; which may be viewed using
 * standard tools.
 */
class PcapFile
{
  public:
    static const int32_t ZONE_DEFAULT = 0; //!< Time zone offset for current location
    static const uint32_t SNAPLEN_DEFAULT =
        65535; //!< Default value for maximum octets to save per packet

  public:
    PcapFile();
    ~PcapFile();

    /**
     * @return true if the 'fail' bit is set in the underlying iostream, false otherwise.
     */
    bool Fail() const;
    /**
     * @return true if the 'eof' bit is set in the underlying iostream, false otherwise.
     */
    bool Eof() const;
    /**
     * Clear all state bits of the underlying iostream.
     */
    void Clear();

    /**
     * Create a new pcap file or open an existing pcap file.  Semantics are
     * similar to the stdc++ io stream classes, but differ in that
     * positions in the file are based on packets not characters.  For example
     * if the file is opened for reading, the file position indicator (seek
     * position) points to the beginning of the first packet in the file, not
     * zero (which would point to the start of the pcap header).
     *
     * Since a pcap file is always a binary file, the file type is automatically
     * selected as a binary file (fstream::binary is automatically ored with the mode
     * field).
     *
     * @param filename String containing the name of the file.
     *
     * @param mode the access mode for the file.
     */
    void Open(const std::string& filename, std::ios::openmode mode);

    /**
     * Close the underlying file.
     */
    void Close();

    /**
     * Initialize the pcap file associated with this object.  This file must have
     * been previously opened with write permissions.
     *
     * @param dataLinkType A data link type as defined in the pcap library.  If
     * you want to make resulting pcap files visible in existing tools, the
     * data link type must match existing definitions, such as PCAP_ETHERNET,
     * PCAP_PPP, PCAP_80211, etc.  If you are storing different kinds of packet
     * data, such as naked TCP headers, you are at liberty to locally define your
     * own data link types.  According to the pcap-linktype man page, "well-known"
     * pcap linktypes range from 0 to 263.  If you use a large random number for
     * your type, chances are small for a collision.
     *
     * @param snapLen An optional maximum size for packets written to the file.
     * Defaults to 65535.  If packets exceed this length they are truncated.
     *
     * @param timeZoneCorrection An integer describing the offset of your local
     * time zone from UTC/GMT.  For example, Pacific Standard Time in the US is
     * GMT-8, so one would enter -8 for that correction.  Defaults to 0 (UTC).
     *
     * @param swapMode Flag indicating a difference in endianness of the
     * writing system. Defaults to false.
     *
     * @param nanosecMode Flag indicating the time resolution of the writing
     * system. Default to false.
     *
     * @warning Calling this method on an existing file will result in the loss
     * any existing data.
     */
    void Init(uint32_t dataLinkType,
              uint32_t snapLen = SNAPLEN_DEFAULT,
              int32_t timeZoneCorrection = ZONE_DEFAULT,
              bool swapMode = false,
              bool nanosecMode = false);

    /**
     * @brief Write next packet to file
     *
     * @param tsSec       Packet timestamp, seconds
     * @param tsUsec      Packet timestamp, microseconds
     * @param data        Data buffer
     * @param totalLen    Total packet length
     *
     */
    void Write(uint32_t tsSec, uint32_t tsUsec, const uint8_t* const data, uint32_t totalLen);

    /**
     * @brief Write next packet to file
     *
     * @param tsSec       Packet timestamp, seconds
     * @param tsUsec      Packet timestamp, microseconds
     * @param p           Packet to write
     *
     */
    void Write(uint32_t tsSec, uint32_t tsUsec, Ptr<const Packet> p);
    /**
     * @brief Write next packet to file
     *
     * @param tsSec       Packet timestamp, seconds
     * @param tsUsec      Packet timestamp, microseconds
     * @param header      Header to write, in front of packet
     * @param p           Packet to write
     *
     */
    void Write(uint32_t tsSec, uint32_t tsUsec, const Header& header, Ptr<const Packet> p);

    /**
     * @brief Read next packet from file
     *
     * @param data        [out] Data buffer
     * @param maxBytes    Allocated data buffer size
     * @param tsSec       [out] Packet timestamp, seconds
     * @param tsUsec      [out] Packet timestamp, microseconds
     * @param inclLen     [out] Included length
     * @param origLen     [out] Original length
     * @param readLen     [out] Number of bytes read
     *
     */
    void Read(uint8_t* const data,
              uint32_t maxBytes,
              uint32_t& tsSec,
              uint32_t& tsUsec,
              uint32_t& inclLen,
              uint32_t& origLen,
              uint32_t& readLen);

    /**
     * @brief Get the swap mode of the file.
     *
     * Pcap files use a magic number that is overloaded to identify both the
     * format of the file itself and the byte ordering of the file.  The magic
     * number (and all data) is written into the file according to the native
     * byte ordering of the writing system.  If a reading application reads
     * the magic number identically (for example 0xa1b2c3d4) then no byte
     * swapping is required to correctly interpret the file data.  If the reading
     * application sees the magic number is byte swapped (for example 0xd4c3b2a1)
     * then it knows that it needs to byteswap appropriate fields in the format.
     *
     * GetSWapMode returns a value indicating whether or not the fields are being
     * byteswapped.  Used primarily for testing the class itself, but may be
     * useful as a flag indicating a difference in endianness of the writing
     * system.
     *
     * @returns swap mode of the file
     */
    bool GetSwapMode();

    /**
     * @brief Get the nanosecond mode of the file.
     *
     * @returns true if the packet timestamps in the PCAP
     * file have nanosecond resolution.
     */
    bool IsNanoSecMode();

    /**
     * @brief Returns the magic number of the pcap file as defined by the magic_number
     * field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns magic number
     */
    uint32_t GetMagic();

    /**
     * @brief Returns the major version of the pcap file as defined by the version_major
     * field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns major version
     */
    uint16_t GetVersionMajor();

    /**
     * @brief Returns the minor version of the pcap file as defined by the version_minor
     * field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns minor version
     */
    uint16_t GetVersionMinor();

    /**
     * @brief Returns the time zone offset of the pcap file as defined by the thiszone
     * field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns time zone offset
     */
    int32_t GetTimeZoneOffset();

    /**
     * @brief Returns the accuracy of timestamps field of the pcap file as defined
     * by the sigfigs field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns accuracy of timestamps
     */
    uint32_t GetSigFigs();

    /**
     * @brief Returns the max length of saved packets field of the pcap file as
     * defined by the snaplen field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns max length of saved packets field
     */
    uint32_t GetSnapLen();

    /**
     * @brief Returns the data link type field of the pcap file as defined by the
     * network field in the pcap global header.
     *
     * See http://wiki.wireshark.org/Development/LibpcapFileFormat
     *
     * @returns data link type field
     */
    uint32_t GetDataLinkType();

    /**
     * @brief Compare two PCAP files packet-by-packet
     *
     * @param f1 First PCAP file name
     * @param f2 Second PCAP file name
     * @param sec [out] Time stamp of first different packet, seconds. Undefined if files don't
     * differ.
     * @param usec [out] Time stamp of first different packet, microseconds. Undefined if files
     * don't differ.
     * @param packets [out] Number of first different packet. Total number of parsed packets if
     * files don't differ.
     * @param snapLen Snap length (if used)
     * @return true if files are different, false otherwise
     */
    static bool Diff(const std::string& f1,
                     const std::string& f2,
                     uint32_t& sec,
                     uint32_t& usec,
                     uint32_t& packets,
                     uint32_t snapLen = SNAPLEN_DEFAULT);

  private:
    /**
     * @brief Pcap file header
     */
    struct PcapFileHeader
    {
        uint32_t m_magicNumber; //!< Magic number identifying this as a pcap file
        uint16_t
            m_versionMajor; //!< Major version identifying the version of pcap used in this file
        uint16_t
            m_versionMinor; //!< Minor version identifying the version of pcap used in this file
        int32_t m_zone;     //!< Time zone correction to be applied to timestamps of packets
        uint32_t m_sigFigs; //!< Unused by pretty much everybody
        uint32_t m_snapLen; //!< Maximum length of packet data stored in records
        uint32_t m_type;    //!< Data link type of packet data
    };

    /**
     * @brief Pcap record header
     */
    struct PcapRecordHeader
    {
        uint32_t m_tsSec;   //!< seconds part of timestamp
        uint32_t m_tsUsec;  //!< microseconds part of timestamp (nsecs for PCAP_NSEC_MAGIC)
        uint32_t m_inclLen; //!< number of octets of packet saved in file
        uint32_t m_origLen; //!< actual length of original packet
    };

    /**
     * @brief Swap a value byte order
     * @param val the value
     * @returns the value with byte order swapped
     */
    uint8_t Swap(uint8_t val);
    /**
     * @brief Swap a value byte order
     * @param val the value
     * @returns the value with byte order swapped
     */
    uint16_t Swap(uint16_t val);
    /**
     * @brief Swap a value byte order
     * @param val the value
     * @returns the value with byte order swapped
     */
    uint32_t Swap(uint32_t val);
    /**
     * @brief Swap the byte order of a Pcap file header
     * @param from original file header
     * @param to swapped file header
     */
    void Swap(PcapFileHeader* from, PcapFileHeader* to);
    /**
     * @brief Swap the byte order of a Pcap record header
     * @param from original record header
     * @param to swapped record header
     */
    void Swap(PcapRecordHeader* from, PcapRecordHeader* to);

    /**
     * @brief Write a Pcap file header
     */
    void WriteFileHeader();
    /**
     * @brief Write a Pcap packet header
     *
     * The pcap header has a fixed length of 24 bytes. The last 4 bytes
     * represent the link-layer type
     *
     * @param tsSec Time stamp (seconds part)
     * @param tsUsec Time stamp (microseconds part)
     * @param totalLen total packet length
     * @returns the length of the packet to write in the Pcap file
     */
    uint32_t WritePacketHeader(uint32_t tsSec, uint32_t tsUsec, uint32_t totalLen);

    /**
     * @brief Read and verify a Pcap file header
     */
    void ReadAndVerifyFileHeader();

    std::string m_filename;      //!< file name
    std::fstream m_file;         //!< file stream
    PcapFileHeader m_fileHeader; //!< file header
    bool m_swapMode;             //!< swap mode
    bool m_nanosecMode;          //!< nanosecond timestamp mode
};

} // namespace ns3

#endif /* PCAP_FILE_H */
