external_entity_codec.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. #include "external_entity_codec.h"
  2. #include <array>
  3. namespace sunrise::middleware::gameplay::external {
  4. namespace {
  5. /** Presence, count, shortcut, and trailing fields use one bit. */
  6. constexpr std::uint8_t kFlagWidth = 1;
  7. /** The wire token carries a 13-bit slot. */
  8. constexpr std::uint8_t kEntitySlotWidth = 13;
  9. /** The wire token carries a four-bit incarnation. */
  10. constexpr std::uint8_t kEntityIncarnationWidth = 4;
  11. /** A raw bubble that fits on wire uses one byte. */
  12. constexpr std::uint8_t kRawBubbleWidth = 8;
  13. /** The optional raw-bubble value is limited to one unsigned byte. */
  14. constexpr std::uint16_t kMaximumRawBubble = 0xFF;
  15. /** A strict remove declares its one-bit body in a signed 16-bit field. */
  16. constexpr std::uint8_t kSubrecordLengthWidth = 16;
  17. /** A create carries one lifecycle byte. */
  18. constexpr std::uint8_t kLifecycleRevisionWidth = 8;
  19. /** A create carries one two-bit entity type. */
  20. constexpr std::uint8_t kEntityTypeWidth = 2;
  21. /** A strict administrative remove contains only its trailing false bit. */
  22. constexpr std::uint16_t kStrictRemoveBodyBits = 1;
  23. /** All flags represented by the explicit five-bit envelope. */
  24. constexpr std::uint16_t kAllowedFlags =
  25. entityCreate | entityUpdate | entityRemove | entityLifecycle | entityAnchor;
  26. /** Explicit flags are sent in this fixed order. */
  27. constexpr std::array<std::uint16_t, 5> kExplicitFlags{
  28. entityCreate, entityUpdate, entityRemove, entityLifecycle, entityAnchor};
  29. /** Measured callback lengths used to reject a changed second pass. */
  30. struct PayloadPlan {
  31. std::size_t baselineBits{};
  32. std::size_t updateBits{};
  33. };
  34. /** Reads one boolean field. */
  35. [[nodiscard]] bool read_flag(encoding::bits::Reader& reader, bool& output) noexcept {
  36. std::uint64_t value = 0;
  37. if (!reader.read(kFlagWidth, value)) {
  38. return false;
  39. }
  40. output = value != 0;
  41. return true;
  42. }
  43. /** Writes one boolean field. */
  44. [[nodiscard]] bool write_flag(encoding::bits::Writer& writer, bool value) noexcept {
  45. return writer.write(value ? 1U : 0U, kFlagWidth);
  46. }
  47. /** @return True when a token fits its 17-bit wire identity. */
  48. [[nodiscard]] bool valid_token(const EntityToken& token) noexcept {
  49. return token.slot <= kMaximumEntitySlot && token.incarnation <= kMaximumEntityIncarnation;
  50. }
  51. /** @return True when a raw bubble is absent or fits the optional byte. */
  52. [[nodiscard]] bool valid_raw_bubble(std::uint16_t rawBubble) noexcept {
  53. return rawBubble == kNoRawBubble || rawBubble <= kMaximumRawBubble;
  54. }
  55. /** @return True when a value is one of the four wire entity types. */
  56. [[nodiscard]] bool valid_type(EntityType type) noexcept {
  57. return type < EntityType::count;
  58. }
  59. /** @return True for create/update records or the exact administrative remove form. */
  60. [[nodiscard]] bool valid_flags(std::uint16_t flags) noexcept {
  61. if ((flags & ~kAllowedFlags) != 0) {
  62. return false;
  63. }
  64. if (flags == entityRemove) {
  65. return true;
  66. }
  67. return (flags & entityRemove) == 0 && (flags & (entityCreate | entityUpdate)) != 0;
  68. }
  69. /** @return The declared callback limit for one type-payload part. */
  70. [[nodiscard]] std::size_t payload_limit(const TypePayloadCodec& codec,
  71. TypePayloadPart part) noexcept {
  72. return part == TypePayloadPart::baseline ? codec.maximumBaselineBits : codec.maximumUpdateBits;
  73. }
  74. /** @return True when callback state and its declared bit limit are bounded. */
  75. [[nodiscard]] bool valid_payload(const TypePayloadCodec& codec,
  76. TypePayloadPart part,
  77. const TypePayload& payload) noexcept {
  78. return payload.byteCount <= payload.state.size()
  79. && payload_limit(codec, part) <= kMaximumTypePayloadBits;
  80. }
  81. /** Validates the canonical record forms accepted by the generic envelope. */
  82. [[nodiscard]] bool valid_record(const EntityRecord& record) noexcept {
  83. if (!valid_token(record.token) || !valid_raw_bubble(record.rawBubble)
  84. || !valid_flags(record.flags)) {
  85. return false;
  86. }
  87. if (record.flags == entityRemove) {
  88. return !record.anchorPresent && !record.trailingState && record.lifecycleRevision == 0
  89. && record.baseline.byteCount == 0 && record.update.byteCount == 0;
  90. }
  91. if (!valid_type(record.type) || (record.anchorPresent && (record.flags & entityAnchor) == 0)
  92. || ((record.flags & entityAnchor) != 0 && record.anchorPresent
  93. && !valid_token(record.anchor))
  94. || record.trailingState) {
  95. return false;
  96. }
  97. if ((record.flags & entityCreate) == 0
  98. && (record.lifecycleRevision != 0 || record.baseline.byteCount != 0)) {
  99. return false;
  100. }
  101. return (record.flags & entityUpdate) != 0 || record.update.byteCount == 0;
  102. }
  103. /** Writes one token in slot-then-incarnation order. */
  104. [[nodiscard]] bool write_token(encoding::bits::Writer& writer, const EntityToken& token) noexcept {
  105. return valid_token(token) && writer.write(token.slot, kEntitySlotWidth)
  106. && writer.write(token.incarnation, kEntityIncarnationWidth);
  107. }
  108. /** Reads one token without changing output on failure. */
  109. [[nodiscard]] bool read_token(encoding::bits::Reader& reader, EntityToken& output) noexcept {
  110. std::uint64_t slot = 0;
  111. std::uint64_t incarnation = 0;
  112. if (!reader.read(kEntitySlotWidth, slot)
  113. || !reader.read(kEntityIncarnationWidth, incarnation)) {
  114. return false;
  115. }
  116. output.slot = static_cast<std::uint16_t>(slot);
  117. output.incarnation = static_cast<std::uint8_t>(incarnation);
  118. return true;
  119. }
  120. /** Measures one pure payload callback before any envelope bits are written. */
  121. [[nodiscard]] bool measure_payload(const TypePayloadCodec& codec,
  122. const EntityRecord& record,
  123. TypePayloadPart part,
  124. std::size_t& bitCount) noexcept {
  125. const TypePayload& payload =
  126. part == TypePayloadPart::baseline ? record.baseline : record.update;
  127. if (codec.write == nullptr || !valid_payload(codec, part, payload)) {
  128. return false;
  129. }
  130. encoding::bits::Writer writer = encoding::bits::Writer::measuring();
  131. std::size_t ignored = 0;
  132. if (!codec.write(codec.context, record.token, record.type, part, payload, writer)
  133. || !writer.finish(ignored) || writer.bit_count() > payload_limit(codec, part)) {
  134. return false;
  135. }
  136. bitCount = writer.bit_count();
  137. return true;
  138. }
  139. /** Runs one payload writer and requires the preflight length to remain exact. */
  140. [[nodiscard]] bool write_payload(const TypePayloadCodec& codec,
  141. const EntityRecord& record,
  142. TypePayloadPart part,
  143. std::size_t expectedBits,
  144. encoding::bits::Writer& writer) noexcept {
  145. const TypePayload& payload =
  146. part == TypePayloadPart::baseline ? record.baseline : record.update;
  147. const std::size_t before = writer.bit_count();
  148. std::size_t ignored = 0;
  149. return codec.write != nullptr
  150. && codec.write(codec.context, record.token, record.type, part, payload, writer)
  151. && writer.finish(ignored) && writer.bit_count() >= before
  152. && writer.bit_count() - before == expectedBits;
  153. }
  154. /** Reads one payload through a bounded callback into temporary state. */
  155. [[nodiscard]] bool read_payload(const TypePayloadCodec& codec,
  156. const EntityToken& token,
  157. EntityType type,
  158. TypePayloadPart part,
  159. encoding::bits::Reader& reader,
  160. TypePayload& output) noexcept {
  161. const std::size_t limit = payload_limit(codec, part);
  162. if (codec.read == nullptr || limit > kMaximumTypePayloadBits) {
  163. return false;
  164. }
  165. const std::size_t before = reader.remaining_bits();
  166. TypePayload candidate{};
  167. std::uint64_t ignored = 0;
  168. if (!codec.read(codec.context, token, type, part, reader, candidate) || !reader.read(0, ignored)
  169. || candidate.byteCount > candidate.state.size()) {
  170. return false;
  171. }
  172. const std::size_t after = reader.remaining_bits();
  173. if (after > before || before - after > limit) {
  174. return false;
  175. }
  176. output = candidate;
  177. return true;
  178. }
  179. /** Resolves the type for an update-only record. */
  180. [[nodiscard]] bool
  181. resolve_type(const TypePayloadCodec& codec, const EntityToken& token, EntityType& output) noexcept {
  182. EntityType candidate = EntityType::count;
  183. if (codec.resolveType == nullptr || !codec.resolveType(codec.context, token, candidate)
  184. || !valid_type(candidate)) {
  185. return false;
  186. }
  187. output = candidate;
  188. return true;
  189. }
  190. /** Builds a complete payload plan before the channel-2 header is touched. */
  191. [[nodiscard]] bool
  192. prepare_batch(const TypePayloadCodec& codec, const EntityBatch& batch, PayloadPlan& plan) noexcept {
  193. plan = {};
  194. if (!valid_raw_bubble(batch.defaultRawBubble)) {
  195. return false;
  196. }
  197. if (!batch.recordPresent) {
  198. return true;
  199. }
  200. if (!valid_record(batch.record)) {
  201. return false;
  202. }
  203. if ((batch.record.flags & entityCreate) != 0
  204. && !measure_payload(codec, batch.record, TypePayloadPart::baseline, plan.baselineBits)) {
  205. return false;
  206. }
  207. return (batch.record.flags & entityUpdate) == 0
  208. || measure_payload(codec, batch.record, TypePayloadPart::update, plan.updateBits);
  209. }
  210. /** Writes the five explicit flag bits or the update shortcut. */
  211. [[nodiscard]] bool write_record_flags(encoding::bits::Writer& writer,
  212. std::uint16_t flags) noexcept {
  213. const bool shortcut = flags == entityUpdate;
  214. if (!write_flag(writer, shortcut)) {
  215. return false;
  216. }
  217. if (shortcut) {
  218. return true;
  219. }
  220. for (const std::uint16_t flag : kExplicitFlags) {
  221. if (!write_flag(writer, (flags & flag) != 0)) {
  222. return false;
  223. }
  224. }
  225. return true;
  226. }
  227. /** Reads the shortcut or five explicit flag bits. */
  228. [[nodiscard]] bool read_record_flags(encoding::bits::Reader& reader,
  229. std::uint16_t& output) noexcept {
  230. bool shortcut = false;
  231. if (!read_flag(reader, shortcut)) {
  232. return false;
  233. }
  234. if (shortcut) {
  235. output = entityUpdate;
  236. return true;
  237. }
  238. std::uint16_t flags = 0;
  239. for (const std::uint16_t flag : kExplicitFlags) {
  240. bool present = false;
  241. if (!read_flag(reader, present)) {
  242. return false;
  243. }
  244. if (present) {
  245. flags |= flag;
  246. }
  247. }
  248. output = flags;
  249. return true;
  250. }
  251. /** Writes one inherited, byte-sized, or reset raw bubble. */
  252. [[nodiscard]] bool write_record_bubble(encoding::bits::Writer& writer,
  253. std::uint16_t defaultRawBubble,
  254. std::uint16_t rawBubble) noexcept {
  255. const bool changed = rawBubble != defaultRawBubble;
  256. if (!write_flag(writer, changed)) {
  257. return false;
  258. }
  259. if (!changed) {
  260. return true;
  261. }
  262. const bool fits = rawBubble <= kMaximumRawBubble;
  263. return write_flag(writer, fits) && (!fits || writer.write(rawBubble, kRawBubbleWidth));
  264. }
  265. /** Reads one inherited, byte-sized, or reset raw bubble. */
  266. [[nodiscard]] bool read_record_bubble(encoding::bits::Reader& reader,
  267. std::uint16_t defaultRawBubble,
  268. std::uint16_t& output) noexcept {
  269. bool changed = false;
  270. if (!read_flag(reader, changed)) {
  271. return false;
  272. }
  273. if (!changed) {
  274. output = defaultRawBubble;
  275. return true;
  276. }
  277. bool fits = false;
  278. if (!read_flag(reader, fits)) {
  279. return false;
  280. }
  281. if (!fits) {
  282. output = kNoRawBubble;
  283. return true;
  284. }
  285. std::uint64_t bubble = 0;
  286. if (!reader.read(kRawBubbleWidth, bubble)) {
  287. return false;
  288. }
  289. output = static_cast<std::uint16_t>(bubble);
  290. return true;
  291. }
  292. /** Writes one validated record body after its token and batch default. */
  293. [[nodiscard]] bool write_record(encoding::bits::Writer& writer,
  294. const TypePayloadCodec& codec,
  295. const EntityRecord& record,
  296. std::uint16_t defaultRawBubble,
  297. const PayloadPlan& plan) noexcept {
  298. if (!write_record_flags(writer, record.flags)) {
  299. return false;
  300. }
  301. if ((record.flags & entityAnchor) != 0
  302. && (!write_flag(writer, record.anchorPresent)
  303. || (record.anchorPresent && !write_token(writer, record.anchor)))) {
  304. return false;
  305. }
  306. if (!write_record_bubble(writer, defaultRawBubble, record.rawBubble)) {
  307. return false;
  308. }
  309. if (record.flags == entityRemove
  310. && !writer.write(kStrictRemoveBodyBits, kSubrecordLengthWidth)) {
  311. return false;
  312. }
  313. if ((record.flags & entityCreate) != 0
  314. && (!writer.write(record.lifecycleRevision, kLifecycleRevisionWidth)
  315. || !writer.write(static_cast<std::uint8_t>(record.type), kEntityTypeWidth)
  316. || !write_payload(
  317. codec, record, TypePayloadPart::baseline, plan.baselineBits, writer))) {
  318. return false;
  319. }
  320. if ((record.flags & entityUpdate) != 0
  321. && !write_payload(codec, record, TypePayloadPart::update, plan.updateBits, writer)) {
  322. return false;
  323. }
  324. return (record.flags & entityRemove) == 0 || write_flag(writer, record.trailingState);
  325. }
  326. /** Reads one record body into temporary state. */
  327. [[nodiscard]] bool read_record(encoding::bits::Reader& reader,
  328. const TypePayloadCodec& codec,
  329. std::uint16_t defaultRawBubble,
  330. EntityRecord& output) noexcept {
  331. EntityRecord candidate{};
  332. candidate.token = output.token;
  333. if (!read_record_flags(reader, candidate.flags) || !valid_flags(candidate.flags)) {
  334. return false;
  335. }
  336. if ((candidate.flags & entityAnchor) != 0
  337. && (!read_flag(reader, candidate.anchorPresent)
  338. || (candidate.anchorPresent && !read_token(reader, candidate.anchor)))) {
  339. return false;
  340. }
  341. if (!read_record_bubble(reader, defaultRawBubble, candidate.rawBubble)) {
  342. return false;
  343. }
  344. if ((candidate.flags & (entityCreate | entityRemove)) == entityRemove) {
  345. std::uint64_t bitLength = 0;
  346. if (!reader.read(kSubrecordLengthWidth, bitLength) || bitLength != kStrictRemoveBodyBits) {
  347. return false;
  348. }
  349. }
  350. if ((candidate.flags & entityCreate) != 0) {
  351. std::uint64_t revision = 0;
  352. std::uint64_t type = 0;
  353. if (!reader.read(kLifecycleRevisionWidth, revision)
  354. || !reader.read(kEntityTypeWidth, type)) {
  355. return false;
  356. }
  357. candidate.lifecycleRevision = static_cast<std::uint8_t>(revision);
  358. candidate.type = static_cast<EntityType>(type);
  359. if (!valid_type(candidate.type)
  360. || !read_payload(codec,
  361. candidate.token,
  362. candidate.type,
  363. TypePayloadPart::baseline,
  364. reader,
  365. candidate.baseline)) {
  366. return false;
  367. }
  368. } else if ((candidate.flags & entityUpdate) != 0
  369. && !resolve_type(codec, candidate.token, candidate.type)) {
  370. return false;
  371. }
  372. if ((candidate.flags & entityUpdate) != 0
  373. && !read_payload(codec,
  374. candidate.token,
  375. candidate.type,
  376. TypePayloadPart::update,
  377. reader,
  378. candidate.update)) {
  379. return false;
  380. }
  381. if ((candidate.flags & entityRemove) != 0
  382. && (!read_flag(reader, candidate.trailingState) || candidate.trailingState)) {
  383. return false;
  384. }
  385. if (!valid_record(candidate)) {
  386. return false;
  387. }
  388. output = candidate;
  389. return true;
  390. }
  391. /** Writes a prepared channel-2 batch without repeating callback preflight. */
  392. [[nodiscard]] bool write_batch_fields(encoding::bits::Writer& writer,
  393. const TypePayloadCodec& codec,
  394. const EntityBatch& batch,
  395. const PayloadPlan& plan) noexcept {
  396. if (!write_flag(writer, batch.recordPresent)
  397. || (batch.recordPresent && !write_token(writer, batch.record.token))) {
  398. return false;
  399. }
  400. const bool defaultPresent = batch.defaultRawBubble != kNoRawBubble;
  401. if (!write_flag(writer, defaultPresent)
  402. || (defaultPresent && !writer.write(batch.defaultRawBubble, kRawBubbleWidth))) {
  403. return false;
  404. }
  405. return !batch.recordPresent
  406. || write_record(writer, codec, batch.record, batch.defaultRawBubble, plan);
  407. }
  408. /** Reads a channel-2 batch into temporary state. */
  409. [[nodiscard]] bool read_batch_fields(encoding::bits::Reader& reader,
  410. const TypePayloadCodec& codec,
  411. EntityBatch& output) noexcept {
  412. EntityBatch candidate{};
  413. if (!read_flag(reader, candidate.recordPresent)
  414. || (candidate.recordPresent && !read_token(reader, candidate.record.token))) {
  415. return false;
  416. }
  417. bool defaultPresent = false;
  418. if (!read_flag(reader, defaultPresent)) {
  419. return false;
  420. }
  421. if (defaultPresent) {
  422. std::uint64_t bubble = 0;
  423. if (!reader.read(kRawBubbleWidth, bubble)) {
  424. return false;
  425. }
  426. candidate.defaultRawBubble = static_cast<std::uint16_t>(bubble);
  427. }
  428. if (candidate.recordPresent
  429. && !read_record(reader, codec, candidate.defaultRawBubble, candidate.record)) {
  430. return false;
  431. }
  432. output = candidate;
  433. return true;
  434. }
  435. /** Writes common and all four fixed channels using one prepared entity plan. */
  436. [[nodiscard]] bool write_frame_fields(encoding::bits::Writer& writer,
  437. const TypePayloadCodec& codec,
  438. const ExternalEntityFrame& frame,
  439. const PayloadPlan& plan) noexcept {
  440. if (!write_flag(writer, frame.commonPresent)
  441. || (frame.commonPresent && !write_common_state(writer, frame.common))) {
  442. return false;
  443. }
  444. // Channels 0 and 1 are empty until their type registries have server producers.
  445. if (!write_flag(writer, false) || !write_flag(writer, false)
  446. || !write_batch_fields(writer, codec, frame.entities, plan)) {
  447. return false;
  448. }
  449. // Channel 3 and its enclosing list stay absent in the generic fallback.
  450. return write_flag(writer, false) && write_flag(writer, false);
  451. }
  452. /** Reads common and all four fixed channels into temporary state. */
  453. [[nodiscard]] bool read_frame_fields(encoding::bits::Reader& reader,
  454. const TypePayloadCodec& codec,
  455. ExternalEntityFrame& output) noexcept {
  456. ExternalEntityFrame candidate{};
  457. if (!read_flag(reader, candidate.commonPresent)
  458. || (candidate.commonPresent && !read_common_state(reader, candidate.common))) {
  459. return false;
  460. }
  461. bool present = false;
  462. if (!read_flag(reader, present) || present || !read_flag(reader, present) || present
  463. || !read_batch_fields(reader, codec, candidate.entities) || !read_flag(reader, present)
  464. || present || !read_flag(reader, present) || present) {
  465. return false;
  466. }
  467. output = candidate;
  468. return true;
  469. }
  470. } // namespace
  471. /** Reads one channel-2 batch and commits the reader and output only on success. */
  472. bool read_entity_batch(encoding::bits::Reader& reader,
  473. const TypePayloadCodec& codec,
  474. EntityBatch& output) noexcept {
  475. encoding::bits::Reader candidateReader = reader;
  476. EntityBatch candidate{};
  477. if (!read_batch_fields(candidateReader, codec, candidate)) {
  478. return false;
  479. }
  480. reader = candidateReader;
  481. output = candidate;
  482. return true;
  483. }
  484. /** Writes one channel-2 batch after a complete fail-closed preflight. */
  485. bool write_entity_batch(encoding::bits::Writer& writer,
  486. const TypePayloadCodec& codec,
  487. const EntityBatch& batch) noexcept {
  488. PayloadPlan plan{};
  489. if (!prepare_batch(codec, batch, plan)) {
  490. return false;
  491. }
  492. encoding::bits::Writer measuring = encoding::bits::Writer::measuring();
  493. std::size_t ignored = 0;
  494. if (!write_batch_fields(measuring, codec, batch, plan) || !measuring.finish(ignored)) {
  495. return false;
  496. }
  497. return write_batch_fields(writer, codec, batch, plan);
  498. }
  499. /** Reads the fixed four-channel wrapper and commits no state on failure. */
  500. bool read_external_entity_frame(encoding::bits::Reader& reader,
  501. const TypePayloadCodec& codec,
  502. ExternalEntityFrame& output) noexcept {
  503. encoding::bits::Reader candidateReader = reader;
  504. ExternalEntityFrame candidate{};
  505. if (!read_frame_fields(candidateReader, codec, candidate)) {
  506. return false;
  507. }
  508. reader = candidateReader;
  509. output = candidate;
  510. return true;
  511. }
  512. /** Writes the fixed four-channel wrapper after a complete fail-closed preflight. */
  513. bool write_external_entity_frame(encoding::bits::Writer& writer,
  514. const TypePayloadCodec& codec,
  515. const ExternalEntityFrame& frame) noexcept {
  516. PayloadPlan plan{};
  517. if (!prepare_batch(codec, frame.entities, plan)) {
  518. return false;
  519. }
  520. encoding::bits::Writer measuring = encoding::bits::Writer::measuring();
  521. std::size_t ignored = 0;
  522. if (!write_frame_fields(measuring, codec, frame, plan) || !measuring.finish(ignored)) {
  523. return false;
  524. }
  525. return write_frame_fields(writer, codec, frame, plan);
  526. }
  527. } // namespace sunrise::middleware::gameplay::external