From 971119edaeef034d12dcca5d9a4de8e8edb1b5f9 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Sun, 2 Sep 2018 13:02:20 +0100 Subject: [PATCH] Implement low-level packet (de)serialisation code. The protocol is going to use TLV messages, containing zero or more fields represented using the same TLV header. Functions for (de)serialising DirectPlay message structures will be built on top of these classes. --- Makefile | 13 +- src/packet.cpp | 181 +++++++++++ src/packet.hpp | 89 ++++++ tests/PacketDeserialiser.cpp | 560 +++++++++++++++++++++++++++++++++++ tests/PacketSerialiser.cpp | 167 +++++++++++ 5 files changed, 1009 insertions(+), 1 deletion(-) create mode 100644 src/packet.cpp create mode 100644 src/packet.hpp create mode 100644 tests/PacketDeserialiser.cpp create mode 100644 tests/PacketSerialiser.cpp diff --git a/Makefile b/Makefile index 484550a..ff087de 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,23 @@ TEST_CXXFLAGS := $(CXXFLAGS) -I./googletest/include/ all: dpnet.dll -dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o src/network.o +check: tests/all-tests.exe + wine tests/all-tests.exe + +dpnet.dll: src/dpnet.o src/dpnet.def src/DirectPlay8Address.o src/DirectPlay8Peer.o src/network.o src/packet.o $(CXX) $(CXXFLAGS) -Wl,--enable-stdcall-fixup -shared -o $@ $^ -ldxguid -lws2_32 -static-libstdc++ -static-libgcc tests/DirectPlay8Address.exe: tests/DirectPlay8Address.o src/DirectPlay8Address.o googletest/src/gtest-all.o googletest/src/gtest_main.o $(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc +tests/PacketSerialiser.exe: tests/PacketSerialiser.o src/packet.o googletest/src/gtest-all.o googletest/src/gtest_main.o + $(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc + +tests/all-tests.exe: tests/DirectPlay8Address.o src/DirectPlay8Address.o \ + tests/PacketSerialiser.o tests/PacketDeserialiser.o src/packet.o \ + googletest/src/gtest-all.o googletest/src/gtest_main.o + $(CXX) $(TEST_CXXFLAGS) -o $@ $^ -ldxguid -lole32 -static-libstdc++ -static-libgcc + src/%.o: src/%.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< diff --git a/src/packet.cpp b/src/packet.cpp new file mode 100644 index 0000000..1235be9 --- /dev/null +++ b/src/packet.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include + +#include "packet.hpp" + +const uint32_t FIELD_TYPE_NULL = 0; +const uint32_t FIELD_TYPE_DWORD = 1; +const uint32_t FIELD_TYPE_DATA = 2; +const uint32_t FIELD_TYPE_WSTRING = 3; + +PacketSerialiser::PacketSerialiser(uint32_t type) +{ + /* Avoid reallocations during packet construction unless we get given a lot of data. */ + sbuf.reserve(4096); + + TLVChunk header; + header.type = type; + header.value_length = 0; + + sbuf.insert(sbuf.begin(), (unsigned char*)(&header), (unsigned char*)(&header + 1)); +} + +std::pair PacketSerialiser::raw_packet() +{ + return std::make_pair(sbuf.data(), sbuf.size()); +} + +void PacketSerialiser::append_null() +{ + TLVChunk header; + header.type = FIELD_TYPE_NULL; + header.value_length = 0; + + sbuf.insert(sbuf.end(), (unsigned char*)(&header), (unsigned char*)(&header + 1)); + + ((TLVChunk*)(sbuf.data()))->value_length += sizeof(header); +} + +void PacketSerialiser::append_dword(DWORD value) +{ + TLVChunk header; + header.type = FIELD_TYPE_DWORD; + header.value_length = sizeof(DWORD); + + sbuf.insert(sbuf.end(), (unsigned char*)(&header), (unsigned char*)(&header + 1)); + sbuf.insert(sbuf.end(), (unsigned char*)(&value), (unsigned char*)(&value + 1)); + + ((TLVChunk*)(sbuf.data()))->value_length += sizeof(header) + sizeof(value); +} + +void PacketSerialiser::append_data(const void *data, size_t size) +{ + TLVChunk header; + header.type = FIELD_TYPE_DATA; + header.value_length = size; + + sbuf.insert(sbuf.end(), (unsigned char*)(&header), (unsigned char*)(&header + 1)); + sbuf.insert(sbuf.end(), (unsigned char*)(data), (unsigned char*)(data) + size); + + ((TLVChunk*)(sbuf.data()))->value_length += sizeof(header) + size; +} + +void PacketSerialiser::append_wstring(const std::wstring &string) +{ + size_t string_bytes = string.length() * sizeof(wchar_t); + + TLVChunk header; + header.type = FIELD_TYPE_WSTRING; + header.value_length = string_bytes; + + sbuf.insert(sbuf.end(), (unsigned char*)(&header), (unsigned char*)(&header + 1)); + sbuf.insert(sbuf.end(), (unsigned char*)(string.data()), (unsigned char*)(string.data()) + string_bytes); + + ((TLVChunk*)(sbuf.data()))->value_length += sizeof(header) + string_bytes; +} + +PacketDeserialiser::PacketDeserialiser(const void *serialised_packet, size_t packet_size) +{ + header = (const TLVChunk*)(serialised_packet); + + if(packet_size < sizeof(TLVChunk) || packet_size < sizeof(TLVChunk) + header->value_length) + { + throw Error::Incomplete(); + } + + const unsigned char *at = header->value; + size_t value_remain = header->value_length; + + while(value_remain > 0) + { + const TLVChunk *field = (TLVChunk*)(at); + + if(value_remain < sizeof(TLVChunk) || value_remain < sizeof(TLVChunk) + field->value_length) + { + throw Error::Malformed(); + } + + fields.push_back(field); + + at += sizeof(TLVChunk) + field->value_length; + value_remain -= sizeof(TLVChunk) + field->value_length; + } +} + +uint32_t PacketDeserialiser::packet_type() +{ + return header->type; +} + +size_t PacketDeserialiser::num_fields() +{ + return fields.size(); +} + +bool PacketDeserialiser::is_null(size_t index) +{ + if(fields.size() <= index) + { + throw Error::MissingField(); + } + + return (fields[index]->type == FIELD_TYPE_NULL); +} + +DWORD PacketDeserialiser::get_dword(size_t index) +{ + if(fields.size() <= index) + { + throw Error::MissingField(); + } + + if(fields[index]->type != FIELD_TYPE_DWORD) + { + throw Error::TypeMismatch(); + } + + if(fields[index]->value_length != sizeof(DWORD)) + { + throw Error::Malformed(); + } + + return *(DWORD*)(fields[index]->value); +} + +std::pair PacketDeserialiser::get_data(size_t index) +{ + if(fields.size() <= index) + { + throw Error::MissingField(); + } + + if(fields[index]->type != FIELD_TYPE_DATA) + { + throw Error::TypeMismatch(); + } + + return std::make_pair((const void*)(fields[index]->value), (size_t)(fields[index]->value_length)); +} + +std::wstring PacketDeserialiser::get_wstring(size_t index) +{ + if(fields.size() <= index) + { + throw Error::MissingField(); + } + + if(fields[index]->type != FIELD_TYPE_WSTRING) + { + throw Error::TypeMismatch(); + } + + if((fields[index]->value_length % sizeof(wchar_t)) != 0) + { + throw Error::Malformed(); + } + + return std::wstring((const wchar_t*)(fields[index]->value), (fields[index]->value_length / sizeof(wchar_t))); +} diff --git a/src/packet.hpp b/src/packet.hpp new file mode 100644 index 0000000..8daec2a --- /dev/null +++ b/src/packet.hpp @@ -0,0 +1,89 @@ +#ifndef DPLITE_PACKET_HPP +#define DPLITE_PACKET_HPP + +#include +#include +#include +#include +#include +#include +#include + +struct TLVChunk +{ + uint32_t type; + uint32_t value_length; + unsigned char value[0]; +}; + +class PacketSerialiser +{ + private: + std::vector sbuf; + + public: + PacketSerialiser(uint32_t type); + + std::pair raw_packet(); + + void append_null(); + void append_dword(DWORD value); + void append_data(const void *data, size_t size); + void append_wstring(const std::wstring &string); +}; + +class PacketDeserialiser +{ + private: + const TLVChunk *header; + std::vector fields; + + public: + class Error: public std::runtime_error + { + protected: + Error(const std::string &what): runtime_error(what) {} + + public: + class Incomplete; + class Malformed; + class MissingField; + class TypeMismatch; + }; + + PacketDeserialiser(const void *serialised_packet, size_t packet_size); + + uint32_t packet_type(); + size_t num_fields(); + + bool is_null(size_t index); + DWORD get_dword(size_t index); + std::pair get_data(size_t index); + std::wstring get_wstring(size_t index); +}; + +class PacketDeserialiser::Error::Incomplete: public Error +{ + public: + Incomplete(const std::string &what = "Incomplete packet"): Error(what) {} +}; + +class PacketDeserialiser::Error::Malformed: public Error +{ + public: + Malformed(const std::string &what = "Malformed packet"): Error(what) {} +}; + +class PacketDeserialiser::Error::MissingField: public Error +{ + public: + MissingField(const std::string &what = "Missing field in packet"): Error(what) {} +}; + +class PacketDeserialiser::Error::TypeMismatch: public Error +{ + public: + TypeMismatch(const std::string &what = "Incorrect field type in packet"): Error(what) {} +}; + +#endif /* !DPLITE_PACKET_HPP */ diff --git a/tests/PacketDeserialiser.cpp b/tests/PacketDeserialiser.cpp new file mode 100644 index 0000000..754d55d --- /dev/null +++ b/tests/PacketDeserialiser.cpp @@ -0,0 +1,560 @@ +#include + +#include "../src/packet.hpp" + +class PacketDeserialiserTest: public ::testing::Test { + protected: + PacketDeserialiser *pd; + + PacketDeserialiserTest(): pd(NULL) {} + + virtual ~PacketDeserialiserTest() + { + delete pd; + } +}; + +class PacketDeserialiserEmpty: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserEmpty, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(1)); +} + +TEST_F(PacketDeserialiserEmpty, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(0)); +} + +TEST_F(PacketDeserialiserEmpty, IsNull) +{ + EXPECT_THROW({ pd->is_null(0); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserEmpty, GetDWORD) +{ + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserEmpty, GetData) +{ + EXPECT_THROW({ pd->get_data(0); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserEmpty, GetWString) +{ + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::MissingField); +} + +class PacketDeserialiserNull: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x02, 0x00, 0x00, 0x00, /* type */ + 0x08, 0x00, 0x00, 0x00, /* value_length */ + + 0x00, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserNull, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(2)); +} + +TEST_F(PacketDeserialiserNull, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(1)); +} + +TEST_F(PacketDeserialiserNull, IsNull) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(0), true); }); + EXPECT_THROW({ pd->is_null(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNull, GetDWORD) +{ + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_dword(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNull, GetData) +{ + EXPECT_THROW({ pd->get_data(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNull, GetWString) +{ + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_wstring(1); }, PacketDeserialiser::Error::MissingField); +} + +class PacketDeserialiserDWORD: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x03, 0x00, 0x00, 0x00, /* type */ + 0x0C, 0x00, 0x00, 0x00, /* value_length */ + + 0x01, 0x00, 0x00, 0x00, /* type */ + 0x04, 0x00, 0x00, 0x00, /* value_length */ + 0x01, 0x23, 0x45, 0x67, /* value */ + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserDWORD, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(3)); +} + +TEST_F(PacketDeserialiserDWORD, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(1)); +} + +TEST_F(PacketDeserialiserDWORD, IsNull) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(0), false); }); + EXPECT_THROW({ pd->is_null(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserDWORD, GetDWORD) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->get_dword(0), (DWORD)(0x67452301)); }); + EXPECT_THROW({ pd->get_dword(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserDWORD, GetData) +{ + EXPECT_THROW({ pd->get_data(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserDWORD, GetWString) +{ + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_wstring(1); }, PacketDeserialiser::Error::MissingField); +} + +class PacketDeserialiserData: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x04, 0x00, 0x00, 0x00, /* type */ + 0x0E, 0x00, 0x00, 0x00, /* value_length */ + + 0x02, 0x00, 0x00, 0x00, /* type */ + 0x06, 0x00, 0x00, 0x00, /* value_length */ + 0xFE, 0xED, 0xBE, 0xEF, /* value */ + 0xAA, 0xAA, + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserData, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(4)); +} + +TEST_F(PacketDeserialiserData, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(1)); +} + +TEST_F(PacketDeserialiserData, IsNull) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(0), false); }); + EXPECT_THROW({ pd->is_null(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserData, GetDWORD) +{ + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_dword(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserData, GetData) +{ + const unsigned char EXPECT[] = { 0xFE, 0xED, 0xBE, 0xEF, 0xAA, 0xAA }; + std::pair got; + + ASSERT_NO_THROW({ got = pd->get_data(0); }); + + std::vector got_data ((const unsigned char*)(got.first), (const unsigned char*)(got.first) + got.second); + std::vector expect_data(EXPECT, EXPECT + sizeof(EXPECT)); + + EXPECT_EQ(got_data, expect_data); + + EXPECT_THROW({ pd->get_data(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserData, GetWString) +{ + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_wstring(1); }, PacketDeserialiser::Error::MissingField); +} + +class PacketDeserialiserWString: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x05, 0x00, 0x00, 0x00, /* type */ + 0x12, 0x00, 0x00, 0x00, /* value_length */ + + 0x03, 0x00, 0x00, 0x00, /* type */ + 0x0A, 0x00, 0x00, 0x00, /* value_length */ + 0x48, 0x00, 0x65, 0x00, /* value */ + 0x6C, 0x00, 0x6C, 0x00, + 0x6F, 0x00, + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserWString, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(5)); +} + +TEST_F(PacketDeserialiserWString, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(1)); +} + +TEST_F(PacketDeserialiserWString, IsNull) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(0), false); }); + EXPECT_THROW({ pd->is_null(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserWString, GetDWORD) +{ + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_dword(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserWString, GetData) +{ + EXPECT_THROW({ pd->get_data(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(1); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserWString, GetWString) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->get_wstring(0), L"Hello"); }); + EXPECT_THROW({ pd->get_wstring(1); }, PacketDeserialiser::Error::MissingField); +} + +class PacketDeserialiserNullDWORDDataWString: public PacketDeserialiserTest { + protected: + virtual void SetUp() override + { + static const unsigned char RAW[] = { + 0x34, 0x12, 0x00, 0x00, /* type */ + 0x31, 0x00, 0x00, 0x00, /* value_length (49) */ + + 0x00, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + + 0x01, 0x00, 0x00, 0x00, /* type */ + 0x04, 0x00, 0x00, 0x00, /* value_length */ + 0xFE, 0xED, 0x00, 0x00, /* value */ + + 0x02, 0x00, 0x00, 0x00, /* type */ + 0x05, 0x00, 0x00, 0x00, /* value_length */ + 0x01, 0x23, 0x45, 0x67, /* value */ + 0x89, + + 0x03, 0x00, 0x00, 0x00, /* type */ + 0x08, 0x00, 0x00, 0x00, /* value_length */ + 0x57, 0x00, 0x53, 0x00, /* value */ + 0x74, 0x00, 0x72, 0x00, + }; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + } +}; + +TEST_F(PacketDeserialiserNullDWORDDataWString, Type) +{ + EXPECT_EQ(pd->packet_type(), (uint32_t)(0x1234)); +} + +TEST_F(PacketDeserialiserNullDWORDDataWString, NumFields) +{ + EXPECT_EQ(pd->num_fields(), (size_t)(4)); +} + +TEST_F(PacketDeserialiserNullDWORDDataWString, IsNull) +{ + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(0), true); }); + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(1), false); }); + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(2), false); }); + EXPECT_NO_THROW({ EXPECT_EQ(pd->is_null(3), false); }); + + EXPECT_THROW({ pd->is_null(4); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNullDWORDDataWString, GetDWORD) +{ + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_NO_THROW({ EXPECT_EQ(pd->get_dword(1), (DWORD)(0xEDFE)); }); + EXPECT_THROW({ pd->get_dword(2); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_dword(3); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_dword(4); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNullDWORDDataWString, GetData) +{ + const unsigned char EXPECT[] = { 0x01, 0x23, 0x45, 0x67, 0x89 }; + std::pair got; + + ASSERT_NO_THROW({ got = pd->get_data(2); }); + + std::vector got_data ((const unsigned char*)(got.first), (const unsigned char*)(got.first) + got.second); + std::vector expect_data(EXPECT, EXPECT + sizeof(EXPECT)); + + EXPECT_EQ(got_data, expect_data); + + EXPECT_THROW({ pd->get_data(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(1); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(3); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_data(4); }, PacketDeserialiser::Error::MissingField); +} + +TEST_F(PacketDeserialiserNullDWORDDataWString, GetWString) +{ + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_wstring(1); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_THROW({ pd->get_wstring(2); }, PacketDeserialiser::Error::TypeMismatch); + EXPECT_NO_THROW({ EXPECT_EQ(pd->get_wstring(3), L"WStr"); }); + EXPECT_THROW({ pd->get_wstring(4); }, PacketDeserialiser::Error::MissingField); +} + +TEST(PacketDeserialiser, NoData) +{ + EXPECT_THROW({ PacketDeserialiser p(NULL, 0); }, PacketDeserialiser::Error::Incomplete); +} + +TEST(PacketDeserialiser, PartialHeader) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + }; + + EXPECT_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }, PacketDeserialiser::Error::Incomplete); +} + +TEST(PacketDeserialiser, PartialData) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, + }; + + EXPECT_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }, PacketDeserialiser::Error::Incomplete); +} + +TEST(PacketDeserialiser, ExtraData) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00 + }; + + EXPECT_NO_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }); +} + +TEST(PacketDeserialiser, FieldShortHeader) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + }; + + EXPECT_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }, PacketDeserialiser::Error::Malformed); +} + +TEST(PacketDeserialiser, FieldTooShort) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x00, + }; + + EXPECT_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }, PacketDeserialiser::Error::Malformed); +} + +TEST(PacketDeserialiser, FieldTooLong) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + + 0x00, + }; + + EXPECT_THROW({ PacketDeserialiser p(RAW, sizeof(RAW)); }, PacketDeserialiser::Error::Malformed); +} + +TEST(PacketDeserialiser, ZeroLengthDWORD) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::Malformed); + + delete pd; +} + +TEST(PacketDeserialiser, UndersizeDWORD) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x00, 0x00, + + 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::Malformed); + + delete pd; +} + +TEST(PacketDeserialiser, OversizeDWORD) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0x00, 0x00, + + 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + EXPECT_THROW({ pd->get_dword(0); }, PacketDeserialiser::Error::Malformed); + + delete pd; +} + +TEST(PacketDeserialiser, ZeroLengthData) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + + EXPECT_NO_THROW({ + auto data = pd->get_data(0); + EXPECT_EQ(data.second, (size_t)(0)); + }); + + delete pd; +} + +TEST(PacketDeserialiser, ZeroLengthWString) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + EXPECT_NO_THROW({ EXPECT_EQ(pd->get_wstring(0), L""); }); + + delete pd; +} + +TEST(PacketDeserialiser, OneByteWString) +{ + const unsigned char RAW[] = { + 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + }; + + PacketDeserialiser *pd = NULL; + + ASSERT_NO_THROW({ pd = new PacketDeserialiser(RAW, sizeof(RAW)); }); + EXPECT_THROW({ pd->get_wstring(0); }, PacketDeserialiser::Error::Malformed); + + delete pd; +} diff --git a/tests/PacketSerialiser.cpp b/tests/PacketSerialiser.cpp new file mode 100644 index 0000000..9041dc3 --- /dev/null +++ b/tests/PacketSerialiser.cpp @@ -0,0 +1,167 @@ +#include + +#include "../src/packet.hpp" + +TEST(PacketSerialiser, Empty) +{ + PacketSerialiser p(0xAA); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0xAA, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +} + +TEST(PacketSerialiser, Null) +{ + PacketSerialiser p(0xBB); + p.append_null(); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0xBB, 0x00, 0x00, 0x00, /* type */ + 0x08, 0x00, 0x00, 0x00, /* value_length */ + + 0x00, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +} + +TEST(PacketSerialiser, DWORD) +{ + PacketSerialiser p(0xAABBCCDD); + p.append_dword(0xFFEEDDCC); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0xDD, 0xCC, 0xBB, 0xAA, /* type */ + 0x0C, 0x00, 0x00, 0x00, /* value_length */ + + 0x01, 0x00, 0x00, 0x00, /* type */ + 0x04, 0x00, 0x00, 0x00, /* value_length */ + 0xCC, 0xDD, 0xEE, 0xFF, /* value */ + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +} + +TEST(PacketSerialiser, Data) +{ + PacketSerialiser p(0x1234); + + const unsigned char DATA[] = { + 0x01, 0x23, 0x45, 0x67, + 0x89, 0xAB, 0xCD, 0xEF, + }; + + p.append_data(DATA, sizeof(DATA)); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0x34, 0x12, 0x00, 0x00, /* type */ + 0x10, 0x00, 0x00, 0x00, /* value_length */ + + 0x02, 0x00, 0x00, 0x00, /* type */ + 0x08, 0x00, 0x00, 0x00, /* value_length */ + 0x01, 0x23, 0x45, 0x67, /* value */ + 0x89, 0xAB, 0xCD, 0xEF, + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +} + +TEST(PacketSerialiser, WString) +{ + PacketSerialiser p(0x1234); + + p.append_wstring(L"Hello, I'm Gabe Newell"); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0x34, 0x12, 0x00, 0x00, /* type */ + 0x34, 0x00, 0x00, 0x00, /* value_length (52) */ + + 0x03, 0x00, 0x00, 0x00, /* type */ + 0x2C, 0x00, 0x00, 0x00, /* value_length (44) */ + 0x48, 0x00, 0x65, 0x00, /* value */ + 0x6C, 0x00, 0x6C, 0x00, + 0x6F, 0x00, 0x2C, 0x00, + 0x20, 0x00, 0x49, 0x00, + 0x27, 0x00, 0x6D, 0x00, + 0x20, 0x00, 0x47, 0x00, + 0x61, 0x00, 0x62, 0x00, + 0x65, 0x00, 0x20, 0x00, + 0x4E, 0x00, 0x65, 0x00, + 0x77, 0x00, 0x65, 0x00, + 0x6C, 0x00, 0x6C, 0x00 + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +} + +TEST(PacketSerialiser, NullDWORDDataWString) +{ + PacketSerialiser p(0x1234); + + p.append_null(); + p.append_dword(0xEDFE); + + const unsigned char DATA[] = { 0x01, 0x23, 0x45, 0x67, 0x89 }; + p.append_data(DATA, sizeof(DATA)); + + p.append_wstring(L"WStr"); + + std::pair raw = p.raw_packet(); + + const unsigned char EXPECT[] = { + 0x34, 0x12, 0x00, 0x00, /* type */ + 0x31, 0x00, 0x00, 0x00, /* value_length (49) */ + + 0x00, 0x00, 0x00, 0x00, /* type */ + 0x00, 0x00, 0x00, 0x00, /* value_length */ + + 0x01, 0x00, 0x00, 0x00, /* type */ + 0x04, 0x00, 0x00, 0x00, /* value_length */ + 0xFE, 0xED, 0x00, 0x00, /* value */ + + 0x02, 0x00, 0x00, 0x00, /* type */ + 0x05, 0x00, 0x00, 0x00, /* value_length */ + 0x01, 0x23, 0x45, 0x67, /* value */ + 0x89, + + 0x03, 0x00, 0x00, 0x00, /* type */ + 0x08, 0x00, 0x00, 0x00, /* value_length */ + 0x57, 0x00, 0x53, 0x00, /* value */ + 0x74, 0x00, 0x72, 0x00, + }; + + std::vector got((unsigned char*)(raw.first), (unsigned char*)(raw.first) + raw.second); + std::vector expect(EXPECT, EXPECT + sizeof(EXPECT)); + + ASSERT_EQ(got, expect); +}