// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPSTUN_H
#define QXMPPSTUN_H

#include "QXmppJingleIq.h"
#include "QXmppLogger.h"

#include <QObject>
#include <QSet>

class CandidatePair;
class QDataStream;
class QUdpSocket;
class QTimer;
class QXmppIceComponentPrivate;
class QXmppIceConnectionPrivate;
class QXmppIcePrivate;
class QXmppIceTransport;
class QXmppStunTransaction;

namespace QXmpp {
struct StunServer;
struct TurnServer;
}  // namespace QXmpp

/*!
    \inmodule QXmpp

    \internal

    The QXmppStunMessage class represents a STUN message.
*/
class QXMPP_EXPORT QXmppStunMessage
{
public:
    /*!
        \brief STUN message method type.

        \value Binding
        \value SharedSecret
        \value Allocate
        \value Refresh
        \value Send
        \value Data
        \value CreatePermission
        \value ChannelBind
    */
    enum MethodType {
        Binding = 0x1,
        SharedSecret = 0x2,
        Allocate = 0x3,
        Refresh = 0x4,
        Send = 0x6,
        Data = 0x7,
        CreatePermission = 0x8,
        ChannelBind = 0x9
    };

    /*!
        \brief STUN message class type.

        \value Request
        \value Indication
        \value Response
        \value Error
    */
    enum ClassType {
        Request = 0x000,
        Indication = 0x010,
        Response = 0x100,
        Error = 0x110
    };

    QXmppStunMessage();

    quint32 cookie() const;
    void setCookie(quint32 cookie);

    QByteArray id() const;
    void setId(const QByteArray &id);

    quint16 messageClass() const;
    quint16 messageMethod() const;

    quint16 type() const;
    void setType(quint16 type);

    // attributes

    quint32 changeRequest() const;
    void setChangeRequest(quint32 changeRequest);

    quint16 channelNumber() const;
    void setChannelNumber(quint16 channelNumber);

    QByteArray data() const;
    void setData(const QByteArray &data);

    quint32 lifetime() const;
    void setLifetime(quint32 changeRequest);

    QByteArray nonce() const;
    void setNonce(const QByteArray &nonce);

    quint32 priority() const;
    void setPriority(quint32 priority);

    QString realm() const;
    void setRealm(const QString &realm);

    QByteArray reservationToken() const;
    void setReservationToken(const QByteArray &reservationToken);

    quint8 requestedTransport() const;
    void setRequestedTransport(quint8 requestedTransport);

    QString software() const;
    void setSoftware(const QString &software);

    QString username() const;
    void setUsername(const QString &username);

    QByteArray encode(const QByteArray &key = QByteArray(), bool addFingerprint = true) const;
    bool decode(const QByteArray &buffer, const QByteArray &key = QByteArray(), QStringList *errors = nullptr);
    QString toString() const;
    static quint16 peekType(const QByteArray &buffer, quint32 &cookie, QByteArray &id);

    // attributes
    int errorCode = 0;
    QString errorPhrase;
    QByteArray iceControlling;
    QByteArray iceControlled;
    QHostAddress changedHost;
    quint16 changedPort = 0;
    QHostAddress mappedHost;
    quint16 mappedPort = 0;
    QHostAddress otherHost;
    quint16 otherPort = 0;
    QHostAddress sourceHost;
    quint16 sourcePort = 0;
    QHostAddress xorMappedHost;
    quint16 xorMappedPort = 0;
    QHostAddress xorPeerHost;
    quint16 xorPeerPort = 0;
    QHostAddress xorRelayedHost;
    quint16 xorRelayedPort = 0;
    bool useCandidate = false;

private:
    quint32 m_cookie;
    QByteArray m_id;
    quint16 m_type = 0;

    // attributes
    QSet<quint16> m_attributes;
    quint32 m_changeRequest = 0;
    quint16 m_channelNumber = 0;
    QByteArray m_data;
    quint32 m_lifetime = 0;
    QByteArray m_nonce;
    quint32 m_priority = 0;
    QString m_realm;
    quint8 m_requestedTransport = 0;
    QByteArray m_reservationToken;
    QString m_software;
    QString m_username;
};

/*!
    \inmodule QXmpp

    \brief The QXmppIceComponent class represents a piece of a media stream
    requiring a single transport address, as defined by RFC 5245
    (Interactive Connectivity Establishment).
*/
class QXMPP_EXPORT QXmppIceComponent : public QXmppLoggable
{
    Q_OBJECT

public:
    ~QXmppIceComponent() override;

    int component() const;
    bool isConnected() const;
    QList<QXmppJingleCandidate> localCandidates() const;

    static QList<QHostAddress> discoverAddresses();
    static QList<QUdpSocket *> reservePorts(const QList<QHostAddress> &addresses, int count, QObject *parent = nullptr);

    Q_SLOT void close();
    Q_SLOT void connectToHost();
    Q_SLOT qint64 sendDatagram(const QByteArray &datagram);

    /*! \brief This signal is emitted once ICE negotiation succeeds. */
    Q_SIGNAL void connected();

