Navigation Menu

EmberEmu / **Hexi ** Public

Header-only,轻量级的 C++ 库,用于二进制数据流处理。网络数据处理变得简单易用!

License

发现 Apache-2.0, MIT 许可

Licenses found

Apache-2.0 LICENSE MIT LICENSE-MIT 65 stars 2 forks Branches Tags Activity

EmberEmu/Hexi

master BranchesTags

Folders and files

Name | Name | Last commit message | Last commit date ---|---|---|--- .github/workflows| .github/workflows docs| docs include| include single_include| single_include tests| tests tools/amalgamate| tools/amalgamate .editorconfig| .editorconfig .gitignore| .gitignore CMakeLists.txt| CMakeLists.txt LICENSE| LICENSE LICENSE-MIT| LICENSE-MIT README.md| README.md

Latest commit

History

82 Commits

Hexi, Easy Peasy Binary Streaming

Hexi 是一个轻量级的、header-only 的 C++23 库,用于安全地处理来自任意来源的二进制数据(但主要是网络数据)。它介于手动从网络缓冲区 memcpy 字节和成熟的序列化库之间。

设计目标是易于使用,在处理不受信任的数据时保证安全,具有合理的灵活性,并将开销保持在最低限度。

Hexi 不提供:版本控制、不同格式之间的转换、基于文本格式的处理、以及卸载洗碗机。

Getting started

将 Hexi 集成到你的项目中很简单!最简单的方法是直接从 single_include 中复制 hexi.h 到你自己的项目中。如果你只想包含你使用的部分,你可以将 include 添加到你的 include 路径中,或者使用 target_link_library 将其集成到你自己的 CMake 项目中。要构建单元测试,请使用 ENABLE_TESTING 运行 CMake。

以下是一些库可能称之为非常简单的动机示例:

#include <hexi.h>
#include <array>
#include <vector>
#include <cstddef>

struct UserPacket {
  uint64_t user_id;
  uint64_t timestamp;
  std::array<uint8_t, 16> ipv6;
};

auto deserialise(std::span<const char> network_buffer) {
  hexi::buffer_adaptor adaptor(network_buffer); // wrap the buffer
  hexi::binary_stream stream(adaptor);     // create a binary stream
  
  // deserialise!
  UserPacket packet;
  stream >> packet;
  return packet;
}

auto serialise(const UserPacket& packet) {
  std::vector<uint8_t> buffer;
  hexi::buffer_adaptor adaptor(buffer); // wrap the buffer
  hexi::binary_stream stream(adaptor); // create a binary stream
  
  // serialise!
  stream << packet;
  return buffer;
}

默认情况下,如果满足直接复制字节的安全要求,Hexi 将尝试序列化基本结构,例如我们的 UserPacket。 但是,出于可移植性的考虑,除非你确定数据布局在写入数据的系统上是相同的,否则不建议这样做。 不用担心,这很容易解决。 另外,我们没有做任何错误处理。 一切都会水到渠成。

Remember these two classes, if nothing else!

你主要处理的两个类是 buffer_adaptorbinary_stream

binary_stream 接受一个容器作为其参数,并用于进行读取和写入。 它对底层容器的细节知之甚少。

为了支持未编写用于 Hexi 的容器,buffer_adaptor 用作 binary_stream 可以与之交互的包装器。 与 binary_stream 一样,它也提供读写操作,但级别较低。

buffer_adaptor 可以包装任何提供 datasize 成员函数的连续容器或视图,并且可以选择提供 resize() 用于写入支持。 从标准库中,这意味着以下内容可以直接使用:

只要它们提供大致相似的 API,许多非标准库容器也可以直接使用。

容器的值类型必须是字节类型(例如 charstd::byteuint8_t)。 如果这造成问题,可以使用 std::as_bytes 作为解决方法。

Hexi 支持自定义容器,包括非连续容器。 事实上,库中包含一个非连续容器。 你只需提供一些函数,例如 readsize,以允许 binary_stream 类能够使用它。

static_buffer.h 提供了一个可以直接与 binary_stream 一起使用的自定义容器的简单示例。

正如所提到的,Hexi 旨在即使在处理不受信任的数据时也能安全使用。 例如,可能存在被操纵的网络消息,试图欺骗你的代码读取越界。

binary_stream 执行边界检查以确保它永远不会读取超过缓冲区可用数据量的数据,并且可以选择允许你指定要读取的数据量的上限。 当你在缓冲区中有多个消息并且想要限制反序列化可能侵入下一个消息时,这非常有用。

buffer_t buffer;
// ... read data
hexi::binary_stream stream(buffer, 32); // will never read more than 32 bytes

Errors happen, it's up to you to handle 'em

