encrypted_runtime.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. #include <Windows.h>
  2. #include <algorithm>
  3. #include <array>
  4. #include <cstdio>
  5. #include "../../../core/logging/log.h"
  6. #include "../../../middleware/secure_channel/runtime.h"
  7. #include "../../../state/runtime/runtime.h"
  8. #include "../internal.h"
  9. #include "activity_transaction/activity_transaction_notifications.h"
  10. #include "bap_connection_publication.h"
  11. #include "internal.h"
  12. #include "push/activity/activity_roster_push.h"
  13. #include "queuez/queuez_outcome_staging.h"
  14. #include "transactions/service_outcome_commit.h"
  15. namespace sunrise::server::bap::encrypted {
  16. namespace {
  17. /**
  18. * Wipes the part of one scratch buffer that may hold written bytes.
  19. * @param buffer Lock-owned scratch storage.
  20. * @param size Largest prefix that may hold transformed bytes.
  21. */
  22. void clear_prefix(std::span<std::byte> buffer, std::size_t size) noexcept {
  23. SecureZeroMemory(buffer.data(), (std::min)(buffer.size(), size));
  24. }
  25. } // namespace
  26. /**
  27. * Authenticates and answers one supported encrypted post-bootstrap request.
  28. * @param session Connection-owned authentication and nonce state.
  29. * @param scratch Lock-owned transform buffers kept off the Client thread stack.
  30. * @param outer Validated encrypted outer frame.
  31. * @param response Caller-owned complete-frame storage.
  32. * @param written Receives encoded response bytes.
  33. * @return True when routing succeeds and any response fits, commits State, and publishes its nonce.
  34. */
  35. bool consume(Session& session,
  36. Scratch& scratch,
  37. const middleware::bap::OuterFrame& outer,
  38. std::span<std::byte> response,
  39. std::size_t& written) noexcept {
  40. written = 0;
  41. session.accountMutationPublished = false;
  42. if (!session.authenticated) {
  43. // Staying silent here looks the same as a decode fault, and both look like a dead link.
  44. core::log::write(core::log::Channel::server,
  45. core::log::Level::warn,
  46. "ev=bap stage=encrypted result=drop reason=unauthenticated");
  47. return false;
  48. }
  49. std::size_t plaintextSize = 0;
  50. const auto& bapState = state::bap();
  51. if (!middleware::secure_channel::open_frame(bapState.sessionKey,
  52. session.receiveNonce,
  53. outer.payload,
  54. scratch.plaintext,
  55. plaintextSize)) {
  56. const std::size_t possiblePlaintextSize =
  57. outer.payload.size() >= middleware::secure_channel::kFrameTagSize
  58. ? outer.payload.size() - middleware::secure_channel::kFrameTagSize
  59. : 0;
  60. clear_prefix(scratch.plaintext, possiblePlaintextSize);
  61. // The service is unreadable while the frame is sealed, so this line names no service.
  62. core::log::write(core::log::Channel::server,
  63. core::log::Level::warn,
  64. "ev=bap svc=none stage=decrypt result=fail");
  65. return false;
  66. }
  67. // Authentication consumes the receive nonce even when the inner service is unsupported.
  68. middleware::secure_channel::advance_nonce(session.receiveNonce);
  69. middleware::bap::RequestFrame frame;
  70. ServiceRoute route;
  71. std::size_t responseBodySize = 0;
  72. std::size_t framedSize = 0;
  73. ServiceOutcome outcome{};
  74. transactions::Publication publication{};
  75. queuez::SessionState nextQueuez = session.queuez;
  76. bool publishesQueuez = false;
  77. bool handled =
  78. middleware::bap::parse_request_payload(std::span(scratch.plaintext).first(plaintextSize),
  79. middleware::bap::FrameType::encrypted,
  80. frame)
  81. && routing::resolve(frame.messageId, route);
  82. if (!handled) {
  83. core::log::write(core::log::Channel::server,
  84. core::log::Level::warn,
  85. "ev=bap svc=none stage=parse result=fail");
  86. }
  87. const bool processesBody = handled && route.responseMode != ResponseMode::none;
  88. const bool sendsReply = handled && route.responseMode == ResponseMode::reply;
  89. // Pure one-way services consume only the authenticated receive nonce.
  90. if (processesBody
  91. && !body::process(route,
  92. session.queuez,
  93. session.activity,
  94. session.matchmakingContext,
  95. frame.body,
  96. scratch.responseBody,
  97. responseBodySize,
  98. outcome)) {
  99. diagnostics::report_failure(frame.messageId, "body");
  100. // A reply-mode service answers with an empty body instead of not at all. The Client
  101. // matches only the head of its pending ring, so one unanswered request jams that ring for
  102. // good and every later reply is rejected, which is worse than a thin reply.
  103. clear_prefix(scratch.responseBody, responseBodySize);
  104. responseBodySize = 0;
  105. outcome = {};
  106. handled = sendsReply;
  107. }
  108. if (handled && sendsReply) {
  109. handled = reply::encode(scratch,
  110. route,
  111. frame.taskId,
  112. bapState.sessionKey,
  113. session.sendNonce,
  114. std::span(scratch.responseBody).first(responseBodySize),
  115. framedSize);
  116. if (!handled) {
  117. diagnostics::report_failure(frame.messageId, "encode");
  118. }
  119. }
  120. // Stage every requested frame and check for caller room before committing State or the nonce.
  121. auto nextSendNonce = session.sendNonce;
  122. if (handled && sendsReply) {
  123. middleware::secure_channel::advance_nonce(nextSendNonce);
  124. }
  125. queuez::StagedPublication queuezPublication{};
  126. if (handled) {
  127. handled = queuez::stage_service_outcome(scratch,
  128. session.queuez,
  129. outcome,
  130. bapState.sessionKey,
  131. nextSendNonce,
  132. scratch.framed,
  133. framedSize,
  134. queuezPublication);
  135. if (handled && queuezPublication.hasState) {
  136. nextQueuez = queuezPublication.after;
  137. publishesQueuez = true;
  138. }
  139. if (!handled) {
  140. diagnostics::report_failure(frame.messageId, "stage");
  141. }
  142. }
  143. const auto* activityPlan = transaction_if<activity_message::ActivityPlan>(outcome);
  144. if (handled && activityPlan != nullptr) {
  145. handled = route.responseMode == ResponseMode::uncorrelatedPush;
  146. if (!handled) {
  147. diagnostics::report_failure(frame.messageId, "route");
  148. } else if (!activity_transaction::stage_notifications(session,
  149. scratch,
  150. *activityPlan,
  151. bapState.sessionKey,
  152. nextSendNonce,
  153. scratch.framed,
  154. framedSize)) {
  155. // The transaction still commits. A push that cannot be built is one lost message, and
  156. // dropping the commit with it would strand the client's reported state for the session.
  157. diagnostics::report_failure(frame.messageId, "notify");
  158. }
  159. }
  160. const bool mutatesAccount =
  161. outcome.hasChangeCharacter || outcome.hasSelectCharacter
  162. || transaction_if<EquipmentSwapTransaction>(outcome) != nullptr
  163. || transaction_if<SocketPlugTransaction>(outcome) != nullptr
  164. || transaction_if<ItemStateTransaction>(outcome) != nullptr
  165. || transaction_if<ItemAcquisitionTransaction>(outcome) != nullptr
  166. || transaction_if<ProfileItemAcquisitionTransaction>(outcome) != nullptr
  167. || transaction_if<ItemDismantleTransaction>(outcome) != nullptr;
  168. // State commits consume and clear their pending payloads. Retain only the small diagnostic
  169. // fields needed after publication; QueueZ after-images stay owned by the transaction variant.
  170. const auto* stagedSocket = transaction_if<SocketPlugTransaction>(outcome);
  171. const std::uint8_t socketLane = stagedSocket == nullptr ? 0 : stagedSocket->pending.socketLane;
  172. const std::uint16_t socketPlugDefinition =
  173. stagedSocket == nullptr ? 0 : stagedSocket->pending.plugDefinitionIndex;
  174. const std::uint8_t socketTargetBucket =
  175. stagedSocket == nullptr ? 0 : stagedSocket->pending.targetBucketId;
  176. const std::uint8_t socketPlugBucket =
  177. stagedSocket == nullptr ? 0 : stagedSocket->pending.plugBucketId;
  178. const auto* stagedItemState = transaction_if<ItemStateTransaction>(outcome);
  179. const std::uint64_t itemStateInstance =
  180. stagedItemState == nullptr ? 0 : stagedItemState->pending.targetInstanceSoid;
  181. const std::uint32_t itemStateFlags =
  182. stagedItemState == nullptr ? 0 : stagedItemState->pending.afterFlags;
  183. const auto* stagedProfile = transaction_if<ProfileItemAcquisitionTransaction>(outcome);
  184. const std::uint32_t profileDefinitionHash =
  185. stagedProfile == nullptr ? 0 : stagedProfile->pending.acquiredDefinitionHash;
  186. const std::int32_t profileQuantity =
  187. stagedProfile == nullptr ? 0 : stagedProfile->pending.acquiredQuantity;
  188. const bool profileActionSource =
  189. stagedProfile != nullptr && stagedProfile->pending.actionSource;
  190. const bool profileAppended = stagedProfile != nullptr && stagedProfile->pending.appended;
  191. // Committing the transaction clears the mutation the member key lives in, so the connection
  192. // fields are captured before the commit and published after it.
  193. const ConnectionFields connection = connection_fields(outcome);
  194. if (handled && processesBody) {
  195. // State changes become visible only after every requested frame and caller byte fit.
  196. handled = framedSize <= response.size() && transactions::commit(outcome, publication);
  197. if (!handled) {
  198. diagnostics::report_failure(frame.messageId, "commit");
  199. }
  200. if (handled) {
  201. std::copy_n(scratch.framed.begin(), framedSize, response.begin());
  202. written = framedSize;
  203. // The caller copy finishes before connection fields are published.
  204. session.sendNonce = nextSendNonce;
  205. if (publishesQueuez) {
  206. session.queuez = nextQueuez;
  207. }
  208. arm_repushes(session, queuezPublication);
  209. publish_connection_fields(session, publication, connection);
  210. // The caller copy is done, so what the staged roster body owes is settled here.
  211. push::activity::commit_staged_roster(session);
  212. commit_staged_advertisement(session);
  213. // Any delivered activity notification resets the client's silence timer. Delay the
  214. // fallback keepalive so this same request does not append a redundant second push.
  215. if (activityPlan != nullptr && framedSize != 0) {
  216. session.activityKeepaliveDueTick = GetTickCount64() + kActivityKeepaliveIntervalMs;
  217. }
  218. session.accountMutationPublished = mutatesAccount;
  219. if (transaction_if<EquipmentSwapTransaction>(outcome) != nullptr) {
  220. std::array<char, core::log::kLineCapacity> line{};
  221. const int count = std::snprintf(
  222. line.data(),
  223. line.size(),
  224. "ev=equip stage=output_publish result=ok framed_bytes=%zu queuez_published=%u "
  225. "family_version=%d family0_version=%d family3_version=%d",
  226. framedSize,
  227. static_cast<unsigned>(publishesQueuez),
  228. session.queuez.family4Version,
  229. session.queuez.family0Version,
  230. session.queuez.family3Version);
  231. if (count > 0) {
  232. core::log::write(core::log::Channel::server,
  233. core::log::Level::debug,
  234. {line.data(), static_cast<std::size_t>(count)});
  235. }
  236. }
  237. if (const auto* transaction = transaction_if<SocketPlugTransaction>(outcome)) {
  238. std::array<char, core::log::kLineCapacity> line{};
  239. const int count = std::snprintf(
  240. line.data(),
  241. line.size(),
  242. "ev=socket_plug stage=output_publish result=ok framed_bytes=%zu "
  243. "queuez_published=%u family_version=%d family0_version=%d "
  244. "family3_version=%d instance=0x%llX lane=%u "
  245. "plug_definition=%u target_bucket=%u plug_bucket=%u",
  246. framedSize,
  247. static_cast<unsigned>(publishesQueuez),
  248. session.queuez.family4Version,
  249. session.queuez.family0Version,
  250. session.queuez.family3Version,
  251. static_cast<unsigned long long>(transaction->update.targetInstanceSoid),
  252. static_cast<unsigned>(socketLane),
  253. static_cast<unsigned>(socketPlugDefinition),
  254. static_cast<unsigned>(socketTargetBucket),
  255. static_cast<unsigned>(socketPlugBucket));
  256. if (count > 0) {
  257. core::log::write(core::log::Channel::server,
  258. core::log::Level::debug,
  259. {line.data(), static_cast<std::size_t>(count)});
  260. }
  261. }
  262. if (const auto* transaction = transaction_if<ItemStateTransaction>(outcome)) {
  263. std::array<char, core::log::kLineCapacity> line{};
  264. const int count = std::snprintf(
  265. line.data(),
  266. line.size(),
  267. "ev=item_state stage=output_publish result=ok framed_bytes=%zu "
  268. "queuez_published=%u family_version=%d instance=0x%llX flags=0x%X",
  269. framedSize,
  270. static_cast<unsigned>(publishesQueuez),
  271. session.queuez.family4Version,
  272. static_cast<unsigned long long>(itemStateInstance),
  273. itemStateFlags);
  274. if (count > 0) {
  275. core::log::write(core::log::Channel::server,
  276. core::log::Level::debug,
  277. {line.data(), static_cast<std::size_t>(count)});
  278. }
  279. }
  280. if (const auto* transaction = transaction_if<ItemAcquisitionTransaction>(outcome)) {
  281. std::array<char, core::log::kLineCapacity> line{};
  282. const int count = std::snprintf(
  283. line.data(),
  284. line.size(),
  285. "ev=acquire stage=output_publish result=ok framed_bytes=%zu "
  286. "queuez_published=%u family_version=%d residents=%u instance=0x%llX",
  287. framedSize,
  288. static_cast<unsigned>(publishesQueuez),
  289. session.queuez.family4Version,
  290. static_cast<unsigned>(session.queuez.family4ResidentCount),
  291. static_cast<unsigned long long>(transaction->update.acquiredInstanceSoid));
  292. if (count > 0) {
  293. core::log::write(core::log::Channel::server,
  294. core::log::Level::debug,
  295. {line.data(), static_cast<std::size_t>(count)});
  296. }
  297. }
  298. if (const auto* transaction =
  299. transaction_if<ProfileItemAcquisitionTransaction>(outcome)) {
  300. std::array<char, core::log::kLineCapacity> line{};
  301. const int count = std::snprintf(
  302. line.data(),
  303. line.size(),
  304. "ev=profile_acquire stage=output_publish result=ok framed_bytes=%zu "
  305. "queuez_published=%u family_version=%d residents=%u definition_hash=0x%08X "
  306. "quantity=%d instance=0x%llX action_source=%u appended_row=%u "
  307. "appended_resident=%u",
  308. framedSize,
  309. static_cast<unsigned>(publishesQueuez),
  310. session.queuez.family4Version,
  311. static_cast<unsigned>(session.queuez.family4ResidentCount),
  312. profileDefinitionHash,
  313. profileQuantity,
  314. static_cast<unsigned long long>(transaction->update.acquiredInstanceSoid),
  315. static_cast<unsigned>(profileActionSource),
  316. static_cast<unsigned>(profileAppended),
  317. static_cast<unsigned>(transaction->update.appendedResident));
  318. if (count > 0) {
  319. core::log::write(core::log::Channel::server,
  320. core::log::Level::debug,
  321. {line.data(), static_cast<std::size_t>(count)});
  322. }
  323. }
  324. if (const auto* transaction = transaction_if<ItemDismantleTransaction>(outcome)) {
  325. std::array<char, core::log::kLineCapacity> line{};
  326. const int count = std::snprintf(
  327. line.data(),
  328. line.size(),
  329. "ev=dismantle stage=output_publish result=ok framed_bytes=%zu "
  330. "queuez_published=%u family_version=%d residents=%u instance=0x%llX",
  331. framedSize,
  332. static_cast<unsigned>(publishesQueuez),
  333. session.queuez.family4Version,
  334. static_cast<unsigned>(session.queuez.family4ResidentCount),
  335. static_cast<unsigned long long>(transaction->update.dismantledInstanceSoid));
  336. if (count > 0) {
  337. core::log::write(core::log::Channel::server,
  338. core::log::Level::debug,
  339. {line.data(), static_cast<std::size_t>(count)});
  340. }
  341. }
  342. }
  343. }
  344. if (!handled) {
  345. // The staged body is dropped, so its grant and its state byte go back for the next push.
  346. push::activity::discard_staged_roster(session);
  347. discard_staged_advertisement(session);
  348. }
  349. clear_prefix(scratch.plaintext, plaintextSize);
  350. clear_prefix(scratch.responseBody, responseBodySize);
  351. clear_prefix(scratch.framed, framedSize);
  352. outcome = {};
  353. SecureZeroMemory(&publication, sizeof publication);
  354. SecureZeroMemory(&queuezPublication, sizeof queuezPublication);
  355. if (handled) {
  356. core::log::write(core::log::Channel::server, core::log::Level::info, route.successEvent);
  357. }
  358. return handled;
  359. }
  360. } // namespace sunrise::server::bap::encrypted