web_service_runtime.cpp 16 KB

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