// SPDX-License-Identifier: LGPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* nymea-remoteproxy
* Tunnel proxy server for the nymea remote access
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-remoteproxy.
*
* nymea-remoteproxy is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* nymea-remoteproxy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nymea-remoteproxy. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "tunnelproxyclient.h"
#include "loggingcategories.h"
#include "server/transportinterface.h"
#include "../engine.h"
#include "../common/slipdataprocessor.h"

namespace remoteproxy {

TunnelProxyClient::TunnelProxyClient(TransportInterface *interface, const QUuid &clientId, const QHostAddress &address, QObject *parent) :
    TransportClient(interface, clientId, address, parent)
{
    // Note: a client is not inactive any more once registered successfully as client or server.
    // This makes sure we have not any inactive sockets connected to the proxy blocking resources.
    // The tunnelproxy server will call activateClient once registered successfully to stop this timer.
    m_inactiveTimer = new QTimer(this);
    m_inactiveTimer->setInterval(Engine::instance()->configuration()->inactiveTimeout());
    m_inactiveTimer->setSingleShot(true);

    connect(m_inactiveTimer, &QTimer::timeout, this, [this](){
        m_interface->killClientConnection(m_clientId, "Tunnelproxy client timeout occurred. The socket was inactive.");
    });

    m_inactiveTimer->start();
}

TunnelProxyClient::Type TunnelProxyClient::type() const
{
    return m_type;
}

void TunnelProxyClient::setType(Type type)
{
    if (m_type == type)
        return;

    m_type = type;
    emit typeChanged(m_type);
}

QList<QByteArray> TunnelProxyClient::processData(const QByteArray &data)
{
    QList<QByteArray> packets;

    // Parse packets depending on the encoded
    if (m_slipEnabled) {
        // Read each byte until we get END byte, then unescape the packet
        for (int i = 0; i < data.length(); i++) {
            quint8 byte = static_cast<quint8>(data.at(i));
            if (byte == SlipDataProcessor::ProtocolByteEnd) {
                // If there is no data...continue since it might be a starting END byte
                if (m_dataBuffer.isEmpty())
                    continue;

                QByteArray frame = SlipDataProcessor::deserializeData(m_dataBuffer);
                if (frame.isNull()) {
                    qCWarning(dcTunnelProxyServerTraffic()) << "Received inconsistant SLIP encoded message. Ignoring data...";
                } else {
                    qCDebug(dcTunnelProxyServerTraffic()) << "Frame received";
                    packets.append(m_dataBuffer);
                    m_dataBuffer.clear();
                }
            } else {
                m_dataBuffer.append(data.at(i));
            }
        }
    } else {
        // Handle json packet fragmentation
        m_dataBuffer.append(data);
        int splitIndex = m_dataBuffer.indexOf("}\n{");
        while (splitIndex > -1) {
            packets.append(m_dataBuffer.left(splitIndex + 1));
            m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - splitIndex - 2);
            splitIndex = m_dataBuffer.indexOf("}\n{");
        }
        if (m_dataBuffer.endsWith("}\n") || m_dataBuffer.endsWith("}")) {
            packets.append(m_dataBuffer);
            m_dataBuffer.clear();
        }
    }

    return packets;
}

void TunnelProxyClient::activateClient()
{
    // This connection has been registered as TypeServer or TypeClient
    m_inactiveTimer->stop();

    // We use the inactive timer from now on only for server connections
    // to see if the connection is still alive. Server connection ping the
    // tunnelproxy every 30 seconds if no other data has been exchanged to
    // keep the connection up. If there is no data for more than one minute,
    // we consider the connection as dead and terminate the connection.

    Q_ASSERT_X(m_type != TypeNone, "TunnelProxyClient", "Activate client called but the client type is not specified yet. Make sure you activate either a client or a server.");
    if (m_type != TypeServer)
        return;

    // We must receive data, transmitt does not mean the socket is not dead
    connect(this, &TransportClient::rxDataCountChanged, this, [this](){
        m_inactiveTimer->start();
    });

    m_inactiveTimer->setInterval(60000);
    m_inactiveTimer->setSingleShot(false);
    m_inactiveTimer->start();
}

QDebug operator<<(QDebug debug, TunnelProxyClient *tunnelProxyClient)
{
    QDebugStateSaver saver(debug);
    debug.nospace() << "TunnelProxyClient(";
    debug.nospace() << tunnelProxyClient->name() << ", ";
    debug.nospace() << tunnelProxyClient->interface()->serverName()<< ", ";
    debug.nospace() << tunnelProxyClient->clientId().toString()<< ", ";
    debug.nospace() << tunnelProxyClient->peerAddress().toString() << ", ";
    debug.nospace() << tunnelProxyClient->creationTimeString() << ")";
    return debug;
}

}
