86 """Generate C++ header with message types and serialization.
88 The file is written to output_dir/bcnp/message_types.h so that
89 users can add output_dir to their include path and use:
90 #include <bcnp/message_types.h>
92 bcnp_dir = output_dir /
"bcnp"
93 bcnp_dir.mkdir(parents=
True, exist_ok=
True)
94 output_file = bcnp_dir /
"message_types.h"
96 namespace = schema.get(
"namespace",
"bcnp")
97 version_parts = schema[
"version"].split(
".")
98 major = int(version_parts[0])
99 minor = int(version_parts[1])
103 lines.append(
"// AUTO-GENERATED by bcnp_codegen.py - DO NOT EDIT")
104 lines.append(f
"// Schema version: {schema['version']}")
105 lines.append(f
"// Schema hash: 0x{schema_hash:08X}")
106 lines.append(
"#pragma once")
108 lines.append(
"#include <algorithm>")
109 lines.append(
"#include <array>")
110 lines.append(
"#include <cstddef>")
111 lines.append(
"#include <cstdint>")
112 lines.append(
"#include <cmath>")
113 lines.append(
"#include <cstring>")
114 lines.append(
"#include <functional>")
115 lines.append(
"#include <limits>")
116 lines.append(
"#include <optional>")
117 lines.append(
"#include <variant>")
119 lines.append(f
"namespace {namespace} {{")
121 lines.append(
"// ============================================================================")
122 lines.append(
"// Protocol Constants")
123 lines.append(
"// ============================================================================")
125 lines.append(f
"constexpr uint8_t kProtocolMajorV3 = {major};")
126 lines.append(f
"constexpr uint8_t kProtocolMinorV3 = {minor};")
127 lines.append(f
"constexpr uint32_t kSchemaHash = 0x{schema_hash:08X}U;")
129 lines.append(
"// Handshake packet structure: \"BCNP\" (4 bytes) + schema hash (4 bytes)")
130 lines.append(
"constexpr std::size_t kHandshakeSize = 8;")
131 lines.append(
"constexpr std::array<uint8_t, 4> kHandshakeMagic = {{'B', 'C', 'N', 'P'}};")
133 lines.append(
"// V3 Header: Major(1) + Minor(1) + Flags(1) + MsgTypeId(2) + MsgCount(2) = 7 bytes")
134 lines.append(
"constexpr std::size_t kHeaderSizeV3 = 7;")
135 lines.append(
"constexpr std::size_t kHeaderMsgTypeIndex = 3;")
136 lines.append(
"constexpr std::size_t kHeaderMsgCountIndex = 5;")
140 lines.append(
"// ============================================================================")
141 lines.append(
"// Message Type IDs")
142 lines.append(
"// ============================================================================")
144 lines.append(
"enum class MessageTypeId : uint16_t {")
145 lines.append(
" Unknown = 0,")
146 for msg
in schema[
"messages"]:
147 lines.append(f
" {msg['name']} = {msg['id']},")
152 lines.append(
"// Message sizes (wire format, bytes)")
153 for msg
in schema[
"messages"]:
155 lines.append(f
"constexpr std::size_t k{msg['name']}Size = {size};")
159 lines.append(
"// ============================================================================")
160 lines.append(
"// Byte Order Utilities (Big-Endian Wire Format)")
161 lines.append(
"// ============================================================================")
163 lines.append(
"namespace detail {")
165 lines.append(
"inline uint16_t LoadU16(const uint8_t* p) {")
166 lines.append(
" return (uint16_t(p[0]) << 8) | uint16_t(p[1]);")
169 lines.append(
"inline uint32_t LoadU32(const uint8_t* p) {")
170 lines.append(
" return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) |")
171 lines.append(
" (uint32_t(p[2]) << 8) | uint32_t(p[3]);")
174 lines.append(
"inline int16_t LoadS16(const uint8_t* p) {")
175 lines.append(
" return static_cast<int16_t>(LoadU16(p));")
178 lines.append(
"inline int32_t LoadS32(const uint8_t* p) {")
179 lines.append(
" return static_cast<int32_t>(LoadU32(p));")
182 lines.append(
"inline void StoreU16(uint16_t v, uint8_t* p) {")
183 lines.append(
" p[0] = static_cast<uint8_t>((v >> 8) & 0xFF);")
184 lines.append(
" p[1] = static_cast<uint8_t>(v & 0xFF);")
187 lines.append(
"inline void StoreU32(uint32_t v, uint8_t* p) {")
188 lines.append(
" p[0] = static_cast<uint8_t>((v >> 24) & 0xFF);")
189 lines.append(
" p[1] = static_cast<uint8_t>((v >> 16) & 0xFF);")
190 lines.append(
" p[2] = static_cast<uint8_t>((v >> 8) & 0xFF);")
191 lines.append(
" p[3] = static_cast<uint8_t>(v & 0xFF);")
194 lines.append(
"inline void StoreS16(int16_t v, uint8_t* p) {")
195 lines.append(
" StoreU16(static_cast<uint16_t>(v), p);")
198 lines.append(
"inline void StoreS32(int32_t v, uint8_t* p) {")
199 lines.append(
" StoreU32(static_cast<uint32_t>(v), p);")
202 lines.append(
"inline int32_t QuantizeFloat(float value, float scale) {")
203 lines.append(
" const double scaled = static_cast<double>(value) * static_cast<double>(scale);")
204 lines.append(
" const double clamped = std::clamp(scaled,")
205 lines.append(
" static_cast<double>(std::numeric_limits<int32_t>::min()),")
206 lines.append(
" static_cast<double>(std::numeric_limits<int32_t>::max()));")
207 lines.append(
" return static_cast<int32_t>(std::llround(clamped));")
210 lines.append(
"inline float DequantizeFloat(int32_t fixed, float scale) {")
211 lines.append(
" return static_cast<float>(static_cast<double>(fixed) / static_cast<double>(scale));")
214 lines.append(
"} // namespace detail")
218 lines.append(
"// ============================================================================")
219 lines.append(
"// Message Structs")
220 lines.append(
"// ============================================================================")
223 for msg
in schema[
"messages"]:
224 desc = msg.get(
"description",
"")
225 lines.append(f
"/// {desc}")
226 lines.append(f
"struct {msg['name']} {{")
227 lines.append(f
" static constexpr MessageTypeId kTypeId = MessageTypeId::{msg['name']};")
228 lines.append(f
" static constexpr std::size_t kWireSize = k{msg['name']}Size;")
230 for field
in msg[
"fields"]:
231 cpp_type = TYPE_INFO[field[
"type"]][1]
232 if field[
"type"] ==
"float32":
234 field_desc = field.get(
"description",
"")
235 unit = field.get(
"unit",
"")
236 comment = f
" // {field_desc}" if field_desc
else ""
238 comment = f
" // {field_desc} ({unit})" if field_desc
else f
" // ({unit})"
239 lines.append(f
" {cpp_type} {field['name']}{{0}};{comment}")
243 lines.append(
" bool Encode(uint8_t* out, std::size_t capacity) const {")
244 lines.append(f
" if (capacity < kWireSize) return false;")
246 for field
in msg[
"fields"]:
247 ftype = field[
"type"]
248 fname = field[
"name"]
249 size = TYPE_INFO[ftype][0]
251 if ftype ==
"float32":
252 scale = field.get(
"scale", 10000)
253 lines.append(f
" if (!std::isfinite({fname})) return false;")
254 lines.append(f
" detail::StoreS32(detail::QuantizeFloat({fname}, {scale}.0f), &out[{offset}]);")
255 elif ftype ==
"int32":
256 lines.append(f
" detail::StoreS32({fname}, &out[{offset}]);")
257 elif ftype ==
"uint32":
258 lines.append(f
" detail::StoreU32({fname}, &out[{offset}]);")
259 elif ftype ==
"int16":
260 lines.append(f
" detail::StoreS16({fname}, &out[{offset}]);")
261 elif ftype ==
"uint16":
262 lines.append(f
" detail::StoreU16({fname}, &out[{offset}]);")
263 elif ftype
in (
"int8",
"uint8"):
264 lines.append(f
" out[{offset}] = static_cast<uint8_t>({fname});")
266 lines.append(
" return true;")
271 lines.append(f
" static std::optional<{msg['name']}> Decode(const uint8_t* data, std::size_t length) {{")
272 lines.append(f
" if (length < kWireSize) return std::nullopt;")
273 lines.append(f
" {msg['name']} msg;")
275 for field
in msg[
"fields"]:
276 ftype = field[
"type"]
277 fname = field[
"name"]
278 size = TYPE_INFO[ftype][0]
280 if ftype ==
"float32":
281 scale = field.get(
"scale", 10000)
282 lines.append(f
" msg.{fname} = detail::DequantizeFloat(detail::LoadS32(&data[{offset}]), {scale}.0f);")
283 lines.append(f
" if (!std::isfinite(msg.{fname})) return std::nullopt;")
284 elif ftype ==
"int32":
285 lines.append(f
" msg.{fname} = detail::LoadS32(&data[{offset}]);")
286 elif ftype ==
"uint32":
287 lines.append(f
" msg.{fname} = detail::LoadU32(&data[{offset}]);")
288 elif ftype ==
"int16":
289 lines.append(f
" msg.{fname} = detail::LoadS16(&data[{offset}]);")
290 elif ftype ==
"uint16":
291 lines.append(f
" msg.{fname} = detail::LoadU16(&data[{offset}]);")
292 elif ftype ==
"uint8":
293 lines.append(f
" msg.{fname} = data[{offset}];")
294 elif ftype ==
"int8":
295 lines.append(f
" msg.{fname} = static_cast<int8_t>(data[{offset}]);")
297 lines.append(
" return msg;")
303 if schema[
"messages"]:
304 lines.append(
"// ============================================================================")
305 lines.append(
"// Message Variant (for type-erased handling)")
306 lines.append(
"// ============================================================================")
308 variant_types =
", ".join(msg[
"name"]
for msg
in schema[
"messages"])
309 lines.append(f
"using Message = std::variant<{variant_types}>;")
313 lines.append(
"// ============================================================================")
314 lines.append(
"// Message Registry")
315 lines.append(
"// ============================================================================")
317 lines.append(
"struct MessageInfo {")
318 lines.append(
" MessageTypeId typeId;")
319 lines.append(
" std::size_t wireSize;")
320 lines.append(
" const char* name;")
323 num_messages = len(schema[
"messages"])
325 lines.append(f
"inline constexpr std::array<MessageInfo, {num_messages}> kMessageRegistry = {{{{")
326 for msg
in schema[
"messages"]:
328 lines.append(f
" {{MessageTypeId::{msg['name']}, {size}, \"{msg['name']}\"}},")
331 lines.append(
"// No messages defined - add message types to your schema and regenerate")
332 lines.append(
"inline constexpr std::array<MessageInfo, 0> kMessageRegistry = {{}};")
334 lines.append(
"inline std::optional<MessageInfo> GetMessageInfo(MessageTypeId typeId) {")
335 lines.append(
" for (const auto& info : kMessageRegistry) {")
336 lines.append(
" if (info.typeId == typeId) return info;")
338 lines.append(
" return std::nullopt;")
341 lines.append(
"inline std::optional<MessageInfo> GetMessageInfo(uint16_t typeId) {")
342 lines.append(
" return GetMessageInfo(static_cast<MessageTypeId>(typeId));")
347 lines.append(
"// ============================================================================")
348 lines.append(
"// Handshake Utilities")
349 lines.append(
"// ============================================================================")
351 lines.append(
"/// Encode handshake with default schema hash")
352 lines.append(
"inline bool EncodeHandshake(uint8_t* out, std::size_t capacity) {")
353 lines.append(
" if (capacity < kHandshakeSize) return false;")
354 lines.append(
" std::memcpy(out, kHandshakeMagic.data(), 4);")
355 lines.append(
" detail::StoreU32(kSchemaHash, &out[4]);")
356 lines.append(
" return true;")
359 lines.append(
"/// Encode handshake with custom schema hash (for testing)")
360 lines.append(
"inline bool EncodeHandshakeWithHash(uint8_t* out, std::size_t capacity, uint32_t schemaHash) {")
361 lines.append(
" if (capacity < kHandshakeSize) return false;")
362 lines.append(
" std::memcpy(out, kHandshakeMagic.data(), 4);")
363 lines.append(
" detail::StoreU32(schemaHash, &out[4]);")
364 lines.append(
" return true;")
367 lines.append(
"inline bool ValidateHandshake(const uint8_t* data, std::size_t length) {")
368 lines.append(
" if (length < kHandshakeSize) return false;")
369 lines.append(
" if (std::memcmp(data, kHandshakeMagic.data(), 4) != 0) return false;")
370 lines.append(
" const uint32_t remoteHash = detail::LoadU32(&data[4]);")
371 lines.append(
" return remoteHash == kSchemaHash;")
374 lines.append(
"/// Validate handshake against custom expected hash (for testing)")
375 lines.append(
"inline bool ValidateHandshakeWithHash(const uint8_t* data, std::size_t length, uint32_t expectedHash) {")
376 lines.append(
" if (length < kHandshakeSize) return false;")
377 lines.append(
" if (std::memcmp(data, kHandshakeMagic.data(), 4) != 0) return false;")
378 lines.append(
" const uint32_t remoteHash = detail::LoadU32(&data[4]);")
379 lines.append(
" return remoteHash == expectedHash;")
382 lines.append(
"inline uint32_t ExtractSchemaHash(const uint8_t* data, std::size_t length) {")
383 lines.append(
" if (length < kHandshakeSize) return 0;")
384 lines.append(
" return detail::LoadU32(&data[4]);")
388 lines.append(f
"}} // namespace {namespace}")
391 output_file.write_text(
"\n".join(lines))
392 print(f
"Generated: {output_file}")