bap_service_body.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #include "../../../../core/logging/log.h"
  2. #include "../../../../middleware/bap/account_translation/account_translation_response.h"
  3. #include "../../../../middleware/bap/activity_host/activity_host_response.h"
  4. #include "../../../../middleware/bap/certificate.h"
  5. #include "../../../../middleware/bap/client_config/client_config_response.h"
  6. #include "../../../../middleware/bap/family_subscription.h"
  7. #include "../../../../middleware/bap/family_unsubscription.h"
  8. #include "../../../../middleware/bap/user_message/user_message_response.h"
  9. #include "../../../../middleware/web_service/messages/opcode505/opcode505_codec.h"
  10. #include "../../../../state/runtime/runtime.h"
  11. #include "../../../web_service/web_service_runtime.h"
  12. #include "../activity_host_manager/activity_host_manager_route.h"
  13. #include "../activity_message/activity_message_route.h"
  14. #include "../internal.h"
  15. #include "../matchmaking/matchmaking_route.h"
  16. #include "../queuez/queuez_state_validation.h"
  17. namespace sunrise::server::bap::encrypted::body {
  18. /**
  19. * Processes the body for one authenticated service route.
  20. * @param route Service route data found earlier.
  21. * @param queuezState Queuez versions and residents set up by this BAP peer.
  22. * @param activitySessionId Activity capability allocated through this BAP session.
  23. * @param matchmakingContext State-owned logical context for this BAP session.
  24. * @param requestBody Borrowed decrypted request body.
  25. * @param output Caller-owned response-body storage.
  26. * @param written Receives encoded body bytes.
  27. * @param outcome Receives one validated transport action or deferred State transaction.
  28. * @return True when the chosen body codec succeeds.
  29. */
  30. bool process(const ServiceRoute& route,
  31. const queuez::SessionState& queuezState,
  32. std::uint64_t activitySessionId,
  33. state::matchmaking::ContextHandle matchmakingContext,
  34. std::span<const std::byte> requestBody,
  35. std::span<std::byte> output,
  36. std::size_t& written,
  37. ServiceOutcome& outcome) noexcept {
  38. outcome = {};
  39. switch (route.bodyCodec) {
  40. case BodyCodec::empty:
  41. written = 0;
  42. return true;
  43. case BodyCodec::accountTranslationResponse: {
  44. const state::AccountState account = state::account_snapshot();
  45. return middleware::bap::account_translation::encode_response(
  46. requestBody, account.primarySoid, output, written);
  47. }
  48. case BodyCodec::activityHostManagerResponse:
  49. return activity_host_manager::encode_response(requestBody,
  50. output,
  51. written,
  52. outcome.activitySessionAllocation,
  53. outcome.hasActivitySessionAllocation);
  54. case BodyCodec::activityMessageRequest:
  55. written = 0;
  56. return activity_message::process(
  57. activitySessionId, requestBody, outcome.activityPlan, outcome.hasActivityTransaction);
  58. case BodyCodec::activityHostResponse: {
  59. const state::SignOnState& signOn = state::sign_on();
  60. return middleware::bap::activity_host::encode_response(
  61. requestBody, signOn.relayAddress, signOn.relayPort, output, written);
  62. }
  63. case BodyCodec::clientConfigResponse:
  64. return middleware::bap::client_config::encode_minimal_response(output, written);
  65. case BodyCodec::familySubscription:
  66. written = 0;
  67. outcome.hasSubscription =
  68. middleware::bap::family_subscription::parse(requestBody, outcome.subscription);
  69. return outcome.hasSubscription;
  70. case BodyCodec::familyUnsubscription: {
  71. written = 0;
  72. outcome.hasUnsubscription =
  73. middleware::bap::family_unsubscription::parse(requestBody, outcome.unsubscription);
  74. return outcome.hasUnsubscription;
  75. }
  76. case BodyCodec::matchmakingResponse:
  77. return matchmaking::encode_response(matchmakingContext,
  78. requestBody,
  79. output,
  80. written,
  81. outcome.matchmakingMutation,
  82. outcome.hasMatchmakingMutation);
  83. case BodyCodec::steamCertificate:
  84. return middleware::bap::certificate::encode_response(requestBody, output, written);
  85. case BodyCodec::userMessageResponse:
  86. return middleware::bap::user_message::encode_minimal_response(output, written);
  87. case BodyCodec::webService: {
  88. middleware::web_service::Message message;
  89. if (middleware::web_service::parse_request(requestBody, message)
  90. && message.opcode == middleware::web_service::messages::opcode505::kOpcode) {
  91. if (!middleware::web_service::messages::opcode505::parse_request(message)
  92. || !queuez::stage_change_character(queuezState, outcome.changeCharacter)
  93. || !middleware::web_service::messages::opcode505::encode_response(
  94. message, outcome.changeCharacter.after.family4Version, output, written)) {
  95. core::log::write(core::log::Channel::server,
  96. core::log::Level::warn,
  97. "ev=ws505 stage=change result=fail");
  98. // The plain status pair still goes out. The Client's Change Character waits on the
  99. // echoed transaction id, so a missing reply hangs it for the rest of the run.
  100. outcome.changeCharacter = {};
  101. return middleware::web_service::encode_response(
  102. message,
  103. middleware::web_service::ResponseShape::statusPair,
  104. middleware::web_service::StatusResponse{},
  105. output,
  106. written);
  107. }
  108. outcome.hasChangeCharacter = true;
  109. return true;
  110. }
  111. web_service::Outcome webOutcome;
  112. if (!sunrise::server::web_service::consume(requestBody, output, written, webOutcome)) {
  113. return false;
  114. }
  115. outcome.hasSubscription = webOutcome.hasSubscription;
  116. outcome.subscription = webOutcome.subscription;
  117. if (webOutcome.hasEquipmentSwap) {
  118. // Equip is an optimistic Character-screen action. Its status-pair value is the exact
  119. // Family-4 revision whose following Queuez frame makes the action authoritative. Stage
  120. // that revision before encoding the reply so the Client cannot complete the action
  121. // against the old object store.
  122. if (!queuez::stage_equipment_swap(queuezState,
  123. webOutcome.equipmentSwap.characterSoid,
  124. outcome.equipmentSwapUpdate)) {
  125. core::log::write(core::log::Channel::server,
  126. core::log::Level::warn,
  127. "ev=ws403 stage=queuez_preflight result=fail");
  128. // Keep the already-encoded sentinel response and publish no mutation, matching
  129. // the change-character failure contract instead of dropping the correlated task.
  130. outcome.equipmentSwapUpdate = {};
  131. } else {
  132. middleware::web_service::StatusResponse status{};
  133. status.value = outcome.equipmentSwapUpdate.after.family4Version;
  134. if (!middleware::web_service::encode_response(
  135. message,
  136. middleware::web_service::ResponseShape::statusPair,
  137. status,
  138. output,
  139. written)) {
  140. core::log::write(core::log::Channel::server,
  141. core::log::Level::warn,
  142. "ev=ws403 stage=response result=fail");
  143. return false;
  144. }
  145. web_service::report_equip_response(message, status.value, output.first(written));
  146. outcome.hasEquipmentSwap = true;
  147. outcome.equipmentSwap = webOutcome.equipmentSwap;
  148. }
  149. }
  150. if (webOutcome.hasSocketPlug) {
  151. // Opcode 903 completes at the exact Family-4 revision carrying the changed resident
  152. // item instance. The resident manifest and character placement remain unchanged.
  153. if (!queuez::stage_socket_plug(queuezState,
  154. webOutcome.socketPlug.accountSoid,
  155. webOutcome.socketPlug.characterSoid,
  156. webOutcome.socketPlug.targetInstanceSoid,
  157. webOutcome.socketPlug.profileChanged,
  158. outcome.socketPlugUpdate)) {
  159. core::log::write(core::log::Channel::server,
  160. core::log::Level::warn,
  161. "ev=socket_plug stage=queuez_preflight result=fail");
  162. outcome.socketPlugUpdate = {};
  163. } else {
  164. middleware::web_service::StatusResponse status{};
  165. status.value = outcome.socketPlugUpdate.after.family4Version;
  166. if (!middleware::web_service::encode_response(
  167. message,
  168. middleware::web_service::ResponseShape::statusPair,
  169. status,
  170. output,
  171. written)) {
  172. core::log::write(core::log::Channel::server,
  173. core::log::Level::warn,
  174. "ev=socket_plug stage=response result=fail");
  175. return false;
  176. }
  177. web_service::report_socket_plug_response(message,
  178. status.value,
  179. webOutcome.socketPlug.targetInstanceSoid,
  180. webOutcome.socketPlug.socketLane,
  181. webOutcome.socketPlug.plugDefinitionIndex,
  182. output.first(written));
  183. outcome.hasSocketPlug = true;
  184. outcome.socketPlug = webOutcome.socketPlug;
  185. }
  186. }
  187. if (webOutcome.hasItemState) {
  188. // Opcode 406 completes at the exact Family-4 revision carrying the changed inventory
  189. // row flags. Placement and every resident item-instance body remain unchanged.
  190. if (!queuez::stage_equipment_swap(
  191. queuezState, webOutcome.itemState.characterSoid, outcome.itemStateUpdate)) {
  192. core::log::write(core::log::Channel::server,
  193. core::log::Level::warn,
  194. "ev=item_state stage=queuez_preflight result=fail");
  195. outcome.itemStateUpdate = {};
  196. } else {
  197. middleware::web_service::StatusResponse status{};
  198. status.value = outcome.itemStateUpdate.after.family4Version;
  199. if (!middleware::web_service::encode_response(
  200. message,
  201. middleware::web_service::ResponseShape::statusPair,
  202. status,
  203. output,
  204. written)) {
  205. core::log::write(core::log::Channel::server,
  206. core::log::Level::warn,
  207. "ev=item_state stage=response result=fail");
  208. return false;
  209. }
  210. outcome.hasItemState = true;
  211. outcome.itemState = webOutcome.itemState;
  212. }
  213. }
  214. if (webOutcome.hasItemAcquisition) {
  215. // A Collections pull is complete only at the exact Family-4 revision that adds both
  216. // the inventory row and its newly resident instance object. Stage that revision before
  217. // re-encoding the correlated status pair, just like an equipment swap.
  218. if (!queuez::stage_item_acquisition(queuezState,
  219. webOutcome.itemAcquisition.accountSoid,
  220. webOutcome.itemAcquisition.characterSoid,
  221. webOutcome.itemAcquisition.acquiredInstanceSoid,
  222. webOutcome.itemAcquisition.profileChanged,
  223. outcome.itemAcquisitionUpdate)) {
  224. core::log::write(core::log::Channel::server,
  225. core::log::Level::warn,
  226. "ev=acquire stage=queuez_preflight result=fail");
  227. outcome.itemAcquisitionUpdate = {};
  228. } else {
  229. middleware::web_service::StatusResponse status{};
  230. status.value = outcome.itemAcquisitionUpdate.after.family4Version;
  231. if (!middleware::web_service::encode_response(
  232. message,
  233. middleware::web_service::ResponseShape::statusPair,
  234. status,
  235. output,
  236. written)) {
  237. core::log::write(core::log::Channel::server,
  238. core::log::Level::warn,
  239. "ev=acquire stage=response result=fail");
  240. return false;
  241. }
  242. web_service::report_item_acquisition_response(
  243. message,
  244. status.value,
  245. webOutcome.itemAcquisition.acquiredInstanceSoid,
  246. output.first(written));
  247. outcome.hasItemAcquisition = true;
  248. outcome.itemAcquisition = webOutcome.itemAcquisition;
  249. }
  250. }
  251. if (webOutcome.hasProfileItemAcquisition) {
  252. // Profile stacks live in the account body. Actionable shaders/modifications also name
  253. // a Family-4 item resident: an existing stack must already own it, while a newly
  254. // appended row adds it atomically at this exact +1 revision.
  255. if (!queuez::stage_profile_item_acquisition(
  256. queuezState,
  257. webOutcome.profileItemAcquisition.accountSoid,
  258. webOutcome.profileItemAcquisition.acquiredInstanceSoid,
  259. webOutcome.profileItemAcquisition.actionSource,
  260. webOutcome.profileItemAcquisition.appended,
  261. outcome.profileItemAcquisitionUpdate)) {
  262. core::log::write(core::log::Channel::server,
  263. core::log::Level::warn,
  264. "ev=profile_acquire stage=queuez_preflight result=fail");
  265. outcome.profileItemAcquisitionUpdate = {};
  266. } else {
  267. middleware::web_service::StatusResponse status{};
  268. status.value = outcome.profileItemAcquisitionUpdate.after.family4Version;
  269. if (!middleware::web_service::encode_response(
  270. message,
  271. middleware::web_service::ResponseShape::statusPair,
  272. status,
  273. output,
  274. written)) {
  275. core::log::write(core::log::Channel::server,
  276. core::log::Level::warn,
  277. "ev=profile_acquire stage=response result=fail");
  278. return false;
  279. }
  280. web_service::report_profile_item_acquisition_response(
  281. message,
  282. status.value,
  283. webOutcome.profileItemAcquisition.acquiredDefinitionHash,
  284. webOutcome.profileItemAcquisition.acquiredQuantity,
  285. output.first(written));
  286. outcome.hasProfileItemAcquisition = true;
  287. outcome.profileItemAcquisition = webOutcome.profileItemAcquisition;
  288. }
  289. }
  290. if (webOutcome.hasItemDismantle) {
  291. // Dismantle is another optimistic Character-screen action. Promise only the exact
  292. // Family-4 revision that carries both the character after-image and the empty
  293. // item-instance release descriptor; otherwise retain the generic sentinel reply and
  294. // publish no removal.
  295. if (!queuez::stage_item_dismantle(queuezState,
  296. webOutcome.itemDismantle.accountSoid,
  297. webOutcome.itemDismantle.characterSoid,
  298. webOutcome.itemDismantle.dismantledInstanceSoid,
  299. webOutcome.itemDismantle.profileChanged,
  300. outcome.itemDismantleUpdate)) {
  301. core::log::write(core::log::Channel::server,
  302. core::log::Level::warn,
  303. "ev=dismantle stage=queuez_preflight result=fail");
  304. outcome.itemDismantleUpdate = {};
  305. } else {
  306. middleware::web_service::StatusResponse status{};
  307. status.value = outcome.itemDismantleUpdate.after.family4Version;
  308. if (!middleware::web_service::encode_response(
  309. message,
  310. middleware::web_service::ResponseShape::statusPair,
  311. status,
  312. output,
  313. written)) {
  314. core::log::write(core::log::Channel::server,
  315. core::log::Level::warn,
  316. "ev=dismantle stage=response result=fail");
  317. return false;
  318. }
  319. web_service::report_item_dismantle_response(
  320. message,
  321. status.value,
  322. webOutcome.itemDismantle.dismantledInstanceSoid,
  323. output.first(written));
  324. outcome.hasItemDismantle = true;
  325. outcome.itemDismantle = webOutcome.itemDismantle;
  326. }
  327. }
  328. // A pick that names the resident character moves nothing, so staging refuses it and the
  329. // reply still stands on its own.
  330. if (webOutcome.hasSelectedCharacter
  331. && queuez::stage_select_character(
  332. queuezState, webOutcome.selectedCharacterSoid, outcome.selectCharacter)) {
  333. outcome.hasSelectCharacter = true;
  334. } else {
  335. outcome.selectCharacter = {};
  336. }
  337. return true;
  338. }
  339. }
  340. written = 0;
  341. return false;
  342. }
  343. } // namespace sunrise::server::bap::encrypted::body