///
//
// icey
// Copyright (c) 2005, icey <https://0state.com>
//
// SPDX-License-Identifier: LGPL-2.1+
//
/// @addtogroup stun
/// @{


#pragma once


#include "icy/buffer.h"
#include "icy/crypto/crypto.h"
#include "icy/net/address.h"
#include "icy/stun/stun.h"

#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>


namespace icy {
namespace stun {


/// The virtual base class for all STUN/TURN attributes.
class STUN_API Attribute
{
public:
    enum Type
    {
        NotExist = 0,
        MappedAddress = 0x0001,
        ResponseAddress = 0x0002, // Not implemented
        ChangeRequest = 0x0003,   // Not implemented
        SourceAddress = 0x0004,   // Not implemented
        ChangedAddress = 0x0005,  // Not implemented
        Username = 0x0006,
        Password = 0x0007, // Not implemented
        MessageIntegrity = 0x0008,
        ErrorCode = 0x0009,
        Bandwidth = 0x0010,          // Not implemented
        DestinationAddress = 0x0011, // Not implemented
        UnknownAttributes = 0x000a,
        ReflectedFrom = 0x000b, // Not implemented
        // TransportPreferences    = 0x000c, // Not implemented
        MagicCookie = 0x000f, // Not implemented, ByteString, 4 bytes
        Realm = 0x0014,
        Nonce = 0x0015,
        XorMappedAddress = 0x0020,
        Software = 0x8022,
        Options = 0x8001, // Not implemented
        AlternateServer = 0x000e,
        Fingerprint = 0x8028, /// TURN
        ChannelNumber = 0x000c,
        Lifetime = 0x000d, // 0x0010: Reserved (was BANDWIDTH)
        XorPeerAddress = 0x0012,
        Data = 0x0013,
        XorRelayedAddress = 0x0016,
        EventPort = 0x0018, // Not implemented
        RequestedTransport = 0x0019,
        DontFragment = 0x001A, // Not implemented
        /// 0x0021: Reserved (was TIMER-VAL)
        ReservationToken = 0x0022, // 8 bytes token value

        /// TURN TCP
        ConnectionID = 0x002a,

        /// ICE
        ICEControlled = 0x8029,
        ICEControlling = 0x802A,
        ICEPriority = 0x0024,
        ICEUseCandidate = 0x0025
    };

    virtual ~Attribute() {}

    /// Returns a deep copy of this attribute.
    virtual std::unique_ptr<Attribute> clone() = 0;

    /// Reads the body (not the type or size) for this
    /// type of attribute from the given buffer.
    /// @param reader Source bit reader positioned at the attribute body.
    virtual void read(BitReader& reader) = 0;

    /// Writes the body (not the type or size) to the given buffer.
    /// @param writer Destination bit writer.
    virtual void write(BitWriter& writer) const = 0;

    /// Creates an attribute of the given wire type and body size.
    /// Returns nullptr if the type is unknown or the size is invalid.
    /// @param type  Wire type code (one of Attribute::Type).
    /// @param size  Body length in bytes as read from the wire header.
    /// @return Owning pointer to the new attribute, or nullptr on failure.
    [[nodiscard]] static std::unique_ptr<Attribute> create(uint16_t type, uint16_t size = 0);

    /// @return The wire type code for this attribute.
    [[nodiscard]] uint16_t type() const;

    /// @return The body length of this attribute in bytes (before padding).
    [[nodiscard]] uint16_t size() const;

    /// Returns the 4-byte alignment padding required for a body of @p size bytes.
    [[nodiscard]] static constexpr uint16_t paddingBytes(uint16_t size)
    {
        return size % 4 == 0 ? 0 : static_cast<uint16_t>(4 - (size % 4));
    }

    /// Returns the body length including 4-byte alignment padding.
    [[nodiscard]] static constexpr uint16_t paddedBytes(uint16_t size)
    {
        return static_cast<uint16_t>(size + paddingBytes(size));
    }

    /// @return The 4-byte alignment padding required for this attribute body.
    [[nodiscard]] uint16_t paddingBytes() const { return paddingBytes(_size); }

