//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2005-2020, Thierry Lelegard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
//----------------------------------------------------------------------------
//!
//!  @file
//!  Representation of a packet for MPE (Multi-Protocol Encapsulation).
//!
//----------------------------------------------------------------------------

#pragma once
#include "tsSection.h"
#include "tsSocketAddress.h"
#include "tsMACAddress.h"

namespace ts {
    //!
    //! Representation of a packet for MPE (Multi-Protocol Encapsulation).
    //!
    //! This implementation has the following restrictions:
    //! - The encapsulated datagrams can be UDP/IP only.
    //! - LLC/SNAP encapsulation is not supported.
    //! - The datagran and address scrambling is not supported.
    //! - Each datagram shall fit into one section.
    //!
    //! @see ETSI EN 301 192, section 7.1.
    //! @ingroup mpeg
    //!
    class TSDUCKDLL MPEPacket
    {
    public:
        //!
        //! Default constructor.
        //! Section is initially marked invalid.
        //!
        MPEPacket();

        //!
        //! Copy constructor.
        //! @param [in] other Another instance to copy.
        //! @param [in] mode The datagram contents are either shared (ts::SHARE) between the two instances or duplicated (ts::COPY).
        //!
        MPEPacket(const MPEPacket& other, ShareMode mode);

        //!
        //! Move constructor.
        //! @param [in,out] other Another instance to move.
        //!
        MPEPacket(MPEPacket&& other) noexcept;

        //!
        //! Constructor from a full datagram (including network headers).
        //! @param [in] datagram Smart pointer to the complete datagram content.
        //! The datagram typically includes the IP and UDP headers. The datagram
        //! is analyzed and marked invalid if no valid UDP/IP header is found.
        //! @param [in] mode The datagram contents are either shared (ts::SHARE)
        //! between the two instances or duplicated (ts::COPY).
        //! @param [in] mac Destination MAC address. If unspecified and the destination
        //! IP address is multicast, the corresponding MAC address is used.
        //! @param [in] pid PID from which the DSM-CC section was read.
        //!
        MPEPacket(ByteBlockPtr datagram, ShareMode mode, const MACAddress& mac = MACAddress(), PID pid = PID_NULL);

        //!
        //! Constructor from a DSM-CC MPE section.
        //! @param [in] section A binary DSM-CC MPE section.
        //!
        MPEPacket(const Section& section);

        //!
        //! Clear content.
        //! Becomes invalid.
        //!
        void clear();

        //!
        //! Assignment operator.
        //! The contents are referenced, and thus shared between the two objects.
        //! @param [in] other Other instance to assign to this object.
        //! @return A reference to this object.
        //!
        MPEPacket& operator=(const MPEPacket& other);

        //!
        //! Move assignment operator.
        //! @param [in,out] other Other instance to move into this object.
        //! @return A reference to this object.
        //!
        MPEPacket& operator=(MPEPacket&& other) noexcept;

        //!
        //! Duplication.
        //! Similar to assignment but the objects are duplicated.
        //! @param [in] other Other instance to duplicate into this object.
        //! @return A reference to this object.
        //!
        MPEPacket& copy(const MPEPacket& other);

        //!
        //! Copy content from a DSM-CC MPE section.
        //! @param [in] section A binary DSM-CC MPE section.
        //! @return A reference to this object.
        //!
        MPEPacket& copy(const Section& section);

        //!
        //! Create a DSM-CC MPE section containing the MPE packet.
        //! @param [out] section A binary DSM-CC MPE section to create.
        //!
        void createSection(Section& section) const;

        //!
        //! Check if the packet has valid content.
        //! @return True if the packet has valid content.
        //!
        bool isValid() const { return _is_valid; }

        //!
        //! Get the source PID.
        //! @return The source PID.
        //!
        PID sourcePID() const { return _source_pid; }

        //!
        //! Set the source PID.
        //! @param [in] pid The source PID.
        //!
        void setSourcePID(PID pid) { _source_pid = pid; }

        //!
        //! Get the destination MAC address.
        //! @return The destination MAC address.
        //!
        MACAddress destinationMACAddress() const { return _dest_mac; }

        //!
        //! Set the destination MAC address.
        //! @param [in] mac The destination MAC address.
        //!
        void setDestinationMACAddress(const MACAddress& mac) { _dest_mac = mac; }

