web_service_runtime.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. #include "web_service_runtime.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <atomic>
  5. #include <cstdio>
  6. #include "../../core/logging/log.h"
  7. #include "../../core/runtime/server_clock.h"
  8. #include "../../middleware/web_service/messages/opcode1801.h"
  9. #include "../../middleware/web_service/messages/opcode1821.h"
  10. #include "../../middleware/web_service/messages/opcode1901.h"
  11. #include "../../middleware/web_service/messages/opcode205.h"
  12. #include "../../middleware/web_service/messages/opcode206.h"
  13. #include "../../middleware/web_service/messages/opcode2400.h"
  14. #include "../../middleware/web_service/messages/opcode501_codec.h"
  15. #include "../../middleware/web_service/messages/opcode501_request_codec.h"
  16. #include "../../middleware/web_service/messages/opcode502.h"
  17. #include "../../middleware/web_service/messages/opcode503.h"
  18. #include "../../middleware/web_service/messages/opcode504.h"
  19. #include "../../middleware/web_service/messages/opcode601/opcode601_codec.h"
  20. #include "../../middleware/web_service/messages/opcode701/opcode701_codec.h"
  21. #include "../../middleware/web_service/messages/opcode702.h"
  22. #include "../../middleware/web_service/messages/opcode801.h"
  23. #include "../../middleware/web_service/messages/opcode901/opcode901_codec.h"
  24. #include "../../middleware/web_service/messages/opcode903.h"
  25. #include "../../middleware/web_service/messages/opcode904/opcode904_codec.h"
  26. #include "../../middleware/web_service/web_service_envelope.h"
  27. #include "../../state/account/account_state.h"
  28. #include "../../state/activity/membership/activity_membership_query.h"
  29. #include "../../state/build_data/runtime.h"
  30. #include "../../state/runtime/runtime.h"
  31. #include "opcode_routes.h"
  32. #include "web_service_actions.h"
  33. namespace sunrise::server::web_service {
  34. /** Web Service opcode used by the Character screen's Equip action. */
  35. constexpr std::uint16_t kEquipOpcode = 403;
  36. /** Web Service opcode used by the Character screen's Unequip action. */
  37. constexpr std::uint16_t kUnequipOpcode = 404;
  38. /** Web Service opcode used by item-state actions such as finisher Favorite. */
  39. constexpr std::uint16_t kItemStateOpcode = 406;
  40. /** Web Service opcode used by the Character screen's Dismantle action. */
  41. constexpr std::uint16_t kItemDismantleOpcode = 402;
  42. /** Web Service opcode used by Collections to create one item instance. */
  43. constexpr std::uint16_t kItemAcquisitionOpcode = 1820;
  44. /** One refusal code covers every reason; the log line names the actual one. */
  45. constexpr std::int32_t kRefusedStatus = middleware::web_service::kRefusedStatusCode;
  46. /**
  47. * Opcodes whose reply may name a resident the client no longer holds.
  48. * Kept sorted; the lookup below is a binary search.
  49. */
  50. constexpr auto kResidentDependentOpcodes =
  51. std::to_array<std::uint16_t>({402, 403, 404, 406, 504, 903, 1801, 1820, 1901, 2400});
  52. /** One refusal line carries both request indices, the clock presence, and the clock verdict. */
  53. constexpr std::size_t kPurchaseLineCapacity = 128;
  54. constexpr std::size_t kEchoLineCapacity = 64;
  55. /** Season of Arrivals artifact vendor row in the installed build's vendor index. */
  56. constexpr std::int16_t kArtifactVendorIndex = 430;
  57. /** Glimmer the artifact vendor charges to reset its mods, as retail charges. */
  58. constexpr std::int32_t kArtifactResetGlimmerCost = 20'000;
  59. /** The artifact vendor's reset row. Every lower row unlocks one mod tier. */
  60. constexpr std::uint16_t kArtifactResetSaleIndex = 5;
  61. /** Issues a strictly increasing family-5 clock, including multiple requests in one second. */
  62. std::uint64_t next_family5_clock() noexcept {
  63. static std::atomic<std::uint64_t> issued{0};
  64. const auto wall = static_cast<std::uint64_t>(core::runtime::server_clock_seconds());
  65. std::uint64_t previous = issued.load(std::memory_order_relaxed);
  66. std::uint64_t next = 0;
  67. do {
  68. next = wall > previous ? wall : previous + 1;
  69. } while (!issued.compare_exchange_weak(previous, next, std::memory_order_relaxed));
  70. return next;
  71. }
  72. /** Records the authoritative world state carried by the client's character write-back. */
  73. bool note_character_writeback(
  74. const middleware::web_service::Message& message,
  75. std::span<const state::account::inventory::PresentedItemRow> presentation) noexcept {
  76. namespace writeback = middleware::web_service::messages::opcode702;
  77. writeback::Request request{};
  78. const bool parsed = writeback::parse_request(message, request);
  79. std::array<char, core::log::kLineCapacity> line{};
  80. const int written = std::snprintf(line.data(),
  81. line.size(),
  82. "ev=activity stage=writeback result=%s world_state=%u "
  83. "activity=%d/%d/%d selector=%d",
  84. parsed ? "ok" : "unparsed",
  85. static_cast<unsigned>(request.worldState),
  86. static_cast<int>(request.activityBytes[0]),
  87. static_cast<int>(request.activityBytes[1]),
  88. static_cast<int>(request.activityBytes[2]),
  89. static_cast<int>(request.activitySelector));
  90. if (written > 0) {
  91. core::log::write(core::log::Channel::server,
  92. parsed ? core::log::Level::info : core::log::Level::warn,
  93. {line.data(), static_cast<std::size_t>(written)});
  94. }
  95. if (parsed && request.hasWorldState) {
  96. state::activity::membership::note_client_writeback(request.worldState
  97. == writeback::kInWorld);
  98. }
  99. return parsed
  100. && (!request.newItems
  101. || state::account::inventory::record_character_seen(*request.newItems,
  102. presentation));
  103. }
  104. /** @return True when a purchase names the seasonal artifact vendor, which is answered here. */
  105. [[nodiscard]] bool names_artifact_vendor(const middleware::web_service::Message& message) noexcept {
  106. namespace purchase_codec = middleware::web_service::messages::opcode901;
  107. purchase_codec::Request purchase{};
  108. return purchase_codec::parse_request(message, purchase)
  109. && purchase.vendorIndex == kArtifactVendorIndex;
  110. }
  111. /**
  112. * Refuses one vendor purchase and answers it.
  113. * No award, cost or stock rule exists yet, so no purchase can succeed. The refusal must still be
  114. * answered, because no answer holds the head of the client's pending queue.
  115. * @param message Parsed purchase request.
  116. * @param response Response-body storage owned by the caller.
  117. * @param written Receives the encoded response size.
  118. * @return True when the refusal was encoded.
  119. */
  120. [[nodiscard]] bool refuse_purchase(const middleware::web_service::Message& message,
  121. std::span<std::byte> response,
  122. std::size_t& written) noexcept {
  123. namespace purchase_codec = middleware::web_service::messages::opcode901;
  124. purchase_codec::Request purchase;
  125. const bool parsed = purchase_codec::parse_request(message, purchase);
  126. std::array<char, kPurchaseLineCapacity> line{};
  127. const int length =
  128. parsed ? std::snprintf(line.data(),
  129. line.size(),
  130. "ev=ws901 stage=purchase result=refuse vendor=%d sale=%d present=%u",
  131. static_cast<int>(purchase.vendorIndex),
  132. static_cast<int>(purchase.saleIndex),
  133. purchase.hasClock ? 1U : 0U)
  134. : std::snprintf(line.data(),
  135. line.size(),
  136. "ev=ws901 stage=purchase result=refuse reason=parse");
  137. if (length > 0) {
  138. core::log::write(core::log::Channel::server,
  139. core::log::Level::warn,
  140. {line.data(), static_cast<std::size_t>(length)});
  141. }
  142. middleware::web_service::StatusResponse status{};
  143. status.code = kRefusedStatus;
  144. // The trailing bool drives a local action effect on the client, so it stays clear.
  145. status.trailingBool = false;
  146. return middleware::web_service::encode_response(
  147. message,
  148. middleware::web_service::ResponseShape::statusPairWithBool,
  149. status,
  150. response,
  151. written);
  152. }
  153. /** Accepts one affordable, unlocked-tier artifact mod and reports the local purchase effect. */
  154. [[nodiscard]] bool purchase_artifact_mod(const middleware::web_service::Message& message,
  155. std::span<std::byte> response,
  156. std::size_t& written,
  157. Outcome& outcome) noexcept {
  158. namespace purchase_codec = middleware::web_service::messages::opcode901;
  159. purchase_codec::Request purchase{};
  160. if (!purchase_codec::parse_request(message, purchase)
  161. || purchase.vendorIndex != kArtifactVendorIndex || purchase.saleIndex < 0
  162. || purchase.saleIndex
  163. >= static_cast<std::int16_t>(state::build_data::kArtifactSaleRowCapacity)) {
  164. return false;
  165. }
  166. const auto saleIndex = static_cast<std::uint16_t>(purchase.saleIndex);
  167. if (saleIndex == kArtifactResetSaleIndex) {
  168. state::ArtifactResetResult reset{};
  169. if (!state::reset_artifact(kArtifactResetGlimmerCost, reset)) {
  170. return false;
  171. }
  172. middleware::web_service::StatusResponse status{};
  173. status.trailingBool = true;
  174. const bool encoded = middleware::web_service::encode_response(
  175. message,
  176. middleware::web_service::ResponseShape::statusPairWithBool,
  177. status,
  178. response,
  179. written);
  180. outcome.hasArtifactReset = encoded;
  181. if (encoded) {
  182. outcome.artifactReset = reset;
  183. }
  184. return encoded;
  185. }
  186. auto* mutation = emplace_mutation<state::PendingArtifactPurchase>(outcome);
  187. if (mutation == nullptr || !state::prepare_artifact_mod_unlock(saleIndex, *mutation)) {
  188. clear_mutation(outcome);
  189. return false;
  190. }
  191. std::array<char, kPurchaseLineCapacity> line{};
  192. const int length = std::snprintf(line.data(),
  193. line.size(),
  194. "ev=ws901 stage=artifact result=ok vendor=%d sale=%d",
  195. static_cast<int>(purchase.vendorIndex),
  196. static_cast<int>(purchase.saleIndex));
  197. if (length > 0) {
  198. core::log::write(core::log::Channel::server,
  199. core::log::Level::info,
  200. {line.data(), static_cast<std::size_t>(length)});
  201. }
  202. middleware::web_service::StatusResponse status{};
  203. status.trailingBool = true;
  204. const bool encoded = middleware::web_service::encode_response(
  205. message,
  206. middleware::web_service::ResponseShape::statusPairWithBool,
  207. status,
  208. response,
  209. written);
  210. if (!encoded) {
  211. // The purchase was written when it was prepared, so a refused reply undoes it.
  212. (void)state::replace_artifact_mod_mask(mutation->afterMask, mutation->beforeMask);
  213. clear_mutation(outcome);
  214. return false;
  215. }
  216. return true;
  217. }
  218. /**
  219. * Answers a request whose own codec refused with the bare correlated echo.
  220. * The Client matches on the echoed transaction id. A missing body is worse than a thin one. It
  221. * under-runs the decoder and takes the BAP connection down.
  222. * @param message Parsed request whose correlation fields are echoed.
  223. * @param response Svc-11 response-body storage owned by the caller.
  224. * @param written Gets the encoded response-body size in bytes.
  225. * @return True when the echo fits.
  226. */
  227. bool encode_echo(const middleware::web_service::Message& message,
  228. std::span<std::byte> response,
  229. std::size_t& written) noexcept {
  230. std::array<char, kEchoLineCapacity> line{};
  231. const int count = std::snprintf(
  232. line.data(), line.size(), "ev=ws stage=body result=echo opcode=%u", message.opcode);
  233. if (count > 0) {
  234. core::log::write(core::log::Channel::server,
  235. core::log::Level::warn,
  236. {line.data(), static_cast<std::size_t>(count)});
  237. }
  238. namespace ws = middleware::web_service;
  239. return ws::encode_response(
  240. message, ws::ResponseShape::generic, ws::StatusResponse{}, response, written);
  241. }
  242. /**
  243. * Encodes the refusal reply for a request whose answer may name a resident the client dropped.
  244. * @param request Whole decrypted svc-10 body.
  245. * @param response Svc-11 response-body storage owned by the caller.
  246. * @param written Gets the encoded response-body size; zero when the opcode is not refused here.
  247. * @param refused Gets true when the opcode is one of the resident-dependent set.
  248. * @return False when neither the refusal nor the bare echo could be encoded.
  249. */
  250. bool encode_resident_dependent_refusal(std::span<const std::byte> request,
  251. std::span<std::byte> response,
  252. std::size_t& written,
  253. bool& refused) noexcept {
  254. written = 0;
  255. refused = false;
  256. middleware::web_service::Message message;
  257. if (!middleware::web_service::parse_request(request, message)
  258. || !std::binary_search(
  259. kResidentDependentOpcodes.begin(), kResidentDependentOpcodes.end(), message.opcode)) {
  260. return true;
  261. }
  262. refused = true;
  263. middleware::web_service::ResponseShape shape{};
  264. resolve_response_shape(message.opcode, shape);
  265. middleware::web_service::StatusResponse status{};
  266. status.code = kRefusedStatus;
  267. return middleware::web_service::encode_response(message, shape, status, response, written)
  268. || encode_echo(message, response, written);
  269. }
  270. /**
  271. * Parses one request, prepares any action it names, and encodes the reply that reports it.
  272. * @param request Whole decrypted svc-10 body.
  273. * @param response Svc-11 response-body storage owned by the caller.
  274. * @param written Gets the encoded response-body size, or zero when the header does not parse.
  275. * @param outcome Gets the prepared action for the caller to publish, and is left empty when
  276. * the action was refused or the reply could not be encoded.
  277. * @return False only when the envelope header does not parse.
  278. */
  279. bool consume(std::span<const std::byte> request,
  280. std::span<std::byte> response,
  281. std::size_t& written,
  282. Outcome& outcome,
  283. std::span<const state::account::inventory::PresentedItemRow> presentation) noexcept {
  284. written = 0;
  285. outcome = {};
  286. middleware::web_service::Message message;
  287. if (!middleware::web_service::parse_request(request, message)) {
  288. core::log::write(
  289. core::log::Channel::server, core::log::Level::warn, "ev=ws stage=parse result=fail");
  290. return false;
  291. }
  292. if (message.opcode == middleware::web_service::messages::opcode702::kOpcode) {
  293. // A write-back the server cannot read is still answered; no reply drops the connection.
  294. static_cast<void>(note_character_writeback(message, presentation));
  295. }
  296. if (message.opcode == middleware::web_service::messages::opcode205::kOpcode) {
  297. state::InvestmentState investment{};
  298. return (state::investment_snapshot(investment)
  299. && middleware::web_service::messages::opcode205::encode_response(
  300. message, investment, next_family5_clock(), response, written))
  301. || encode_echo(message, response, written);
  302. }
  303. if (message.opcode == middleware::web_service::messages::opcode503::kOpcode) {
  304. middleware::web_service::messages::opcode503::Request bootstrap;
  305. const bool parsed =
  306. middleware::web_service::messages::opcode503::parse_request(message, bootstrap);
  307. // The request's own key is echoed and adopted. An authored id here costs the ship and the
  308. // banner.
  309. if (!bootstrap.hasPrimarySoid) {
  310. bootstrap.primarySoid = state::account_snapshot().primarySoid;
  311. }
  312. state::InvestmentState investment{};
  313. if (!parsed || !state::investment_snapshot(investment)
  314. || !middleware::web_service::messages::opcode503::encode_response(
  315. message, bootstrap, investment, next_family5_clock(), response, written)) {
  316. return encode_echo(message, response, written);
  317. }
  318. if (bootstrap.hasPrimarySoid && !state::set_primary_soid(bootstrap.primarySoid)) {
  319. core::log::write(core::log::Channel::server,
  320. core::log::Level::warn,
  321. "ev=ws503 stage=adopt result=fail");
  322. }
  323. return true;
  324. }
  325. if (message.opcode == middleware::web_service::messages::opcode501::kOpcode) {
  326. middleware::web_service::messages::opcode501::Request created{};
  327. std::uint64_t characterSoid = 0;
  328. if (!middleware::web_service::messages::opcode501::parse_request(message, created)
  329. || !state::create_character(
  330. created.characterClass, created.gender, created.race, characterSoid)) {
  331. core::log::write(
  332. core::log::Channel::server, core::log::Level::warn, "ev=ws501 stage=create result=fail");
  333. // The client still needs an answer even when creation fails; falling back to the
  334. // selected character keeps the response shape valid instead of leaving it unanswered.
  335. characterSoid = state::account::selected_character_soid(state::account_snapshot());
  336. }
  337. return middleware::web_service::messages::opcode501::encode_response(
  338. message, characterSoid, response, written)
  339. || encode_echo(message, response, written);
  340. }
  341. if (message.opcode == middleware::web_service::messages::opcode502::kOpcode) {
  342. middleware::web_service::messages::opcode502::Request targeted{};
  343. if (!middleware::web_service::messages::opcode502::parse_request(message, targeted)
  344. || !state::delete_character(targeted.characterSoid)) {
  345. core::log::write(
  346. core::log::Channel::server, core::log::Level::warn, "ev=ws502 stage=delete result=fail");
  347. }
  348. return encode_echo(message, response, written);
  349. }
  350. // The artifact vendor is answered here. Every other vendor purchase falls through to the
  351. // shared response-shape path, which runs the action and answers its status: an action that
  352. // prepared no mutation is answered with the refused code.
  353. if (message.opcode == middleware::web_service::messages::opcode901::kOpcode
  354. && names_artifact_vendor(message)) {
  355. return purchase_artifact_mod(message, response, written, outcome)
  356. || refuse_purchase(message, response, written)
  357. || encode_echo(message, response, written);
  358. }
  359. if (message.opcode == middleware::web_service::messages::opcode601::kOpcode) {
  360. return middleware::web_service::messages::opcode601::encode_response(
  361. message, response, written)
  362. || encode_echo(message, response, written);
  363. }
  364. // A subscribe whose body does not parse is still answered; only the subscription is dropped.
  365. middleware::queuez::Subscription subscription;
  366. const bool subscribes =
  367. message.opcode == middleware::web_service::messages::opcode206::kOpcode
  368. && middleware::web_service::messages::opcode206::parse_request(message, subscription);
  369. // The action runs before its reply is encoded, because the reply reports whether it worked.
  370. // Most actions fill the outcome only after preparing a whole transition. WS-701 also accepts
  371. // a valid no-op heartbeat, so that one success is tracked separately from mutation presence.
  372. bool dispatched = true;
  373. bool acceptedWithoutMutation = false;
  374. bool profileSetupRefused = false;
  375. if (message.opcode == middleware::web_service::messages::opcode1801::kOpcode) {
  376. claim_record(message, outcome);
  377. } else if (message.opcode == middleware::web_service::messages::opcode504::kOpcode) {
  378. select_character(message, outcome);
  379. } else if (message.opcode == kItemDismantleOpcode) {
  380. dismantle_item(message, outcome);
  381. } else if (message.opcode == kEquipOpcode) {
  382. mutate_equipment(message, false, outcome);
  383. } else if (message.opcode == kUnequipOpcode) {
  384. mutate_equipment(message, true, outcome);
  385. } else if (message.opcode == middleware::web_service::messages::opcode801::kOpcode) {
  386. mutate_subclass_selection(message, outcome);
  387. } else if (message.opcode == middleware::web_service::messages::opcode1821::kOpcode) {
  388. equip_title(message, outcome);
  389. } else if (message.opcode == middleware::web_service::messages::opcode903::kOpcode) {
  390. mutate_socket_plug(message, outcome);
  391. } else if (message.opcode == middleware::web_service::messages::opcode1901::kOpcode) {
  392. mutate_equipped_socket_plug(message, outcome);
  393. } else if (message.opcode == kItemStateOpcode) {
  394. mutate_item_state(message, outcome);
  395. } else if (message.opcode == middleware::web_service::messages::opcode701::kOpcode) {
  396. const state::SettingsUpdateDisposition disposition = mutate_settings(message, outcome);
  397. acceptedWithoutMutation = disposition == state::SettingsUpdateDisposition::acceptedNoChange;
  398. profileSetupRefused = outcome.profileSetupRefused;
  399. } else if (message.opcode == kItemAcquisitionOpcode) {
  400. acquire_item(message, outcome);
  401. } else if (message.opcode == middleware::web_service::messages::opcode2400::kOpcode) {
  402. claim_season_pass_reward(message, outcome);
  403. } else if (message.opcode == middleware::web_service::messages::opcode901::kOpcode) {
  404. purchase_item(message, outcome);
  405. } else if (message.opcode == middleware::web_service::messages::opcode904::kOpcode) {
  406. acquire_quest(message, outcome);
  407. } else {
  408. dispatched = false;
  409. }
  410. const bool prepared = outcome.hasSelectedCharacter || outcome.hasTitleEquip
  411. || outcome.hasRecordClaim || has_mutation(outcome);
  412. middleware::web_service::ResponseShape shape{};
  413. resolve_response_shape(message.opcode, shape);
  414. middleware::web_service::StatusResponse status{};
  415. if (awaits_family4_version(message.opcode)) {
  416. // Nothing is published from here. A staged mutation re-encodes this with its own revision.
  417. status.value = middleware::web_service::kNoFamily4Publication;
  418. }
  419. if ((dispatched && !prepared && !acceptedWithoutMutation) || profileSetupRefused) {
  420. status.code = kRefusedStatus;
  421. }
  422. if (!middleware::web_service::encode_response(message, shape, status, response, written)) {
  423. // The echo carries no status, so nothing may be published against it.
  424. outcome = {};
  425. return encode_echo(message, response, written);
  426. }
  427. if (subscribes) {
  428. // Publish the subscription only after its correlated response is complete.
  429. outcome.hasSubscription = true;
  430. outcome.subscription = subscription;
  431. }
  432. return true;
  433. }
  434. } // namespace sunrise::server::web_service