bap_route.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #include <Windows.h>
  2. #include <array>
  3. #include <atomic>
  4. #include <cstdio>
  5. #include <limits>
  6. #include "../../core/logging/log.h"
  7. #include "../../state/matchmaking/matchmaking_state.h"
  8. #include "internal.h"
  9. #include "runtime.h"
  10. namespace sunrise::server::bap {
  11. namespace {
  12. SRWLOCK g_lock{SRWLOCK_INIT};
  13. std::array<Session, kSessionCount> g_sessions{};
  14. Scratch g_scratch{};
  15. std::uint64_t g_accountGeneration{};
  16. /** Arms every other active peer after one shared-account transaction is published. */
  17. void publish_account_mutation(Session& origin) noexcept {
  18. origin.accountMutationPublished = false;
  19. g_accountGeneration = g_accountGeneration == (std::numeric_limits<std::uint64_t>::max)()
  20. ? 1
  21. : g_accountGeneration + 1;
  22. origin.accountGeneration = g_accountGeneration;
  23. origin.accountResyncGeneration = g_accountGeneration;
  24. origin.accountResyncArmed = false;
  25. std::size_t armed = 0;
  26. for (auto& peer : g_sessions) {
  27. if (&peer == &origin || peer.id == 0 || !peer.authenticated || !peer.queuez.family4Active) {
  28. continue;
  29. }
  30. peer.accountResyncGeneration = g_accountGeneration;
  31. peer.accountResyncArmed = true;
  32. ++armed;
  33. }
  34. std::array<char, core::log::kLineCapacity> line{};
  35. const int count = std::snprintf(line.data(),
  36. line.size(),
  37. "ev=queuez stage=peer_resync_arm result=ok generation=%llu "
  38. "origin=%u peers=%zu",
  39. static_cast<unsigned long long>(g_accountGeneration),
  40. origin.id,
  41. armed);
  42. if (count > 0) {
  43. core::log::write(core::log::Channel::server,
  44. core::log::Level::debug,
  45. {line.data(), static_cast<std::size_t>(count)});
  46. }
  47. }
  48. /** @param id Nonzero connection id. @return Matching open session, or null. */
  49. [[nodiscard]] Session* session_for(std::uint32_t id) noexcept {
  50. if (id == 0 || id > g_sessions.size()) {
  51. return nullptr;
  52. }
  53. auto& session = g_sessions[id - 1];
  54. return session.id == id ? &session : nullptr;
  55. }
  56. /** @param session Its secrets and identity are wiped. */
  57. void clear_session(Session& session) noexcept {
  58. SecureZeroMemory(&session, sizeof session);
  59. }
  60. /**
  61. * Releases an authenticated session's optional matchmaking context.
  62. * @param session Open session that may not have finished server hello.
  63. * @return True when there was no context, or the active generation was released.
  64. */
  65. [[nodiscard]] bool release_matchmaking_context(Session& session) noexcept {
  66. if (session.matchmakingContext.generation == state::matchmaking::kInvalidGeneration) {
  67. session.matchmakingContext = {};
  68. return true;
  69. }
  70. if (!state::matchmaking::release_context(session.matchmakingContext)) {
  71. return false;
  72. }
  73. session.matchmakingContext = {};
  74. return true;
  75. }
  76. /** @param id Session-slot id. @return True when the slot is opened. */
  77. [[nodiscard]] bool open_session(std::uint32_t id) noexcept {
  78. if (id == 0 || id > g_sessions.size()) {
  79. return false;
  80. }
  81. auto& session = g_sessions[id - 1];
  82. if (session.id != 0 && !release_matchmaking_context(session)) {
  83. return false;
  84. }
  85. clear_session(session);
  86. session.id = id;
  87. return true;
  88. }
  89. /** @param id Session-slot id. @return True when the slot is cleared. */
  90. [[nodiscard]] bool close_session(std::uint32_t id) noexcept {
  91. if (id == 0 || id > g_sessions.size()) {
  92. return false;
  93. }
  94. auto& session = g_sessions[id - 1];
  95. if (session.id != 0 && !release_matchmaking_context(session)) {
  96. return false;
  97. }
  98. clear_session(session);
  99. return true;
  100. }
  101. /**
  102. * Routes one validated frame through its connection-owned session.
  103. * @param request Frame event and caller-owned buffers.
  104. * @param response Receives encoded response size.
  105. * @return True when the frame is valid and its service is handled.
  106. */
  107. [[nodiscard]] bool consume_frame(const client::network::BapRequest& request,
  108. client::network::BapResponse& response) noexcept {
  109. middleware::bap::OuterFrame frame;
  110. if (!middleware::bap::parse_frame(request.frame, frame)) {
  111. return false;
  112. }
  113. auto* session = session_for(request.connectionId);
  114. if (session == nullptr) {
  115. return false;
  116. }
  117. bool handled = false;
  118. if (frame.frameType == middleware::bap::FrameType::encrypted) {
  119. handled = encrypted::consume(*session, g_scratch, frame, request.response, response.size);
  120. } else {
  121. handled = plaintext::consume(*session, g_scratch, frame, request.response, response.size);
  122. }
  123. if (!handled) {
  124. return false;
  125. }
  126. if (frame.frameType == middleware::bap::FrameType::encrypted
  127. && session->accountMutationPublished) {
  128. publish_account_mutation(*session);
  129. }
  130. // A frame response can carry one already-due push in the same bounded socket write.
  131. bool touchesScratch = true;
  132. std::size_t deferred = 0;
  133. if (response.size < request.response.size()
  134. && encrypted::consume_deferred(*session,
  135. g_scratch,
  136. request.response.subspan(response.size),
  137. deferred,
  138. touchesScratch)) {
  139. response.size += deferred;
  140. }
  141. return true;
  142. }
  143. /**
  144. * Services one timed poll for a session that may owe a deferred push.
  145. * @param request Poll event and caller-owned output buffer.
  146. * @param response Receives encoded notification size.
  147. * @param touchesScratch Set when the attempt reaches a scratch buffer.
  148. * @return True when a notification is published.
  149. */
  150. [[nodiscard]] bool consume_poll(const client::network::BapRequest& request,
  151. client::network::BapResponse& response,
  152. bool& touchesScratch) noexcept {
  153. static std::atomic_bool reported{false};
  154. if (!reported.exchange(true, std::memory_order_relaxed)) {
  155. core::log::write(
  156. core::log::Channel::server, core::log::Level::info, "ev=queuez stage=poll result=ok");
  157. }
  158. auto* session = session_for(request.connectionId);
  159. return session != nullptr
  160. && encrypted::consume_deferred(
  161. *session, g_scratch, request.response, response.size, touchesScratch);
  162. }
  163. } // namespace
  164. /** Applies one serialized BAP connection lifecycle event. */
  165. bool consume(const client::network::BapRequest& request,
  166. client::network::BapResponse& response) noexcept {
  167. response = {};
  168. AcquireSRWLockExclusive(&g_lock);
  169. bool success = false;
  170. // Polls report whether they reached scratch.
  171. bool touchesScratch = request.event != client::network::BapEvent::poll;
  172. // Hold the session lock across cryptographic counter reads and updates.
  173. switch (request.event) {
  174. case client::network::BapEvent::open:
  175. success = open_session(request.connectionId);
  176. break;
  177. case client::network::BapEvent::frame:
  178. success = consume_frame(request, response);
  179. break;
  180. case client::network::BapEvent::poll:
  181. success = consume_poll(request, response, touchesScratch);
  182. break;
  183. case client::network::BapEvent::close:
  184. success = close_session(request.connectionId);
  185. break;
  186. }
  187. // Decrypted frames can contain runtime-only keys or tokens, so scratch never outlives the call.
  188. if (touchesScratch) {
  189. SecureZeroMemory(&g_scratch, sizeof g_scratch);
  190. }
  191. ReleaseSRWLockExclusive(&g_lock);
  192. return success;
  193. }
  194. /** Securely erases every connection-owned nonce and transform buffer. */
  195. void shutdown() noexcept {
  196. AcquireSRWLockExclusive(&g_lock);
  197. for (auto& session : g_sessions) {
  198. if (session.id != 0
  199. && session.matchmakingContext.generation != state::matchmaking::kInvalidGeneration) {
  200. // State erases runtime descriptors before the opaque association is cleared.
  201. (void)state::matchmaking::release_context(session.matchmakingContext);
  202. }
  203. }
  204. SecureZeroMemory(g_sessions.data(), sizeof g_sessions);
  205. SecureZeroMemory(&g_scratch, sizeof g_scratch);
  206. g_accountGeneration = 0;
  207. ReleaseSRWLockExclusive(&g_lock);
  208. }
  209. } // namespace sunrise::server::bap