session_messages.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. #include "session_messages.h"
  2. #include <array>
  3. #include "../../encoding/bit_raw.h"
  4. #include "../../protobuf/codec.h"
  5. #include "session_state.h"
  6. namespace sunrise::middleware::gameplay::group {
  7. namespace {
  8. namespace bits = encoding::bits;
  9. /** The protocol version is a 16-bit value field. */
  10. constexpr std::uint8_t kProtocolWidth = 16;
  11. /** The join sequence is a 32-bit value field. */
  12. constexpr std::uint8_t kSequenceWidth = 32;
  13. /** Single-bit flags. */
  14. constexpr std::uint8_t kFlagWidth = 1;
  15. /** The boot kind is three bits. */
  16. constexpr std::uint8_t kBootKindWidth = 3;
  17. /** The boot reason is five bits. */
  18. constexpr std::uint8_t kBootReasonWidth = 5;
  19. /** Time samples are 64-bit value fields. */
  20. constexpr std::uint8_t kSampleWidth = 64;
  21. // --- Membership update, message id 30 -------------------------------------------------------
  22. /** Protobuf field numbers of the membership root. */
  23. constexpr std::uint32_t kRootRevision = 1;
  24. /** See kRootRevision. */
  25. constexpr std::uint32_t kRootHostIndex = 2;
  26. /** See kRootRevision. */
  27. constexpr std::uint32_t kRootSuccession = 3;
  28. /** See kRootRevision. */
  29. constexpr std::uint32_t kRootMemberCount = 4;
  30. /** See kRootRevision. */
  31. constexpr std::uint32_t kRootMemberMask = 5;
  32. /** See kRootRevision. */
  33. constexpr std::uint32_t kRootMember = 6;
  34. /** Protobuf field numbers of one member. Field 9 is not published. */
  35. constexpr std::uint32_t kMemberAddress = 1;
  36. /** See kMemberAddress. */
  37. constexpr std::uint32_t kMemberMachineId = 2;
  38. /** See kMemberAddress. */
  39. constexpr std::uint32_t kMemberJoinId = 3;
  40. /** See kMemberAddress. */
  41. constexpr std::uint32_t kMemberIdB = 8;
  42. /** See kMemberAddress. */
  43. constexpr std::uint32_t kMemberPlayerSlot = 10;
  44. /** See kMemberAddress. */
  45. constexpr std::uint32_t kMemberFlagA = 11;
  46. /** See kMemberAddress. */
  47. constexpr std::uint32_t kMemberFlagB = 12;
  48. /** Member field 8 has no recovered meaning. Zero is the value an empty id decodes to. */
  49. constexpr std::uint64_t kMemberIdEmpty = 0;
  50. /** Member fields 11 and 12 have no recovered meaning. Zero is their cleared value. */
  51. constexpr std::uint64_t kMemberFlagClear = 0;
  52. /** The protobuf body is length-prefixed with thirteen bits. */
  53. constexpr std::uint8_t kProtobufLengthWidth = 13;
  54. /** Both revision words are 32 bits. */
  55. constexpr std::uint8_t kRevisionWidth = 32;
  56. /** Both delta counts are six bits. */
  57. constexpr std::uint8_t kDeltaCountWidth = 6;
  58. /** A peer-delta index is six bits. */
  59. constexpr std::uint8_t kDeltaIndexWidth = 6;
  60. /** A member state is four bits. */
  61. constexpr std::uint8_t kMemberStateWidth = 4;
  62. /** The third connection value. */
  63. constexpr std::uint8_t kConnectionValueWidth = 8;
  64. /** The join compatibility word, and the trailing session-state hash. */
  65. constexpr std::uint8_t kWordWidth = 32;
  66. /** The join timestamp. It is a value field, so it goes out most significant byte first. */
  67. constexpr std::uint8_t kJoinTimestampWidth = 64;
  68. /** A clear flag ahead of the revision pair publishes it. Its absence decodes as -1. */
  69. constexpr std::uint64_t kRevisionPairPresent = 0;
  70. /** Base revision 0 selects a complete snapshot. Any other value is a delta against that revision.
  71. */
  72. constexpr std::uint32_t kCompleteSnapshotBase = 0;
  73. /** The word after the base revision is never read by the consumer. */
  74. constexpr std::uint32_t kUnreadWord = 0;
  75. /** A set mode bit means a whole peer-delta entry follows the index. */
  76. constexpr std::uint64_t kDeltaEntryFull = 1;
  77. /** A player-delta index is five bits, one narrower than a peer-delta index. */
  78. constexpr std::uint8_t kPlayerIndexWidth = 5;
  79. /** The member index a player row names. */
  80. constexpr std::uint8_t kPlayerMemberWidth = 6;
  81. /** The member's own player index. The decoder refuses any value but zero, so a member publishes
  82. * at most one player this way. */
  83. constexpr std::uint8_t kPlayerOwnedIndexWidth = 1;
  84. /** The session player-add counter, which the consumer keeps modulo 2^20. */
  85. constexpr std::uint8_t kPlayerSequenceWidth = 20;
  86. /** Value the decoder requires of the member's own player index. */
  87. constexpr std::uint64_t kPlayerOwnedIndexZero = 0;
  88. /** A clear flag ends a player row after its identity group. The profile block it would gate has
  89. * no writer here, so no row carries one. */
  90. constexpr std::uint64_t kPlayerProfileAbsent = 0;
  91. /** This host publishes no 264-byte identity block and neither trailing delta-entry flag. */
  92. constexpr std::uint64_t kEntryFieldAbsent = 0;
  93. /** The four tail groups are all omitted, which leaves the consumer's own values alone. */
  94. constexpr std::uint64_t kTailGroupAbsent = 0;
  95. /** Tail groups omitted, one presence bit each. The encoder writes four, not five. */
  96. constexpr std::size_t kTailGroupCount = 4;
  97. /** Largest protobuf body the message codec accepts. */
  98. constexpr std::size_t kProtobufCapacity = 5972;
  99. /** One encoded member submessage cannot exceed this. */
  100. constexpr std::size_t kMemberBytes = 128;
  101. /** Machine identities are published as eight bytes in memory order. */
  102. constexpr std::size_t kMachineIdBytes = 8;
  103. /** Bits in one byte. */
  104. constexpr unsigned kByteBits = 8;
  105. /** Mask of one byte. */
  106. constexpr std::uint64_t kByteMask = 0xFF;
  107. /**
  108. * Appends one member as a length-delimited submessage.
  109. * @param writer Open protobuf writer for the root message.
  110. * @param member Member to publish.
  111. * @return True when the whole submessage fit.
  112. */
  113. [[nodiscard]] bool write_member(protobuf::Writer& writer, const MembershipMember& member) noexcept {
  114. std::array<std::byte, kMachineIdBytes> machineId{};
  115. for (std::size_t index = 0; index < machineId.size(); ++index) {
  116. machineId[index] =
  117. static_cast<std::byte>((member.machineId >> (index * kByteBits)) & kByteMask);
  118. }
  119. std::array<std::byte, kMemberBytes> storage{};
  120. protobuf::Writer body(storage);
  121. if (!body.write_length_delimited(kMemberAddress, member.address)
  122. || !body.write_length_delimited(kMemberMachineId, machineId)
  123. || !body.write_varint(kMemberJoinId, member.joinId)
  124. || !body.write_varint(kMemberIdB, kMemberIdEmpty)) {
  125. return false;
  126. }
  127. if (member.ownsPlayerSlot && !body.write_varint(kMemberPlayerSlot, member.playerSlot)) {
  128. return false;
  129. }
  130. if (!body.write_varint(kMemberFlagA, kMemberFlagClear)
  131. || !body.write_varint(kMemberFlagB, kMemberFlagClear)) {
  132. return false;
  133. }
  134. return writer.write_length_delimited(kRootMember, {storage.data(), body.size()});
  135. }
  136. /**
  137. * Encodes the membership protobuf body.
  138. * @param body Snapshot to publish.
  139. * @param storage Caller-owned protobuf storage.
  140. * @param size Receives the encoded byte count.
  141. * @return True when every field fit.
  142. */
  143. [[nodiscard]] bool write_membership_protobuf(const MembershipUpdate& body,
  144. std::span<std::byte> storage,
  145. std::size_t& size) noexcept {
  146. const std::uint64_t count = static_cast<std::uint64_t>(body.members.size());
  147. // Members occupy indices 0 upward, so the occupied-slot mask follows from the count.
  148. const std::uint64_t mask = (std::uint64_t{1} << count) - 1;
  149. protobuf::Writer writer(storage);
  150. if (!writer.write_varint(kRootRevision, body.revision)
  151. || !writer.write_varint(kRootHostIndex, body.hostMemberIndex)
  152. || !writer.write_varint(kRootSuccession, body.successionIndex)
  153. || !writer.write_varint(kRootMemberCount, count)
  154. || !writer.write_varint(kRootMemberMask, mask)) {
  155. return false;
  156. }
  157. for (const MembershipMember& member : body.members) {
  158. if (!write_member(writer, member)) {
  159. return false;
  160. }
  161. }
  162. size = writer.size();
  163. return true;
  164. }
  165. /**
  166. * Writes one peer-delta entry.
  167. * @param writer Open writer.
  168. * @param index Member index the entry names.
  169. * @param member Member whose state the entry publishes.
  170. * @return True when every field fit.
  171. */
  172. [[nodiscard]] bool
  173. write_peer_delta(bits::Writer& writer, std::size_t index, const MembershipMember& member) noexcept {
  174. if (!writer.write(index, kDeltaIndexWidth) || !writer.write(kDeltaEntryFull, kFlagWidth)
  175. || !writer.write(static_cast<std::uint64_t>(member.state), kMemberStateWidth)
  176. || !writer.write(member.connectionPresent ? 1U : 0U, kFlagWidth)) {
  177. return false;
  178. }
  179. if (member.connectionPresent
  180. && (!writer.write(member.joinCompatibility, kWordWidth)
  181. || !writer.write(member.joinTimestamp, kJoinTimestampWidth)
  182. || !writer.write(member.connectionValue, kConnectionValueWidth))) {
  183. return false;
  184. }
  185. return writer.write(kEntryFieldAbsent, kFlagWidth)
  186. && writer.write(kEntryFieldAbsent, kFlagWidth)
  187. && writer.write(kEntryFieldAbsent, kFlagWidth);
  188. }
  189. /**
  190. * Writes one player-delta entry carrying an identity and no profile block.
  191. * @param writer Open writer.
  192. * @param player Player row to publish.
  193. * @return True when every field fit.
  194. */
  195. [[nodiscard]] bool write_player_delta(bits::Writer& writer,
  196. const MembershipPlayer& player) noexcept {
  197. return writer.write(player.slot, kPlayerIndexWidth) && writer.write(kDeltaEntryFull, kFlagWidth)
  198. && writer.write(1U, kFlagWidth) && bits::write_raw_u64(writer, player.playerId)
  199. && writer.write(player.memberIndex, kPlayerMemberWidth)
  200. && writer.write(kPlayerOwnedIndexZero, kPlayerOwnedIndexWidth)
  201. && writer.write(player.addSequence, kPlayerSequenceWidth)
  202. && writer.write(player.flag ? 1U : 0U, kFlagWidth)
  203. && writer.write(kPlayerProfileAbsent, kFlagWidth);
  204. }
  205. } // namespace
  206. /** Writes a peer-connect body. */
  207. bool write_peer_connect(bits::Writer& writer, const PeerConnect& body) noexcept {
  208. return writer.write(body.protocolVersion, kProtocolWidth)
  209. && bits::write_raw_u64(writer, body.machineId)
  210. && bits::write_raw_u64(writer, body.sessionId);
  211. }
  212. /** Writes a join-complete body. */
  213. bool write_join_complete(bits::Writer& writer, const JoinComplete& body) noexcept {
  214. return bits::write_raw_u64(writer, body.sessionId)
  215. && bits::write_raw_u64(writer, body.machineId)
  216. && writer.write(body.joinSequence, kSequenceWidth);
  217. }
  218. /** Reads a join-complete body. */
  219. bool read_join_complete(bits::Reader& reader, JoinComplete& output) noexcept {
  220. JoinComplete candidate{};
  221. std::uint64_t sequence = 0;
  222. if (!bits::read_raw_u64(reader, candidate.sessionId)
  223. || !bits::read_raw_u64(reader, candidate.machineId)
  224. || !reader.read(kSequenceWidth, sequence)) {
  225. return false;
  226. }
  227. candidate.joinSequence = static_cast<std::uint32_t>(sequence);
  228. output = candidate;
  229. return true;
  230. }
  231. /** Reads a join-abort body. */
  232. bool read_join_abort(bits::Reader& reader, SessionNotice& output) noexcept {
  233. std::uint64_t flag = 0;
  234. SessionNotice candidate{};
  235. if (!bits::read_raw_u64(reader, candidate.sessionId)
  236. || !bits::read_raw_u64(reader, candidate.machineId) || !reader.read(kFlagWidth, flag)) {
  237. return false;
  238. }
  239. candidate.flag = flag != 0;
  240. output = candidate;
  241. return true;
  242. }
  243. /** Reads a session-identity-only body. */
  244. bool read_session_only(bits::Reader& reader, std::uint64_t& output) noexcept {
  245. return bits::read_raw_u64(reader, output);
  246. }
  247. /** Writes a session-identity-only body. */
  248. bool write_session_only(bits::Writer& writer, std::uint64_t sessionId) noexcept {
  249. return bits::write_raw_u64(writer, sessionId);
  250. }
  251. /** Writes a session-disband body. */
  252. bool write_session_disband(bits::Writer& writer, const SessionNotice& body) noexcept {
  253. return bits::write_raw_u64(writer, body.sessionId)
  254. && bits::write_raw_u64(writer, body.machineId)
  255. && writer.write(body.flag ? 1U : 0U, kFlagWidth);
  256. }
  257. /** Writes a session-boot body. */
  258. bool write_session_boot(bits::Writer& writer, const SessionBoot& body) noexcept {
  259. return bits::write_raw_u64(writer, body.sessionId) && writer.write(body.kind, kBootKindWidth)
  260. && writer.write(body.reason, kBootReasonWidth)
  261. && bits::write_raw_u64(writer, body.machineId);
  262. }
  263. /** Reads a time-synchronize body. */
  264. bool read_time_synchronize(bits::Reader& reader, TimeSynchronize& output) noexcept {
  265. std::uint64_t variant = 0;
  266. TimeSynchronize candidate{};
  267. if (!bits::read_raw_u64(reader, candidate.sessionId) || !reader.read(kFlagWidth, variant)
  268. || !reader.read(kSampleWidth, candidate.sampleA)) {
  269. return false;
  270. }
  271. candidate.threeSample = variant != 0;
  272. if (candidate.threeSample
  273. && (!reader.read(kSampleWidth, candidate.sampleB)
  274. || !reader.read(kSampleWidth, candidate.sampleC))) {
  275. return false;
  276. }
  277. output = candidate;
  278. return true;
  279. }
  280. /** Writes a time-synchronize body. */
  281. bool write_time_synchronize(bits::Writer& writer, const TimeSynchronize& body) noexcept {
  282. if (!bits::write_raw_u64(writer, body.sessionId)
  283. || !writer.write(body.threeSample ? 1U : 0U, kFlagWidth)
  284. || !writer.write(body.sampleA, kSampleWidth)) {
  285. return false;
  286. }
  287. if (!body.threeSample) {
  288. return true;
  289. }
  290. return writer.write(body.sampleB, kSampleWidth) && writer.write(body.sampleC, kSampleWidth);
  291. }
  292. /** Writes a complete-snapshot membership update. */
  293. bool write_membership_update(bits::Writer& writer, const MembershipUpdate& body) noexcept {
  294. // The consumer refuses the message unless the base revision is below the message revision,
  295. // and a complete snapshot always publishes base revision 0.
  296. if (body.revision == 0 || body.members.size() > kMemberCapacity
  297. || body.players.size() > kPlayerCapacity) {
  298. return false;
  299. }
  300. for (const MembershipPlayer& player : body.players) {
  301. if (player.slot >= kPlayerCapacity || player.memberIndex >= kMemberCapacity) {
  302. return false;
  303. }
  304. }
  305. std::array<std::byte, kProtobufCapacity> protobufStorage{};
  306. std::size_t protobufSize = 0;
  307. if (!write_membership_protobuf(body, protobufStorage, protobufSize)) {
  308. return false;
  309. }
  310. if (!bits::write_raw_u64(writer, body.hostMachineId)
  311. || !writer.write(protobufSize, kProtobufLengthWidth)
  312. || !bits::write_raw(writer, {protobufStorage.data(), protobufSize})
  313. || !writer.write(kRevisionPairPresent, kFlagWidth)
  314. || !writer.write(kCompleteSnapshotBase, kRevisionWidth)
  315. || !writer.write(kUnreadWord, kRevisionWidth)
  316. || !writer.write(body.members.size(), kDeltaCountWidth)
  317. || !writer.write(body.players.size(), kDeltaCountWidth)) {
  318. return false;
  319. }
  320. for (std::size_t index = 0; index < body.members.size(); ++index) {
  321. if (!write_peer_delta(writer, index, body.members[index])) {
  322. return false;
  323. }
  324. }
  325. for (const MembershipPlayer& player : body.players) {
  326. if (!write_player_delta(writer, player)) {
  327. return false;
  328. }
  329. }
  330. for (std::size_t group = 0; group < kTailGroupCount; ++group) {
  331. if (!writer.write(kTailGroupAbsent, kFlagWidth)) {
  332. return false;
  333. }
  334. }
  335. // The consumer hashes its own state after applying and compares. The replica layout must
  336. // stay in step with it.
  337. return writer.write(session_state_hash(body), kWordWidth);
  338. }
  339. } // namespace sunrise::middleware::gameplay::group