    /// @return The body length including 4-byte alignment padding.
    [[nodiscard]] uint16_t paddedBytes() const { return paddedBytes(_size); }

    /// Advances the reader past any 4-byte alignment padding that follows
    /// this attribute's body.
    /// @param reader Reader to advance.
    void consumePadding(BitReader& reader) const;

    /// Writes zero-fill padding bytes to align this attribute to a 4-byte
    /// boundary.
    /// @param writer Writer to append padding to.
    void writePadding(BitWriter& writer) const;

    static constexpr uint16_t TypeID = 0;

    /// @return Human-readable name for this attribute's type.
    [[nodiscard]] std::string typeString();

    /// @param type Wire type code.
    /// @return Human-readable name for the given type code.
    [[nodiscard]] static std::string typeString(uint16_t type);

protected:
    /// @param type Wire type code for this attribute.
    /// @param size Initial body length in bytes.
    Attribute(uint16_t type, uint16_t size = 0);

    /// Updates the stored body length.
    /// @param size New body length in bytes.
    void setLength(uint16_t size);

    uint16_t _type;
    uint16_t _size;
};


/// Implements a STUN/TURN attribute that contains a socket address.
/// Handles XOR encoding/decoding for address and port as required by
/// RFC 5389 section 15.2.
class STUN_API AddressAttribute : public Attribute
{
public:
    /// @param type Wire type code (e.g. XorMappedAddress::TypeID).
    /// @param ipv4 When true, initialises the size for IPv4; otherwise IPv6.
    AddressAttribute(uint16_t type, bool ipv4 = true);

    /// Copy constructor; duplicates the stored address.
    AddressAttribute(const AddressAttribute& r);

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t IPv4Size = 8;
    static constexpr uint16_t IPv6Size = 20;

    /// @return The STUN address family (IPv4, IPv6, or Undefined) of the stored address.
    [[nodiscard]] stun::AddressFamily family() const
    {
        switch (_address.family()) {
            case net::Address::IPv4:
                return stun::AddressFamily::IPv4;
            case net::Address::IPv6:
                return stun::AddressFamily::IPv6;
        }
        return stun::AddressFamily::Undefined;
    }

    /// @return The decoded socket address stored in this attribute.
    [[nodiscard]] virtual net::Address address() const;

    /// @copydoc Attribute::read
    void read(BitReader& reader) override;

    /// @copydoc Attribute::write
    void write(BitWriter& writer) const override;

    /// Sets the address to encode into this attribute.
    /// @param addr Address to store.
    virtual void setAddress(const net::Address& addr)
    {
        _address = addr;
        switch (addr.family()) {
            case net::Address::IPv4:
                setLength(IPv4Size);
                break;
            case net::Address::IPv6:
                setLength(IPv6Size);
                break;
        }
    }

    /// @return true if this attribute type uses XOR encoding (RFC 5389).
    [[nodiscard]] bool isXorType() const
    {
        auto t = type();
        return t == 0x0020 || t == 0x0012 || t == 0x0016; // XorMapped, XorPeer, XorRelayed
    }

private:
    net::Address _address;
};


/// Implements a STUN/TURN attribute that holds an 8-bit integer.
class STUN_API UInt8Attribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    UInt8Attribute(uint16_t type);

    /// Copy constructor.
    UInt8Attribute(const UInt8Attribute& r);

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t Size = 1;

    /// @return The stored 8-bit value.
    [[nodiscard]] uint8_t value() const { return _bits; }

    /// Sets the stored 8-bit value.
    /// @param bits Value to store.
    void setValue(uint8_t bits) { _bits = bits; }

    /// Returns the state of a single bit within the stored byte.
    /// @param index Bit position (0 = LSB, 7 = MSB).
    /// @return true if the bit is set.
    [[nodiscard]] bool getBit(int index) const;

    /// Sets or clears a single bit within the stored byte.
    /// @param index Bit position (0 = LSB, 7 = MSB).
    /// @param value true to set, false to clear.
    void setBit(int index, bool value);

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    uint8_t _bits;
};


/// Implements a STUN/TURN attribute that holds a 32-bit integer.
class STUN_API UInt32Attribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    UInt32Attribute(uint16_t type);

