sensor_auth_update.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. #pragma once
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <span>
  6. #include "../../encoding/bit_writer.h"
  7. #include "activity_patch_epoch_parser.h"
  8. #include "definition.h"
  9. #include "squad_auth_body.h"
  10. #include "squad_sense_state.h"
  11. namespace sunrise::middleware::bap::activity_message::sensor_auth_update {
  12. /** The client roster and its bubble grants both use activity message type 5. */
  13. inline constexpr std::uint32_t kMessageType = 5;
  14. /** The authority table is 64 usable bubbles plus one fallback slot. */
  15. inline constexpr std::size_t kAuthoritySlotCount = 65;
  16. /** Bubble 64 is in the table but the world controller cannot enter it, so it is never granted. */
  17. inline constexpr std::uint8_t kMaximumGrantBubble = 63;
  18. /** A grant token of zero equals the client's cleared mirror, so it grants nothing. */
  19. inline constexpr std::uint16_t kMinimumGrantToken = 1;
  20. /** Keys the client's top-level roster arrays hold. The wire count is wider and must be clamped. */
  21. inline constexpr std::size_t kTopLevelGroupCapacity = 256;
  22. /**
  23. * Groups one host snapshot retains and publishes across the top-level and all bubble lists.
  24. * This bounds fixed host storage; only one bubble list is active in the client manager at a time.
  25. */
  26. inline constexpr std::size_t kPublishedGroupCapacity = 256;
  27. /** Registrations the downstream ClientRef manager holds for top-level plus one active bubble. */
  28. inline constexpr std::size_t kClientGroupCapacity = 128;
  29. /** ClientRef sync records top-level plus one active bubble may create in one manager. */
  30. inline constexpr std::size_t kClientRecordCapacity = 3072;
  31. /** Exact auth bodies one snapshot accepts, bounded by the widest authored roster object. */
  32. inline constexpr std::size_t kAuthOverrideCapacity = 1280;
  33. /** Authored scene sensors are slot type 43 and consume reflection schema 0x8080626B. */
  34. inline constexpr std::uint8_t kAuthoredSceneSlotType = 43;
  35. inline constexpr std::uint32_t kAuthoredSceneAuthSchema = 0x8080626B;
  36. /** Fixed fields plus bounded dependency references and event keys in the complete Auth schema. */
  37. inline constexpr std::size_t kAuthoredSceneBaseAuthBitCount = 74;
  38. inline constexpr std::size_t kAuthoredSceneDependencyBitCount = 55;
  39. inline constexpr std::size_t kAuthoredSceneEventBitCount = 32;
  40. inline constexpr std::size_t kAuthoredSceneMaximumDependencyCount = 8;
  41. inline constexpr std::size_t kAuthoredSceneMaximumEventCount = 32;
  42. inline constexpr std::size_t kAuthoredSceneMaximumAuthBitCount =
  43. kAuthoredSceneBaseAuthBitCount
  44. + kAuthoredSceneDependencyBitCount * kAuthoredSceneMaximumDependencyCount
  45. + kAuthoredSceneEventBitCount * kAuthoredSceneMaximumEventCount;
  46. inline constexpr std::size_t kAuthoredSceneMaximumAuthByteCount =
  47. (kAuthoredSceneMaximumAuthBitCount + 7U) / 8U;
  48. /** Complete baseline body: signed activation generation, clear bool, and empty collections. */
  49. inline constexpr std::uint16_t kAuthoredSceneAuthBitCount = kAuthoredSceneBaseAuthBitCount;
  50. inline constexpr std::uint8_t kAuthoredSceneAuthByteCount = (kAuthoredSceneAuthBitCount + 7U) / 8U;
  51. /** The widest statically bounded Auth body is type 19, at 53,150 bits. */
  52. inline constexpr std::size_t kAuthOverrideByteCapacity = (53'150U + 7U) / 8U;
  53. /** Lifetime states that pass the player-spawn gate. */
  54. inline constexpr std::array<std::uint8_t, 3> kSpawnGateLifetimeStates = {3, 6, 10};
  55. /** Highest lifetime state inside the client's jump table. */
  56. inline constexpr std::uint8_t kMaximumLifetimeState = 10;
  57. /** The lifetime field stores its value plus one in four bits. */
  58. inline constexpr std::uint8_t kLifetimeWidth = 4;
  59. inline constexpr std::uint32_t kLifetimeBias = 1;
  60. /** Slot flag bit for a block that carries a sense reset bit. */
  61. inline constexpr std::uint8_t kSlotSenseFlag = 1;
  62. /** Slot flag bit for a block that carries an auth reset bit and its delta root. */
  63. inline constexpr std::uint8_t kSlotAuthFlag = 2;
  64. /** The widest slice-set index the type-17 spawn override's bias-1 field accepts. */
  65. inline constexpr std::uint32_t kMaximumSpawnSliceSet = 0x1FF;
  66. /** The unset spawn-set hash. An override carrying it disables the override it was meant to arm. */
  67. inline constexpr std::uint32_t kAbsentSpawnSetHash = kEmptyNameHash;
  68. /** One bubble handed to this client, as a change against its own per-bubble mirror. */
  69. struct Grant final {
  70. std::uint8_t bubble{};
  71. std::uint16_t token{};
  72. };
  73. /**
  74. * One roster group and its slots, in slot-index order.
  75. * A slot the object declares but no descriptor names is not here, so an ordinal is not an index
  76. * and `slotIndices` carries each slot's own, from its descriptor.
  77. */
  78. struct Group final {
  79. /** Package object tag retained off-wire for exact override target validation. */
  80. std::uint32_t objectTag{};
  81. std::uint32_t key{};
  82. std::span<const std::uint8_t> slotTypes{};
  83. std::span<const std::uint8_t> slotFlags{};
  84. std::span<const std::uint16_t> slotIndices{};
  85. /**
  86. * Revision for this one registry key. The wire carries one byte beside every key, so an
  87. * unrelated group's revision must not tear this group down and rebuild it.
  88. */
  89. std::uint8_t stateSequence{};
  90. /** False keeps hand-built callers on Snapshot::stateSequence for compatibility. */
  91. bool hasStateSequence{};
  92. /** True only for a generated mission group whose non-overridden slots seed empty deltas. */
  93. bool missionSeedOnly{};
  94. };
  95. /** One exact, already-registered slot body substituted into phase 2. */
  96. struct AuthOverride final {
  97. std::array<std::byte, kAuthOverrideByteCapacity> body{};
  98. std::uint32_t objectTag{};
  99. std::uint32_t key{};
  100. std::uint32_t authSchema{};
  101. std::uint16_t slotIndex{};
  102. std::uint16_t bitCount{};
  103. std::uint8_t slotType{};
  104. std::uint16_t byteCount{};
  105. /** Internal provenance: this body was compiled and revalidated against the pinned SDK. */
  106. bool sdkCompiled{};
  107. bool present{};
  108. };
  109. /** A full squad Sense baseline, including its delta root bit. */
  110. struct SenseOverride final {
  111. std::array<std::byte, squad_sense::kMaximumByteCount> body{};
  112. std::uint32_t objectTag{};
  113. std::uint32_t key{};
  114. std::uint32_t counter{};
  115. std::size_t bitCount{};
  116. std::size_t byteCount{};
  117. std::uint16_t slotIndex{};
  118. std::uint8_t slotType{};
  119. };
  120. /** Sub-blocks the delta's field 1 may carry. The wire array declares one element per bubble. */
  121. inline constexpr std::size_t kBubbleSubBlockCapacity = 64;
  122. /** Keys one sub-block may carry. Its wire key array declares 96. */
  123. inline constexpr std::size_t kBubbleKeyCapacity = 96;
  124. /** The widest bubble a sub-block may name. The client masks the index it compares to six bits. */
  125. inline constexpr std::uint32_t kMaximumSubBlockBubble = 63;
  126. /**
  127. * One per-bubble roster sub-block, an element of the delta's field 1. Its keys register only
  128. * while that bubble is current, so a normal key exists in every selectable state.
  129. */
  130. struct BubbleSubBlock final {
  131. std::uint32_t bubble{};
  132. /** Keys registered while that bubble is current. Each one also needs a group to seed it. */
  133. std::span<const std::uint32_t> keys{};
  134. };
  135. /** Which groups one destination publishes and which of them binds the player. */
  136. struct Roster final {
  137. /**
  138. * Every group this body registers, top-level first and per-bubble after.
  139. * Phase 2 seeds all of them. The client applies auth state only once every object registered
  140. * in the current bubble is seeded, so a group left out holds back the whole apply.
  141. */
  142. std::array<Group, kPublishedGroupCapacity> groups{};
  143. std::size_t groupCount{};
  144. /** Leading groups that go in the delta's top-level key list. The rest are per-bubble only. */
  145. std::size_t topLevelGroupCount{};
  146. /** Group whose first type-13 block carries the player key. It must be one that registers. */
  147. std::uint32_t playerKeyGroup{};
  148. /** Sub-blocks published in the delta's field 1. None leaves that field one zero bit. */
  149. std::span<const BubbleSubBlock> bubbleSubBlocks{};
  150. };
  151. /** Everything one `sensor_auth_update` carries. */
  152. struct Snapshot final {
  153. /** Message 52's payload, echoed exactly. A wrong epoch skips phase 2 and reports nothing. */
  154. patch_epoch::PatchEpoch patchEpoch{};
  155. Roster roster{};
  156. /** Exact typed bodies for slots already present in `roster`. */
  157. std::span<const AuthOverride> authOverrides{};
  158. std::span<const SenseOverride> senseOverrides{};
  159. Grant grant{};
  160. /** Message 12's member record key. Zero leaves every type-13 block inert. */
  161. std::uint64_t playerKey{};
  162. /** Compatibility revision used by groups without an explicit per-key value. */
  163. std::uint8_t stateSequence{};
  164. /** The participation record's region index. Its `+8` latch needs it. */
  165. std::uint32_t region{};
  166. std::uint32_t spawnSetHash{};
  167. std::uint32_t spawnSliceSet{};
  168. std::uint8_t lifetime{};
  169. bool hasGrant{};
  170. bool hasRegion{};
  171. bool hasSpawnOverride{};
  172. /** Hold the client's spawn while it loads by emitting `awaiting_client_sync`. */
  173. bool awaitClientSync{};
  174. /** Register the groups and seed no object. Separates no components from no auth state. */
  175. bool phaseOneOnly{};
  176. /**
  177. * Fill the type-35 mission-director and type-18 script-runtime bodies.
  178. * These two are what an encounter bubble's script objects are authored from, and they shipped
  179. * bodyless until their layouts were recovered from the client's own field tables. Gated
  180. * because a wrong width does not fail this host's width check: it shifts every block after it
  181. * in the same phase-2 stream, which costs the player their spawn rather than just the
  182. * encounter. Off restores the previous bodyless behaviour with no rebuild.
  183. */
  184. bool authorDirectorBodies{};
  185. /**
  186. * Fill the type-37 body, the last published slot that still ships bodyless.
  187. * A measured run showed every other published slot carrying a body (13:224, 16:7, 17:520,
  188. * 18:386, 35:359) and 37 carrying zero. Same gate rationale as the director bodies: a wrong
  189. * width shifts every block after it in the same phase-2 stream, so off restores the bodyless
  190. * behaviour with no rebuild.
  191. */
  192. bool authorWideRecordBodies{};
  193. /**
  194. * Fill the participation body on every type-13 slot, not only the group's first.
  195. * The gate reads the record of the object the player datum names. Only one type-13 slot
  196. * gets the body, so filling the first slot alone can miss that object.
  197. */
  198. bool keyOnEveryParticipationSlot{};
  199. };
  200. /**
  201. * Encodes one `sensor_auth_update` body.
  202. * Phase 2 has no resync point, so a one-bit slip corrupts every later block in silence. Every
  203. * writer checks its own end position and the encode fails rather than shipping a slipped body.
  204. * @param snapshot Patch epoch, optional bubble grant, and the destination's roster.
  205. * @param output Caller storage, left unchanged when validation fails or it is too small.
  206. * @param written Receives the encoded size on success or zero on failure.
  207. * @return True when the whole zero-padded body fits.
  208. */
  209. [[nodiscard]] bool encode_sensor_auth_update(const Snapshot& snapshot,
  210. std::span<std::byte> output,
  211. std::size_t& written) noexcept;
  212. /** Bits before the enable latch, no bubble block: 8 hardwipe, 128 epoch, 1 present, 64 token. */
  213. inline constexpr std::size_t kLatchBitWithoutGrant = 201;
  214. /** A bubble block adds the 65-bit authority mask, two head bits, three per element, one token. */
  215. inline constexpr std::size_t kBubbleBlockBits =
  216. kAuthoritySlotCount + 2 + 3 * kAuthoritySlotCount + 16;
  217. /** Each patch-epoch element is an unsigned 64-bit wire value. */
  218. inline constexpr std::uint8_t kEpochWidth = 64;
  219. /** The unchecked hardwipe token is one byte, before the patch epoch. */
  220. inline constexpr std::uint8_t kHardwipeWidth = 8;
  221. /** The unchecked activity token follows the bubble block. */
  222. inline constexpr std::uint8_t kActivityTokenWidth = 64;
  223. /** Changed authority tokens use the schema's unsigned 16-bit field. */
  224. inline constexpr std::uint8_t kGrantTokenWidth = 16;
  225. /** Every presence bit and every loop continuation bit is one bit wide. */
  226. inline constexpr std::uint8_t kPresenceWidth = 1;
  227. /** Both roster delta counts use the same 9-bit field. */
  228. inline constexpr std::uint8_t kDeltaCountWidth = 9;
  229. /** The delta's key array starts here, measured from the delta's own root bit. */
  230. inline constexpr std::size_t kDeltaKeysBit = 12;
  231. /** The presence mask is eight words wide whatever the key count. */
  232. inline constexpr std::size_t kDeltaMaskWords = 8;
  233. /** A registry key and the per-object block's length field are both 32 bits. */
  234. inline constexpr std::uint8_t kKeyWidth = 32;
  235. /** The object reference is a bias-1 slot type and a bias-32768 slot index. */
  236. inline constexpr std::uint8_t kSlotTypeWidth = 7;
  237. inline constexpr std::uint8_t kSlotIndexWidth = 16;
  238. inline constexpr std::uint32_t kSlotTypeBias = 1;
  239. inline constexpr std::uint32_t kSlotIndexBias = 32768;
  240. /** Widest unbiased slot type carried by the bias-1 7-bit field; type zero is valid. */
  241. inline constexpr std::uint8_t kMaximumSlotType =
  242. static_cast<std::uint8_t>(((std::uint32_t{1} << kSlotTypeWidth) - 1U) - kSlotTypeBias);
  243. /** The widest slot index the biased 16-bit field carries. One above it wraps to zero. */
  244. inline constexpr std::uint16_t kMaximumSlotIndex = 32767;
  245. /**
  246. * Slot types whose Auth carries a lane that faults the stock client from a wire-legal value.
  247. * Only value, index and resolve lanes are listed. A count lane needs no entry, because the schema
  248. * codec caps every dynamic count at the nested schema's declared array length.
  249. */
  250. inline constexpr std::array<std::uint8_t, 5> kWireFaultSlotTypes = {1, 8, 17, 38, 68};
  251. /**
  252. * @param slotType Slot type an Auth override names.
  253. * @return True when no schema-compiled Auth body may carry that slot type.
  254. */
  255. [[nodiscard]] constexpr bool refused_compiled_slot_type(std::uint8_t slotType) noexcept {
  256. for (const std::uint8_t refused : kWireFaultSlotTypes) {
  257. if (refused == slotType) {
  258. return true;
  259. }
  260. }
  261. return false;
  262. }
  263. /** Elements in the slot-type-1 requested-count array. Its wire count lane is wider. */
  264. inline constexpr std::size_t kSquadRequestedCountArrayLength = 8;
  265. /** The per-entry state byte is stored biased, so the wire value never goes negative. */
  266. inline constexpr std::uint32_t kStateByteBias = 0x80;
  267. /** The biased state byte wraps above this value. */
  268. inline constexpr std::uint8_t kMaximumStateSequence = 127;
  269. /** @param keyCount Published group count. @return Bit position of the delta's presence mask. */
  270. [[nodiscard]] constexpr std::size_t delta_mask_bit(std::size_t keyCount) noexcept {
  271. return kDeltaKeysBit + 32 * keyCount + 1;
  272. }
  273. /** @param keyCount Published group count. @return Bit position of the delta's state count. */
  274. [[nodiscard]] constexpr std::size_t delta_state_count_bit(std::size_t keyCount) noexcept {
  275. return delta_mask_bit(keyCount) + 32 * kDeltaMaskWords + 1;
  276. }
  277. /** The sub-block count and each nested count are 7-bit fields with no presence bit. */
  278. inline constexpr std::uint8_t kBubbleCountWidth = 7;
  279. /** The sub-block's own key is a signed 32-bit field, so its wire value carries the -2^31 bias. */
  280. inline constexpr std::uint32_t kBubbleKeyBias = 0x80000000;
  281. /** The sub-block presence mask is three words whatever the key count. */
  282. inline constexpr std::size_t kBubbleMaskWords = 3;
  283. /**
  284. * Bits one sub-block costs before its keys: the element's own presence bit, its bubble key, the
  285. * four remaining flags, both counts and the presence mask. These are the 5 flags per sub-block
  286. * that close the schema's 325-flag sum.
  287. */
  288. inline constexpr std::size_t kBubbleSubBlockFixedBits =
  289. kPresenceWidth + kKeyWidth + kPresenceWidth + kPresenceWidth + kBubbleCountWidth
  290. + kPresenceWidth + 32 * kBubbleMaskWords + kPresenceWidth + kBubbleCountWidth;
  291. /** Bits one key costs inside a sub-block: the key itself, then its state byte. */
  292. inline constexpr std::size_t kBubbleSubBlockKeyBits = kKeyWidth + 8;
  293. /**
  294. * @param subBlocks Sub-blocks the delta carries, which must not be empty.
  295. * @return Bits the whole field-1 half costs, after the field's own presence bit.
  296. */
  297. [[nodiscard]] constexpr std::size_t
  298. bubble_bits(std::span<const BubbleSubBlock> subBlocks) noexcept {
  299. std::size_t bits = kBubbleCountWidth;
  300. for (const BubbleSubBlock& block : subBlocks) {
  301. bits += kBubbleSubBlockFixedBits + kBubbleSubBlockKeyBits * block.keys.size();
  302. }
  303. return bits;
  304. }
  305. /**
  306. * @param keyCount Top-level group count.
  307. * @param subBlocks Sub-blocks the delta carries, empty when field 1 is absent.
  308. * @return Total delta size from its own root bit.
  309. */
  310. [[nodiscard]] constexpr std::size_t delta_bits(std::size_t keyCount,
  311. std::span<const BubbleSubBlock> subBlocks) noexcept {
  312. return delta_state_count_bit(keyCount) + kDeltaCountWidth + 8 * keyCount + 1
  313. + (subBlocks.empty() ? 0 : bubble_bits(subBlocks));
  314. }
  315. /**
  316. * Writes zero bits in chunks the writer accepts.
  317. * @param writer Body writer.
  318. * @param count Bits to write.
  319. * @return True when every bit fits.
  320. */
  321. [[nodiscard]] bool pad_bits(encoding::bits::Writer& writer, std::size_t count) noexcept;
  322. /**
  323. * Writes the bubble authority block.
  324. * @param writer Body writer positioned after the block's present bit.
  325. * @param grant The one bubble to hand over.
  326. * @return True when the whole block fits.
  327. */
  328. [[nodiscard]] bool write_bubble_block(encoding::bits::Writer& writer, const Grant& grant) noexcept;
  329. /**
  330. * Writes the phase-1 roster delta, which registers the group keys.
  331. * @param writer Body writer positioned after the enable latch.
  332. * @param roster Groups to register, in publish order.
  333. * @param stateSequence Biased per-entry state byte.
  334. * @return True when the whole delta fits and lands on its own end bit.
  335. */
  336. [[nodiscard]] bool write_roster_delta(encoding::bits::Writer& writer,
  337. const Roster& roster,
  338. std::uint8_t stateSequence) noexcept;
  339. /**
  340. * Reports how many bits of auth body one slot carries.
  341. * @param snapshot Message input, which decides the type-13 body width.
  342. * @param slotType Slot type from the group's slot array.
  343. * @param carriesPlayerKey True for the one type-13 block that binds the player.
  344. * @return Body bits, or zero for a seed-only block.
  345. */
  346. [[nodiscard]] std::size_t
  347. auth_body_bits(const Snapshot& snapshot, std::uint8_t slotType, bool carriesPlayerKey) noexcept;
  348. /**
  349. * Writes one slot's auth body.
  350. * @param writer Body writer positioned after the auth delta's root bit.
  351. * @param snapshot Message input.
  352. * @param slotType Slot type from the group's slot array.
  353. * @param carriesPlayerKey True for the one type-13 block that binds the player.
  354. * @return True when the body fits and matches its declared width.
  355. */
  356. [[nodiscard]] bool write_auth_body(encoding::bits::Writer& writer,
  357. const Snapshot& snapshot,
  358. std::uint8_t slotType,
  359. bool carriesPlayerKey) noexcept;
  360. /**
  361. * Writes one per-object state block.
  362. * The 32-bit length counts the remainder, which includes the reset bit, the auth root bit and the
  363. * sense bit. Leaving the reset bit out of it desyncs by 3 bits with nothing reported.
  364. * @param writer Body writer positioned at the block's continuation bit.
  365. * @param snapshot Message input.
  366. * @param objectTag Package object tag of the owning group.
  367. * @param key Registry key of the owning group.
  368. * @param slotType Slot type from the group's slot array.
  369. * @param slotIndex Slot ordinal, which is also the slot's index.
  370. * @param flags Sense and auth emit bits for that slot type.
  371. * @param missionSeedOnly True to suppress the authored typed body unless an override matches.
  372. * @param carriesPlayerKey True for the one type-13 block that binds the player.
  373. * @return True when the block fits and lands on its declared end bit.
  374. */
  375. [[nodiscard]] bool write_object_block(encoding::bits::Writer& writer,
  376. const Snapshot& snapshot,
  377. std::uint32_t objectTag,
  378. std::uint32_t key,
  379. std::uint8_t slotType,
  380. std::uint16_t slotIndex,
  381. std::uint8_t flags,
  382. bool missionSeedOnly,
  383. bool carriesPlayerKey) noexcept;
  384. } // namespace sunrise::middleware::bap::activity_message::sensor_auth_update