web_service_runtime.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. #include "web_service_runtime.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <atomic>
  5. #include <chrono>
  6. #include <cstdio>
  7. #include "../../core/logging/log.h"
  8. #include "../../middleware/encoding/bit_reader.h"
  9. #include "../../middleware/web_service/messages/opcode1801.h"
  10. #include "../../middleware/web_service/messages/opcode1821.h"
  11. #include "../../middleware/web_service/messages/opcode1901.h"
  12. #include "../../middleware/web_service/messages/opcode205.h"
  13. #include "../../middleware/web_service/messages/opcode206.h"
  14. #include "../../middleware/web_service/messages/opcode2400.h"
  15. #include "../../middleware/web_service/messages/opcode501_codec.h"
  16. #include "../../middleware/web_service/messages/opcode503.h"
  17. #include "../../middleware/web_service/messages/opcode504.h"
  18. #include "../../middleware/web_service/messages/opcode601/opcode601_codec.h"
  19. #include "../../middleware/web_service/messages/opcode701/opcode701_codec.h"
  20. #include "../../middleware/web_service/messages/opcode702.h"
  21. #include "../../middleware/web_service/messages/opcode801.h"
  22. #include "../../middleware/web_service/messages/opcode901/opcode901_codec.h"
  23. #include "../../middleware/web_service/messages/opcode904/opcode904_codec.h"
  24. #include "../../middleware/web_service/messages/opcode903.h"
  25. #include "../../middleware/web_service/web_service_envelope.h"
  26. #include "../../state/account/account_state.h"
  27. #include "../../state/activity/membership/activity_membership_query.h"
  28. #include "../../state/progression/seasonal_experience.h"
  29. #include "../../state/runtime/runtime.h"
  30. #include "opcode_routes.h"
  31. #include "web_service_actions.h"
  32. namespace sunrise::server::web_service {
  33. /** Web Service opcode used by the Character screen's Equip action. */
  34. constexpr std::uint16_t kEquipOpcode = 403;
  35. /** Web Service opcode used by the Character screen's Unequip action. */
  36. constexpr std::uint16_t kUnequipOpcode = 404;
  37. /** Web Service opcode used by item-state actions such as finisher Favorite. */
  38. constexpr std::uint16_t kItemStateOpcode = 406;
  39. /** Web Service opcode used by the Character screen's Dismantle action. */
  40. constexpr std::uint16_t kItemDismantleOpcode = 402;
  41. /** Web Service opcode used by Collections to create one item instance. */
  42. constexpr std::uint16_t kItemAcquisitionOpcode = 1820;
  43. /**
  44. * Logical status of a refused action. The descriptor biases logical zero to the wire success the
  45. * Client expects, so any other logical value reports a refusal. Its five bits hold no error
  46. * taxonomy, so one code covers every reason and the log line names the actual one.
  47. */
  48. constexpr std::int32_t kRefusedStatus = 1;
  49. constexpr auto kResidentDependentOpcodes =
  50. std::to_array<std::uint16_t>({402, 403, 404, 406, 504, 903, 1801, 1820, 1901, 2400});
  51. /** One refusal line carries both request indices, the clock presence, and the clock verdict. */
  52. constexpr std::size_t kPurchaseLineCapacity = 128;
  53. constexpr std::size_t kEchoLineCapacity = 64;
  54. /**
  55. * Status code answered to a purchase request.
  56. * Any non-zero value refuses. Zero is the success code, so it must not be used here.
  57. */
  58. constexpr std::int32_t kPurchaseRefusedCode = 1;
  59. /** Season of Arrivals artifact vendor row in the installed build's vendor index. */
  60. constexpr std::int16_t kArtifactVendorIndex = 430;
  61. constexpr std::int32_t kArtifactResetGlimmerCost = 20'000;
  62. /**
  63. * Reads the server's own clock for the purchase clock rule.
  64. * The system clock counts from the Unix epoch, which is the same base the request field uses.
  65. * @return Current time in Unix seconds.
  66. */
  67. [[nodiscard]] std::int64_t server_clock_seconds() noexcept {
  68. const auto sinceEpoch = std::chrono::system_clock::now().time_since_epoch();
  69. return std::chrono::duration_cast<std::chrono::seconds>(sinceEpoch).count();
  70. }
  71. /** Issues a strictly increasing family-5 clock, including multiple requests in one second. */
  72. [[nodiscard]] std::uint64_t next_family5_clock() noexcept {
  73. static std::atomic<std::uint64_t> issued{0};
  74. const auto wall = static_cast<std::uint64_t>(server_clock_seconds());
  75. std::uint64_t previous = issued.load(std::memory_order_relaxed);
  76. std::uint64_t next = 0;
  77. do {
  78. next = wall > previous ? wall : previous + 1;
  79. } while (!issued.compare_exchange_weak(previous, next, std::memory_order_relaxed));
  80. return next;
  81. }
  82. /** Records the authoritative world state carried by the client's character write-back. */
  83. void note_character_writeback(const middleware::web_service::Message& message) noexcept {
  84. namespace writeback = middleware::web_service::messages::opcode702;
  85. writeback::Request request{};
  86. const bool parsed = writeback::parse_request(message, request);
  87. std::array<char, core::log::kLineCapacity> line{};
  88. const int written = std::snprintf(line.data(),
  89. line.size(),
  90. "ev=activity stage=writeback result=%s world_state=%u",
  91. parsed ? "ok" : "unparsed",
  92. static_cast<unsigned>(request.worldState));
  93. if (written > 0) {
  94. core::log::write(core::log::Channel::server,
  95. core::log::Level::info,
  96. {line.data(), static_cast<std::size_t>(written)});
  97. }
  98. if (parsed) {
  99. state::activity::membership::note_client_writeback(request.worldState
  100. == writeback::kInWorld);
  101. }
  102. }
  103. /** @return True when a purchase names the seasonal artifact vendor, which is answered here. */
  104. [[nodiscard]] bool names_artifact_vendor(const middleware::web_service::Message& message) noexcept {
  105. namespace purchase_codec = middleware::web_service::messages::opcode901;
  106. purchase_codec::Request purchase{};
  107. return purchase_codec::parse_request(message, purchase)
  108. && purchase.vendorIndex == kArtifactVendorIndex;
  109. }
  110. /**
  111. * Refuses one vendor purchase and answers it.
  112. * No award, cost or stock rule exists yet, so no purchase can succeed. The refusal must still be
  113. * answered, because no answer holds the head of the client's pending queue.
  114. * @param message Parsed purchase request.
  115. * @param response Response-body storage owned by the caller.
  116. * @param written Receives the encoded response size.
  117. * @return True when the refusal was encoded.
  118. */
  119. [[nodiscard]] bool refuse_purchase(const middleware::web_service::Message& message,
  120. std::span<std::byte> response,
  121. std::size_t& written) noexcept {
  122. namespace purchase_codec = middleware::web_service::messages::opcode901;
  123. purchase_codec::Request purchase;
  124. const bool parsed = purchase_codec::parse_request(message, purchase);
  125. // The clock verdict is logged, never acted on. Nothing can pass while the route refuses.
  126. const auto policy = purchase_codec::check_clock(purchase, server_clock_seconds());
  127. std::array<char, kPurchaseLineCapacity> line{};
  128. const int length =
  129. parsed ? std::snprintf(
  130. line.data(),
  131. line.size(),
  132. "ev=ws901 stage=purchase result=refuse vendor=%d sale=%d present=%u policy=%s",
  133. static_cast<int>(purchase.vendorIndex),
  134. static_cast<int>(purchase.saleIndex),
  135. purchase.hasClock ? 1U : 0U,
  136. purchase_codec::clock_policy_name(policy))
  137. : std::snprintf(line.data(),
  138. line.size(),
  139. "ev=ws901 stage=purchase result=refuse reason=parse");
  140. if (length > 0) {
  141. core::log::write(core::log::Channel::server,
  142. core::log::Level::error,
  143. {line.data(), static_cast<std::size_t>(length)});
  144. }
  145. middleware::web_service::StatusResponse status{};
  146. status.code = kPurchaseRefusedCode;
  147. // The trailing bool drives a local action effect on the client, so it stays clear.
  148. status.trailingBool = false;
  149. return middleware::web_service::encode_response(
  150. message,
  151. middleware::web_service::ResponseShape::statusPairWithBool,
  152. status,
  153. response,
  154. written);
  155. }
  156. /** Accepts one affordable, unlocked-tier artifact mod and reports the local purchase effect. */
  157. [[nodiscard]] bool purchase_artifact_mod(const middleware::web_service::Message& message,
  158. std::span<std::byte> response,
  159. std::size_t& written,
  160. Outcome& outcome) noexcept {
  161. namespace purchase_codec = middleware::web_service::messages::opcode901;
  162. purchase_codec::Request purchase{};
  163. if (!purchase_codec::parse_request(message, purchase)
  164. || purchase.vendorIndex != kArtifactVendorIndex || purchase.saleIndex < 0
  165. || purchase.saleIndex
  166. >= static_cast<std::int16_t>(
  167. state::progression::seasonal_experience::kArtifactSaleCount)) {
  168. return false;
  169. }
  170. const auto saleIndex = static_cast<std::uint16_t>(purchase.saleIndex);
  171. if (saleIndex == 5) {
  172. state::ArtifactResetResult reset{};
  173. if (!state::reset_artifact(kArtifactResetGlimmerCost, reset)) {
  174. return false;
  175. }
  176. middleware::web_service::StatusResponse status{};
  177. status.trailingBool = true;
  178. const bool encoded = middleware::web_service::encode_response(
  179. message,
  180. middleware::web_service::ResponseShape::statusPairWithBool,
  181. status,
  182. response,
  183. written);
  184. outcome.hasArtifactReset = encoded;
  185. if (encoded) {
  186. outcome.artifactReset = reset;
  187. }
  188. return encoded;
  189. }
  190. auto* mutation = emplace_mutation<state::PendingArtifactPurchase>(outcome);
  191. if (mutation == nullptr || !state::prepare_artifact_mod_unlock(saleIndex, *mutation)) {
  192. clear_mutation(outcome);
  193. return false;
  194. }
  195. std::array<char, kPurchaseLineCapacity> line{};
  196. const int length =
  197. std::snprintf(line.data(),
  198. line.size(),
  199. "ev=ws901 stage=artifact result=ok vendor=%d sale=%d policy=%s",
  200. static_cast<int>(purchase.vendorIndex),
  201. static_cast<int>(purchase.saleIndex),
  202. purchase_codec::clock_policy_name(
  203. purchase_codec::check_clock(purchase, server_clock_seconds())));
  204. if (length > 0) {
  205. core::log::write(core::log::Channel::server,
  206. core::log::Level::info,
  207. {line.data(), static_cast<std::size_t>(length)});
  208. }
  209. middleware::web_service::StatusResponse status{};
  210. status.trailingBool = true;
  211. const bool encoded = middleware::web_service::encode_response(
  212. message,
  213. middleware::web_service::ResponseShape::statusPairWithBool,
  214. status,
  215. response,
  216. written);
  217. if (!encoded) {
  218. clear_mutation(outcome);
  219. return false;
  220. }
  221. return true;
  222. }
  223. /**
  224. * Answers a request whose own codec refused with the bare correlated echo.
  225. * The Client matches on the echoed transaction id. A missing body is worse than a thin one. It
  226. * under-runs the decoder and takes the BAP connection down.
  227. * @param message Parsed request whose correlation fields are echoed.
  228. * @param response Svc-11 response-body storage owned by the caller.
  229. * @param written Gets the encoded response-body size in bytes.
  230. * @return True when the echo fits.
  231. */
  232. bool encode_echo(const middleware::web_service::Message& message,
  233. std::span<std::byte> response,
  234. std::size_t& written) noexcept {
  235. std::array<char, kEchoLineCapacity> line{};
  236. const int count = std::snprintf(
  237. line.data(), line.size(), "ev=ws stage=body result=echo opcode=%u", message.opcode);
  238. if (count > 0) {
  239. core::log::write(core::log::Channel::server,
  240. core::log::Level::warn,
  241. {line.data(), static_cast<std::size_t>(count)});
  242. }
  243. namespace ws = middleware::web_service;
  244. return ws::encode_response(
  245. message, ws::ResponseShape::generic, ws::StatusResponse{}, response, written);
  246. }
  247. /** Narrow semantic result from the prefix of reflected WS-701 schema 0x80807603. */
  248. struct ProfileSetupMarker {
  249. bool present{};
  250. bool completed{};
  251. };
  252. /** Reads the presence bit that precedes every optional WS-701 schema node. */
  253. [[nodiscard]] bool read_ws701_presence(middleware::encoding::bits::Reader& reader,
  254. bool& present) noexcept {
  255. std::uint64_t value = 0;
  256. if (!reader.read(1, value)) {
  257. return false;
  258. }
  259. present = value != 0;
  260. return true;
  261. }
  262. /** Consumes one optional fixed-width field without retaining it. */
  263. [[nodiscard]] bool skip_ws701_optional(middleware::encoding::bits::Reader& reader,
  264. std::size_t widthBits) noexcept {
  265. bool present = false;
  266. return read_ws701_presence(reader, present) && (!present || reader.skip(widthBits));
  267. }
  268. /**
  269. * Reads only enough of WS-701 schema 0x80807603 to reach preference path 0.1.1.0.
  270. *
  271. * PR #71 maps that first preference scalar as the one-bit profile-setup marker. Everything after
  272. * it belongs to the broader settings-write implementation and is deliberately left to that work.
  273. * This function therefore validates the complete prefix, not the remainder of the request.
  274. */
  275. [[nodiscard]] bool parse_profile_setup_marker(const middleware::web_service::Message& message,
  276. ProfileSetupMarker& output) noexcept {
  277. output = {};
  278. if (message.opcode != middleware::web_service::messages::opcode701::kOpcode) {
  279. return false;
  280. }
  281. middleware::encoding::bits::Reader reader(message.payload);
  282. bool present = false;
  283. // 0.0? client metadata.
  284. if (!read_ws701_presence(reader, present)) {
  285. return false;
  286. }
  287. if (present) {
  288. // 0.0.0? [128] optional 64-bit publicity expiries.
  289. bool publicityPresent = false;
  290. if (!read_ws701_presence(reader, publicityPresent)) {
  291. return false;
  292. }
  293. if (publicityPresent) {
  294. for (std::size_t index = 0; index < 128; ++index) {
  295. if (!skip_ws701_optional(reader, 64)) {
  296. return false;
  297. }
  298. }
  299. }
  300. // 0.0.1? [13] required 32-bit seen-message values.
  301. bool seenMessagesPresent = false;
  302. if (!read_ws701_presence(reader, seenMessagesPresent)
  303. || (seenMessagesPresent && !reader.skip(13U * 32U))) {
  304. return false;
  305. }
  306. }
  307. // 0.1? account data.
  308. bool accountPresent = false;
  309. if (!read_ws701_presence(reader, accountPresent)) {
  310. return false;
  311. }
  312. if (!accountPresent) {
  313. return true;
  314. }
  315. // 0.1.0? [2] optional calibration vectors, each containing two required real32 values.
  316. bool calibrationPresent = false;
  317. if (!read_ws701_presence(reader, calibrationPresent)) {
  318. return false;
  319. }
  320. if (calibrationPresent) {
  321. for (std::size_t index = 0; index < 2; ++index) {
  322. bool vectorPresent = false;
  323. if (!read_ws701_presence(reader, vectorPresent)
  324. || (vectorPresent && !reader.skip(2U * 32U))) {
  325. return false;
  326. }
  327. }
  328. }
  329. // 0.1.1? preference record.
  330. bool preferencesPresent = false;
  331. if (!read_ws701_presence(reader, preferencesPresent)) {
  332. return false;
  333. }
  334. if (!preferencesPresent) {
  335. return true;
  336. }
  337. // 0.1.1.0? one-bit profile-setup marker.
  338. if (!read_ws701_presence(reader, output.present)) {
  339. return false;
  340. }
  341. if (!output.present) {
  342. return true;
  343. }
  344. std::uint64_t completed = 0;
  345. if (!reader.read(1, completed)) {
  346. return false;
  347. }
  348. output.completed = completed != 0;
  349. return true;
  350. }
  351. bool encode_resident_dependent_refusal(std::span<const std::byte> request,
  352. std::span<std::byte> response,
  353. std::size_t& written,
  354. bool& refused) noexcept {
  355. written = 0;
  356. refused = false;
  357. middleware::web_service::Message message;
  358. if (!middleware::web_service::parse_request(request, message)
  359. || !std::binary_search(
  360. kResidentDependentOpcodes.begin(), kResidentDependentOpcodes.end(), message.opcode)) {
  361. return true;
  362. }
  363. refused = true;
  364. middleware::web_service::ResponseShape shape{};
  365. resolve_response_shape(message.opcode, shape);
  366. middleware::web_service::StatusResponse status{};
  367. status.code = kRefusedStatus;
  368. return middleware::web_service::encode_response(message, shape, status, response, written)
  369. || encode_echo(message, response, written);
  370. }
  371. /**
  372. * Parses one request, prepares any action it names, and encodes the reply that reports it.
  373. * @param request Whole decrypted svc-10 body.
  374. * @param response Svc-11 response-body storage owned by the caller.
  375. * @param written Gets the encoded response-body size, or zero when the header does not parse.
  376. * @param outcome Gets the prepared action for the caller to publish, and is left empty when
  377. * the action was refused or the reply could not be encoded.
  378. * @return False only when the envelope header does not parse.
  379. */
  380. bool consume(std::span<const std::byte> request,
  381. std::span<std::byte> response,
  382. std::size_t& written,
  383. Outcome& outcome) noexcept {
  384. written = 0;
  385. outcome = {};
  386. middleware::web_service::Message message;
  387. if (!middleware::web_service::parse_request(request, message)) {
  388. core::log::write(
  389. core::log::Channel::server, core::log::Level::warn, "ev=ws stage=parse result=fail");
  390. return false;
  391. }
  392. if (message.opcode == middleware::web_service::messages::opcode702::kOpcode) {
  393. note_character_writeback(message);
  394. }
  395. if (message.opcode == middleware::web_service::messages::opcode205::kOpcode) {
  396. state::InvestmentState investment{};
  397. return (state::investment_snapshot(investment)
  398. && middleware::web_service::messages::opcode205::encode_response(
  399. message, investment, next_family5_clock(), response, written))
  400. || encode_echo(message, response, written);
  401. }
  402. if (message.opcode == middleware::web_service::messages::opcode503::kOpcode) {
  403. middleware::web_service::messages::opcode503::Request bootstrap;
  404. const bool parsed =
  405. middleware::web_service::messages::opcode503::parse_request(message, bootstrap);
  406. // The request's own key is echoed and adopted. An authored id here costs the ship and the
  407. // banner.
  408. if (!bootstrap.hasPrimarySoid) {
  409. bootstrap.primarySoid = state::account_snapshot().primarySoid;
  410. }
  411. state::InvestmentState investment{};
  412. if (!parsed || !state::investment_snapshot(investment)
  413. || !middleware::web_service::messages::opcode503::encode_response(
  414. message, bootstrap, investment, next_family5_clock(), response, written)) {
  415. return encode_echo(message, response, written);
  416. }
  417. if (bootstrap.hasPrimarySoid && !state::set_primary_soid(bootstrap.primarySoid)) {
  418. core::log::write(core::log::Channel::server,
  419. core::log::Level::warn,
  420. "ev=ws503 stage=adopt result=fail");
  421. }
  422. return true;
  423. }
  424. if (message.opcode == middleware::web_service::messages::opcode501::kOpcode) {
  425. // Returns a SOID family three already publishes. The request body is not parsed.
  426. const std::uint64_t characterSoid =
  427. state::account::selected_character_soid(state::account_snapshot());
  428. return middleware::web_service::messages::opcode501::encode_response(
  429. message, characterSoid, response, written)
  430. || encode_echo(message, response, written);
  431. }
  432. // The artifact vendor is answered here. Every other vendor purchase falls through to the
  433. // shared response-shape path, which runs the action and answers its status: an action that
  434. // prepared no mutation is answered with the refused code.
  435. if (message.opcode == middleware::web_service::messages::opcode901::kOpcode
  436. && names_artifact_vendor(message)) {
  437. return purchase_artifact_mod(message, response, written, outcome)
  438. || refuse_purchase(message, response, written)
  439. || encode_echo(message, response, written);
  440. }
  441. if (message.opcode == middleware::web_service::messages::opcode601::kOpcode) {
  442. return middleware::web_service::messages::opcode601::encode_response(
  443. message, response, written)
  444. || encode_echo(message, response, written);
  445. }
  446. // A subscribe whose body does not parse is still answered; only the subscription is dropped.
  447. middleware::queuez::Subscription subscription;
  448. const bool subscribes =
  449. message.opcode == middleware::web_service::messages::opcode206::kOpcode
  450. && middleware::web_service::messages::opcode206::parse_request(message, subscription);
  451. // The action runs before its reply is encoded, because the reply reports whether it worked.
  452. // Most actions fill the outcome only after preparing a whole transition. WS-701 also accepts
  453. // a valid no-op heartbeat, so that one success is tracked separately from mutation presence.
  454. bool dispatched = true;
  455. bool acceptedWithoutMutation = false;
  456. bool profileSetupRefused = false;
  457. if (message.opcode == middleware::web_service::messages::opcode1801::kOpcode) {
  458. claim_record(message, outcome);
  459. } else if (message.opcode == middleware::web_service::messages::opcode504::kOpcode) {
  460. select_character(message, outcome);
  461. } else if (message.opcode == kItemDismantleOpcode) {
  462. dismantle_item(message, outcome);
  463. } else if (message.opcode == kEquipOpcode) {
  464. mutate_equipment(message, false, outcome);
  465. } else if (message.opcode == kUnequipOpcode) {
  466. mutate_equipment(message, true, outcome);
  467. } else if (message.opcode == middleware::web_service::messages::opcode801::kOpcode) {
  468. mutate_subclass_selection(message, outcome);
  469. } else if (message.opcode == middleware::web_service::messages::opcode1821::kOpcode) {
  470. equip_title(message, outcome);
  471. } else if (message.opcode == middleware::web_service::messages::opcode903::kOpcode) {
  472. mutate_socket_plug(message, outcome);
  473. } else if (message.opcode == middleware::web_service::messages::opcode1901::kOpcode) {
  474. mutate_equipped_socket_plug(message, outcome);
  475. } else if (message.opcode == kItemStateOpcode) {
  476. mutate_item_state(message, outcome);
  477. } else if (message.opcode == middleware::web_service::messages::opcode701::kOpcode) {
  478. const state::SettingsUpdateDisposition disposition = mutate_settings(message, outcome);
  479. acceptedWithoutMutation = disposition == state::SettingsUpdateDisposition::acceptedNoChange;
  480. // The completion marker is applied here. The shared status path below reports the result.
  481. ProfileSetupMarker marker{};
  482. const bool parsed = parse_profile_setup_marker(message, marker);
  483. if (!parsed) {
  484. // Preserve Sunrise's existing WS-701 success behavior outside this narrow feature.
  485. // PR #71 owns complete settings-write validation and can later subsume this prefix.
  486. core::log::write(core::log::Channel::server,
  487. core::log::Level::warn,
  488. "ev=ws701 stage=profile_setup result=ignored reason=prefix_parse");
  489. } else if (marker.present && marker.completed) {
  490. if (!state::complete_profile_setup()) {
  491. profileSetupRefused = true;
  492. } else {
  493. core::log::write(core::log::Channel::server,
  494. core::log::Level::info,
  495. "ev=ws701 stage=profile_setup result=complete marker=1");
  496. }
  497. }
  498. } else if (message.opcode == kItemAcquisitionOpcode) {
  499. acquire_item(message, outcome);
  500. } else if (message.opcode == middleware::web_service::messages::opcode2400::kOpcode) {
  501. claim_season_pass_reward(message, outcome);
  502. } else if (message.opcode == middleware::web_service::messages::opcode901::kOpcode) {
  503. purchase_item(message, outcome);
  504. } else if (message.opcode == middleware::web_service::messages::opcode904::kOpcode) {
  505. acquire_quest(message, outcome);
  506. } else {
  507. dispatched = false;
  508. }
  509. const bool prepared = outcome.hasSelectedCharacter || outcome.hasTitleEquip
  510. || outcome.hasRecordClaim || has_mutation(outcome);
  511. middleware::web_service::ResponseShape shape{};
  512. resolve_response_shape(message.opcode, shape);
  513. middleware::web_service::StatusResponse status{};
  514. if ((dispatched && !prepared && !acceptedWithoutMutation) || profileSetupRefused) {
  515. status.code = kRefusedStatus;
  516. }
  517. if (!middleware::web_service::encode_response(message, shape, status, response, written)) {
  518. // The echo carries no status, so nothing may be published against it.
  519. outcome = {};
  520. return encode_echo(message, response, written);
  521. }
  522. if (subscribes) {
  523. // Publish the subscription only after its correlated response is complete.
  524. outcome.hasSubscription = true;
  525. outcome.subscription = subscription;
  526. }
  527. return true;
  528. }
  529. } // namespace sunrise::server::web_service