activity_transaction_notifications.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #include "activity_transaction_notifications.h"
  2. #include "../../../../core/logging/log.h"
  3. #include "../../../gameplay/gameplay_advertisement.h"
  4. #include "../push/activity/activity_arrival.h"
  5. #include "../push/activity/activity_global_state_push.h"
  6. #include "../push/activity/activity_membership_push.h"
  7. #include "../push/activity/activity_message_push.h"
  8. #include "../push/activity/activity_roster_push.h"
  9. namespace sunrise::server::bap::encrypted::activity_transaction {
  10. namespace {
  11. /**
  12. * Reports whether the citizen advertisement this membership body would carry is still coming.
  13. * The client applies one membership update per revision, so a body sent before the region's host
  14. * session exists spends that revision on a record no later push can fill. Holding costs one
  15. * keepalive.
  16. * @param activity Prepared activity transaction, whose region this body publishes.
  17. * @return True when the push has to wait.
  18. */
  19. [[nodiscard]] bool advertisement_pending(const activity_message::ActivityPlan& activity) noexcept {
  20. // Take the delta's region, not the committed one. Staging runs before the commit, so the
  21. // committed value still names the region the player has left.
  22. const server::gameplay::AdvertisementState state = server::gameplay::advertisement_state(
  23. push::activity::planned_region(activity.membershipMutation, activity.sessionId).index);
  24. if (state != server::gameplay::AdvertisementState::pending) {
  25. return false;
  26. }
  27. core::log::write(core::log::Channel::server,
  28. core::log::Level::debug,
  29. "ev=gameplay stage=membership result=held reason=no_host_session");
  30. return true;
  31. }
  32. /**
  33. * Stages the whole host snapshot the client's state-refresh request asks for.
  34. * The order matches the keepalive: the global state, then membership, then the roster, because the
  35. * roster's participation key binds to the player the membership publishes.
  36. * @param session Connection-owned roster counters, advanced only by a staged roster.
  37. * @param scratch Lock-owned transform buffers.
  38. * @param activity Prepared activity transaction carrying the membership snapshot.
  39. * @param key Active AES-GCM session key.
  40. * @param nonce Local send nonce advanced only by complete staged notifications.
  41. * @param response Lock-owned complete-frame staging storage.
  42. * @param written Existing staged byte count, updated only by complete notifications.
  43. * @return True when at least one of the three notifications was staged.
  44. */
  45. [[nodiscard]] bool stage_refresh(Session& session,
  46. Scratch& scratch,
  47. const activity_message::ActivityPlan& activity,
  48. std::span<const std::byte, state::kAesKeySize> key,
  49. std::array<std::byte, state::kBapNonceSize>& nonce,
  50. std::span<std::byte> response,
  51. std::size_t& written) noexcept {
  52. bool staged = push::activity::append_global_state_notification(
  53. scratch, activity.sessionId, key, nonce, response, written);
  54. if (activity.membershipMutation.hasSnapshot && !advertisement_pending(activity)) {
  55. staged = push::activity::append_membership_notification(
  56. scratch, activity, key, nonce, response, written)
  57. || staged;
  58. }
  59. return push::activity::append_roster_notification(
  60. session, scratch, key, nonce, response, written, false)
  61. || staged;
  62. }
  63. /**
  64. * Stages what one client-authoritative delta owes: membership, the roster, or both.
  65. * The roster follows membership, because its participation key binds to the player membership
  66. * publishes.
  67. * @param session Connection-owned roster counters, advanced only by a staged roster.
  68. * @param scratch Lock-owned transform buffers.
  69. * @param activity Prepared activity transaction and its region-move flag.
  70. * @param key Active AES-GCM session key.
  71. * @param nonce Local send nonce advanced only by complete staged notifications.
  72. * @param response Lock-owned complete-frame staging storage.
  73. * @param written Existing staged byte count, updated only by complete notifications.
  74. * @return True when every notification the delta owed was staged.
  75. */
  76. [[nodiscard]] bool stage_authoritative(Session& session,
  77. Scratch& scratch,
  78. const activity_message::ActivityPlan& activity,
  79. std::span<const std::byte, state::kAesKeySize> key,
  80. std::array<std::byte, state::kBapNonceSize>& nonce,
  81. std::span<std::byte> response,
  82. std::size_t& written) noexcept {
  83. bool staged = false;
  84. bool held = false;
  85. if (activity.membershipMutation.hasSnapshot) {
  86. held = advertisement_pending(activity);
  87. if (!held) {
  88. staged = push::activity::append_membership_notification(
  89. scratch, activity, key, nonce, response, written);
  90. }
  91. }
  92. if (activity.regionMoved) {
  93. staged = push::activity::append_roster_notification(
  94. session, scratch, key, nonce, response, written, false)
  95. || staged;
  96. }
  97. // A held membership is not a failed staging. A false here drops the very commit that moved
  98. // the region the held body waits for. The keepalive publishes it on a later slice.
  99. return staged || held;
  100. }
  101. } // namespace
  102. /**
  103. * Stages the notifications one activity transaction requests.
  104. * @param session Connection-owned roster counters, advanced only by a staged roster.
  105. * @param scratch Lock-owned transform buffers.
  106. * @param activity Prepared activity transaction and delivery selection.
  107. * @param key Active AES-GCM session key.
  108. * @param nonce Local send nonce advanced only by complete staged notifications.
  109. * @param response Lock-owned complete-frame staging storage.
  110. * @param written Existing staged byte count, updated only by complete notifications.
  111. * @return True when every requested notification is staged.
  112. */
  113. bool stage_notifications(Session& session,
  114. Scratch& scratch,
  115. const activity_message::ActivityPlan& activity,
  116. std::span<const std::byte, state::kAesKeySize> key,
  117. std::array<std::byte, state::kBapNonceSize>& nonce,
  118. std::span<std::byte> response,
  119. std::size_t& written) noexcept {
  120. // Each encoder refuses an absent session itself, so a plan that delivers nothing needs no
  121. // session at all. Message type 52 is the one that arrives on an unallocated link.
  122. if (activity.delivery == activity_message::Delivery::joinNotifications) {
  123. return push::activity::append_join_notifications(
  124. scratch, activity, key, nonce, response, written);
  125. }
  126. if (activity.delivery == activity_message::Delivery::entitySlotNotification) {
  127. return push::activity::append_entity_slot_notification(scratch,
  128. activity.sessionId,
  129. activity.entitySlotMutation.mask,
  130. key,
  131. nonce,
  132. response,
  133. written);
  134. }
  135. if (activity.delivery == activity_message::Delivery::membershipNotification) {
  136. return push::activity::append_membership_notification(
  137. scratch, activity, key, nonce, response, written);
  138. }
  139. if (activity.delivery == activity_message::Delivery::refreshNotifications) {
  140. return stage_refresh(session, scratch, activity, key, nonce, response, written);
  141. }
  142. if (activity.delivery == activity_message::Delivery::authoritativeNotifications) {
  143. return stage_authoritative(session, scratch, activity, key, nonce, response, written);
  144. }
  145. return activity.delivery == activity_message::Delivery::none;
  146. }
  147. } // namespace sunrise::server::bap::encrypted::activity_transaction