默认的错误处理机制是异常。 遇到读取数据的问题时,将抛出一个从 hexi::exception 派生的异常。 这些是:

可以通过指定 no_throw 作为模板参数来禁用 binary_stream 中的异常,如下所示:

hexi::binary_stream<buf_type, hexi::no_throw> stream(...);

虽然这可以防止 binary_stream 本身抛出异常,但它并不能阻止异常从较低级别传播。 例如,如果写入时分配失败,包装的 std::vector 仍然可能抛出 std::bad_alloc

无论你使用哪种错误处理机制,都可以按如下方式检查 binary_stream 的状态:

hexi::binary_stream<buf_type, hexi::no_throw> stream(...);
// ... assume an error happens
// simplest way to check whether any errors have occurred
if (!stream) {
  // handle error
}
// or we can get the state
if (auto state = stream.state(); state != hexi::stream_state::ok) {
  // handle error
}

Writing portable code is easy peasy

在第一个示例中,只有当写入数据的程序以与我们自己的程序相同的方式布局所有内容时,读取我们的 UserPacket 才能按预期工作。 由于架构差异、编译器标志等原因,情况可能并非如此。

以下是相同的示例,但以可移植的方式进行操作。

#include <hexi.h>
#include <span>
#include <string>
#include <vector>
#include <cstddef>
#include <cstdint>

struct UserPacket {
  uint64_t user_id;
  std::string username;
  uint64_t timestamp;
  uint8_t has_optional_field;
  uint32_t optional_field; // pretend this is big endian in the protocol

  // deserialise
  auto& operator>>(auto& stream) {
    stream >> user_id >> username >> timestamp >> has_optional_field;
    if (has_optional_field) {
      stream >> optional_field;
      hexi::endian::big_to_native_inplace(optional_field);
    }
    // we can manually trigger an error if something went wrong
    // stream.set_error_state();
    return stream;
  }

  // serialise
  auto& operator<<(auto& stream) const {
    stream << user_id << username << timestamp << has_optional_field;
    if (has_optional_field) {
      stream << hexi::endian::native_to_big(optional_field);
    }
    return stream;
  }
};

// pretend we're reading network data
void read() {
  std::vector<char> buffer;
  const auto bytes_read = socket.read(buffer);
  // ... logic for determing packet type, etc
  bool result {};
  switch (packet_type) {
    case packet_type::user_packet:
      result = handle_user_packet(buffer);
      break;
  }
  // ... handle result
}

auto handle_user_packet(std::span<const char> buffer) {
  hexi::buffer_adaptor adaptor(buffer);
  hexi::binary_stream stream(adaptor);
  UserPacket packet;
  stream >> packet;
  if (stream) {
    // ... do something with the packet
    return true;
  } else {
    return false;
  }
}

由于 binary_stream 是一个模板,因此最简单的方法是允许编译器执行类型推导。

如果你希望函数体位于源文件中,建议你为你自己的 binary_stream 类型提供你自己的 using 别名。 另一种方法是使用多态等效项 pmc::buffer_adaptorpmc::binary_stream,它们允许你在运行时更改底层缓冲区类型,但代价是虚拟调用开销,并且缺少一些与多态性不太一致的功能。

如何构建你的代码取决于你,这只是一种方法。

Uh, one more thing...

使用 binary_stream 时,字符串始终被视为 null 结尾。 写入 char*std::string_viewstd::string 始终会将终止字节写入流。 如果你需要其他操作,请使用其中一个 put 函数。

同样,读取到 std::string 假定缓冲区包含 null 终止符。 如果不是,则将返回一个空字符串。 如果你知道字符串的长度或者需要支持自定义终止/标记值,请使用 get()find_first_of()

What else is in the box?

以下是一些包含的额外功能的快速概述。

Before we wrap up, look at these tidbits...

我们已经到了概述的结尾,但如果你决定尝试 Hexi,还有更多内容需要发现。 以下是一些美味的食物:

要了解更多信息,请查看 docs/examples 中的示例!

Thanks for listening! Now go unload the dis[C Make Lists](include/CMakeLists.txt)hwasher.

About

Header-only,轻量级的 C++ 库,用于二进制数据流处理。网络数据处理变得简单易用!

Topics

serialization header-only buffer-management serialization-library serialisation binary-stream

Resources

Readme

License

发现 Apache-2.0, MIT 许可

Licenses found

Apache-2.0 LICENSE MIT LICENSE-MIT Activity Custom properties

Stars

65 stars

Watchers

2 watching

Forks

2 forks Report repository

Releases

3 tags

Packages 0

No packages published

Contributors 2

Languages

Footer

GitHub © 2025 GitHub, Inc.

Footer navigation