#pragma once #include "data.hpp" 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 ip 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, 1); enet_host_destroy(host); } void pull(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: onDisconnect(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::CHUNK) { 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; memcpy(&l, event.packet->data + sizeof(server_packet_type) + sizeof(salt), sizeof(l)); salt ^= l; LOG_D("Handshake done"); ready = true; break; } onData(event.channelID, event.packet); enet_packet_destroy(event.packet); break; } case ENET_EVENT_TYPE_NONE: break; } } } virtual void onDisconnect(enet_uint32 reason) { LOG_D("Client disconnected with reason " << reason); ready = false; } virtual void onData(enet_uint8 channel, ENetPacket* packet) { LOG_D("Data from server " << packet->dataLength << " on " << channel); } bool send(client_packet_type type, ENetPacket* packet, channel_type channel) { assert(packet->dataLength >= HEADER_SIZE); if(!ready) { LOG_W("Contacting server before handshake"); enet_packet_destroy(packet); return false; } memcpy(packet->data, &type, sizeof(client_packet_type)); memcpy(packet->data + sizeof(client_packet_type), &salt, sizeof(salt)); 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 = {}) { auto packet = enet_packet_create(NULL, HEADER_SIZE + size, flags.value_or(channel == channel_type::RELIABLE ? ENET_PACKET_FLAG_RELIABLE : 0)); memcpy(packet->data + HEADER_SIZE, data, size); return send(type, packet, channel); } template bool send(client_packet_type type, const D& payload, channel_type channel, std::optional flags = {}) { return send(type, &payload, sizeof(payload), channel, flags); } constexpr bool isReady() const { return ready; } protected: ENetHost *host; ENetPeer *peer; salt_t salt; bool ready = false; }; }