web_service_runtime.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #include "web_service_runtime.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <atomic>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <string_view>
  8. #include "../../core/logging/log.h"
  9. #include "../../core/runtime/server_clock.h"
  10. #include "../../middleware/encoding/bit_reader.h"
  11. #include "../../middleware/encoding/byte_order.h"
  12. #include "../../middleware/web_service/messages/opcode1820.h"
  13. #include "../../middleware/web_service/messages/opcode1901.h"
  14. #include "../../middleware/web_service/messages/opcode205.h"
  15. #include "../../middleware/web_service/messages/opcode206.h"
  16. #include "../../middleware/web_service/messages/opcode402.h"
  17. #include "../../middleware/web_service/messages/opcode403.h"
  18. #include "../../middleware/web_service/messages/opcode406.h"
  19. #include "../../middleware/web_service/messages/opcode501_codec.h"
  20. #include "../../middleware/web_service/messages/opcode503.h"
  21. #include "../../middleware/web_service/messages/opcode504.h"
  22. #include "../../middleware/web_service/messages/opcode601/opcode601_codec.h"
  23. #include "../../middleware/web_service/messages/opcode701/opcode701_codec.h"
  24. #include "../../middleware/web_service/messages/opcode702.h"
  25. #include "../../middleware/web_service/messages/opcode801.h"
  26. #include "../../middleware/web_service/messages/opcode901/opcode901_codec.h"
  27. #include "../../middleware/web_service/messages/opcode903.h"
  28. #include "../../middleware/web_service/web_service_envelope.h"
  29. #include "../../state/account/account_state.h"
  30. #include "../../state/activity/membership/activity_membership_query.h"
  31. #include "../../state/build_data/runtime.h"
  32. #include "../../state/runtime/runtime.h"
  33. #include "internal.h"
  34. #include "opcode_routes.h"
  35. #include "web_service_actions.h"
  36. namespace sunrise::server::web_service {
  37. namespace {
  38. namespace messages = middleware::web_service::messages;
  39. /** One ordinary event line carries an opcode and its fixed prefix. */
  40. constexpr std::size_t kOpcodeLineCapacity = 64;
  41. /** One refusal line carries both request indices, the clock presence, and the clock verdict. */
  42. constexpr std::size_t kPurchaseLineCapacity = 128;
  43. /** A request trace keeps enough payload to identify an item-action descriptor. */
  44. constexpr std::size_t kRequestPayloadTraceBytes = 192;
  45. /** Marks a trace that stopped at the cap, so a short hex string is not read as a short payload. */
  46. constexpr std::string_view kTruncated = " truncated=1";
  47. /** The mutation variant's first alternative is the empty one, so index zero prepared nothing. */
  48. constexpr std::size_t kNoMutation = 0;
  49. /**
  50. * Logs the Web Service opcode and a bounded payload trace.
  51. * One svc-10 frame looks like any other, and the opcode drives the client's queuez state machine.
  52. * @param message Parsed request envelope and borrowed payload.
  53. */
  54. void report_request(const middleware::web_service::Message& message) noexcept {
  55. std::array<char, core::log::kLineCapacity> line{};
  56. const int prefix =
  57. std::snprintf(line.data(),
  58. line.size(),
  59. "ev=ws stage=request opcode=%u transaction=%u payload_bytes=%zu payload_hex=",
  60. static_cast<unsigned>(message.opcode),
  61. static_cast<unsigned>(message.transactionId),
  62. message.payload.size());
  63. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  64. return;
  65. }
  66. std::size_t length = static_cast<std::size_t>(prefix);
  67. const std::size_t traced =
  68. (std::min)(message.payload.size(), static_cast<std::size_t>(kRequestPayloadTraceBytes));
  69. (void)core::log::append_hex(line, length, message.payload.first(traced));
  70. if (traced != message.payload.size() && length + kTruncated.size() < line.size()) {
  71. std::memcpy(line.data() + length, kTruncated.data(), kTruncated.size());
  72. length += kTruncated.size();
  73. }
  74. core::log::write(core::log::Channel::server, core::log::Level::info, {line.data(), length});
  75. }
  76. /**
  77. * Refuses one vendor purchase and answers it.
  78. * No award, cost or stock rule exists yet, so no purchase can succeed. The refusal must still be
  79. * answered, because no answer holds the head of the client's pending queue.
  80. * @param message Parsed purchase request.
  81. * @param response Response-body storage owned by the caller.
  82. * @param written Receives the encoded response size.
  83. * @return True when the refusal was encoded.
  84. */
  85. [[nodiscard]] bool refuse_purchase(const middleware::web_service::Message& message,
  86. std::span<std::byte> response,
  87. std::size_t& written) noexcept {
  88. messages::opcode901::Request purchase;
  89. const bool parsed = messages::opcode901::parse_request(message, purchase);
  90. // The clock verdict is logged, never acted on. Nothing can pass while the route refuses.
  91. const auto policy =
  92. messages::opcode901::check_clock(purchase, core::runtime::server_clock_seconds());
  93. std::array<char, kPurchaseLineCapacity> line{};
  94. const int length =
  95. parsed ? std::snprintf(
  96. line.data(),
  97. line.size(),
  98. "ev=ws901 stage=purchase result=refuse vendor=%d sale=%d present=%u policy=%s",
  99. static_cast<int>(purchase.vendorIndex),
  100. static_cast<int>(purchase.saleIndex),
  101. purchase.hasClock ? 1U : 0U,
  102. messages::opcode901::clock_policy_name(policy))
  103. : std::snprintf(line.data(),
  104. line.size(),
  105. "ev=ws901 stage=purchase result=refuse reason=parse");
  106. report_line(core::log::Level::error, line, length);
  107. middleware::web_service::StatusResponse status{};
  108. status.code = middleware::web_service::kRefusedStatusCode;
  109. // A refused purchase grants nothing, so no Family-4 revision carries its result.
  110. status.value = middleware::web_service::kNoFamily4Publication;
  111. // The trailing bool drives a local action effect on the client, so it stays clear.
  112. status.trailingBool = false;
  113. return middleware::web_service::encode_response(
  114. message,
  115. middleware::web_service::ResponseShape::statusPairWithBool,
  116. status,
  117. response,
  118. written);
  119. }
  120. /**
  121. * Answers a request whose own codec refused with the bare correlated echo.
  122. * The Client matches on the echoed transaction id. A missing body under-runs its decoder and
  123. * takes the BAP connection down, so a thin body is always sent.
  124. * @param message Parsed request whose correlation fields are echoed.
  125. * @param response Svc-11 response-body storage owned by the caller.
  126. * @param written Gets the encoded response-body size in bytes.
  127. * @return True when the echo fits.
  128. */
  129. bool encode_echo(const middleware::web_service::Message& message,
  130. std::span<std::byte> response,
  131. std::size_t& written) noexcept {
  132. std::array<char, kOpcodeLineCapacity> line{};
  133. const int count = std::snprintf(
  134. line.data(), line.size(), "ev=ws stage=body result=echo opcode=%u", message.opcode);
  135. report_line(core::log::Level::warn, line, count);
  136. namespace ws = middleware::web_service;
  137. return ws::encode_response(
  138. message, ws::ResponseShape::generic, ws::StatusResponse{}, response, written);
  139. }
  140. /**
  141. * Issues the family-5 server clock the Client extrapolates its family-5 time from.
  142. * The wire field counts whole seconds. A repeated value reads as no change and stalls the
  143. * Client's family-5 boot task, so the issued count must strictly increase.
  144. * @return Unix seconds, always greater than the previous call's result.
  145. */
  146. [[nodiscard]] std::uint64_t next_family5_clock() noexcept {
  147. static std::atomic<std::uint64_t> issued{0};
  148. const auto wall = static_cast<std::uint64_t>(core::runtime::server_clock_seconds());
  149. std::uint64_t previous = issued.load(std::memory_order_relaxed);
  150. std::uint64_t next = 0;
  151. do {
  152. next = wall > previous ? wall : previous + 1;
  153. } while (!issued.compare_exchange_weak(previous, next, std::memory_order_relaxed));
  154. return next;
  155. }
  156. /**
  157. * Records the world state the character write-back reports.
  158. * The body is client-owned state; the world-state field is the one value the host acts on.
  159. * @param message Parsed ws-702 envelope.
  160. */
  161. void note_character_writeback(const middleware::web_service::Message& message) noexcept {
  162. messages::opcode702::Request request;
  163. const bool parsed = messages::opcode702::parse_request(message, request);
  164. std::array<char, core::log::kLineCapacity> line{};
  165. const int written = std::snprintf(line.data(),
  166. line.size(),
  167. "ev=activity stage=writeback result=%s world_state=%u",
  168. parsed ? "ok" : "unparsed",
  169. static_cast<unsigned>(request.worldState));
  170. if (written > 0) {
  171. core::log::write(core::log::Channel::server,
  172. core::log::Level::info,
  173. {line.data(), static_cast<std::size_t>(written)});
  174. }
  175. if (parsed) {
  176. state::activity::membership::note_client_writeback(request.worldState
  177. == messages::opcode702::kInWorld);
  178. }
  179. }
  180. /**
  181. * Applies the report that carries no answer of its own beyond the shared status pair.
  182. * @param message Parsed request envelope and borrowed payload.
  183. */
  184. void note_reports(const middleware::web_service::Message& message) noexcept {
  185. if (message.opcode == messages::opcode702::kOpcode) {
  186. note_character_writeback(message);
  187. }
  188. }
  189. } // namespace
  190. /** Re-encodes a prepared reply as a refusal after its Queuez staging failed. */
  191. bool encode_staging_refusal(const middleware::web_service::Message& message,
  192. std::span<std::byte> response,
  193. std::size_t& written) noexcept {
  194. middleware::web_service::ResponseShape shape{};
  195. resolve_response_shape(message.opcode, shape);
  196. middleware::web_service::StatusResponse status{};
  197. status.code = middleware::web_service::kRefusedStatusCode;
  198. status.value = middleware::web_service::kNoFamily4Publication;
  199. return middleware::web_service::encode_response(message, shape, status, response, written);
  200. }
  201. /** Answers one Web Service request when its caller has no action to publish. */
  202. bool consume(std::span<const std::byte> request,
  203. std::span<std::byte> response,
  204. std::size_t& written) noexcept {
  205. Outcome outcome;
  206. return consume(request, response, written, outcome);
  207. }
  208. /** Parses one request, prepares any action it names, and encodes the reply that reports it. */
  209. bool consume(std::span<const std::byte> request,
  210. std::span<std::byte> response,
  211. std::size_t& written,
  212. Outcome& outcome) noexcept {
  213. written = 0;
  214. outcome = {};
  215. middleware::web_service::Message message;
  216. if (!middleware::web_service::parse_request(request, message)) {
  217. core::log::write(
  218. core::log::Channel::server, core::log::Level::warn, "ev=ws stage=parse result=fail");
  219. return false;
  220. }
  221. report_request(message);
  222. note_reports(message);
  223. if (message.opcode == messages::opcode205::kOpcode) {
  224. state::InvestmentState investment{};
  225. return (state::investment_snapshot(investment)
  226. && messages::opcode205::encode_response(
  227. message, investment, next_family5_clock(), response, written))
  228. || encode_echo(message, response, written);
  229. }
  230. if (message.opcode == messages::opcode503::kOpcode) {
  231. messages::opcode503::Request bootstrap;
  232. const bool parsed = messages::opcode503::parse_request(message, bootstrap);
  233. // The request's own key is echoed and adopted. An authored id here costs the ship and the
  234. // banner.
  235. if (!bootstrap.hasPrimarySoid) {
  236. bootstrap.primarySoid = state::account_snapshot().primarySoid;
  237. }
  238. state::InvestmentState investment{};
  239. if (!parsed || !state::investment_snapshot(investment)
  240. || !messages::opcode503::encode_response(
  241. message, bootstrap, investment, next_family5_clock(), response, written)) {
  242. return encode_echo(message, response, written);
  243. }
  244. if (bootstrap.hasPrimarySoid && !state::set_primary_soid(bootstrap.primarySoid)) {
  245. core::log::write(core::log::Channel::server,
  246. core::log::Level::warn,
  247. "ev=ws503 stage=adopt result=fail");
  248. }
  249. return true;
  250. }
  251. if (message.opcode == messages::opcode501::kOpcode) {
  252. // Returns a SOID family three already publishes. The request body is not parsed.
  253. const std::uint64_t characterSoid =
  254. state::account::selected_character_soid(state::account_snapshot());
  255. return messages::opcode501::encode_response(message, characterSoid, response, written)
  256. || encode_echo(message, response, written);
  257. }
  258. // Runs before the shared response-shape path, which would answer the success status.
  259. if (message.opcode == messages::opcode901::kOpcode) {
  260. return refuse_purchase(message, response, written)
  261. || encode_echo(message, response, written);
  262. }
  263. if (message.opcode == messages::opcode601::kOpcode) {
  264. return messages::opcode601::encode_response(message, response, written)
  265. || encode_echo(message, response, written);
  266. }
  267. // A subscribe whose body does not parse is still answered; only the subscription is dropped.
  268. middleware::queuez::Subscription subscription;
  269. const bool subscribes = message.opcode == messages::opcode206::kOpcode
  270. && messages::opcode206::parse_request(message, subscription);
  271. // The action runs before its reply is encoded, because the reply reports whether it worked.
  272. // Most actions fill the outcome only after preparing a whole transition. WS-701 also accepts
  273. // a valid no-op heartbeat, so that one success is tracked separately from mutation presence.
  274. bool dispatched = true;
  275. bool acceptedWithoutMutation = false;
  276. if (message.opcode == messages::opcode504::kOpcode) {
  277. select_character(message, outcome);
  278. } else if (message.opcode == messages::opcode402::kOpcode) {
  279. dismantle_item(message, outcome);
  280. } else if (message.opcode == messages::opcode403::kOpcode) {
  281. mutate_equipment(message, false, outcome);
  282. } else if (message.opcode == messages::opcode403::kUnequipOpcode) {
  283. mutate_equipment(message, true, outcome);
  284. } else if (message.opcode == messages::opcode801::kOpcode) {
  285. mutate_subclass_selection(message, outcome);
  286. } else if (message.opcode == messages::opcode903::kOpcode) {
  287. mutate_socket_plug(message, outcome);
  288. } else if (message.opcode == messages::opcode1901::kOpcode) {
  289. mutate_equipped_socket_plug(message, outcome);
  290. } else if (message.opcode == messages::opcode406::kOpcode) {
  291. mutate_item_state(message, outcome);
  292. } else if (message.opcode == messages::opcode701::kOpcode) {
  293. const state::SettingsUpdateDisposition disposition = mutate_settings(message, outcome);
  294. acceptedWithoutMutation = disposition == state::SettingsUpdateDisposition::acceptedNoChange;
  295. } else if (message.opcode == messages::opcode1820::kOpcode) {
  296. acquire_item(message, outcome);
  297. } else {
  298. dispatched = false;
  299. }
  300. const bool prepared = outcome.hasSelectedCharacter || outcome.mutation.index() != kNoMutation;
  301. middleware::web_service::ResponseShape shape{};
  302. resolve_response_shape(message.opcode, shape);
  303. middleware::web_service::StatusResponse status{};
  304. if (awaits_family4_version(message.opcode)) {
  305. // Nothing is published from here. A staged mutation re-encodes this with its own revision.
  306. status.value = middleware::web_service::kNoFamily4Publication;
  307. }
  308. if (dispatched && !prepared && !acceptedWithoutMutation) {
  309. status.code = middleware::web_service::kRefusedStatusCode;
  310. }
  311. if (!middleware::web_service::encode_response(message, shape, status, response, written)) {
  312. // The echo carries no status, so nothing may be published against it.
  313. outcome = {};
  314. return encode_echo(message, response, written);
  315. }
  316. if (subscribes) {
  317. // Publish the subscription only after its correlated response is complete.
  318. outcome.hasSubscription = true;
  319. outcome.subscription = subscription;
  320. }
  321. return true;
  322. }
  323. } // namespace sunrise::server::web_service