flycast/core/network/dcnet.cpp

261 lines
5.7 KiB
C++

/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include <asio.hpp>
#include "types.h"
#include "netservice.h"
#include "util/tsqueue.h"
#include "oslib/oslib.h"
#include "emulator.h"
#include <thread>
#include <memory>
#include <array>
#ifndef __ANDROID__
//#define DEBUG_PPP 1
#endif
#ifdef DEBUG_PPP
#include <sys/time.h>
#endif
namespace net::modbba
{
static TsQueue<u8> in_buffer;
static TsQueue<u8> out_buffer;
class DCNetService : public Service
{
public:
bool start() override;
void stop() override;
void writeModem(u8 b) override;
int readModem() override;
int modemAvailable() override;
void receiveEthFrame(const u8 *frame, u32 size) override;
};
class Socket
{
public:
Socket(asio::io_context& io_context, const asio::ip::tcp::endpoint& endpoint)
: socket(io_context), timer(io_context)
{
socket.connect(endpoint);
os_notify("Connected to DCNet cloud", 5000);
receive();
sendIfAny({});
}
~Socket() {
if (dumpfp != nullptr)
fclose(dumpfp);
}
private:
void receive() {
socket.async_read_some(asio::buffer(recvBuffer),
std::bind(&Socket::onRecv, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
void onRecv(asio::error_code ec, size_t len)
{
if (ec) {
ERROR_LOG(NETWORK, "onRecv error: %s", ec.message().c_str());
return;
}
pppdump(recvBuffer.data(), len, false);
for (size_t i = 0; i < len; i++)
in_buffer.push(recvBuffer[i]);
receive();
}
void sendIfAny(const std::error_code& ec)
{
if (ec) {
ERROR_LOG(NETWORK, "sendIfAny timer error: %s", ec.message().c_str());
return;
}
if (!sending)
{
for (; !out_buffer.empty() && sendBufSize < sendBuffer.size(); sendBufSize++)
{
sendBuffer[sendBufSize] = out_buffer.pop();
if (sendBufSize != 0 && sendBuffer[sendBufSize] == 0x7e) {
sendBufSize++;
break;
}
}
if ((sendBufSize > 1 && sendBuffer[sendBufSize - 1] == 0x7e)
|| sendBufSize == sendBuffer.size())
{
pppdump(sendBuffer.data(), sendBufSize, true);
asio::async_write(socket, asio::buffer(sendBuffer, sendBufSize),
std::bind(&Socket::onSent, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
sending = true;
}
}
timer.expires_at(timer.expiry() + asio::chrono::milliseconds(5));
timer.async_wait(std::bind(&Socket::sendIfAny, this, asio::placeholders::error));
}
void onSent(asio::error_code ec, size_t len)
{
sending = false;
sendBufSize = 0;
if (ec) {
ERROR_LOG(NETWORK, "onRecv error: %s", ec.message().c_str());
return;
}
}
void pppdump(uint8_t *buf, int len, bool egress)
{
#ifdef DEBUG_PPP
if (!len)
return;
const auto& timems = []() -> time_t {
timeval tv;
gettimeofday(&tv, nullptr);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
};
if (dumpfp == nullptr)
{
dumpfp = fopen("ppp.dump", "a");
if (dumpfp == nullptr)
return;
time_t now;
time(&now);
u32 reset_time = ntohl((u32)now);
fputc(7, dumpfp); // Reset time
fwrite(&reset_time, sizeof(reset_time), 1, dumpfp);
dump_last_time_ms = timems();
}
u32 delta = (timems() - dump_last_time_ms) / 100;
if (delta < 256) {
fputc(6, dumpfp); // Time step (short)
fwrite(&delta, 1, 1, dumpfp);
}
else
{
delta = ntohl(delta);
fputc(5, dumpfp); // Time step (long)
fwrite(&delta, sizeof(delta), 1, dumpfp);
}
dump_last_time_ms = timems();
fputc(egress ? 1 : 2, dumpfp); // Sent/received data
uint16_t slen = htons(len);
fwrite(&slen, 2, 1, dumpfp);
fwrite(buf, 1, len, dumpfp);
#endif
}
asio::ip::tcp::socket socket;
asio::steady_timer timer;
std::array<u8, 1500> recvBuffer;
std::array<u8, 1500> sendBuffer;
u32 sendBufSize = 0;
bool sending = false;
FILE *dumpfp = nullptr;
time_t dump_last_time_ms;
};
class DCNetThread
{
public:
void start()
{
verify(!thread.joinable());
io_context = std::make_unique<asio::io_context>();
thread = std::thread(&DCNetThread::run, this);
}
void stop()
{
if (!thread.joinable())
return;
io_context->stop();
thread.join();
io_context.reset();
}
private:
void run();
std::thread thread;
std::unique_ptr<asio::io_context> io_context;
};
static DCNetThread thread;
bool DCNetService::start()
{
emu.setNetworkState(true);
thread.start();
return true;
}
void DCNetService::stop() {
thread.stop();
emu.setNetworkState(false);
}
void DCNetService::writeModem(u8 b) {
out_buffer.push(b);
}
int DCNetService::readModem()
{
if (in_buffer.empty())
return -1;
else
return in_buffer.pop();
}
int DCNetService::modemAvailable() {
return in_buffer.size();
}
void DCNetService::receiveEthFrame(unsigned char const*, unsigned int) {
// TODO
}
void DCNetThread::run()
{
try {
asio::ip::tcp::resolver resolver(*io_context);
auto it = resolver.resolve("dcnet.flyca.st", "7654");
if (it.empty())
throw std::runtime_error("Can't find dcnet host");
asio::ip::tcp::endpoint endpoint = *it.begin();
Socket socket(*io_context, endpoint);
io_context->run();
} catch (const std::runtime_error& e) {
ERROR_LOG(NETWORK, "DCNetThread::run error: %s", e.what());
}
}
}