    /// Copy constructor.
    UInt32Attribute(const UInt32Attribute& r);

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t Size = 4;

    /// @return The stored 32-bit value.
    [[nodiscard]] uint32_t value() const { return _bits; }

    /// Sets the stored 32-bit value.
    /// @param bits Value to store.
    void setValue(uint32_t bits) { _bits = bits; }

    /// Returns the state of a single bit within the stored word.
    /// @param index Bit position (0 = LSB, 31 = MSB).
    /// @return true if the bit is set.
    [[nodiscard]] bool getBit(int index) const;

    /// Sets or clears a single bit within the stored word.
    /// @param index Bit position (0 = LSB, 31 = MSB).
    /// @param value true to set, false to clear.
    void setBit(int index, bool value);

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    uint32_t _bits;
};


/// Implements a STUN/TURN attribute that holds a 64-bit integer.
class STUN_API UInt64Attribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    UInt64Attribute(uint16_t type);

    /// Copy constructor.
    UInt64Attribute(const UInt64Attribute& r);

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t Size = 8;

    /// @return The stored 64-bit value.
    [[nodiscard]] uint64_t value() const { return _bits; }

    /// Sets the stored 64-bit value.
    /// @param bits Value to store.
    void setValue(uint64_t bits) { _bits = bits; }

    /// Returns the state of a single bit within the stored quad-word.
    /// @param index Bit position (0 = LSB, 63 = MSB).
    /// @return true if the bit is set.
    [[nodiscard]] bool getBit(int index) const;

    /// Sets or clears a single bit within the stored quad-word.
    /// @param index Bit position (0 = LSB, 63 = MSB).
    /// @param value true to set, false to clear.
    void setBit(int index, bool value);

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    uint64_t _bits;
};

/// Implements a zero-length STUN/TURN flag attribute (presence implies the flag is set).
class STUN_API FlagAttribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    FlagAttribute(uint16_t type);

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t Size = 0;

    /// No-op: flag attributes carry no body bytes.
    void read(BitReader&) override { /* flags have no body */ }

    /// No-op: flag attributes carry no body bytes.
    void write(BitWriter&) const override { /* flags have no body */ }
};


/// Implements a STUN/TURN attribute that holds an arbitrary byte string.
/// Used for Username, Password, Realm, Nonce, Software, Data, and similar attributes.
class STUN_API StringAttribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    /// @param size Initial body length in bytes (0 for variable-length attributes).
    StringAttribute(uint16_t type, uint16_t size = 0);

    /// Copy constructor; duplicates stored bytes.
    StringAttribute(const StringAttribute& r);
    virtual ~StringAttribute();

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    /// @return Pointer to the raw byte buffer.
    [[nodiscard]] const char* bytes() const { return _bytes.data(); }

    /// Replaces the stored bytes with a copy of the given buffer and
    /// updates the attribute's reported size.
    /// @param bytes Source data pointer.
    /// @param size  Number of bytes to copy.
    void setBytes(const char* bytes, size_t size);

    /// @return The stored bytes as a std::string.
    [[nodiscard]] std::string asString() const;

    /// Copies a null-terminated string into the attribute, using strlen
    /// to determine the length.
    /// @param bytes Null-terminated source string.
    void copyBytes(const char* bytes);

    /// Copies an arbitrary block of memory into the attribute.
    /// @param bytes Source data pointer.
    /// @param size  Number of bytes to copy.
    void copyBytes(const void* bytes, size_t size);

    /// Returns a single byte from the stored buffer.
    /// @param index Zero-based byte offset.
    /// @return The byte value at @p index.
    [[nodiscard]] uint8_t getByte(int index) const;

    /// Overwrites a single byte in the stored buffer.
    /// @param index Zero-based byte offset.
    /// @param value New value to write.
    void setByte(int index, uint8_t value);

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    std::vector<char> _bytes;
};


