<commit_before>//
// Aspia Project
// Copyright (C) 2018 <NAME> <<EMAIL>>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

#include "network/network_channel.h"

#include <QHostAddress>
#include <QNetworkProxy>

#include "crypto/encryptor.h"

namespace aspia {

namespace {

constexpr uint32_t kMaxMessageSize = 16 * 1024 * 1024; // 16 MB
constexpr int64_t kMaxWriteSize = 1200; // 1200 bytes

QByteArray createWriteBuffer(const QByteArray& message_buffer)
{
    uint32_t message_size = message_buffer.size();
    if (!message_size || message_size > kMaxMessageSize)
        return QByteArray();

    uint8_t length_data[4];
    int length_data_size = 1;

    length_data[0] = message_size & 0x7F;
    if (message_size > 0x7F) // 127 bytes
    {
        length_data[0] |= 0x80;
        length_data[length_data_size++] = message_size >> 7 & 0x7F;

        if (message_size > 0x3FFF) // 16383 bytes
        {
            length_data[1] |= 0x80;
            length_data[length_data_size++] = message_size >> 14 & 0x7F;

            if (message_size > 0x1FFFF) // 2097151 bytes
            {
                length_data[2] |= 0x80;
                length_data[length_data_size++] = message_size >> 21 & 0xFF;
            }
        }
    }

    QByteArray write_buffer;
    write_buffer.resize(length_data_size + message_size);

    memcpy(write_buffer.data(), length_data, length_data_size);
    memcpy(write_buffer.data() + length_data_size, message_buffer.constData(), message_size);

    return write_buffer;
}

} // namespace

NetworkChannel::NetworkChannel(Type channel_type, QTcpSocket* socket, QObject* parent)
    : QObject(parent),
      type_(channel_type),
      socket_(socket)
{
    Q_ASSERT(!socket_.isNull());

    socket_->setParent(this);

    if (type_ == Type::CLIENT_CHANNEL)
        connect(socket_, &QTcpSocket::connected, this, &NetworkChannel::onConnected);

    connect(socket_, &QTcpSocket::bytesWritten, this, &NetworkChannel::onBytesWritten);
    connect(socket_, &QTcpSocket::readyRead, this, &NetworkChannel::onReadyRead);

    connect(socket_, &QTcpSocket::disconnected, this, &NetworkChannel::disconnected,
            Qt::QueuedConnection);

    connect(socket_, QOverload<QTcpSocket::SocketError>::of(&QTcpSocket::error),
            this, &NetworkChannel::onError,
            Qt::QueuedConnection);
}

// static
NetworkChannel* NetworkChannel::createClient(QObject* parent)
{
    return new NetworkChannel(Type::CLIENT_CHANNEL, new QTcpSocket(), parent);
}

void NetworkChannel::connectToHost(const QString& address, int port)
{
    if (type_ == Type::SERVER_CHANNEL)
    {
        qWarning("The channel is server. The method invocation is invalid.");
        return;
    }

    socket_->setProxy(QNetworkProxy::NoProxy);
    socket_->connectToHost(address, port);
}

QString NetworkChannel::peerAddress() const
{
    QHostAddress address = socket_->peerAddress();

    bool ok = false;
    QHostAddress ipv4_address(address.toIPv4Address(&ok));
    if (ok)
        return ipv4_address.toString();

    return address.toString();
}

void NetworkChannel::start()
{
    if (isStarted())
        return;

    read_.paused = false;

    // Start receiving messages.
    onReadyRead();
}

void NetworkChannel::stop()
{
    state_ = State::NOT_CONNECTED;

    if (socket_->state() != QTcpSocket::UnconnectedState)
    {
        socket_->abort();

        if (socket_->state() != QTcpSocket::UnconnectedState)
            socket_->waitForDisconnected();
    }
}

void NetworkChannel::pause()
{
    read_.paused = true;
}

void NetworkChannel::send(const QByteArray& buffer)
{
    if (buffer.isEmpty())
    {
        emit errorOccurred("An attempt was made to send an empty message.");
        stop();
        return;
    }

    // If the write buffer is empty, then no write operation is currently performed.
    if (write_.buffer.isEmpty() && write_.queue.isEmpty())
    {
        // Start the write operation.
        scheduleWrite(buffer);
    }
    else
    {
        // Add the buffer to the queue for sending.
        write_.queue.push_back(buffer);
    }
}

void NetworkChannel::onConnected()
{
    state_ = State::CONNECTED;

    // Disable the Nagle algorithm for the socket.
    socket_->setSocketOption(QTcpSocket::LowDelayOption, 1);

    if (type_ == Type::SERVER_CHANNEL)
    {
        encryptor_.reset(new Encryptor(Encryptor::Mode::SERVER));

        // Start reading hello message.
        onReadyRead();
    }
    else
    {
        Q_ASSERT(type_ == Type::CLIENT_CHANNEL);
        encryptor_.reset(new Encryptor(Encryptor::Mode::CLIENT));

        write_.buffer = createWriteBuffer(encryptor_->helloMessage());
        if (write_.buffer.isEmpty())
        {
            emit errorOccurred(tr("Error in encryption key exchange."));
            stop();
            return;
        }

        // Write hello message to server.
        socket_->write(write_.buffer);<commit_msg>- Using keep alive option for socket.<commit_after>//
// Aspia Project
// Copyright (C) 2018 <NAME> <<EMAIL>>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

#include "network/network_channel.h"

#include <QHostAddress>
#include <QNetworkProxy>

#if defined(Q_OS_WIN)
#include <winsock2.h>
#include <mstcpip.h>
#endif

#include "base/errno_logging.h"
#include "crypto/encryptor.h"

namespace aspia {

namespace {

constexpr uint32_t kMaxMessageSize = 16 * 1024 * 1024; // 16 MB
constexpr int64_t kMaxWriteSize = 1200; // 1200 bytes

QByteArray createWriteBuffer(const QByteArray& message_buffer)
{
    uint32_t message_size = message_buffer.size();
    if (!message_size || message_size > kMaxMessageSize)
        return QByteArray();

    uint8_t length_data[4];
    int length_data_size = 1;

    length_data[0] = message_size & 0x7F;
    if (message_size > 0x7F) // 127 bytes
    {
        length_data[0] |= 0x80;
        length_data[length_data_size++] = message_size >> 7 & 0x7F;

        if (message_size > 0x3FFF) // 16383 bytes
        {
            length_data[1] |= 0x80;
            length_data[length_data_size++] = message_size >> 14 & 0x7F;

            if (message_size > 0x1FFFF) // 2097151 bytes
            {
                length_data[2] |= 0x80;
                length_data[length_data_size++] = message_size >> 21 & 0xFF;
            }
        }
    }

    QByteArray write_buffer;
    write_buffer.resize(length_data_size + message_size);

    memcpy(write_buffer.data(), length_data, length_data_size);
    memcpy(write_buffer.data() + length_data_size, message_buffer.constData(), message_size);

    return write_buffer;
}

} // namespace

NetworkChannel::NetworkChannel(Type channel_type, QTcpSocket* socket, QObject* parent)
    : QObject(parent),
      type_(channel_type),
      socket_(socket)
{
    Q_ASSERT(!socket_.isNull());

    socket_->setParent(this);
    socket_->setSocketOption(QTcpSocket::KeepAliveOption, 1);

    if (type_ == Type::CLIENT_CHANNEL)
        connect(socket_, &QTcpSocket::connected, this, &NetworkChannel::onConnected);

    connect(socket_, &QTcpSocket::bytesWritten, this, &NetworkChannel::onBytesWritten);
    connect(socket_, &QTcpSocket::readyRead, this, &NetworkChannel::onReadyRead);

    connect(socket_, &QTcpSocket::disconnected, this, &NetworkChannel::disconnected,
            Qt::QueuedConnection);

    connect(socket_, QOverload<QTcpSocket::SocketError>::of(&QTcpSocket::error),
            this, &NetworkChannel::onError,
            Qt::QueuedConnection);
}

// static
NetworkChannel* NetworkChannel::createClient(QObject* parent)
{
    return new NetworkChannel(Type::CLIENT_CHANNEL, new QTcpSocket(), parent);
}

void NetworkChannel::connectToHost(const QString& address, int port)
{
    if (type_ == Type::SERVER_CHANNEL)
    {
        qWarning("The channel is server. The method invocation is invalid.");
        return;
    }

    socket_->setProxy(QNetworkProxy::NoProxy);
    socket_->connectToHost(address, port);
}

QString NetworkChannel::peerAddress() const
{
    QHostAddress address = socket_->peerAddress();

    bool ok = false;
    QHostAddress ipv4_address(address.toIPv4Address(&ok));
    if (ok)
        return ipv4_address.toString();

    return address.toString();
}

void NetworkChannel::start()
{
    if (isStarted())
        return;

    read_.paused = false;

    // Start receiving messages.
    onReadyRead();
}

void NetworkChannel::stop()
{
    state_ = State::NOT_CONNECTED;

    if (socket_->state() != QTcpSocket::UnconnectedState)
    {
        socket_->abort();

        if (socket_->state() != QTcpSocket::UnconnectedState)
            socket_->waitForDisconnected();
    }
}

void NetworkChannel::pause()
{
    read_.paused = true;
}

void NetworkChannel::send(const QByteArray& buffer)
{
    if (buffer.isEmpty())
    {
        emit errorOccurred("An attempt was made to send an empty message.");
        stop();
        return;
    }

    // If the write buffer is empty, then no write operation is currently performed.
    if (write_.buffer.isEmpty() && write_.queue.isEmpty())
    {
        // Start the write operation.
        scheduleWrite(buffer);
    }
    else
    {
        // Add the buffer to the queue for sending.
        write_.queue.push_back(buffer);
    }
}

void NetworkChannel::onConnected()
{
    state_ = State::CONNECTED;

    // Disable the Nagle algorithm for the socket.
    socket_->setSocketOption(QTcpSocket::LowDelayOption, 1);

#if defined(Q_OS_WIN)
    struct tcp_keepalive alive;

    alive.onoff = 1; // On.
    alive.keepalivetime = 30000; // 30 seconds.
    alive.keepaliveinterval = 5000; // 5 seconds.

    DWORD bytes_returned;

    if (WSAIoctl(socket_->socketDescriptor(), SIO_KEEPALIVE_VALS,
                 &alive, sizeof(alive), nullptr, 0, &bytes_returned,
                 nullptr, nullptr) == SOCKET_ERROR)
    {
        qWarningErrno("WSAIoctl failed");
    }

#endif

    if (type_ == Type::SERVER_CHANNEL)
    {
        encryptor_.reset(new Encryptor(Encryptor::Mode::SERVER));

        // Start reading hello message.
        onReadyRead();
    }
    else
    {
        Q_ASSERT(type_ == Type::CLIENT_CHANNEL);
        encryptor_.reset(new Encryptor(Encryptor::Mode::CLIENT));

        write_.buffer = createWriteBuffer(encryptor_->helloMessage());
        if (write_.buffer.isEmpty())
        {
            emit errorOccurred(tr("Error in encryption key exchange."));
            stop();
            return;
        }

        // Write hello message to server.
        socket_->write(write_.buffer);
{'fraction_non_alphanumeric': 0.11719418306244654, 'fraction_numerical': 0.01573994867408041, 'mean_word_length': 3.0579659840333218, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 63, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5304410', 'n_tokens_mistral': 3658, 'n_tokens_neox': 3436, 'n_words': 862}