bap_service_body.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. #include <array>
  2. #include <atomic>
  3. #include <cstdio>
  4. #include <limits>
  5. #include "../../../../core/logging/log.h"
  6. #include "../../../../middleware/bap/account_translation/account_translation_response.h"
  7. #include "../../../../middleware/bap/activity_host/activity_host_response.h"
  8. #include "../../../../middleware/bap/certificate.h"
  9. #include "../../../../middleware/bap/client_config/client_config_response.h"
  10. #include "../../../../middleware/bap/family_subscription.h"
  11. #include "../../../../middleware/bap/family_unsubscription.h"
  12. #include "../../../../middleware/bap/user_message/user_message_response.h"
  13. #include "../../../../middleware/encoding/byte_order.h"
  14. #include "../../../../middleware/web_service/messages/opcode501_request_codec.h"
  15. #include "../../../../middleware/web_service/messages/opcode502.h"
  16. #include "../../../../middleware/web_service/messages/opcode505/opcode505_codec.h"
  17. #include "../../../../state/activity/membership/activity_membership_query.h"
  18. #include "../../../../state/runtime/runtime.h"
  19. #include "../../../gameplay/group/group_host_sessions.h"
  20. #include "../../../web_service/opcode_routes.h"
  21. #include "../../../web_service/web_service_runtime.h"
  22. #include "../activity_host_manager/activity_host_manager_route.h"
  23. #include "../activity_message/activity_message_route.h"
  24. #include "../internal.h"
  25. #include "../matchmaking/matchmaking_route.h"
  26. #include "../queuez/queuez_state_validation.h"
  27. #include "state/investment/store_internal.h"
  28. namespace sunrise::server::bap::encrypted::body {
  29. namespace {
  30. /** Process-wide identity paired with the single account SOID. */
  31. std::atomic<std::uint64_t> g_translatedIdentity{0};
  32. /** Replaces an optimistic Web Service reply when its deferred transaction cannot be staged. */
  33. [[nodiscard]] bool refuse_web_action(const middleware::web_service::Message& message,
  34. std::span<std::byte> output,
  35. std::size_t& written) noexcept {
  36. // The refusal is the same record as the success, so it takes the opcode's own shape.
  37. middleware::web_service::ResponseShape shape{};
  38. sunrise::server::web_service::resolve_response_shape(message.opcode, shape);
  39. middleware::web_service::StatusResponse status{};
  40. status.code = 1;
  41. status.value = middleware::web_service::kNoFamily4Publication;
  42. return middleware::web_service::encode_response(message, shape, status, output, written);
  43. }
  44. /** Accepts only the first account-translation identity and its retries. */
  45. [[nodiscard]] bool pairs_identity(std::uint64_t identity) noexcept {
  46. std::uint64_t claimed = 0;
  47. // A repeat of the same identity still pairs: the peer re-asks until the flag sticks.
  48. return g_translatedIdentity.compare_exchange_strong(
  49. claimed, identity, std::memory_order_relaxed)
  50. || claimed == identity;
  51. }
  52. } // namespace
  53. /**
  54. * Processes the body for one authenticated service route.
  55. * @param route Service route data found earlier.
  56. * @param queuezState Queuez versions and residents set up by this BAP peer.
  57. * @param activity Exact ActivityClient generation owned by this BAP session.
  58. * @param matchmakingContext State-owned logical context for this BAP session.
  59. * @param requestBody Borrowed decrypted request body.
  60. * @param output Caller-owned response-body storage.
  61. * @param written Receives encoded body bytes.
  62. * @param outcome Receives one validated transport action or deferred State transaction.
  63. * @return True when the chosen body codec succeeds.
  64. */
  65. bool process(const ServiceRoute& route,
  66. const queuez::SessionState& queuezState,
  67. const ActivityClientBinding& activity,
  68. const RosterDecodeMap& rosterDecode,
  69. state::matchmaking::ContextHandle matchmakingContext,
  70. std::span<const std::byte> requestBody,
  71. std::span<std::byte> output,
  72. std::size_t& written,
  73. ServiceOutcome& outcome,
  74. std::span<const state::account::inventory::PresentedItemRow> presentation) noexcept {
  75. outcome = {};
  76. switch (route.bodyCodec) {
  77. case BodyCodec::empty:
  78. written = 0;
  79. return true;
  80. case BodyCodec::accountTranslationResponse: {
  81. const state::AccountState account = state::account_snapshot();
  82. std::uint64_t identity = 0;
  83. server::gameplay::group::HostSessionBinding host{};
  84. const bool parsed =
  85. middleware::bap::account_translation::request_identity(requestBody, identity);
  86. // The primary host asks with identity zero and receives the live region session. Remote
  87. // activity rows ask by activity-session id. Only other nonzero identities pair to account.
  88. const std::uint64_t liveSession =
  89. state::activity::membership::live_region_session(state::activity::kAbsentSessionId);
  90. const bool zeroHandle =
  91. parsed && identity == 0 && liveSession != state::activity::kAbsentSessionId;
  92. const bool logicalHost =
  93. parsed && identity != 0
  94. && server::gameplay::group::host_session_for_activity(identity, host)
  95. && host.target.sessionId == identity;
  96. const bool accountIdentity =
  97. parsed && identity != 0 && !logicalHost && pairs_identity(identity);
  98. const std::uint64_t soid = zeroHandle ? liveSession
  99. : logicalHost ? host.target.sessionId
  100. : accountIdentity ? account.primarySoid
  101. : 0;
  102. std::array<char, core::log::kLineCapacity> line{};
  103. const int count = std::snprintf(line.data(),
  104. line.size(),
  105. "ev=queuez stage=translate result=%s identity=0x%016llX "
  106. "soid=0x%016llX",
  107. zeroHandle ? "paired kind=zero_handle"
  108. : logicalHost ? "paired kind=logical_host"
  109. : accountIdentity ? "paired kind=account"
  110. : "unpaired",
  111. static_cast<unsigned long long>(identity),
  112. static_cast<unsigned long long>(soid));
  113. if (count > 0) {
  114. core::log::write(core::log::Channel::server,
  115. core::log::Level::info,
  116. {line.data(), static_cast<std::size_t>(count)});
  117. }
  118. return middleware::bap::account_translation::encode_response(
  119. requestBody, soid, output, written);
  120. }
  121. case BodyCodec::activityHostManagerResponse: {
  122. auto* allocation = emplace_transaction<state::activity::PendingAllocation>(outcome);
  123. if (allocation == nullptr) {
  124. return false;
  125. }
  126. bool hasAllocation = false;
  127. const bool encoded = activity_host_manager::encode_response(
  128. requestBody, output, written, *allocation, hasAllocation);
  129. if (!encoded || !hasAllocation) {
  130. clear_transaction(outcome);
  131. }
  132. return encoded;
  133. }
  134. case BodyCodec::activityMessageRequest: {
  135. written = 0;
  136. auto* plan = emplace_transaction<activity_message::ActivityPlan>(outcome);
  137. if (plan == nullptr) {
  138. return false;
  139. }
  140. bool hasTransaction = false;
  141. const bool processed =
  142. activity_message::process(activity, rosterDecode, requestBody, *plan, hasTransaction);
  143. if (!processed || !hasTransaction) {
  144. clear_transaction(outcome);
  145. }
  146. return processed;
  147. }
  148. case BodyCodec::activityHostResponse: {
  149. const state::SignOnState& signOn = state::sign_on();
  150. return middleware::bap::activity_host::encode_response(
  151. requestBody, signOn.relayAddress, signOn.relayPort, output, written);
  152. }
  153. case BodyCodec::clientConfigResponse:
  154. return middleware::bap::client_config::encode_minimal_response(output, written);
  155. case BodyCodec::familySubscription: {
  156. written = 0;
  157. outcome.hasSubscription =
  158. middleware::bap::family_subscription::parse(requestBody, outcome.subscription);
  159. return outcome.hasSubscription;
  160. }
  161. case BodyCodec::familyUnsubscription: {
  162. written = 0;
  163. outcome.hasUnsubscription =
  164. middleware::bap::family_unsubscription::parse(requestBody, outcome.unsubscription);
  165. return outcome.hasUnsubscription;
  166. }
  167. case BodyCodec::matchmakingResponse: {
  168. auto* mutation = emplace_transaction<state::matchmaking::PendingMutation>(outcome);
  169. if (mutation == nullptr) {
  170. return false;
  171. }
  172. bool hasMutation = false;
  173. const bool encoded = matchmaking::encode_response(
  174. matchmakingContext, requestBody, output, written, *mutation, hasMutation);
  175. if (!hasMutation) {
  176. clear_transaction(outcome);
  177. }
  178. return encoded;
  179. }
  180. case BodyCodec::steamCertificate:
  181. return middleware::bap::certificate::encode_response(requestBody, output, written);
  182. case BodyCodec::userMessageResponse:
  183. return middleware::bap::user_message::encode_minimal_response(output, written);
  184. case BodyCodec::webService: {
  185. middleware::web_service::Message message;
  186. if (middleware::web_service::parse_request(requestBody, message)
  187. && message.opcode == middleware::web_service::messages::opcode505::kOpcode) {
  188. auto* changeCharacter = emplace_transaction<queuez::ChangeCharacter>(outcome);
  189. if (!middleware::web_service::messages::opcode505::parse_request(message)
  190. || changeCharacter == nullptr
  191. || !queuez::stage_change_character(queuezState, *changeCharacter)
  192. || !middleware::web_service::messages::opcode505::encode_response(
  193. message, changeCharacter->after.family4Version, output, written)) {
  194. core::log::write(core::log::Channel::server,
  195. core::log::Level::warn,
  196. "ev=ws505 stage=change result=fail");
  197. clear_transaction(outcome);
  198. return refuse_web_action(message, output, written);
  199. }
  200. outcome.hasChangeCharacter = true;
  201. return true;
  202. }
  203. // Character deletion. A refusal still answers with the status pair: the roster screen
  204. // waits on the echoed transaction id, so a missing reply hangs it for the rest of the run.
  205. if (middleware::web_service::parse_request(requestBody, message)
  206. && message.opcode == middleware::web_service::messages::opcode502::kOpcode) {
  207. const auto refuse = [&]() noexcept {
  208. middleware::web_service::StatusResponse status{};
  209. status.code = middleware::web_service::kRefusedStatusCode;
  210. // A refused delete stages nothing, so the version wait must be told not to wait.
  211. status.value = middleware::web_service::kNoFamily4Publication;
  212. return middleware::web_service::encode_response(
  213. message,
  214. middleware::web_service::ResponseShape::statusPair,
  215. status,
  216. output,
  217. written);
  218. };
  219. middleware::web_service::messages::opcode502::Request deletion{};
  220. if (!middleware::web_service::messages::opcode502::parse_request(message, deletion)
  221. || deletion.characterSoid == 0
  222. || !state::delete_character(deletion.characterSoid)) {
  223. core::log::write(core::log::Channel::server,
  224. core::log::Level::warn,
  225. "ev=ws502 stage=delete result=fail");
  226. return refuse();
  227. }
  228. // The account is already changed and written (delete_character persists through the
  229. // investment store internally), but the caller can still drop the frame afterwards --
  230. // a stale entity lease, a refused commit, or a response that does not fit -- and the
  231. // outcome flag at the end of this file only lands on the handled path. Without this
  232. // the character is gone from disk while the client keeps a roster that names it.
  233. sunrise::server::bap::arm_account_resync_everywhere();
  234. // The roster shrank and delete_character has already moved the selection onto a
  235. // survivor, so the client needs the Family-4 character object moved with it. Opcode
  236. // 502 feeds the Client's Family-4 version wait: the reply has to name the revision the
  237. // wait should expect, the staged one when a publication is coming and the no-wait
  238. // constant when none is -- left at its default the field reads as revision zero, which
  239. // the wait blocks on, and the roster screen never finishes the delete it just asked for.
  240. std::int32_t deletionVersion = middleware::web_service::kNoFamily4Publication;
  241. const std::uint64_t survivorSoid =
  242. state::account::selected_character_soid(state::account_snapshot());
  243. auto* deletionSelect = emplace_transaction<queuez::SelectCharacter>(outcome);
  244. if (survivorSoid != 0 && deletionSelect != nullptr
  245. && queuez::stage_select_character(queuezState, survivorSoid, *deletionSelect)) {
  246. outcome.hasSelectCharacter = true;
  247. deletionVersion = deletionSelect->after.family4Version;
  248. } else {
  249. clear_transaction(outcome);
  250. sunrise::server::bap::arm_account_resync_everywhere();
  251. }
  252. core::log::write(
  253. core::log::Channel::server, core::log::Level::info, "ev=ws502 stage=delete result=ok");
  254. middleware::web_service::StatusResponse deletionStatus{};
  255. deletionStatus.value = deletionVersion;
  256. return middleware::web_service::encode_response(
  257. message,
  258. middleware::web_service::ResponseShape::statusPair,
  259. deletionStatus,
  260. output,
  261. written);
  262. }
  263. // Character creation. Both outcomes answer in opcode 501's own response shape, which
  264. // carries the new SOID: the generic status pair omits that field and the Client fatally
  265. // disconnects trying to decode it.
  266. if (middleware::web_service::parse_request(requestBody, message)
  267. && message.opcode == middleware::web_service::messages::opcode501::kOpcode) {
  268. middleware::web_service::StatusResponse creationRefusal{};
  269. creationRefusal.code = middleware::web_service::kRefusedStatusCode;
  270. creationRefusal.value = middleware::web_service::kNoFamily4Publication;
  271. middleware::web_service::messages::opcode501::Request creation{};
  272. std::uint64_t characterSoid = 0;
  273. if (!middleware::web_service::messages::opcode501::parse_request(message, creation)
  274. || !state::create_character(
  275. creation.characterClass, creation.gender, creation.race, characterSoid)) {
  276. core::log::write(core::log::Channel::server,
  277. core::log::Level::warn,
  278. "ev=ws501 stage=create result=fail");
  279. return middleware::web_service::messages::opcode501::encode_response(
  280. message, creationRefusal, 0, output, written);
  281. }
  282. // As in the delete branch: the account is already changed and written, but the caller
  283. // can still drop the frame, and the outcome flag only lands on the handled path.
  284. sunrise::server::bap::arm_account_resync_everywhere();
  285. // create_character marks the new character selected, and the client is waiting for the
  286. // Family-4 character object to move onto it. A change-character patch cannot do that
  287. // here: it refuses unless family three is in its normal phase, and the creation flow
  288. // leaves it outside that phase, so the client would wait forever on a publication that
  289. // was never staged. Selecting is both ungated and what the client actually asked for.
  290. std::int32_t creationVersion = middleware::web_service::kNoFamily4Publication;
  291. auto* creationSelect = emplace_transaction<queuez::SelectCharacter>(outcome);
  292. if (creationSelect != nullptr
  293. && queuez::stage_select_character(queuezState, characterSoid, *creationSelect)) {
  294. outcome.hasSelectCharacter = true;
  295. creationVersion = creationSelect->after.family4Version;
  296. } else {
  297. clear_transaction(outcome);
  298. sunrise::server::bap::arm_account_resync_everywhere();
  299. }
  300. core::log::write(core::log::Channel::server,
  301. core::log::Level::info,
  302. "ev=ws501 stage=create result=ok");
  303. middleware::web_service::StatusResponse creationStatus{};
  304. creationStatus.value = creationVersion;
  305. return middleware::web_service::messages::opcode501::encode_response(
  306. message, creationStatus, characterSoid, output, written);
  307. }
  308. state::investment::store::Transaction investmentTransaction;
  309. if (!investmentTransaction.ready()) {
  310. return false;
  311. }
  312. web_service::Outcome webOutcome;
  313. if (!sunrise::server::web_service::consume(
  314. requestBody, output, written, webOutcome, presentation)) {
  315. return false;
  316. }
  317. if (webOutcome.hasTitleEquip) {
  318. // Promise the revision that refreshes the Seals widget's action binding.
  319. if (queuezState.family4Version != (std::numeric_limits<std::int32_t>::max)()) {
  320. middleware::web_service::StatusResponse status{};
  321. status.value = queuezState.family4Version + 1;
  322. if (!middleware::web_service::encode_response(
  323. message,
  324. middleware::web_service::ResponseShape::statusPair,
  325. status,
  326. output,
  327. written)) {
  328. core::log::write(core::log::Channel::server,
  329. core::log::Level::warn,
  330. "ev=title_equip stage=response result=fail");
  331. sunrise::server::bap::arm_account_resync_everywhere();
  332. return false;
  333. }
  334. }
  335. sunrise::server::bap::arm_account_resync_everywhere();
  336. }
  337. outcome.hasSubscription = webOutcome.hasSubscription;
  338. outcome.hasRecordClaim = webOutcome.hasRecordClaim;
  339. outcome.hasArtifactReset = webOutcome.hasArtifactReset;
  340. outcome.artifactReset = webOutcome.artifactReset;
  341. outcome.subscription = webOutcome.subscription;
  342. const auto* equipmentSwap =
  343. web_service::mutation_if<state::PendingEquipmentSwap>(webOutcome);
  344. const auto* subclassSelection =
  345. web_service::mutation_if<state::PendingSubclassSelection>(webOutcome);
  346. const auto* socketPlug = web_service::mutation_if<state::PendingSocketPlug>(webOutcome);
  347. const auto* itemState = web_service::mutation_if<state::PendingItemState>(webOutcome);
  348. const auto* artifactPurchase =
  349. web_service::mutation_if<state::PendingArtifactPurchase>(webOutcome);
  350. const auto* itemAcquisition =
  351. web_service::mutation_if<state::PendingItemAcquisition>(webOutcome);
  352. const auto* profileItemAcquisition =
  353. web_service::mutation_if<state::PendingProfileItemAcquisition>(webOutcome);
  354. const auto* itemDismantle =
  355. web_service::mutation_if<state::PendingItemDismantle>(webOutcome);
  356. const auto* recordRewardGrant =
  357. web_service::mutation_if<state::PendingRecordRewardGrant>(webOutcome);
  358. auto* seasonPassReward =
  359. web_service::mutation_if<state::PendingSeasonPassReward>(webOutcome);
  360. if (webOutcome.hasArtifactReset) {
  361. if (queuezState.family4Version == (std::numeric_limits<std::int32_t>::max)()) {
  362. return refuse_web_action(message, output, written);
  363. }
  364. middleware::web_service::StatusResponse status{};
  365. status.value = queuezState.family4Version + 1;
  366. if (!middleware::web_service::encode_response(
  367. message,
  368. middleware::web_service::ResponseShape::statusPairWithBool,
  369. status,
  370. output,
  371. written)) {
  372. return false;
  373. }
  374. }
  375. if (artifactPurchase != nullptr) {
  376. auto* transaction = emplace_transaction<ArtifactPurchaseTransaction>(outcome);
  377. if (transaction == nullptr
  378. || !queuez::stage_equipment_swap(
  379. queuezState, artifactPurchase->characterSoid, transaction->update)) {
  380. core::log::write(core::log::Channel::server,
  381. core::log::Level::warn,
  382. "ev=ws901 stage=queuez_preflight result=fail");
  383. clear_transaction(outcome);
  384. return refuse_web_action(message, output, written);
  385. }
  386. middleware::web_service::StatusResponse status{};
  387. status.value = transaction->update.after.family4Version;
  388. status.trailingBool = true;
  389. if (!middleware::web_service::encode_response(
  390. message,
  391. middleware::web_service::ResponseShape::statusPairWithBool,
  392. status,
  393. output,
  394. written)) {
  395. clear_transaction(outcome);
  396. return refuse_web_action(message, output, written);
  397. }
  398. transaction->pending =
  399. web_service::take_mutation<state::PendingArtifactPurchase>(webOutcome);
  400. }
  401. const auto* settingsUpdate =
  402. web_service::mutation_if<state::PendingSettingsUpdate>(webOutcome);
  403. if (settingsUpdate != nullptr) {
  404. // WS-701 promises no immediate QueueZ after-image, so State alone is delayed.
  405. if (emplace_transaction<state::PendingSettingsUpdate>(outcome, *settingsUpdate)
  406. == nullptr) {
  407. return refuse_web_action(message, output, written);
  408. }
  409. }
  410. if (equipmentSwap != nullptr) {
  411. // Promise the Family-4 revision carrying this optimistic equip.
  412. auto* transaction = emplace_transaction<EquipmentSwapTransaction>(outcome);
  413. if (transaction == nullptr
  414. || !queuez::stage_equipment_swap(
  415. queuezState, equipmentSwap->characterSoid, transaction->update)) {
  416. core::log::write(core::log::Channel::server,
  417. core::log::Level::warn,
  418. "ev=ws403 stage=queuez_preflight result=fail");
  419. clear_transaction(outcome);
  420. return refuse_web_action(message, output, written);
  421. }
  422. middleware::web_service::StatusResponse status{};
  423. status.value = transaction->update.after.family4Version;
  424. if (!middleware::web_service::encode_response(
  425. message,
  426. middleware::web_service::ResponseShape::statusPair,
  427. status,
  428. output,
  429. written)) {
  430. core::log::write(core::log::Channel::server,
  431. core::log::Level::warn,
  432. "ev=ws403 stage=response result=fail");
  433. clear_transaction(outcome);
  434. return refuse_web_action(message, output, written);
  435. }
  436. transaction->pending =
  437. web_service::take_mutation<state::PendingEquipmentSwap>(webOutcome);
  438. }
  439. if (subclassSelection != nullptr) {
  440. // Promise the Family-4 revision carrying the subclass socket change.
  441. auto* transaction = emplace_transaction<SubclassSelectionTransaction>(outcome);
  442. if (transaction == nullptr
  443. || !queuez::stage_subclass_selection(queuezState,
  444. subclassSelection->accountSoid,
  445. subclassSelection->characterSoid,
  446. subclassSelection->subclassInstanceSoid,
  447. transaction->update)) {
  448. core::log::write(core::log::Channel::server,
  449. core::log::Level::warn,
  450. "ev=subclass_select stage=queuez_preflight result=fail");
  451. clear_transaction(outcome);
  452. return refuse_web_action(message, output, written);
  453. }
  454. middleware::web_service::StatusResponse status{};
  455. status.value = transaction->update.after.family4Version;
  456. if (!middleware::web_service::encode_response(
  457. message,
  458. middleware::web_service::ResponseShape::statusPair,
  459. status,
  460. output,
  461. written)) {
  462. core::log::write(core::log::Channel::server,
  463. core::log::Level::warn,
  464. "ev=subclass_select stage=response result=fail");
  465. clear_transaction(outcome);
  466. return refuse_web_action(message, output, written);
  467. }
  468. transaction->pending =
  469. web_service::take_mutation<state::PendingSubclassSelection>(webOutcome);
  470. }
  471. if (socketPlug != nullptr) {
  472. // Promise the Family-4 revision carrying the changed item instance.
  473. auto* transaction = emplace_transaction<SocketPlugTransaction>(outcome);
  474. if (transaction == nullptr
  475. || !queuez::stage_socket_plug(queuezState,
  476. socketPlug->accountSoid,
  477. socketPlug->characterSoid,
  478. socketPlug->targetInstanceSoid,
  479. socketPlug->profileChanged,
  480. transaction->update)) {
  481. core::log::write(core::log::Channel::server,
  482. core::log::Level::warn,
  483. "ev=socket_plug stage=queuez_preflight result=fail");
  484. clear_transaction(outcome);
  485. return refuse_web_action(message, output, written);
  486. }
  487. middleware::web_service::StatusResponse status{};
  488. status.value = transaction->update.after.family4Version;
  489. if (!middleware::web_service::encode_response(
  490. message,
  491. middleware::web_service::ResponseShape::statusPair,
  492. status,
  493. output,
  494. written)) {
  495. core::log::write(core::log::Channel::server,
  496. core::log::Level::warn,
  497. "ev=socket_plug stage=response result=fail");
  498. clear_transaction(outcome);
  499. return refuse_web_action(message, output, written);
  500. }
  501. transaction->pending = web_service::take_mutation<state::PendingSocketPlug>(webOutcome);
  502. }
  503. if (itemState != nullptr) {
  504. // Promise the Family-4 revision carrying the changed row flags.
  505. auto* transaction = emplace_transaction<ItemStateTransaction>(outcome);
  506. if (transaction == nullptr
  507. || !queuez::stage_equipment_swap(
  508. queuezState, itemState->characterSoid, transaction->update)) {
  509. core::log::write(core::log::Channel::server,
  510. core::log::Level::warn,
  511. "ev=item_state stage=queuez_preflight result=fail");
  512. clear_transaction(outcome);
  513. return refuse_web_action(message, output, written);
  514. }
  515. middleware::web_service::StatusResponse status{};
  516. status.value = transaction->update.after.family4Version;
  517. if (!middleware::web_service::encode_response(
  518. message,
  519. middleware::web_service::ResponseShape::statusPair,
  520. status,
  521. output,
  522. written)) {
  523. core::log::write(core::log::Channel::server,
  524. core::log::Level::warn,
  525. "ev=item_state stage=response result=fail");
  526. clear_transaction(outcome);
  527. return refuse_web_action(message, output, written);
  528. }
  529. transaction->pending = web_service::take_mutation<state::PendingItemState>(webOutcome);
  530. }
  531. if (itemAcquisition != nullptr) {
  532. // Promise the revision adding both the inventory row and resident instance.
  533. auto* transaction = emplace_transaction<ItemAcquisitionTransaction>(outcome);
  534. if (transaction == nullptr
  535. || !queuez::stage_item_acquisition(queuezState,
  536. itemAcquisition->accountSoid,
  537. itemAcquisition->characterSoid,
  538. itemAcquisition->acquiredInstanceSoid,
  539. itemAcquisition->updates_account(),
  540. transaction->update)) {
  541. core::log::write(core::log::Channel::server,
  542. core::log::Level::warn,
  543. "ev=acquire stage=queuez_preflight result=fail");
  544. clear_transaction(outcome);
  545. return refuse_web_action(message, output, written);
  546. }
  547. middleware::web_service::StatusResponse status{};
  548. status.value = transaction->update.after.family4Version;
  549. if (!middleware::web_service::encode_response(
  550. message,
  551. middleware::web_service::ResponseShape::statusPair,
  552. status,
  553. output,
  554. written)) {
  555. core::log::write(core::log::Channel::server,
  556. core::log::Level::warn,
  557. "ev=acquire stage=response result=fail");
  558. clear_transaction(outcome);
  559. return refuse_web_action(message, output, written);
  560. }
  561. transaction->pending =
  562. web_service::take_mutation<state::PendingItemAcquisition>(webOutcome);
  563. }
  564. if (profileItemAcquisition != nullptr) {
  565. // Actionable profile stacks may add a resident in the same revision.
  566. auto* transaction = emplace_transaction<ProfileItemAcquisitionTransaction>(outcome);
  567. if (transaction == nullptr
  568. || !queuez::stage_profile_item_acquisition(
  569. queuezState,
  570. profileItemAcquisition->accountSoid,
  571. profileItemAcquisition->acquiredInstanceSoid,
  572. profileItemAcquisition->actionSource,
  573. profileItemAcquisition->appended,
  574. transaction->update)) {
  575. core::log::write(core::log::Channel::server,
  576. core::log::Level::warn,
  577. "ev=profile_acquire stage=queuez_preflight result=fail");
  578. clear_transaction(outcome);
  579. return refuse_web_action(message, output, written);
  580. }
  581. middleware::web_service::StatusResponse status{};
  582. status.value = transaction->update.after.family4Version;
  583. if (!middleware::web_service::encode_response(
  584. message,
  585. middleware::web_service::ResponseShape::statusPair,
  586. status,
  587. output,
  588. written)) {
  589. core::log::write(core::log::Channel::server,
  590. core::log::Level::warn,
  591. "ev=profile_acquire stage=response result=fail");
  592. clear_transaction(outcome);
  593. return refuse_web_action(message, output, written);
  594. }
  595. transaction->pending =
  596. web_service::take_mutation<state::PendingProfileItemAcquisition>(webOutcome);
  597. }
  598. if (itemDismantle != nullptr) {
  599. // Promise the revision carrying the character update and resident release.
  600. auto* transaction = emplace_transaction<ItemDismantleTransaction>(outcome);
  601. if (transaction == nullptr
  602. || !queuez::stage_item_dismantle(queuezState,
  603. itemDismantle->accountSoid,
  604. itemDismantle->characterSoid,
  605. itemDismantle->dismantledInstanceSoid,
  606. itemDismantle->profileChanged,
  607. transaction->update)) {
  608. core::log::write(core::log::Channel::server,
  609. core::log::Level::warn,
  610. "ev=dismantle stage=queuez_preflight result=fail");
  611. clear_transaction(outcome);
  612. return refuse_web_action(message, output, written);
  613. }
  614. middleware::web_service::StatusResponse status{};
  615. status.value = transaction->update.after.family4Version;
  616. if (!middleware::web_service::encode_response(
  617. message,
  618. middleware::web_service::ResponseShape::statusPair,
  619. status,
  620. output,
  621. written)) {
  622. core::log::write(core::log::Channel::server,
  623. core::log::Level::warn,
  624. "ev=dismantle stage=response result=fail");
  625. clear_transaction(outcome);
  626. return refuse_web_action(message, output, written);
  627. }
  628. transaction->pending =
  629. web_service::take_mutation<state::PendingItemDismantle>(webOutcome);
  630. }
  631. if (recordRewardGrant != nullptr) {
  632. auto* transaction = emplace_transaction<RecordRewardGrantTransaction>(outcome);
  633. std::array<std::uint64_t, state::kRecordRewardGrantCapacity> residents{};
  634. std::size_t residentCount = 0;
  635. for (std::size_t index = 0; index < recordRewardGrant->rewardCount; ++index) {
  636. const auto& reward = recordRewardGrant->rewards[index];
  637. if (reward.kind == state::RecordRewardKind::characterInstance
  638. || reward.appendedProfileResident) {
  639. residents[residentCount++] = reward.instanceSoid;
  640. }
  641. }
  642. const bool staged =
  643. transaction != nullptr
  644. && queuez::stage_record_reward_grant(queuezState,
  645. recordRewardGrant->accountSoid,
  646. recordRewardGrant->characterSoid,
  647. std::span(residents).first(residentCount),
  648. transaction->update);
  649. if (!staged) {
  650. core::log::write(core::log::Channel::server,
  651. core::log::Level::warn,
  652. "ev=ws1801 stage=reward_preflight result=fail");
  653. clear_transaction(outcome);
  654. return refuse_web_action(message, output, written);
  655. }
  656. middleware::web_service::StatusResponse status{};
  657. status.value = transaction->update.after.family4Version;
  658. if (!middleware::web_service::encode_response(
  659. message,
  660. middleware::web_service::ResponseShape::statusPair,
  661. status,
  662. output,
  663. written)) {
  664. core::log::write(core::log::Channel::server,
  665. core::log::Level::warn,
  666. "ev=ws1801 stage=reward_response result=fail");
  667. clear_transaction(outcome);
  668. return refuse_web_action(message, output, written);
  669. }
  670. transaction->pending =
  671. web_service::take_mutation<state::PendingRecordRewardGrant>(webOutcome);
  672. }
  673. if (seasonPassReward != nullptr) {
  674. auto* transaction = emplace_transaction<SeasonPassRewardTransaction>(outcome);
  675. if (transaction == nullptr) {
  676. core::log::write(core::log::Channel::server,
  677. core::log::Level::warn,
  678. "ev=ws2400 stage=reward_preflight result=fail");
  679. return refuse_web_action(message, output, written);
  680. }
  681. bool staged = false;
  682. std::int32_t stagedVersion = 0;
  683. if (const auto* itemGrant =
  684. std::get_if<state::PendingItemAcquisition>(&seasonPassReward->grant)) {
  685. auto& update = transaction->update.emplace<queuez::ItemAcquisition>();
  686. staged = queuez::stage_item_acquisition(queuezState,
  687. itemGrant->accountSoid,
  688. itemGrant->characterSoid,
  689. itemGrant->acquiredInstanceSoid,
  690. true,
  691. update);
  692. stagedVersion = update.after.family4Version;
  693. } else if (const auto* profileGrant = std::get_if<state::PendingProfileItemAcquisition>(
  694. &seasonPassReward->grant)) {
  695. auto& update = transaction->update.emplace<queuez::ProfileItemAcquisition>();
  696. staged = queuez::stage_profile_item_acquisition(queuezState,
  697. profileGrant->accountSoid,
  698. profileGrant->acquiredInstanceSoid,
  699. profileGrant->actionSource,
  700. profileGrant->appended,
  701. update);
  702. stagedVersion = update.after.family4Version;
  703. } else if (const auto* bundle =
  704. std::get_if<state::PendingDirectItemBundle>(&seasonPassReward->grant)) {
  705. staged = queuez::stage_direct_item_bundle(queuezState,
  706. bundle->accountSoid,
  707. bundle->characterSoid,
  708. bundle->firstInstanceSoid,
  709. bundle->itemCount,
  710. stagedVersion);
  711. } else if (const auto* resources =
  712. std::get_if<state::PendingRecordRewardGrant>(&seasonPassReward->grant)) {
  713. auto& update = transaction->update.emplace<queuez::RecordRewardGrant>();
  714. staged = queuez::stage_record_reward_grant(
  715. queuezState, resources->accountSoid, resources->characterSoid, {}, update);
  716. stagedVersion = update.after.family4Version;
  717. }
  718. if (!staged) {
  719. core::log::write(core::log::Channel::server,
  720. core::log::Level::warn,
  721. "ev=ws2400 stage=reward_preflight result=fail");
  722. clear_transaction(outcome);
  723. return refuse_web_action(message, output, written);
  724. }
  725. middleware::web_service::StatusResponse status{};
  726. status.value = stagedVersion;
  727. if (!middleware::web_service::encode_response(
  728. message,
  729. middleware::web_service::ResponseShape::statusPair,
  730. status,
  731. output,
  732. written)) {
  733. core::log::write(core::log::Channel::server,
  734. core::log::Level::warn,
  735. "ev=ws2400 stage=response result=fail");
  736. clear_transaction(outcome);
  737. return refuse_web_action(message, output, written);
  738. }
  739. transaction->pending =
  740. web_service::take_mutation<state::PendingSeasonPassReward>(webOutcome);
  741. }
  742. if (webOutcome.hasRecordClaim && !has_transaction(outcome)
  743. && queuezState.family4Version != (std::numeric_limits<std::int32_t>::max)()) {
  744. // A plain claim is made authoritative by the following account resync.
  745. middleware::web_service::StatusResponse status{};
  746. status.value = queuezState.family4Version + 1;
  747. if (!middleware::web_service::encode_response(
  748. message,
  749. middleware::web_service::ResponseShape::statusPair,
  750. status,
  751. output,
  752. written)) {
  753. core::log::write(core::log::Channel::server,
  754. core::log::Level::warn,
  755. "ev=ws1801 stage=response result=fail");
  756. sunrise::server::bap::arm_account_resync_everywhere();
  757. return false;
  758. }
  759. }
  760. // Every pick is answered, the current character included; character select waits on it.
  761. if (webOutcome.hasSelectedCharacter) {
  762. auto* selectCharacter = emplace_transaction<queuez::SelectCharacter>(outcome);
  763. if (selectCharacter != nullptr
  764. && queuez::stage_select_character(
  765. queuezState, webOutcome.selectedCharacterSoid, *selectCharacter)) {
  766. outcome.hasSelectCharacter = true;
  767. } else {
  768. clear_transaction(outcome);
  769. sunrise::server::bap::arm_account_resync_everywhere();
  770. }
  771. }
  772. return investmentTransaction.commit();
  773. }
  774. }
  775. written = 0;
  776. return false;
  777. }
  778. } // namespace sunrise::server::bap::encrypted::body