service_outcome_commit.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #include "service_outcome_commit.h"
  2. #include <array>
  3. #include <cstdio>
  4. #include "../../../../core/logging/log.h"
  5. #include "../../../../state/activity/runtime.h"
  6. #include "../../../../state/matchmaking/matchmaking_state.h"
  7. #include "../../../../state/runtime/runtime.h"
  8. #include "../bap_connection_publication.h"
  9. #include "../internal.h"
  10. namespace sunrise::server::bap::encrypted::transactions {
  11. namespace {
  12. namespace slots = state::activity::entity_slots;
  13. /** Log names for each lease operation, in the enum's own order. */
  14. constexpr std::array<const char*, 4> kLeaseKinds = {"none", "join", "grant", "release"};
  15. /** Retains one newly committed private ActivityClient generation for its BAP link. */
  16. [[nodiscard]] bool retain_private(std::uint64_t sessionId, Publication& publication) noexcept {
  17. state::activity::SessionBinding binding{};
  18. if (!state::activity::snapshot_binding(sessionId, binding)
  19. || !state::activity::retain_binding(binding)) {
  20. return false;
  21. }
  22. publication.activity.session = binding;
  23. publication.activity.source = binding;
  24. publication.activity.role = ActivityClientRole::privateCurrent;
  25. publication.hasActivitySessionBinding = true;
  26. return true;
  27. }
  28. /** Retains one exact advertised public target before its join mutation commits. */
  29. [[nodiscard]] bool retain_public(const activity_message::ActivityPlan& plan,
  30. Publication& publication) noexcept {
  31. server::gameplay::group::HostSessionBinding current{};
  32. if (!server::gameplay::group::host_session_for_activity(plan.sessionId, current)
  33. || current.generation != plan.publicHost.generation
  34. || current.groupSessionId != plan.publicHost.groupSessionId
  35. || current.regionIndex != plan.publicHost.regionIndex
  36. || current.source.sessionId != plan.publicHost.source.sessionId
  37. || current.source.createdRevision != plan.publicHost.source.createdRevision
  38. || current.target.sessionId != plan.publicHost.target.sessionId
  39. || current.target.createdRevision != plan.publicHost.target.createdRevision
  40. || !state::activity::binding_matches(current.source)
  41. || !state::activity::binding_matches(current.target)
  42. || !server::gameplay::group::retain_host_session(current.generation)) {
  43. return false;
  44. }
  45. if (!state::activity::retain_binding(current.target)) {
  46. server::gameplay::group::release_host_session(current.generation);
  47. return false;
  48. }
  49. publication.activity.session = current.target;
  50. publication.activity.source = current.source;
  51. publication.activity.groupSessionId = current.groupSessionId;
  52. publication.activity.hostGeneration = current.generation;
  53. publication.activity.advertisedRegion = current.regionIndex;
  54. publication.activity.role = ActivityClientRole::publicTarget;
  55. publication.hasActivitySessionBinding = true;
  56. return true;
  57. }
  58. /** Releases provisional activity owners when the following State commit fails. */
  59. void discard_activity_publication(Publication& publication) noexcept {
  60. if (publication.activity.hostGeneration != 0) {
  61. server::gameplay::group::release_host_session(publication.activity.hostGeneration);
  62. }
  63. if (publication.activity.session.sessionId != state::activity::kAbsentSessionId) {
  64. state::activity::release_binding(publication.activity.session);
  65. }
  66. publication = {};
  67. }
  68. /**
  69. * Reports one entity-slot lease change.
  70. * The client prints only `failed to create` when it has no free index, and nothing else on this
  71. * path reports the lease, so a failed create reads the same as an empty grant without this line.
  72. * @param mutation Plan as it was before the commit consumed it.
  73. * @param committed Whether the commit succeeded.
  74. */
  75. void report_lease(const slots::PendingMutation& mutation, bool committed) noexcept {
  76. std::size_t held = 0;
  77. std::size_t reserved = 0;
  78. const bool known = slots::lease_counts(mutation.sessionId, held, reserved);
  79. const auto kind = static_cast<std::size_t>(mutation.kind);
  80. std::array<char, core::log::kLineCapacity> line{};
  81. const int written =
  82. std::snprintf(line.data(),
  83. line.size(),
  84. "ev=activity stage=entity_slots result=%s kind=%s soid=0x%llX "
  85. "requested=%zu picked=%zu held=%zu reserved=%zu known=%u",
  86. committed ? "ok" : "fail",
  87. kind < kLeaseKinds.size() ? kLeaseKinds[kind] : "bad",
  88. static_cast<unsigned long long>(mutation.sessionId),
  89. mutation.requestedCount,
  90. slots::slot_count(mutation.mask),
  91. held,
  92. reserved,
  93. known ? 1U : 0U);
  94. if (written > 0) {
  95. core::log::write(core::log::Channel::server,
  96. committed ? core::log::Level::debug : core::log::Level::warn,
  97. {line.data(), static_cast<std::size_t>(written)});
  98. }
  99. }
  100. } // namespace
  101. /**
  102. * Commits at most one delayed State transaction.
  103. * @param outcome Checked service result whose pending transaction is used up.
  104. * @param publication Gets connection fields to publish after the output copy.
  105. * @return True when there is no transaction, or the one transaction commits.
  106. */
  107. bool commit(ServiceOutcome& outcome, Publication& publication) noexcept {
  108. publication = {};
  109. if (auto* allocation = transaction_if<state::activity::PendingAllocation>(outcome)) {
  110. const std::uint64_t sessionId = allocation->sessionId;
  111. std::uint64_t bindingGeneration = 0;
  112. if (sessionId == state::activity::kAbsentSessionId
  113. || !reserve_activity_binding_generation(bindingGeneration)
  114. || !state::activity::commit(*allocation)) {
  115. return false;
  116. }
  117. if (!retain_private(sessionId, publication)) {
  118. static_cast<void>(state::activity::release_session(sessionId));
  119. return false;
  120. }
  121. publication.activity.bindingGeneration = bindingGeneration;
  122. return true;
  123. }
  124. if (auto* plan = transaction_if<activity_message::ActivityPlan>(outcome)) {
  125. if (plan->mutationDomain == activity_message::MutationDomain::entitySlots) {
  126. const bool joins = plan->delivery == activity_message::Delivery::joinNotifications;
  127. const bool validJoinIntent =
  128. plan->bindingIntent == activity_message::BindingIntent::preserveCurrent
  129. || plan->bindingIntent == activity_message::BindingIntent::publicTarget;
  130. if (joins && !validJoinIntent) {
  131. return false;
  132. }
  133. std::uint64_t bindingGeneration = 0;
  134. if (joins && !reserve_activity_binding_generation(bindingGeneration)) {
  135. return false;
  136. }
  137. if (joins && plan->bindingIntent == activity_message::BindingIntent::publicTarget
  138. && !retain_public(*plan, publication)) {
  139. return false;
  140. }
  141. // The commit consumes the plan, so the counts are taken from a copy of it.
  142. const slots::PendingMutation attempted = plan->entitySlotMutation;
  143. const bool committed = slots::commit(plan->entitySlotMutation);
  144. report_lease(attempted, committed);
  145. if (!committed) {
  146. discard_activity_publication(publication);
  147. return false;
  148. }
  149. // The keepalive only finds a link that is bound to a session. A link that allocated
  150. // its own session carries the same id, so this rebinds it to itself.
  151. if (joins && plan->sessionId != state::activity::kAbsentSessionId) {
  152. publication.hasActivitySessionBinding = true;
  153. if (plan->bindingIntent == activity_message::BindingIntent::preserveCurrent) {
  154. publication.preservesActivitySessionBinding = true;
  155. } else if (plan->bindingIntent != activity_message::BindingIntent::publicTarget) {
  156. discard_activity_publication(publication);
  157. return false;
  158. }
  159. publication.activity.bindingGeneration = bindingGeneration;
  160. }
  161. return true;
  162. }
  163. if (plan->mutationDomain == activity_message::MutationDomain::membership) {
  164. return state::activity::membership::commit(plan->membershipMutation);
  165. }
  166. // The retained patch epoch is connection state, so it commits nothing here.
  167. return plan->mutationDomain == activity_message::MutationDomain::patchEpoch;
  168. }
  169. if (auto* mutation = transaction_if<state::matchmaking::PendingMutation>(outcome)) {
  170. return state::matchmaking::commit(*mutation);
  171. }
  172. if (auto* transaction = transaction_if<EquipmentSwapTransaction>(outcome)) {
  173. const bool committed = state::commit_equipment_swap(transaction->pending);
  174. core::log::write(core::log::Channel::server,
  175. committed ? core::log::Level::debug : core::log::Level::warn,
  176. committed ? "ev=equip stage=transaction_commit result=ok"
  177. : "ev=equip stage=transaction_commit result=fail");
  178. return committed;
  179. }
  180. if (auto* transaction = transaction_if<ItemAcquisitionTransaction>(outcome)) {
  181. const bool committed = state::commit_item_acquisition(transaction->pending);
  182. core::log::write(core::log::Channel::server,
  183. committed ? core::log::Level::debug : core::log::Level::warn,
  184. committed ? "ev=acquire stage=transaction_commit result=ok"
  185. : "ev=acquire stage=transaction_commit result=fail");
  186. return committed;
  187. }
  188. if (auto* transaction = transaction_if<SocketPlugTransaction>(outcome)) {
  189. const bool committed = state::commit_socket_plug(transaction->pending);
  190. core::log::write(core::log::Channel::server,
  191. committed ? core::log::Level::debug : core::log::Level::warn,
  192. committed ? "ev=socket_plug stage=transaction_commit result=ok"
  193. : "ev=socket_plug stage=transaction_commit result=fail");
  194. return committed;
  195. }
  196. if (auto* transaction = transaction_if<ItemStateTransaction>(outcome)) {
  197. const bool committed = state::commit_item_state(transaction->pending);
  198. core::log::write(core::log::Channel::server,
  199. committed ? core::log::Level::debug : core::log::Level::warn,
  200. committed ? "ev=item_state stage=transaction_commit result=ok"
  201. : "ev=item_state stage=transaction_commit result=fail");
  202. return committed;
  203. }
  204. if (auto* transaction = transaction_if<ProfileItemAcquisitionTransaction>(outcome)) {
  205. const bool committed = state::commit_profile_item_acquisition(transaction->pending);
  206. core::log::write(core::log::Channel::server,
  207. committed ? core::log::Level::debug : core::log::Level::warn,
  208. committed ? "ev=profile_acquire stage=transaction_commit result=ok"
  209. : "ev=profile_acquire stage=transaction_commit result=fail");
  210. return committed;
  211. }
  212. if (auto* transaction = transaction_if<ItemDismantleTransaction>(outcome)) {
  213. const bool committed = state::commit_item_dismantle(transaction->pending);
  214. core::log::write(core::log::Channel::server,
  215. committed ? core::log::Level::debug : core::log::Level::warn,
  216. committed ? "ev=dismantle stage=transaction_commit result=ok"
  217. : "ev=dismantle stage=transaction_commit result=fail");
  218. return committed;
  219. }
  220. return true;
  221. }
  222. } // namespace sunrise::server::bap::encrypted::transactions