/// Implements a STUN/TURN attribute that holds a list of attribute type codes.
/// Used by the UNKNOWN-ATTRIBUTES attribute (RFC 5389 section 15.9).
class STUN_API UInt16ListAttribute : public Attribute
{
public:
    /// @param type Wire type code for the concrete attribute.
    /// @param size Initial body length in bytes.
    UInt16ListAttribute(uint16_t type, uint16_t size);

    /// Copy constructor; duplicates the type list.
    UInt16ListAttribute(const UInt16ListAttribute& r);
    virtual ~UInt16ListAttribute();

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    /// @return Number of attribute type codes in the list.
    [[nodiscard]] size_t size() const;

    /// Returns the type code at the given list position.
    /// @param index Zero-based list index.
    /// @return Attribute type code at @p index.
    [[nodiscard]] uint16_t getType(int index) const;

    /// Overwrites the type code at the given list position.
    /// @param index Zero-based list index.
    /// @param value New attribute type code.
    void setType(int index, uint16_t value);

    /// Appends a type code to the list and updates the attribute size.
    /// @param value Attribute type code to append.
    void addType(uint16_t value);

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    std::vector<uint16_t> _attrTypes;
};


/// Implements the STUN MESSAGE-INTEGRITY attribute (RFC 5389 section 15.4).
/// On write, computes an HMAC-SHA1 over the message bytes preceding this
/// attribute when a key is set. On read, captures the raw HMAC bytes and
/// the input bytes needed to verify them later via verifyHmac().
class STUN_API MessageIntegrity : public Attribute
{
public:
    MessageIntegrity();
    MessageIntegrity(const MessageIntegrity& r);
    virtual ~MessageIntegrity();

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t TypeID = 0x0008;
    static constexpr uint16_t Size = 20; ///< HMAC-SHA1 output is always 20 bytes.

    /// Verifies the stored HMAC against the stored input bytes using @p key.
    /// @param key HMAC key (MD5 of username:realm:password for long-term creds).
    /// @return true if the computed HMAC matches the stored HMAC.
    [[nodiscard]] bool verifyHmac(std::string_view key) const;

    /// @return The raw message bytes captured at read time, used for HMAC verification.
    [[nodiscard]] std::string input() const { return _input; }

    /// @return The raw 20-byte HMAC value as read from the wire.
    [[nodiscard]] std::string hmac() const { return _hmac; }

    /// @return The HMAC key set for outgoing message signing (empty if not set).
    [[nodiscard]] std::string key() const { return _key; }

    /// Sets the raw message bytes used as HMAC input during verification.
    /// @param input Byte string of the message up to this attribute.
    void setInput(const std::string& input) { _input = input; }

    /// Sets the raw HMAC value (used when copying a received attribute).
    /// @param hmac 20-byte HMAC string.
    void setHmac(const std::string& hmac) { _hmac = hmac; }

    /// Sets the HMAC key; triggers HMAC computation on write().
    /// @param key MD5 digest of the long-term credential (username:realm:password).
    void setKey(const std::string& key) { _key = key; }

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    std::string _input;
    std::string _hmac;
    std::string _key;
};


/// Implements the STUN ERROR-CODE attribute (RFC 5389 section 15.6).
/// Encodes a 3-digit error code as a class (hundreds digit) and number
/// (tens + units digits), plus an optional UTF-8 reason phrase.
class STUN_API ErrorCode : public Attribute
{
public:
    /// @param size Initial body length in bytes (must be >= MinSize).
    ErrorCode(uint16_t size = MinSize);

    /// Copy constructor.
    ErrorCode(const ErrorCode& r);
    virtual ~ErrorCode();

    /// @copydoc Attribute::clone
    std::unique_ptr<Attribute> clone() override;

    static constexpr uint16_t TypeID = 0x0009;
    static constexpr uint16_t MinSize = 4; ///< 4 bytes before the reason phrase.

    /// Sets the error code, splitting it into class and number fields.
    /// @param code 3-digit error code (e.g. 401, 438).
    void setErrorCode(int code);

    /// Sets the UTF-8 reason phrase and updates the attribute size.
    /// @param reason Human-readable error description.
    void setReason(const std::string& reason);

