/
yaseeeeechka
/
CenterControl
Обзор
Документация
Войти
/
yaseeeeechka
/
CenterControl
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CenterControl_TCP/include/MsgProtocol.h
1 153 строки
46 KB
Yaroslava
New structure of Repo. TCP impl
11 дек 2025, 21:12
11 дек 2025, 21:12
6fdf9d7
Код
Авторство
О чём код?
#ifndef CENTERCONTROL_MSGPROTOCOL_H #define CENTERCONTROL_MSGPROTOCOL_H #include <algorithm> #include <cstring> #include <vector> #include <string> #include <memory> #include <stdexcept> constexpr size_t kMaxWindowLen = 10; // uint32 - 10 знаков constexpr size_t kMaxLoginLen = 16; constexpr size_t kMaxPasswdLen = 16; constexpr size_t kMaxStatusLen = 10; // uint32 - 10 знаков //------------------------------- ENUMS --------------------- ----------- enum class WINDOWS : unsigned int { AUTH = 0, REGISTER = 1, ROOMS, CHAT, GAME, SCORES }; enum class STATUS : unsigned int { RET_ERROR = 0, RET_SUCCESS = 1, RET_ALREADY_LOGGED_IN = 2, RET_ROOM_FULL = 3, RET_UNDEFINED }; enum class ROOM_ACTION : uint16_t { LIST_ROOMS = 0, CREATE_ROOM = 1, DELETE_ROOM = 2, JOIN_ROOM = 3, LEAVE_ROOM = 4, LIST_PLAYERS = 5 }; enum class CHAT_ACTION : uint16_t { SEND_MESSAGE = 0, // клиент -> сервер: отправить текст HISTORY = 1 // клиент -> сервер: запрос истории // сервер -> клиент: ответы с историей }; enum class GAME_ACTION : uint16_t { START = 0, // создать игру в комнате QUERY_STATE = 1, // спросить состояние игры LEAVE = 2, // выйти из игры READY = 3, // игрок зашёл на экран игры (готов) MOVE = 4, PUSH = 5 }; enum class SCORES_ACTION : uint16_t { GET_TOP = 0 }; enum class MOVE_DIR : uint8_t { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3 }; // Структура для клиента (UI) struct RoomInfo { std::string name; uint8_t players = 0; // 0..4 bool inGame = false; // идёт ли игра в комнате }; struct ChatMessageDTO { std::string user; std::string text; }; struct PlayerStateDTO { std::string user; float x = 0.0f; float y = 0.0f; int score = 0; }; struct ScoreDTO { std::string user; int score = 0; std::string datetime; }; //------------------------------- PAYLOAD INTERFACE -------------------------------- class IPayload { public: virtual ~IPayload() = default; virtual std::vector<char> GetRawPayload() = 0; virtual void CreateFromRawData(const std::vector<char> &data) = 0; virtual size_t Size() = 0; }; // ------------------------------- ROOMS PAYLOAD -------------------------------- // Форматы: // Запрос LIST: [action(2)] // Ответ LIST: [action(2)][count(2)] *count x { [nameLen(2)][name][players(1)] } // // Запрос CREATE:[action(2)=CREATE][nameLen(2)][name] // Ответ CREATE:[action(2)=CREATE][statusLen(2)][statusStr] // // Запрос JOIN: [action(2)=JOIN][nameLen(2)][roomName] // Ответ JOIN: [action(2)=JOIN][nameLen(2)][roomName][statusLen(2)][statusStr] class RoomsPayload : public IPayload { public: RoomsPayload() = default; // LIST-запрос static std::shared_ptr<RoomsPayload> ListRequest() { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::LIST_ROOMS; return p; } // CREATE-запрос static std::shared_ptr<RoomsPayload> CreateRequest(std::string roomName) { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::CREATE_ROOM; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<RoomsPayload> DeleteRequest(std::string roomName) { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::DELETE_ROOM; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<RoomsPayload> JoinRequest(std::string roomName) { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::JOIN_ROOM; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<RoomsPayload> LeaveRequest(std::string roomName) { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::LEAVE_ROOM; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<RoomsPayload> ListPlayersRequest(std::string roomName) { auto p = std::make_shared<RoomsPayload>(); p->action_ = ROOM_ACTION::LIST_PLAYERS; p->room_name_ = std::move(roomName); return p; } // Сериализация std::vector<char> GetRawPayload() { std::vector<char> out; out.reserve(Size()); uint16_t act = static_cast<uint16_t>(action_); out.insert(out.end(), reinterpret_cast<const char *>(&act), reinterpret_cast<const char *>(&act) + sizeof(act)); if (action_ == ROOM_ACTION::LIST_ROOMS) { if (!rooms_.empty()) { uint16_t cnt = static_cast<uint16_t>(rooms_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&cnt), reinterpret_cast<const char *>(&cnt) + sizeof(cnt)); for (auto &r : rooms_) { uint16_t nlen = static_cast<uint16_t>(r.name.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), r.name.begin(), r.name.end()); // players out.push_back(static_cast<char>(r.players)); // inGame флаг: 0 или 1 uint8_t gameFlag = r.inGame ? 1u : 0u; out.push_back(static_cast<char>(gameFlag)); } } } else if (action_ == ROOM_ACTION::CREATE_ROOM) { uint16_t nlen = static_cast<uint16_t>(room_name_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), room_name_.begin(), room_name_.end()); if (status_ != STATUS::RET_UNDEFINED) { std::string st = std::to_string(static_cast<unsigned int>(status_)); uint16_t slen = static_cast<uint16_t>(st.size()); out.insert(out.end(), reinterpret_cast<const char *>(&slen), reinterpret_cast<const char *>(&slen) + sizeof(slen)); out.insert(out.end(), st.begin(), st.end()); } } else if (action_ == ROOM_ACTION::DELETE_ROOM) { uint16_t nlen = static_cast<uint16_t>(room_name_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), room_name_.begin(), room_name_.end()); if (status_ != STATUS::RET_UNDEFINED) { std::string st = std::to_string(static_cast<unsigned int>(status_)); uint16_t slen = static_cast<uint16_t>(st.size()); out.insert(out.end(), reinterpret_cast<const char *>(&slen), reinterpret_cast<const char *>(&slen) + sizeof(slen)); out.insert(out.end(), st.begin(), st.end()); } } else if (action_ == ROOM_ACTION::DELETE_ROOM || action_ == ROOM_ACTION::JOIN_ROOM || action_ == ROOM_ACTION::LEAVE_ROOM) { uint16_t nlen = static_cast<uint16_t>(room_name_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), room_name_.begin(), room_name_.end()); // ---- status ---- if (status_ != STATUS::RET_UNDEFINED) { std::string st = std::to_string(static_cast<unsigned int>(status_)); uint16_t slen = static_cast<uint16_t>(st.size()); out.insert(out.end(), reinterpret_cast<const char *>(&slen), reinterpret_cast<const char *>(&slen) + sizeof(slen)); out.insert(out.end(), st.begin(), st.end()); } } else if (action_ == ROOM_ACTION::LIST_PLAYERS) { // Формат ответа: // [action(2)][roomNameLen(2)][roomName][count(2)] *count x { [len(2)][name] } uint16_t nlen = static_cast<uint16_t>(room_name_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), room_name_.begin(), room_name_.end()); uint16_t cnt = static_cast<uint16_t>(players_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&cnt), reinterpret_cast<const char *>(&cnt) + sizeof(cnt)); for (const auto &p: players_) { uint16_t plen = static_cast<uint16_t>(p.size()); out.insert(out.end(), reinterpret_cast<const char *>(&plen), reinterpret_cast<const char *>(&plen) + sizeof(plen)); out.insert(out.end(), p.begin(), p.end()); } } return out; } void CreateFromRawData(const std::vector<char> &data) { size_t off = 0; if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: missing action"); uint16_t act; std::memcpy(&act, &data[off], sizeof(act)); action_ = static_cast<ROOM_ACTION>(act); off += 2; if (action_ == ROOM_ACTION::LIST_ROOMS) { if (off < data.size()) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: LIST missing count"); uint16_t cnt; std::memcpy(&cnt, &data[off], sizeof(cnt)); off += 2; rooms_.clear(); rooms_.reserve(cnt); for (uint16_t i = 0; i < cnt; ++i) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: name size missing"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("RoomsPayload: name data too short"); std::string name(data.begin() + off, data.begin() + off + nlen); off += nlen; if (off + 1 > data.size()) throw std::runtime_error("RoomsPayload: players missing"); uint8_t players = static_cast<uint8_t>(data[off]); off += 1; if (off + 1 > data.size()) throw std::runtime_error("RoomsPayload: inGame flag missing"); uint8_t gameFlag = static_cast<uint8_t>(data[off]); off += 1; bool inGame = (gameFlag != 0); rooms_.push_back(RoomInfo{std::move(name), players, inGame}); } } } else if (action_ == ROOM_ACTION::CREATE_ROOM) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: CREATE missing name size"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("RoomsPayload: CREATE name short"); room_name_ = std::string(data.begin() + off, data.begin() + off + nlen); off += nlen; if (off < data.size()) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: CREATE missing status size"); uint16_t slen; std::memcpy(&slen, &data[off], sizeof(slen)); off += 2; if (off + slen > data.size()) throw std::runtime_error("RoomsPayload: CREATE status short"); std::string st(data.begin() + off, data.begin() + off + slen); off += slen; unsigned long v = std::strtoul(st.c_str(), nullptr, 10); status_ = static_cast<STATUS>(v); } } else if (action_ == ROOM_ACTION::DELETE_ROOM) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: DELETE missing name size"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("RoomsPayload: DELETE name short"); room_name_ = std::string(data.begin() + off, data.begin() + off + nlen); off += nlen; if (off < data.size()) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: DELETE missing status size"); uint16_t slen; std::memcpy(&slen, &data[off], sizeof(slen)); off += 2; if (off + slen > data.size()) throw std::runtime_error("RoomsPayload: DELETE status short"); std::string st(data.begin() + off, data.begin() + off + slen); off += slen; unsigned long v = std::strtoul(st.c_str(), nullptr, 10); status_ = static_cast<STATUS>(v); } } else if (action_ == ROOM_ACTION::DELETE_ROOM || action_ == ROOM_ACTION::JOIN_ROOM || action_ == ROOM_ACTION::LEAVE_ROOM) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: NAME missing size"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("RoomsPayload: NAME short"); room_name_ = std::string(data.begin() + off, data.begin() + off + nlen); off += nlen; if (off < data.size()) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: STATUS missing size"); uint16_t slen; std::memcpy(&slen, &data[off], sizeof(slen)); off += 2; if (off + slen > data.size()) throw std::runtime_error("RoomsPayload: STATUS short"); std::string st(data.begin() + off, data.begin() + off + slen); off += slen; unsigned long v = std::strtoul(st.c_str(), nullptr, 10); status_ = static_cast<STATUS>(v); } } else if (action_ == ROOM_ACTION::LIST_PLAYERS) { // [roomNameLen(2)][roomName][count(2)] *count x { [len(2)][name] } if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: LIST_PLAYERS missing room name size"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("RoomsPayload: LIST_PLAYERS room name short"); room_name_ = std::string(data.begin() + off, data.begin() + off + nlen); off += nlen; if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: LIST_PLAYERS missing count"); uint16_t cnt; std::memcpy(&cnt, &data[off], sizeof(cnt)); off += 2; players_.clear(); players_.reserve(cnt); for (uint16_t i = 0; i < cnt; ++i) { if (off + 2 > data.size()) throw std::runtime_error("RoomsPayload: LIST_PLAYERS name size missing"); uint16_t plen; std::memcpy(&plen, &data[off], sizeof(plen)); off += 2; if (off + plen > data.size()) throw std::runtime_error("RoomsPayload: LIST_PLAYERS name short"); std::string pname(data.begin() + off, data.begin() + off + plen); off += plen; players_.push_back(std::move(pname)); } } } size_t Size() override { // с запасом return 2 + 2 + 64 * 10 + 1 + 64; } // helpers ROOM_ACTION GetAction() const { return action_; } const std::vector<RoomInfo> &GetRooms() const { return rooms_; } void SetRooms(std::vector<RoomInfo> r) { rooms_ = std::move(r); } const std::string &GetRoomName() const { return room_name_; } void SetStatus(STATUS st) { status_ = st; } STATUS GetStatus() const { return status_; } void SetPlayers(std::vector<std::string> players) { players_ = std::move(players); } const std::vector<std::string> &GetPlayers() const { return players_; } private: ROOM_ACTION action_ = ROOM_ACTION::LIST_ROOMS; std::vector<RoomInfo> rooms_; std::string room_name_; STATUS status_ = STATUS::RET_UNDEFINED; std::vector<std::string> players_; }; //------------------------------- CHAT PAYLOAD -------------------------------- class ChatPayload : public IPayload { public: ChatPayload() = default; // Отправка сообщения (room == "" => глобальный чат) static std::shared_ptr<ChatPayload> SendRequest(std::string text, std::string room) { auto p = std::make_shared<ChatPayload>(); p->action_ = CHAT_ACTION::SEND_MESSAGE; p->text_ = std::move(text); p->room_ = std::move(room); return p; } // Запрос истории для указанной комнаты ("" => глобальный чат) static std::shared_ptr<ChatPayload> HistoryRequest(std::string room) { auto p = std::make_shared<ChatPayload>(); p->action_ = CHAT_ACTION::HISTORY; p->room_ = std::move(room); return p; } std::vector<char> GetRawPayload() override { std::vector<char> out; out.reserve(Size()); // action uint16_t act = static_cast<uint16_t>(action_); out.insert(out.end(), reinterpret_cast<const char *>(&act), reinterpret_cast<const char *>(&act) + sizeof(act)); // room name uint16_t rlen = static_cast<uint16_t>(room_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&rlen), reinterpret_cast<const char *>(&rlen) + sizeof(rlen)); out.insert(out.end(), room_.begin(), room_.end()); if (action_ == CHAT_ACTION::SEND_MESSAGE) { uint16_t tlen = static_cast<uint16_t>(text_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&tlen), reinterpret_cast<const char *>(&tlen) + sizeof(tlen)); out.insert(out.end(), text_.begin(), text_.end()); } else if (action_ == CHAT_ACTION::HISTORY) { // Если messages_ пуст — это запрос (клиент -> сервер): больше ничего не пишем. // Если не пуст — это ответ (сервер -> клиент). if (!messages_.empty()) { uint16_t cnt = static_cast<uint16_t>(messages_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&cnt), reinterpret_cast<const char *>(&cnt) + sizeof(cnt)); for (auto &m: messages_) { uint16_t ulen = static_cast<uint16_t>(m.user.size()); out.insert(out.end(), reinterpret_cast<const char *>(&ulen), reinterpret_cast<const char *>(&ulen) + sizeof(ulen)); out.insert(out.end(), m.user.begin(), m.user.end()); uint16_t tlen = static_cast<uint16_t>(m.text.size()); out.insert(out.end(), reinterpret_cast<const char *>(&tlen), reinterpret_cast<const char *>(&tlen) + sizeof(tlen)); out.insert(out.end(), m.text.begin(), m.text.end()); } } } return out; } void CreateFromRawData(const std::vector<char> &data) override { size_t off = 0; if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: missing action"); uint16_t act; std::memcpy(&act, &data[off], sizeof(act)); action_ = static_cast<CHAT_ACTION>(act); off += 2; // room if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: missing room size"); uint16_t rlen; std::memcpy(&rlen, &data[off], sizeof(rlen)); off += 2; if (off + rlen > data.size()) throw std::runtime_error("ChatPayload: room name too short"); room_ = std::string(data.begin() + off, data.begin() + off + rlen); off += rlen; if (action_ == CHAT_ACTION::SEND_MESSAGE) { if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: SEND missing text size"); uint16_t tlen; std::memcpy(&tlen, &data[off], sizeof(tlen)); off += 2; if (off + tlen > data.size()) throw std::runtime_error("ChatPayload: SEND text short"); text_ = std::string(data.begin() + off, data.begin() + off + tlen); off += tlen; } else if (action_ == CHAT_ACTION::HISTORY) { // Запрос HISTORY: может содержать только action+room (без count) if (off >= data.size()) { messages_.clear(); return; } if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: HISTORY missing count"); uint16_t cnt; std::memcpy(&cnt, &data[off], sizeof(cnt)); off += 2; messages_.clear(); messages_.reserve(cnt); for (uint16_t i = 0; i < cnt; ++i) { if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: HISTORY user size missing"); uint16_t ulen; std::memcpy(&ulen, &data[off], sizeof(ulen)); off += 2; if (off + ulen > data.size()) throw std::runtime_error("ChatPayload: HISTORY user short"); std::string user(data.begin() + off, data.begin() + off + ulen); off += ulen; if (off + 2 > data.size()) throw std::runtime_error("ChatPayload: HISTORY text size missing"); uint16_t tlen; std::memcpy(&tlen, &data[off], sizeof(tlen)); off += 2; if (off + tlen > data.size()) throw std::runtime_error("ChatPayload: HISTORY text short"); std::string text(data.begin() + off, data.begin() + off + tlen); off += tlen; messages_.push_back(ChatMessageDTO{std::move(user), std::move(text)}); } } } size_t Size() override { // грубая верхняя оценка return 2 + 2 + 64 + 2 + 64 * 10; } CHAT_ACTION GetAction() const { return action_; } const std::string &GetText() const { return text_; } const std::string &GetRoom() const { return room_; } void SetRoom(std::string room) { room_ = std::move(room); } const std::vector<ChatMessageDTO> &GetMessages() const { return messages_; } void SetMessages(std::vector<ChatMessageDTO> msgs) { messages_ = std::move(msgs); } void SetAction(CHAT_ACTION a) { action_ = a; } private: CHAT_ACTION action_ = CHAT_ACTION::SEND_MESSAGE; std::string room_; // "" = глобальный чат std::string text_; std::vector<ChatMessageDTO> messages_; }; //------------------------------- Game PAYLOAD -------------------------------- class GamePayload : public IPayload { public: GamePayload() = default; static std::shared_ptr<GamePayload> StartRequest(std::string roomName) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::START; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<GamePayload> QueryStateRequest(std::string roomName) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::QUERY_STATE; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<GamePayload> LeaveRequest(std::string roomName) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::LEAVE; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<GamePayload> ReadyRequest(std::string roomName) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::READY; p->room_name_ = std::move(roomName); return p; } static std::shared_ptr<GamePayload> MoveRequest(const std::string &room, uint8_t dir) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::MOVE; p->room_name_ = room; p->move_dir_ = dir; return p; } static std::shared_ptr<GamePayload> PushRequest(std::string roomName) { auto p = std::make_shared<GamePayload>(); p->action_ = GAME_ACTION::PUSH; p->room_name_ = std::move(roomName); return p; } // Формат при ответе сервера (когда status_ != RET_UNDEFINED): // [action(2)] // [roomNameLen(2)][roomName] // [statusLen(2)][statusStr] // [spawnIndex(1)][countdown(1)] // [playersCount(2)] // *playersCount x { // [userLen(2)][user] // [x(float4)][y(float4)] // } // // При запросе клиента на QUERY_STATE/START/READY/LEAVE можно отправлять // только [action][roomNameLen][roomName] (status_ = RET_UNDEFINED). std::vector<char> GetRawPayload() override { std::vector<char> out; out.reserve(Size()); // action uint16_t act = static_cast<uint16_t>(action_); out.insert(out.end(), reinterpret_cast<const char *>(&act), reinterpret_cast<const char *>(&act) + sizeof(act)); // room name uint16_t nlen = static_cast<uint16_t>(room_name_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&nlen), reinterpret_cast<const char *>(&nlen) + sizeof(nlen)); out.insert(out.end(), room_name_.begin(), room_name_.end()); // ==== ЗАПРОС КЛИЕНТА → СЕРВЕР ==== if (status_ == STATUS::RET_UNDEFINED) { // Для MOVE нужно дослать 1 байт направления if (action_ == GAME_ACTION::MOVE) { out.push_back(static_cast<char>(move_dir_)); } // Для START / READY / QUERY_STATE / LEAVE больше ничего не пишем return out; } // ==== ОТВЕТ СЕРВЕРА → КЛИЕНТА ==== // статус std::string st = std::to_string(static_cast<unsigned int>(status_)); uint16_t slen = static_cast<uint16_t>(st.size()); out.insert(out.end(), reinterpret_cast<const char *>(&slen), reinterpret_cast<const char *>(&slen) + sizeof(slen)); out.insert(out.end(), st.begin(), st.end()); // spawn_index и countdown out.push_back(static_cast<char>(spawn_index_)); out.push_back(static_cast<char>(countdown_)); // список игроков uint16_t cnt = static_cast<uint16_t>(players_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&cnt), reinterpret_cast<const char *>(&cnt) + sizeof(cnt)); for (const auto &p: players_) { uint16_t ulen = static_cast<uint16_t>(p.user.size()); out.insert(out.end(), reinterpret_cast<const char*>(&ulen), reinterpret_cast<const char*>(&ulen) + sizeof(ulen)); out.insert(out.end(), p.user.begin(), p.user.end()); float fx = p.x; float fy = p.y; out.insert(out.end(), reinterpret_cast<const char*>(&fx), reinterpret_cast<const char*>(&fx) + sizeof(fx)); out.insert(out.end(), reinterpret_cast<const char*>(&fy), reinterpret_cast<const char*>(&fy) + sizeof(fy)); int32_t sc = static_cast<int32_t>(p.score); out.insert(out.end(), reinterpret_cast<const char*>(&sc), reinterpret_cast<const char*>(&sc) + sizeof(sc)); } return out; } void CreateFromRawData(const std::vector<char> &data) override { size_t off = 0; if (off + 2 > data.size()) throw std::runtime_error("GamePayload: missing action"); uint16_t act; std::memcpy(&act, &data[off], sizeof(act)); action_ = static_cast<GAME_ACTION>(act); off += 2; if (off + 2 > data.size()) throw std::runtime_error("GamePayload: missing room size"); uint16_t nlen; std::memcpy(&nlen, &data[off], sizeof(nlen)); off += 2; if (off + nlen > data.size()) throw std::runtime_error("GamePayload: room name short"); room_name_ = std::string(data.begin() + off, data.begin() + off + nlen); off += nlen; // После roomName может быть: // - для запросов: либо ничего (обычные действия), либо 1 байт направления (MOVE) // - для ответов: статус, потом spawn/countdown и список игроков if (off >= data.size()) { // Классический запрос без доп. полей status_ = STATUS::RET_UNDEFINED; players_.clear(); return; } // Для MOVE-запроса клиент → сервер формат: // [action][roomNameLen][roomName][moveDir] // Т.е. если статус ещё не писали, но action == MOVE и остался 1 байт — это направление. if (action_ == GAME_ACTION::MOVE && status_ == STATUS::RET_UNDEFINED) { // если это запрос, там вообще не должно быть статуса; просто читаем 1 байт dir if (off + 1 > data.size()) { throw std::runtime_error("GamePayload: MOVE missing direction"); } move_dir_ = static_cast<uint8_t>(data[off]); off += 1; // запрос, без статуса и без списка игроков players_.clear(); return; } // ==== дальше — ветка для ОТВЕТОВ сервера -> клиента ==== // статус if (off + 2 > data.size()) throw std::runtime_error("GamePayload: missing status size"); uint16_t slen; std::memcpy(&slen, &data[off], sizeof(slen)); off += 2; if (off + slen > data.size()) throw std::runtime_error("GamePayload: status short"); std::string st(data.begin() + off, data.begin() + off + slen); off += slen; unsigned long v = std::strtoul(st.c_str(), nullptr, 10); status_ = static_cast<STATUS>(v); // spawn_index и countdown (если есть) if (off + 2 > data.size()) { spawn_index_ = 0; countdown_ = 255; players_.clear(); return; } spawn_index_ = static_cast<uint8_t>(data[off]); countdown_ = static_cast<uint8_t>(data[off + 1]); off += 2; // дальше может быть список игроков if (off >= data.size()) { players_.clear(); return; } if (off + 2 > data.size()) throw std::runtime_error("GamePayload: missing players count"); uint16_t cnt; std::memcpy(&cnt, &data[off], sizeof(cnt)); off += 2; players_.clear(); players_.reserve(cnt); for (uint16_t i = 0; i < cnt; ++i) { if (off + 2 > data.size()) throw std::runtime_error("GamePayload: player name size missing"); uint16_t ulen; std::memcpy(&ulen, &data[off], sizeof(ulen)); off += 2; if (off + ulen > data.size()) throw std::runtime_error("GamePayload: player name short"); std::string user(data.begin() + off, data.begin() + off + ulen); off += ulen; if (off + sizeof(float) * 2 > data.size()) throw std::runtime_error("GamePayload: player coords short"); float fx, fy; std::memcpy(&fx, &data[off], sizeof(fx)); off += sizeof(fx); std::memcpy(&fy, &data[off], sizeof(fy)); off += sizeof(fy); int32_t sc = 0; if (off + sizeof(sc) > data.size()) throw std::runtime_error("GamePayload: player score short"); std::memcpy(&sc, &data[off], sizeof(sc)); off += sizeof(sc); players_.push_back(PlayerStateDTO{std::move(user), fx, fy, sc}); } } size_t Size() override { // Грубая верхняя оценка, чтобы не делать лишних реаллокаций // (влияет только на reserve, не на протокол) return 2 + 2 + 64 + 2 + 4 + 2 + players_.size() * (2 + 32 + 8); } // Getters / setters GAME_ACTION GetAction() const { return action_; } const std::string &GetRoomName() const { return room_name_; } STATUS GetStatus() const { return status_; } void SetStatus(STATUS s) { status_ = s; } uint8_t GetSpawnIndex() const { return spawn_index_; } void SetSpawnIndex(uint8_t idx) { spawn_index_ = idx; } uint8_t GetCountdown() const { return countdown_; } void SetCountdown(uint8_t c) { countdown_ = c; } const std::vector<PlayerStateDTO> &GetPlayers() const { return players_; } void SetPlayers(std::vector<PlayerStateDTO> players) { players_ = std::move(players); } uint8_t GetMoveDirection() const { return move_dir_; } void SetMoveDirection(uint8_t d) { move_dir_ = d; } private: GAME_ACTION action_ = GAME_ACTION::START; std::string room_name_; STATUS status_ = STATUS::RET_UNDEFINED; uint8_t spawn_index_ = 0; uint8_t countdown_ = 255; // 255 = ожидание игроков / нет таймера uint8_t move_dir_ = 0; std::vector<PlayerStateDTO> players_; }; //------------------------------- SCORES PAYLOAD -------------------------------- class ScoresPayload : public IPayload { public: ScoresPayload() = default; // Запрос на топ: клиент -> сервер static std::shared_ptr<ScoresPayload> TopRequest(uint16_t limit) { auto p = std::make_shared<ScoresPayload>(); p->action_ = SCORES_ACTION::GET_TOP; p->limit_ = limit; return p; } std::vector<char> GetRawPayload() override { std::vector<char> out; out.reserve(Size()); // action uint16_t act = static_cast<uint16_t>(action_); out.insert(out.end(), reinterpret_cast<const char*>(&act), reinterpret_cast<const char*>(&act) + sizeof(act)); // limit (для запросов и ответов одинаково) out.insert(out.end(), reinterpret_cast<const char*>(&limit_), reinterpret_cast<const char*>(&limit_) + sizeof(limit_)); // Если scores_ пуст — это запрос (клиент → сервер), больше ничего не пишем if (scores_.empty()) { return out; } // Ответ сервера: [action][limit][count][...] uint16_t cnt = static_cast<uint16_t>(scores_.size()); out.insert(out.end(), reinterpret_cast<const char*>(&cnt), reinterpret_cast<const char*>(&cnt) + sizeof(cnt)); for (const auto &s : scores_) { // user uint16_t ulen = static_cast<uint16_t>(s.user.size()); out.insert(out.end(), reinterpret_cast<const char*>(&ulen), reinterpret_cast<const char*>(&ulen) + sizeof(ulen)); out.insert(out.end(), s.user.begin(), s.user.end()); // score int32_t sc = static_cast<int32_t>(s.score); out.insert(out.end(), reinterpret_cast<const char*>(&sc), reinterpret_cast<const char*>(&sc) + sizeof(sc)); // datetime uint16_t dlen = static_cast<uint16_t>(s.datetime.size()); out.insert(out.end(), reinterpret_cast<const char*>(&dlen), reinterpret_cast<const char*>(&dlen) + sizeof(dlen)); out.insert(out.end(), s.datetime.begin(), s.datetime.end()); } return out; } void CreateFromRawData(const std::vector<char> &data) override { size_t off = 0; if (off + 2 > data.size()) throw std::runtime_error("ScoresPayload: missing action"); uint16_t act; std::memcpy(&act, &data[off], sizeof(act)); action_ = static_cast<SCORES_ACTION>(act); off += 2; if (off + 2 > data.size()) throw std::runtime_error("ScoresPayload: missing limit"); std::memcpy(&limit_, &data[off], sizeof(limit_)); off += 2; // Если дальше ничего нет — это запрос (клиент -> сервер) if (off >= data.size()) { scores_.clear(); return; } // Ответ сервера if (off + 2 > data.size()) throw std::runtime_error("ScoresPayload: missing count"); uint16_t cnt; std::memcpy(&cnt, &data[off], sizeof(cnt)); off += 2; scores_.clear(); scores_.reserve(cnt); for (uint16_t i = 0; i < cnt; ++i) { if (off + 2 > data.size()) throw std::runtime_error("ScoresPayload: user len missing"); uint16_t ulen; std::memcpy(&ulen, &data[off], sizeof(ulen)); off += 2; if (off + ulen > data.size()) throw std::runtime_error("ScoresPayload: user short"); std::string user(data.begin() + off, data.begin() + off + ulen); off += ulen; if (off + sizeof(int32_t) > data.size()) throw std::runtime_error("ScoresPayload: score short"); int32_t sc; std::memcpy(&sc, &data[off], sizeof(sc)); off += sizeof(sc); if (off + 2 > data.size()) throw std::runtime_error("ScoresPayload: datetime len missing"); uint16_t dlen; std::memcpy(&dlen, &data[off], sizeof(dlen)); off += 2; if (off + dlen > data.size()) throw std::runtime_error("ScoresPayload: datetime short"); std::string dt(data.begin() + off, data.begin() + off + dlen); off += dlen; scores_.push_back(ScoreDTO{std::move(user), sc, std::move(dt)}); } } size_t Size() override { // грубая верхняя оценка return 2 + 2 + 2 + scores_.size() * (2 + 32 + 4 + 2 + 32); } void SetScores(std::vector<ScoreDTO> s) { scores_ = std::move(s); } const std::vector<ScoreDTO>& GetScores() const { return scores_; } uint16_t GetLimit() const { return limit_; } void SetLimit(uint16_t limit) { limit_ = limit; } private: SCORES_ACTION action_ = SCORES_ACTION::GET_TOP; uint16_t limit_ = 100; std::vector<ScoreDTO> scores_; }; //------------------------------- AUTH PAYLOAD -------------------------------- /* * Формат данных AuthPayload: * [2 байта размер логина][логин][2 байта размер пароля][пароль][2 байта размер статуса][статус] */ class AuthPayload : public IPayload { public: AuthPayload() = default; AuthPayload(std::string login, std::string password, STATUS status = STATUS::RET_UNDEFINED) : login_(std::move(login)), password_(std::move(password)), status_(status) { } ~AuthPayload() override = default; std::vector<char> GetRawPayload() override { std::vector<char> out; out.reserve(Size()); // ---- логин ---- uint16_t login_size = static_cast<uint16_t>(login_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&login_size), reinterpret_cast<const char *>(&login_size) + sizeof(login_size)); out.insert(out.end(), login_.begin(), login_.end()); // ---- пароль ---- uint16_t password_size = static_cast<uint16_t>(password_.size()); out.insert(out.end(), reinterpret_cast<const char *>(&password_size), reinterpret_cast<const char *>(&password_size) + sizeof(password_size)); out.insert(out.end(), password_.begin(), password_.end()); // ---- статус ---- std::string status_str = std::to_string(static_cast<unsigned int>(status_)); uint16_t status_size = static_cast<uint16_t>(status_str.size()); out.insert(out.end(), reinterpret_cast<const char *>(&status_size), reinterpret_cast<const char *>(&status_size) + sizeof(status_size)); out.insert(out.end(), status_str.begin(), status_str.end()); return out; } void CreateFromRawData(const std::vector<char> &data) override { size_t offset = 0; // ---- логин ---- if (offset + 2 > data.size()) throw std::runtime_error("Invalid AuthPayload: missing login size"); uint16_t login_size; std::memcpy(&login_size, &data[offset], sizeof(login_size)); offset += 2; if (offset + login_size > data.size()) throw std::runtime_error("Invalid AuthPayload: login data too short"); login_ = std::string(data.begin() + offset, data.begin() + offset + login_size); offset += login_size; // ---- пароль ---- if (offset + 2 > data.size()) throw std::runtime_error("Invalid AuthPayload: missing password size"); uint16_t password_size; std::memcpy(&password_size, &data[offset], sizeof(password_size)); offset += 2; if (offset + password_size > data.size()) throw std::runtime_error( "Invalid AuthPayload: password data too short"); password_ = std::string(data.begin() + offset, data.begin() + offset + password_size); offset += password_size; // ---- статус ---- if (offset + 2 > data.size()) throw std::runtime_error("Invalid AuthPayload: missing status size"); uint16_t status_size; std::memcpy(&status_size, &data[offset], sizeof(status_size)); offset += 2; if (offset + status_size > data.size()) throw std::runtime_error("Invalid AuthPayload: status data too short"); std::string status_str(data.begin() + offset, data.begin() + offset + status_size); offset += status_size; unsigned long status_val = std::strtoul(status_str.c_str(), nullptr, 10); status_ = static_cast<STATUS>(status_val); } size_t Size() override { // Максимальная оценка (необязательно точная, т.к. поля переменной длины) return 2 + kMaxLoginLen + 2 + kMaxPasswdLen + 2 + kMaxStatusLen; } // Getters const std::string &GetLogin() const { return login_; } const std::string &GetPassword() const { return password_; } STATUS GetStatus() const { return status_; } private: std::string login_; std::string password_; STATUS status_ = STATUS::RET_UNDEFINED; }; //------------------------------- MSG PROTOCOL -------------------------------- /* Формат MsgProtocol: [2 байта тип окна][4 байта размер payload][payload] */ class MsgProtocol { public: MsgProtocol() = default; MsgProtocol(WINDOWS window, std::shared_ptr<IPayload> payload) : window_(window), payload_(std::move(payload)) { } ~MsgProtocol() = default; std::vector<char> GetRawPayload() { if (!payload_) throw std::runtime_error("MsgProtocol: payload is null"); std::vector<char> out; std::vector<char> payload_data = payload_->GetRawPayload(); // ---- окно ---- uint16_t window_type = static_cast<uint16_t>(window_); out.insert(out.end(), reinterpret_cast<const char *>(&window_type), reinterpret_cast<const char *>(&window_type) + sizeof(window_type)); // ---- размер ---- uint32_t payload_size = static_cast<uint32_t>(payload_data.size()); out.insert(out.end(), reinterpret_cast<const char *>(&payload_size), reinterpret_cast<const char *>(&payload_size) + sizeof(payload_size)); // ---- данные ---- out.insert(out.end(), payload_data.begin(), payload_data.end()); return out; } void CreateFromRawData(const std::vector<char> &data) { size_t offset = 0; if (offset + 2 > data.size()) throw std::runtime_error("MsgProtocol: missing window type"); uint16_t window_type; std::memcpy(&window_type, &data[offset], sizeof(window_type)); window_ = static_cast<WINDOWS>(window_type); offset += 2; if (offset + 4 > data.size()) throw std::runtime_error("MsgProtocol: missing payload size"); uint32_t payload_size; std::memcpy(&payload_size, &data[offset], sizeof(payload_size)); offset += 4; if (offset + payload_size > data.size()) throw std::runtime_error("MsgProtocol: incomplete payload data"); std::vector<char> payload_data(data.begin() + offset, data.begin() + offset + payload_size); // --- поддерживаем все окна с AuthPayload --- switch (window_) { case WINDOWS::AUTH: case WINDOWS::REGISTER: payload_ = std::make_shared<AuthPayload>(); break; case WINDOWS::ROOMS: payload_ = std::make_shared<RoomsPayload>(); break; case WINDOWS::CHAT: payload_ = std::make_shared<ChatPayload>(); break; case WINDOWS::GAME: payload_ = std::make_shared<GamePayload>(); break; case WINDOWS::SCORES: payload_ = std::make_shared<ScoresPayload>(); break; default: throw std::runtime_error("MsgProtocol: unsupported window type"); } payload_->CreateFromRawData(payload_data); } WINDOWS GetWindowType() const { return window_; } std::shared_ptr<IPayload> GetPayload() const { return payload_; } private: WINDOWS window_ = WINDOWS::AUTH; std::shared_ptr<IPayload> payload_; }; #endif //CENTERCONTROL_MSGPROTOCOL_H