web_service_runtime.cpp 14 KB

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