flycast/core/network/naomi_network.h

227 lines
4.8 KiB
C++

/*
Copyright 2022 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/>.
*/
#pragma once
#include "types.h"
#include "net_platform.h"
#include "miniupnp.h"
#include "cfg/option.h"
#include "emulator.h"
#include "oslib/oslib.h"
#include <algorithm>
#include <atomic>
#include <future>
#include <vector>
class NaomiNetwork
{
public:
class Exception : public FlycastException
{
public:
Exception(const std::string& reason) : FlycastException(reason) {}
};
~NaomiNetwork() { shutdown(); }
std::future<bool> startNetworkAsync()
{
networkStopping = false;
_startNow = false;
return std::async(std::launch::async, [this] {
ThreadName _("NaomiNetwork-start");
bool res = startNetwork();
emu.setNetworkState(res);
return res;
});
}
void shutdown()
{
enableNetworkBroadcast(false);
emu.setNetworkState(false);
if (sock != INVALID_SOCKET)
{
closesocket(sock);
sock = INVALID_SOCKET;
}
}
bool receive(u8 *data, u32 size, u16 *packetNumber)
{
poll();
if (receivedData.empty())
return false;
size = std::min(size, (u32)receivedData.size());
memcpy(data, receivedData.data(), size);
receivedData.erase(receivedData.begin(), receivedData.begin() + size);
*packetNumber = this->packetNumber;
return true;
}
void send(u8 *data, u32 size, u16 packetNumber)
{
verify(size < sizeof(Packet::data.payload));
Packet packet(Data);
memcpy(packet.data.payload, data, size);
packet.data.packetNumber = packetNumber;
send(&nextPeer, &packet, packet.size(size));
}
int getSlotCount() const { return slotCount; }
int getSlotId() const { return slotId; }
void startNow() {
if (config::ActAsServer)
_startNow = true;
}
private:
enum PacketType : u16 {
SyncReq,
SyncReply,
Start,
Data,
Ack,
NAck
};
#pragma pack(push, 1)
struct Packet
{
Packet(PacketType type = SyncReq) : type(type) {}
PacketType type;
union {
struct {
u16 nodeId;
u16 nextNodePort;
u32 nextNodeIp;
} sync;
struct {
u16 nodeCount;
} start;
struct {
u16 packetNumber;
u8 payload[0x4000];
} data;
};
size_t size(size_t dataSize = 0) const
{
size_t sz = sizeof(type);
switch (type) {
case SyncReq:
case SyncReply:
sz += sizeof(sync);
break;
case Start:
sz += sizeof(start);
break;
case Data:
sz += sizeof(data.packetNumber) + dataSize;
break;
default:
break;
}
return sz;
}
};
#pragma pack(pop)
bool init();
void createSocket();
bool startNetwork();
void poll()
{
Packet packet;
sockaddr_in addr;
while (true)
{
socklen_t len = sizeof(addr);
int rc = recvfrom(sock, (char *)&packet, sizeof(packet), 0, (sockaddr *)&addr, &len);
if (rc == -1)
{
int error = get_last_error();
if (error == L_EWOULDBLOCK || error == L_EAGAIN)
break;
#ifdef _WIN32
if (error == WSAECONNRESET)
// Happens if the previous send resulted in an ICMP Port Unreachable message
break;
#endif
throw Exception("Receive error: errno " + std::to_string(error));
}
if (rc < (int)packet.size(0))
throw Exception("Receive error: truncated packet");
receive(&addr, &packet, rc);
}
}
bool receive(const sockaddr_in *addr, const Packet *packet, u32 size);
void sendAck(const sockaddr_in *addr, bool ack = true)
{
Packet packet(ack ? Ack : NAck);
send(addr, &packet, packet.size());
}
void send(const sockaddr_in *addr, const Packet *packet, u32 size)
{
int rc = sendto(sock, (const char *)packet, size, 0,
(sockaddr *)addr, sizeof(*addr));
if (rc != (int)size)
throw Exception("Send failed: errno " + std::to_string(get_last_error()));
DEBUG_LOG(NETWORK, "Sent port %d pckt %d size %x", ntohs(addr->sin_port), packet->type, size - (u32)packet->size(0));
}
sock_t sock = INVALID_SOCKET;
int slotCount = 0;
int slotId = 0;
std::atomic<bool> networkStopping{ false };
MiniUPnP miniupnp;
sockaddr_in nextPeer;
std::vector<u8> receivedData;
u16 packetNumber = 0;
bool _startNow = false;
// Server stuff
struct Slave
{
int state;
sockaddr_in addr;
};
std::vector<Slave> slaves;
// Client stuff
u32 serverIp;
public:
static constexpr u16 SERVER_PORT = 37391;
};
extern NaomiNetwork naomiNetwork;
void SetNaomiNetworkConfig(int node);
bool NaomiNetworkSupported();