        //!
        //! Get the source IP address.
        //! @return The source IP address.
        //!
        IPAddress sourceIPAddress() const;

        //!
        //! Set the source IP address.
        //! @param [in] ip The source IP address.
        //!
        void setSourceIPAddress(const IPAddress& ip);

        //!
        //! Get the destination IP address.
        //! @return The destination IP address.
        //!
        IPAddress destinationIPAddress() const;

        //!
        //! Set the destination IP address.
        //! @param [in] ip The destination IP address.
        //!
        void setDestinationIPAddress(const IPAddress& ip);

        //!
        //! Get the source UDP port.
        //! @return The source UDP port.
        //!
        uint16_t sourceUDPPort() const;

        //!
        //! Set the source UDP port.
        //! @param [in] port The source UDP port.
        //!
        void setSourceUDPPort(uint16_t port);

        //!
        //! Get the destination UDP port.
        //! @return The destination UDP port.
        //!
        uint16_t destinationUDPPort() const;

        //!
        //! Set the destination UDP port.
        //! @param [in] port The destination UDP port.
        //!
        void setDestinationUDPPort(uint16_t port);

        //!
        //! Get the source socket address.
        //! @return The source socket address.
        //!
        SocketAddress sourceSocket() const;

        //!
        //! Set the source socket address.
        //! @param [in] sock The source socket address.
        //!
        void setSourceSocket(const SocketAddress& sock);

        //!
        //! Get the destination socket address.
        //! @return The destination socket address.
        //!
        SocketAddress destinationSocket() const;

        //!
        //! Set the destination socket address.
        //! @param [in] sock The destination socket address.
        //!
        void setDestinationSocket(const SocketAddress& sock);

        //!
        //! Access to the binary content of the UDP message.
        //! Do not modify content.
        //! @return Address of the binary content of the UDP message payload.
        //! May be invalidated after modification in section.
        //!
        const uint8_t* udpMessage() const;

        //!
        //! Size of the binary content of the UDP message.
        //! @return Size of the binary content of the UDP message.
        //!
        size_t udpMessageSize() const;

        //!
        //! Access to the binary content of the complete network datagram.
        //! Do not modify content.
        //! @return Address of the binary content of the complete network datagram.
        //! May be invalidated after modification in section.
        //!
        const uint8_t* datagram() const { return _is_valid && !_datagram.isNull() ? _datagram->data() : nullptr; }

        //!
        //! Size of the binary content of the complete network datagram.
        //! @return Size of the binary content of the complete network datagram.
        //!
        size_t datagramSize() const { return _is_valid && !_datagram.isNull() ? _datagram->size() : 0; }

        //!
        //! Replace the binary content of the UDP message.
        //! @param [in] data Address of the binary content of the UDP message payload.
        //! @param [in] size Size of the binary content of the UDP message.
        //! May be invalidated after modification in section.
        //! @return True on success, false on invalid parameters.
        //!
        bool setUDPMessage(const uint8_t* data, size_t size);

    private:
        // Private fields
        bool         _is_valid;    // A valid datagram is present.
        PID          _source_pid;  // Source PID (informational).
        MACAddress   _dest_mac;    // Destination MAC address (in DSM-CC section).
        ByteBlockPtr _datagram;    // Full binary content of the datagram.

        // Locate UDP payload and size in a datagram.
        // Output parameters are optional. Return false on error.
        static bool FindUDP(const uint8_t* dgAddress, size_t dgSize, const uint8_t** udpHeader = nullptr, const uint8_t** udpAddress = nullptr, size_t* udpSize = nullptr);

        // Make sure the UDP datagram is valid.
        // If not valid, reallocate a new datagram area.
        // If force is true, reallocate all the time.
        // If reallocated, udpSize is the size of the UDP payload.
        void configureUDP(bool force, size_t udpSize);

        // Locate UDP payload and size in our datagram.
        // Output parameters are optional. Return false on error.
        bool findUDP(const uint8_t** udpHeader = nullptr, const uint8_t** udpAddress = nullptr, size_t* udpSize = nullptr) const;
        bool findUDP(uint8_t** udpHeader = nullptr, uint8_t** udpAddress = nullptr, size_t* udpSize = nullptr);

        // Inaccessible operations
        MPEPacket(const MPEPacket&) = delete;
    };
}
