#pragma once #include "data.hpp" #include "PacketView.hpp" #include namespace net { class Client { public: static constexpr auto HEADER_SIZE = sizeof(client_packet_type) + sizeof(salt_t); Client(const connection& ct) { auto addr = [&] { if(auto addr = ct.toAddress()) return addr.value(); FATAL("Invalid server address format"); }(); host = enet_host_create(NULL, 1, CHANNEL_COUNT, 0, 0); if(host == nullptr) { FATAL("Network client creation failed"); } LOG_D("Connecting to " << ct); salt = std::rand(); peer = enet_host_connect(host, &addr, CHANNEL_COUNT, salt); if(peer == nullptr) { FATAL("Cannot contact server " << ct); } if(ENetEvent event; enet_host_service(host, &event, TIMEOUT) <= 0 || event.type != ENET_EVENT_TYPE_CONNECT) { enet_peer_reset(peer); FATAL("Connection to server " << ct << " failed"); } LOG_I("Connected to server " << ct); } ~Client() { LOG_D("Leaving server"); enet_peer_disconnect_now(peer, (enet_uint32)disconnect_reason::QUIT); enet_host_destroy(host); } template void pull(R onData, D onDisconnect, int delay = 0) { ENetEvent event; while(enet_host_service(host, &event, delay) > 0) { switch(event.type) { case ENET_EVENT_TYPE_CONNECT: LOG_D("Client reconnected"); break; case ENET_EVENT_TYPE_DISCONNECT: LOG_D("Client disconnected with reason " << event.data); ready = false; onDisconnect((disconnect_reason)event.data); break; case ENET_EVENT_TYPE_RECEIVE: { if(event.packet->dataLength < sizeof(server_packet_type)) { LOG_D("Empty packet from server"); break; } const server_packet_type type = static_cast(*event.packet->data); if(type < server_packet_type::BROADCASTED) { if(event.packet->dataLength < sizeof(server_packet_type) + sizeof(salt)) { LOG_D("Wrong salted packet size"); break; } if(memcmp(&salt, event.packet->data + sizeof(server_packet_type), sizeof(salt)) != 0) { LOG_D("Wrong server salt"); break; } } if(type == server_packet_type::CHALLENGE) { if(event.packet->dataLength != sizeof(server_packet_type) + 2 * sizeof(salt)) { LOG_D("Wrong challenge packet size"); break; } salt_t l; PacketReader(event.packet).read(l); salt ^= l; LOG_D("Handshake done"); ready = true; break; } onData(event.packet, (channel_type)event.channelID); enet_packet_destroy(event.packet); break; } case ENET_EVENT_TYPE_NONE: break; } } TracyPlot("CltNetUp", (int64_t)host->outgoingBandwidth); TracyPlot("CltNetDown", (int64_t)host->incomingBandwidth); TracyPlot("CltNetRTT", (int64_t)peer->roundTripTime); } bool send(packet_t* packet, channel_type channel) { return enet_peer_send(peer, (enet_uint8)channel, packet) == 0; } bool send(client_packet_type type, const void *data, size_t size, channel_type channel, std::optional flags = {}) { return send(makePacket(type, data, size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0), salt).get(), channel); } template bool send(client_packet_type type, const D& data, channel_type channel, std::optional flags = {}) { return send(type, &data, sizeof(data), channel, flags); } static PacketWriter makePacket(client_packet_type type, const void* data, size_t size, enet_uint32 flags, salt_t salt) { auto packet = PacketWriter(sizeof(client_packet_type) + sizeof(salt_t) + size, flags); packet.write(type); packet.write(salt); if (data != nullptr) packet.write(data, size); return packet; } constexpr bool isReady() const { return ready; } constexpr salt_t getSalt() const { return salt; } protected: ENetHost *host; ENetPeer *peer; salt_t salt; bool ready = false; }; }