Mads
Multi-Agent Distributed System
Loading...
Searching...
No Matches
wire_format.hpp
Go to the documentation of this file.
1/*
2Internal helper: encodes MADS's self-describing wire frame header (see
3agent.cpp's original comment, reproduced below) and the payload encoding it
4wraps. Extracted out of Agent so the broker can emit an ordinary MADS-format
5frame too (ZMQ_DEVELOPMENT.md §2.2, the live subscription table) without
6duplicating the format. Not part of the installed SDK (src/detail/ is
7excluded from the LIB_HEADERS install glob in CMakeLists.txt).
8
9Self-describing frame header (see REFACTOR.md §1.3 / MSGPACK.md §5.1).
10
11A message published in the *extended* format carries a small header part right
12after the topic:
13
14 [ topic ] [ header ] [ payload ] (data)
15 [ topic ] [ header ] [ meta ] [ raw bytes ] (blob, has_blob flag set)
16
17The header begins with the 4-byte magic "MADS" and has a fixed size, which
18makes it reliably distinguishable from legacy frames:
19
20 - legacy data: [ topic ] [ snappy(json) ] (2 parts)
21 - legacy blob: [ topic ] [ json meta ] [ raw bytes ] (3 parts)
22
23A reader first checks part #1 for the magic + exact size; if absent it falls
24back to the legacy part-count interpretation. Legacy peers never see a header
25because the header is only emitted for non-default (e.g. MsgPack) formats.
26*/
27#pragma once
28
29#include <cstdint>
30#include <cstring>
31#include <nlohmann/json.hpp>
32#include <snappy.h>
33#include <string>
34
35#include "../mads.hpp"
36
37namespace Mads::detail {
38
39constexpr char WIRE_MAGIC[4] = {'M', 'A', 'D', 'S'};
40constexpr uint8_t WIRE_HDR_VERSION = 1;
41constexpr uint8_t WIRE_FLAG_BLOB = 0x01;
42constexpr size_t WIRE_HEADER_SIZE = 4 /*magic*/ + 1 /*ver*/ + 1 /*format*/ +
43 1 /*compression*/ + 1 /*flags*/ +
44 4 /*schema*/;
45
46enum class Comp : uint8_t { None = 0, Snappy = 1 };
47
48struct WireHeader {
50 uint8_t format = static_cast<uint8_t>(WireFormat::Json);
51 uint8_t compression = static_cast<uint8_t>(Comp::None);
52 bool has_blob = false;
53 uint32_t schema = LIB_VERSION_NUM;
54};
55
56inline std::string make_wire_header(WireFormat fmt, Comp comp, bool has_blob) {
57 std::string h;
58 h.reserve(WIRE_HEADER_SIZE);
59 h.append(WIRE_MAGIC, 4);
60 h.push_back(static_cast<char>(WIRE_HDR_VERSION));
61 h.push_back(static_cast<char>(fmt));
62 h.push_back(static_cast<char>(comp));
63 h.push_back(static_cast<char>(has_blob ? WIRE_FLAG_BLOB : 0));
64 uint32_t schema = LIB_VERSION_NUM;
65 h.push_back(static_cast<char>((schema >> 24) & 0xFF));
66 h.push_back(static_cast<char>((schema >> 16) & 0xFF));
67 h.push_back(static_cast<char>((schema >> 8) & 0xFF));
68 h.push_back(static_cast<char>(schema & 0xFF));
69 return h;
70}
71
72// Returns true and fills `out` if `part` is a valid frame header.
73inline bool parse_wire_header(const std::string &part, WireHeader &out) {
74 if (part.size() != WIRE_HEADER_SIZE)
75 return false;
76 if (std::memcmp(part.data(), WIRE_MAGIC, 4) != 0)
77 return false;
78 out.hdr_version = static_cast<uint8_t>(part[4]);
79 out.format = static_cast<uint8_t>(part[5]);
80 out.compression = static_cast<uint8_t>(part[6]);
81 out.has_blob = (static_cast<uint8_t>(part[7]) & WIRE_FLAG_BLOB) != 0;
82 out.schema = (static_cast<uint32_t>(static_cast<uint8_t>(part[8])) << 24) |
83 (static_cast<uint32_t>(static_cast<uint8_t>(part[9])) << 16) |
84 (static_cast<uint32_t>(static_cast<uint8_t>(part[10])) << 8) |
85 static_cast<uint32_t>(static_cast<uint8_t>(part[11]));
86 return true;
87}
88
89// Resolve a compression policy to the concrete codec for a payload of the
90// given size. Compression::Auto compresses only at/above the threshold.
91inline Comp resolve_compression(Compression policy, size_t size) {
92 switch (policy) {
93 case Compression::None:
94 return Comp::None;
95 case Compression::Snappy:
96 return Comp::Snappy;
97 case Compression::Auto:
98 default:
99 return size >= COMPRESSION_AUTO_THRESHOLD ? Comp::Snappy : Comp::None;
100 }
101}
102
103// Encode a JSON object into the bytes for the given wire format.
104inline std::string encode_payload(const nlohmann::json &j, WireFormat fmt) {
105 if (fmt == WireFormat::MsgPack) {
106 auto v = nlohmann::json::to_msgpack(j);
107 return std::string(reinterpret_cast<const char *>(v.data()), v.size());
108 }
109 return j.dump();
110}
111
112// Materialise an encoded payload into JSON *text* (the representation the
113// rest of MADS expects). Returns false on any decompression/decoding
114// failure.
115inline bool decode_to_json_text(const std::string &raw, uint8_t format,
116 uint8_t comp, std::string &json_text_out) {
117 const std::string *bytes = &raw;
118 std::string uncompressed;
119 if (comp == static_cast<uint8_t>(Comp::Snappy)) {
120 if (!snappy::Uncompress(raw.data(), raw.size(), &uncompressed))
121 return false;
122 bytes = &uncompressed;
123 }
124 if (format == static_cast<uint8_t>(WireFormat::MsgPack)) {
125 try {
126 nlohmann::json j = nlohmann::json::from_msgpack(*bytes);
127 json_text_out = j.dump();
128 } catch (...) {
129 return false;
130 }
131 } else {
132 // Already JSON text (possibly after decompression).
133 json_text_out = *bytes;
134 }
135 return true;
136}
137
138} // namespace Mads::detail
constexpr char WIRE_MAGIC[4]
constexpr uint8_t WIRE_HDR_VERSION
std::string encode_payload(const nlohmann::json &j, WireFormat fmt)
constexpr size_t WIRE_HEADER_SIZE
Comp resolve_compression(Compression policy, size_t size)
bool decode_to_json_text(const std::string &raw, uint8_t format, uint8_t comp, std::string &json_text_out)
std::string make_wire_header(WireFormat fmt, Comp comp, bool has_blob)
constexpr uint8_t WIRE_FLAG_BLOB
bool parse_wire_header(const std::string &part, WireHeader &out)