    /*!
        \brief This signal is emitted when a data packet is received.

        \a datagram.
    */
    Q_SIGNAL void datagramReceived(const QByteArray &datagram);

    /*! This signal is emitted when the gathering state of local candidates changes. */
    Q_SIGNAL void gatheringStateChanged();

    /*! \brief This signal is emitted when the list of local candidates changes. */
    Q_SIGNAL void localCandidatesChanged();

private:
    QXmppIceComponent(int component, QXmppIcePrivate *config, QObject *parent = nullptr);

    Q_SLOT void checkCandidates();
    Q_SLOT void handleDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port);
    Q_SLOT void turnConnected();
    void handleStunResponse(QXmppIceTransport *transport, const QXmppStunMessage &response);
    Q_SLOT void updateGatheringState();

    const std::unique_ptr<QXmppIceComponentPrivate> d;
    friend class QXmppIceComponentPrivate;
    friend class QXmppIceConnection;
};

/*!
    \inmodule QXmpp

    \brief The QXmppIceConnection class represents a set of UDP sockets
    capable of performing Interactive Connectivity Establishment (RFC 5245).

    A typical example is:

    \code
    QXmppIceConnection *connection = new QXmppIceConnection();
    connection->setIceControlling(true);
    connection->addComponent(1);

    // if needed, set STUN / TURN configuration
    // connection->setStunServer(..);
    // connection->setTurnServer(..);

    // start listening
    connection->bind(QXmppIceComponent::discoverAddresses());

    // receive remote information: user, password, candidates
    // ...

    // set remote information and start connecting
    connection->setRemoteUser("foo");
    connection->setRemoteUser("bar");
    connection->addRemoteCandidate(..);
    connection->connectToHost();
    \endcode
*/
class QXMPP_EXPORT QXmppIceConnection : public QXmppLoggable
{
    Q_OBJECT

    /*!
        \property QXmppIceConnection::gatheringState

        The ICE gathering state, that is the discovery of local candidates
        \since QXmpp 0.9.3
    */
    Q_PROPERTY(QXmppIceConnection::GatheringState gatheringState READ gatheringState NOTIFY gatheringStateChanged)

public:
    /*!
        This enum describes the gathering state of the ICE connection.

        \since QXmpp 0.9.3

        \value NewGatheringState
        \value BusyGatheringState
        \value CompleteGatheringState
    */
    enum GatheringState {
        NewGatheringState,
        BusyGatheringState,
        CompleteGatheringState
    };
    Q_ENUM(GatheringState)

    QXmppIceConnection(QObject *parent = nullptr);
    ~QXmppIceConnection() override;

    QXmppIceComponent *component(int component);
    void addComponent(int component);
    void setIceControlling(bool controlling);

    QList<QXmppJingleCandidate> localCandidates() const;
    QString localUser() const;
    QString localPassword() const;

    void addRemoteCandidate(const QXmppJingleCandidate &candidate);
    void setRemoteUser(const QString &user);
    void setRemotePassword(const QString &password);

    void setStunServers(const QList<QXmpp::StunServer> &servers);
    void setTurnServer(const QXmpp::TurnServer &);
#if QXMPP_DEPRECATED_SINCE(1, 11)
    [[deprecated("Use setStunServers(QList<StunServer>)")]]
    void setStunServers(const QList<QPair<QHostAddress, quint16>> &servers);
    [[deprecated("Use setStunServers(QList<StunServer>)")]]
    void setStunServer(const QHostAddress &host, quint16 port = 3478);
    [[deprecated("Use setTurnServer(TurnServer)")]]
    void setTurnServer(const QHostAddress &host, quint16 port = 3478);
    [[deprecated("Use setTurnServer(TurnServer)")]]
    void setTurnUser(const QString &user);
    [[deprecated("Use setTurnServer(TurnServer)")]]
    void setTurnPassword(const QString &password);
#endif

    bool bind(const QList<QHostAddress> &addresses);
    bool isConnected() const;

    // documentation needs to be here, see https://stackoverflow.com/questions/49192523/
    /*!
        Returns the ICE gathering state, that is the discovery of local
        candidates.

        \since QXmpp 0.9.3
    */
    GatheringState gatheringState() const;

    /*! \brief This signal is emitted once ICE negotiation succeeds. */
    Q_SIGNAL void connected();

    /*! \brief This signal is emitted when ICE negotiation fails. */
    Q_SIGNAL void disconnected();

    /*!
        \brief This signal is emitted when the gathering state of local candidates changes.

        \since QXmpp 0.9.3
    */
    Q_SIGNAL void gatheringStateChanged();

    /*! \brief This signal is emitted when the list of local candidates changes. */
    Q_SIGNAL void localCandidatesChanged();

    Q_SLOT void close();
    Q_SLOT void connectToHost();

private:
    Q_SLOT void slotConnected();
    Q_SLOT void slotGatheringStateChanged();
    Q_SLOT void slotTimeout();

    const std::unique_ptr<QXmppIceConnectionPrivate> d;
};

#endif