    /// @return The full 3-digit error code (class * 100 + number).
    [[nodiscard]] int errorCode() const;

    /// @return The hundreds digit of the error code (e.g. 4 for a 4xx error).
    [[nodiscard]] uint8_t errorClass() const { return _class; }

    /// @return The tens+units portion of the error code (0-99).
    [[nodiscard]] uint8_t errorNumber() const { return _number; }

    /// @return The reason phrase string (may be empty).
    [[nodiscard]] const std::string& reason() const { return _reason; }

    void read(BitReader& reader) override;
    void write(BitWriter& writer) const override;

private:
    uint8_t _class;
    uint8_t _number;
    std::string _reason;
};


//
/// Attribute macros
//

#define DECLARE_FIXLEN_STUN_ATTRIBUTE(Name, Type, Derives) \
                                                           \
    class Name : public Derives                            \
    {                                                      \
    public:                                                \
        static constexpr uint16_t TypeID = Type;           \
        Name()                                             \
            : Derives(TypeID) {};                          \
        std::unique_ptr<Attribute> clone() override        \
        {                                                  \
            return std::make_unique<Name>(*this);          \
        }                                                  \
        virtual ~Name() {};                                \
    };

#define DECLARE_STUN_ATTRIBUTE(Name, Type, Derives, Length) \
                                                            \
    class Name : public Derives                             \
    {                                                       \
    public:                                                 \
        static constexpr uint16_t TypeID = Type;            \
        Name(uint16_t size = Length)                        \
            : Derives(TypeID, size) {};                     \
        std::unique_ptr<Attribute> clone() override         \
        {                                                   \
            return std::make_unique<Name>(*this);           \
        }                                                   \
        virtual ~Name() {};                                 \
    };


//
/// Attribute declarations
//

/// Address attributes
DECLARE_FIXLEN_STUN_ATTRIBUTE(MappedAddress, 0x0001, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ResponseAddress, 0x0002, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ChangedAddress, 0x0005, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ReflectedFrom, 0x000b, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(AlternateServer, 0x000e, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(SourceAddress, 0x0004, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(DestinationAddress, 0x0011, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(XorMappedAddress, 0x0020, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(XorPeerAddress, 0x0012, AddressAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(XorRelayedAddress, 0x0016, AddressAttribute)

/// uint32_t attributes
DECLARE_FIXLEN_STUN_ATTRIBUTE(Fingerprint, 0x8028, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(RequestedTransport, 0x0019, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ChangeRequest, 0x0003, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(Lifetime, 0x000d, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(Bandwidth, 0x0010, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(Options, 0x8001, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ChannelNumber, 0x000c, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ICEPriority, 0x0024, UInt32Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ConnectionID, 0x002a, UInt32Attribute)

/// UInt8 attributes
DECLARE_FIXLEN_STUN_ATTRIBUTE(EventPort, 0x0018, UInt8Attribute)

/// UInt32 list attributes
DECLARE_STUN_ATTRIBUTE(UnknownAttributes, 0x000a, UInt16ListAttribute, 0)

/// String attributes
DECLARE_STUN_ATTRIBUTE(Username, 0x0006, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(Password, 0x0007, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(MagicCookie, 0x000f, StringAttribute, 4)
DECLARE_STUN_ATTRIBUTE(Data, 0x0013, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(Realm, 0x0014, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(Nonce, 0x0015, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(Software, 0x8022, StringAttribute, 0)
DECLARE_STUN_ATTRIBUTE(ReservationToken, 0x0022, StringAttribute, 8)

/// UInt64 attributes
DECLARE_FIXLEN_STUN_ATTRIBUTE(ICEControlling, 0x802A, UInt64Attribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(ICEControlled, 0x8029, UInt64Attribute)

/// Flag attributes
DECLARE_FIXLEN_STUN_ATTRIBUTE(ICEUseCandidate, 0x0025, FlagAttribute)
DECLARE_FIXLEN_STUN_ATTRIBUTE(DontFragment, 0x001A, FlagAttribute)
} // namespace stun
} // namespace icy


/// @}
