activity_membership_region_writer.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #include "replicate_membership.h"
  2. namespace sunrise::middleware::bap::activity_message::replicate_membership {
  3. namespace {
  4. /** The host table has one fixed record for each of 64 bubbles. */
  5. constexpr std::size_t kRegionCount = 64;
  6. /** Each bubble's state-zero slice-set index is 8 times its bubble index. */
  7. constexpr std::uint32_t kRegionIndexStride = 8;
  8. /** Region indices use the signed 32-bit midpoint as their wire bias. */
  9. constexpr std::uint32_t kRegionIndexBias = 0x80000000U;
  10. /** Each record carries one transition-token byte for every member slot. */
  11. constexpr std::size_t kRegionTokenCount = 32;
  12. /** Spawn and teleport state fields use 3 bits. */
  13. constexpr std::uint8_t kStateBitWidth = 3;
  14. /** Teleport slice-set indices use 10 bits. */
  15. constexpr std::uint8_t kSliceSetBitWidth = 10;
  16. /** Signed host-state fields add 1 before writing their unsigned value. */
  17. constexpr std::int32_t kSignedFieldBias = 1;
  18. /** Ambassadorship state fields are 2 bits at bias 1, so wire 2 stores the assigned state 1. */
  19. constexpr std::uint64_t kAmbassadorAssigned = 2;
  20. /** Member-slot fields are 6 bits at bias 1. */
  21. constexpr std::uint8_t kSlotBitWidth = 6;
  22. /**
  23. * Ambassador slot named by a record with no advertisement, at bias 1, so wire 2 stores slot 1.
  24. * It must never store the client's own slot 0. A record naming slot 0 makes the client claim the
  25. * region, and the next differing slot then revokes it for good.
  26. */
  27. constexpr std::uint64_t kUnadvertisedAmbassadorSlot = 2;
  28. /** The join-descriptor element count is an unclamped 8-bit field; only 128 is usable. */
  29. constexpr std::uint64_t kDescriptorCount = 128;
  30. /**
  31. * Writes one state-zero region record.
  32. * A record carrying the citizen advertisement is 1,024 bits longer than the others.
  33. * @param writer Fixed-buffer writer sitting at the record start.
  34. * @param bubble Bubble index, 0 through 63.
  35. * @param snapshot Transition token and optional citizen advertisement.
  36. * @return True when every fixed field fits.
  37. */
  38. [[nodiscard]] bool write_region(encoding::bits::Writer& writer,
  39. std::size_t bubble,
  40. const MembershipSnapshot& snapshot) noexcept {
  41. const std::uint32_t region = static_cast<std::uint32_t>(bubble) * kRegionIndexStride;
  42. const bool advertise = snapshot.citizen.present
  43. && static_cast<std::int32_t>(region) == snapshot.citizen.regionIndex;
  44. const std::uint64_t ambassadorSlot =
  45. advertise ? static_cast<std::uint64_t>(snapshot.citizen.ambassadorSlot) + kSignedFieldBias
  46. : kUnadvertisedAmbassadorSlot;
  47. bool encoded = writer.write(kRegionIndexBias + region, 32) && writer.write(1, 2)
  48. && writer.write(0, 8) && writer.write(kAmbassadorAssigned, 2)
  49. && writer.write(ambassadorSlot, kSlotBitWidth) && writer.write(0, 1)
  50. && writer.write(0, 32) && writer.write(0, 8) && writer.write(0, 32);
  51. for (std::size_t member = 0; encoded && member < kRegionTokenCount; ++member) {
  52. encoded = writer.write(snapshot.transitionToken, 8);
  53. }
  54. if (!advertise) {
  55. return encoded && writer.write(0, 8) && writer.write(0, 64);
  56. }
  57. encoded = encoded && writer.write(kDescriptorCount, 8);
  58. for (const std::byte value : snapshot.citizen.descriptor) {
  59. encoded = encoded && writer.write(std::to_integer<std::uint64_t>(value), 8);
  60. }
  61. return encoded && writer.write(snapshot.citizen.onlineSessionId, 64);
  62. }
  63. /**
  64. * Writes one host key, low byte first.
  65. * @param writer Fixed-buffer writer sitting after the host presence bit.
  66. * @param key Host-order key shared with the populated member.
  67. * @return True when all 8 key bytes fit.
  68. */
  69. [[nodiscard]] bool write_host_key(encoding::bits::Writer& writer, std::uint64_t key) noexcept {
  70. for (std::size_t index = 0; index < sizeof key; ++index) {
  71. if (!writer.write((key >> (index * 8U)) & 0xFFU, 8)) {
  72. return false;
  73. }
  74. }
  75. return true;
  76. }
  77. /**
  78. * Writes the current host-state tail after all region records.
  79. * @param writer Fixed-buffer writer sitting at the first spawn field.
  80. * @param snapshot Reflected host key, spawn state and teleport state.
  81. * @return True when the host-present tail fits.
  82. */
  83. [[nodiscard]] bool write_host_tail(encoding::bits::Writer& writer,
  84. const MembershipSnapshot& snapshot) noexcept {
  85. // Both state fields are 3 bits at bias 1, so a logical -1 encodes as wire zero, which is fatal
  86. // in each: the spawn reader then refuses every spawn, and the teleport reader arms a teleport
  87. // every cycle and de-instantiates the world. So a negative mirror is sent as a neutral zero.
  88. const auto spawnState = static_cast<std::uint8_t>(
  89. (snapshot.spawn.state < 0 ? 0 : snapshot.spawn.state) + kSignedFieldBias);
  90. const auto teleportState = static_cast<std::uint8_t>(
  91. (snapshot.teleport.state < 0 ? 0 : snapshot.teleport.state) + kSignedFieldBias);
  92. const auto sliceSetIndex =
  93. static_cast<std::uint32_t>(snapshot.teleport.sliceSetIndex + kSignedFieldBias);
  94. return writer.write(snapshot.spawn.opaqueByte, 8) && writer.write(spawnState, kStateBitWidth)
  95. && writer.write(snapshot.spawn.opaqueValue, 64) && writer.write(0, 4)
  96. && writer.write(teleportState, kStateBitWidth)
  97. && writer.write(snapshot.teleport.token, 8)
  98. && writer.write(sliceSetIndex, kSliceSetBitWidth)
  99. && writer.write(snapshot.teleport.sliceSetHash, 32) && writer.write(0, 1)
  100. && writer.write(1, 1) && write_host_key(writer, snapshot.identity.memberKey)
  101. && writer.write(0, 1) && writer.write(0, 1);
  102. }
  103. } // namespace
  104. /** Writes all 64 state-zero regions and the host-present tail. */
  105. bool write_region_block(encoding::bits::Writer& writer,
  106. const MembershipSnapshot& snapshot) noexcept {
  107. bool encoded = writer.bit_count() == kRegionBlockStartBit;
  108. for (std::size_t bubble = 0; encoded && bubble < kRegionCount; ++bubble) {
  109. encoded = write_region(writer, bubble, snapshot);
  110. }
  111. return encoded && write_host_tail(writer, snapshot)
  112. && writer.bit_count() == region_block_end_bit(snapshot);
  113. }
  114. } // namespace sunrise::middleware::bap::activity_message::replicate_membership