Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
protobuf_wire.h
Go to the documentation of this file.
1#pragma once
2
3// BSD 3-Clause License
4// Copyright (c) 2025
5// See the LICENSE file in the project root for full license information.
6
20#include <cstddef>
21#include <cstdint>
22#include <cstring>
23#include <optional>
24#include <string>
25#include <vector>
26
27namespace kcenon { namespace monitoring { namespace protobuf_wire {
28
29enum class wire_type : std::uint8_t {
30 varint = 0,
31 fixed64 = 1,
33 fixed32 = 5
34};
35
39inline void encode_varint(std::vector<std::uint8_t>& out, std::uint64_t value) {
40 while (value >= 0x80) {
41 out.push_back(static_cast<std::uint8_t>((value & 0x7F) | 0x80));
42 value >>= 7;
43 }
44 out.push_back(static_cast<std::uint8_t>(value));
45}
46
50inline void encode_tag(std::vector<std::uint8_t>& out,
51 std::uint32_t field_number,
52 wire_type wt) {
53 encode_varint(out,
54 (static_cast<std::uint64_t>(field_number) << 3) |
55 static_cast<std::uint64_t>(wt));
56}
57
61inline void encode_fixed64(std::vector<std::uint8_t>& out, std::uint64_t value) {
62 for (int i = 0; i < 8; ++i) {
63 out.push_back(static_cast<std::uint8_t>((value >> (i * 8)) & 0xFF));
64 }
65}
66
70inline void encode_length_delimited(std::vector<std::uint8_t>& out,
71 const std::uint8_t* data,
72 std::size_t size) {
73 encode_varint(out, static_cast<std::uint64_t>(size));
74 out.insert(out.end(), data, data + size);
75}
76
78inline void encode_string_field(std::vector<std::uint8_t>& out,
79 std::uint32_t field_number,
80 const std::string& value) {
81 if (value.empty()) {
82 return; // proto3 defaults: skip empty string
83 }
84 encode_tag(out, field_number, wire_type::length_delimited);
86 out,
87 reinterpret_cast<const std::uint8_t*>(value.data()),
88 value.size());
89}
90
92inline void encode_bytes_field(std::vector<std::uint8_t>& out,
93 std::uint32_t field_number,
94 const std::vector<std::uint8_t>& value) {
95 if (value.empty()) {
96 return; // proto3 defaults: skip empty bytes
97 }
98 encode_tag(out, field_number, wire_type::length_delimited);
99 encode_length_delimited(out, value.data(), value.size());
100}
101
103inline void encode_uint64_field(std::vector<std::uint8_t>& out,
104 std::uint32_t field_number,
105 std::uint64_t value) {
106 if (value == 0) {
107 return;
108 }
109 encode_tag(out, field_number, wire_type::varint);
110 encode_varint(out, value);
111}
112
114inline void encode_uint64_field_always(std::vector<std::uint8_t>& out,
115 std::uint32_t field_number,
116 std::uint64_t value) {
117 encode_tag(out, field_number, wire_type::varint);
118 encode_varint(out, value);
119}
120
122inline void encode_enum_field(std::vector<std::uint8_t>& out,
123 std::uint32_t field_number,
124 std::int32_t value) {
125 if (value == 0) {
126 return;
127 }
128 encode_tag(out, field_number, wire_type::varint);
129 encode_varint(out, static_cast<std::uint64_t>(value));
130}
131
133inline void encode_bool_field(std::vector<std::uint8_t>& out,
134 std::uint32_t field_number,
135 bool value) {
136 if (!value) {
137 return;
138 }
139 encode_tag(out, field_number, wire_type::varint);
140 encode_varint(out, 1);
141}
142
144inline void encode_fixed64_field(std::vector<std::uint8_t>& out,
145 std::uint32_t field_number,
146 std::uint64_t value) {
147 if (value == 0) {
148 return;
149 }
150 encode_tag(out, field_number, wire_type::fixed64);
151 encode_fixed64(out, value);
152}
153
155inline void encode_message_field(std::vector<std::uint8_t>& out,
156 std::uint32_t field_number,
157 const std::vector<std::uint8_t>& serialized) {
158 encode_tag(out, field_number, wire_type::length_delimited);
159 encode_length_delimited(out, serialized.data(), serialized.size());
160}
161
162// ---------------------------------------------------------------------------
163// Decoder
164// ---------------------------------------------------------------------------
165
169class reader {
170public:
171 reader(const std::uint8_t* data, std::size_t size)
172 : data_(data), size_(size), pos_(0) {}
173
174 bool eof() const { return pos_ >= size_; }
175 std::size_t position() const { return pos_; }
176 std::size_t size() const { return size_; }
177
178 std::optional<std::uint64_t> read_varint() {
179 std::uint64_t result = 0;
180 int shift = 0;
181 while (pos_ < size_) {
182 std::uint8_t byte = data_[pos_++];
183 result |= static_cast<std::uint64_t>(byte & 0x7F) << shift;
184 if ((byte & 0x80) == 0) {
185 return result;
186 }
187 shift += 7;
188 if (shift > 63) {
189 return std::nullopt;
190 }
191 }
192 return std::nullopt;
193 }
194
195 std::optional<std::uint64_t> read_fixed64() {
196 if (pos_ + 8 > size_) {
197 return std::nullopt;
198 }
199 std::uint64_t result = 0;
200 for (int i = 0; i < 8; ++i) {
201 result |= static_cast<std::uint64_t>(data_[pos_++]) << (i * 8);
202 }
203 return result;
204 }
205
206 std::optional<std::uint32_t> read_fixed32() {
207 if (pos_ + 4 > size_) {
208 return std::nullopt;
209 }
210 std::uint32_t result = 0;
211 for (int i = 0; i < 4; ++i) {
212 result |= static_cast<std::uint32_t>(data_[pos_++]) << (i * 8);
213 }
214 return result;
215 }
216
222 bool read_length_delimited(const std::uint8_t** out_ptr,
223 std::size_t* out_len) {
224 auto len = read_varint();
225 if (!len) return false;
226 if (pos_ + *len > size_) return false;
227 *out_ptr = data_ + pos_;
228 *out_len = static_cast<std::size_t>(*len);
229 pos_ += *len;
230 return true;
231 }
232
233 bool read_string(std::string& out) {
234 const std::uint8_t* ptr = nullptr;
235 std::size_t len = 0;
236 if (!read_length_delimited(&ptr, &len)) return false;
237 out.assign(reinterpret_cast<const char*>(ptr), len);
238 return true;
239 }
240
241 bool read_bytes(std::vector<std::uint8_t>& out) {
242 const std::uint8_t* ptr = nullptr;
243 std::size_t len = 0;
244 if (!read_length_delimited(&ptr, &len)) return false;
245 out.assign(ptr, ptr + len);
246 return true;
247 }
248
251 switch (wt) {
253 return read_varint().has_value();
255 return read_fixed64().has_value();
257 const std::uint8_t* ptr;
258 std::size_t len;
259 return read_length_delimited(&ptr, &len);
260 }
262 return read_fixed32().has_value();
263 }
264 return false;
265 }
266
267private:
268 const std::uint8_t* data_;
269 std::size_t size_;
270 std::size_t pos_;
271};
272
276inline bool decode_tag(reader& r,
277 std::uint32_t& field_number,
278 wire_type& wt) {
279 auto tag = r.read_varint();
280 if (!tag) return false;
281 field_number = static_cast<std::uint32_t>(*tag >> 3);
282 wt = static_cast<wire_type>(*tag & 0x07);
283 return true;
284}
285
286// ---------------------------------------------------------------------------
287// Hex helpers for Jaeger/Zipkin trace and span IDs
288// ---------------------------------------------------------------------------
289
294inline std::vector<std::uint8_t> hex_to_bytes(const std::string& hex) {
295 auto nibble = [](char c) -> int {
296 if (c >= '0' && c <= '9') return c - '0';
297 if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
298 if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
299 return -1;
300 };
301 std::string s = hex;
302 if (s.size() % 2 == 1) {
303 s.insert(s.begin(), '0');
304 }
305 std::vector<std::uint8_t> out;
306 out.reserve(s.size() / 2);
307 for (std::size_t i = 0; i < s.size(); i += 2) {
308 int hi = nibble(s[i]);
309 int lo = nibble(s[i + 1]);
310 if (hi < 0 || lo < 0) {
311 return {}; // invalid; caller treats empty as absent
312 }
313 out.push_back(static_cast<std::uint8_t>((hi << 4) | lo));
314 }
315 return out;
316}
317
321inline std::string bytes_to_hex(const std::vector<std::uint8_t>& bytes) {
322 static const char hex_chars[] = "0123456789abcdef";
323 std::string out;
324 out.reserve(bytes.size() * 2);
325 for (std::uint8_t b : bytes) {
326 out.push_back(hex_chars[(b >> 4) & 0x0F]);
327 out.push_back(hex_chars[b & 0x0F]);
328 }
329 return out;
330}
331
336inline std::vector<std::uint8_t> left_pad(const std::vector<std::uint8_t>& in,
337 std::size_t width) {
338 if (in.size() >= width) return in;
339 std::vector<std::uint8_t> out(width, 0);
340 std::memcpy(out.data() + (width - in.size()), in.data(), in.size());
341 return out;
342}
343
344}}} // namespace kcenon::monitoring::protobuf_wire
Minimal protobuf wire reader used for round-trip tests.
std::optional< std::uint64_t > read_fixed64()
std::optional< std::uint32_t > read_fixed32()
bool read_bytes(std::vector< std::uint8_t > &out)
std::optional< std::uint64_t > read_varint()
reader(const std::uint8_t *data, std::size_t size)
bool read_length_delimited(const std::uint8_t **out_ptr, std::size_t *out_len)
Read a length-delimited payload. Returns pointer into the underlying buffer and the length....
bool skip_field(wire_type wt)
Skip a field whose wire type is given.
void encode_fixed64_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::uint64_t value)
Encode a fixed64 field.
void encode_string_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::string &value)
Encode a string field.
void encode_bool_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, bool value)
Encode a bool field (proto3 skips false).
bool decode_tag(reader &r, std::uint32_t &field_number, wire_type &wt)
Decode a tag into (field_number, wire_type).
std::vector< std::uint8_t > left_pad(const std::vector< std::uint8_t > &in, std::size_t width)
Left-pad bytes to a target width. Used to normalize 8-byte trace IDs to Jaeger's 16-byte on-wire widt...
void encode_uint64_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::uint64_t value)
Encode a uint32 / uint64 / int64 varint field (skips zero).
void encode_enum_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::int32_t value)
Encode an enum field (always written when nonzero).
void encode_bytes_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::vector< std::uint8_t > &value)
Encode a bytes field.
std::string bytes_to_hex(const std::vector< std::uint8_t > &bytes)
Encode raw bytes as a lowercase hex string.
void encode_message_field(std::vector< std::uint8_t > &out, std::uint32_t field_number, const std::vector< std::uint8_t > &serialized)
Encode an embedded message field given its pre-serialized bytes.
void encode_tag(std::vector< std::uint8_t > &out, std::uint32_t field_number, wire_type wt)
Encode a tag (field_number << 3 | wire_type) as a varint.
void encode_varint(std::vector< std::uint8_t > &out, std::uint64_t value)
Encode an unsigned varint into the buffer.
std::vector< std::uint8_t > hex_to_bytes(const std::string &hex)
Decode a hexadecimal string into bytes. Odd-length strings are zero-padded on the left; non-hex chara...
void encode_uint64_field_always(std::vector< std::uint8_t > &out, std::uint32_t field_number, std::uint64_t value)
Encode a uint32 field that is not allowed to be skipped even if zero.
@ varint
int32/int64/uint32/uint64/sint*‍/bool/enum
@ length_delimited
string/bytes/embedded messages/packed repeated
void encode_fixed64(std::vector< std::uint8_t > &out, std::uint64_t value)
Encode a fixed64 little-endian value (used for Zipkin timestamps).
void encode_length_delimited(std::vector< std::uint8_t > &out, const std::uint8_t *data, std::size_t size)
Encode a length-delimited byte sequence.