activity_message_route.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. #include "activity_message_route.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <bit>
  5. #include <cstdio>
  6. #include "../../../../core/logging/log.h"
  7. #include "../../../../core/settings/settings.h"
  8. #include "../../../../middleware/bap/activity_message/activity_client_identity_parser.h"
  9. #include "../../../../middleware/bap/activity_message/activity_client_keepalive_validator.h"
  10. #include "../../../../middleware/bap/activity_message/activity_high_water_validator.h"
  11. #include "../../../../middleware/bap/activity_message/activity_join_request_parser.h"
  12. #include "../../../../middleware/bap/activity_message/activity_membership_acknowledgement_parser.h"
  13. #include "../../../../middleware/bap/activity_message/activity_message_request_parser.h"
  14. #include "../../../../middleware/bap/activity_message/activity_state_refresh_parser.h"
  15. #include "../../../../middleware/bap/activity_message/client_authoritative_data.h"
  16. #include "../../../../middleware/bap/activity_message/entity_authority.h"
  17. #include "../../../../middleware/bap/activity_message/entity_slots.h"
  18. #include "../../../../middleware/bap/activity_message/incident.h"
  19. #include "../../../../state/activity/runtime.h"
  20. #include "membership/activity_membership_route.h"
  21. #include "middleware/bap/activity_message/activity_entity_slot_request_parser.h"
  22. #include "patch_epoch/activity_patch_epoch_route.h"
  23. namespace sunrise::server::bap::encrypted::activity_message {
  24. namespace {
  25. namespace service = middleware::bap::activity_message;
  26. namespace authority = service::entity_authority;
  27. namespace client_keepalive = service::client_keepalive;
  28. namespace high_water = service::high_water;
  29. namespace epoch_message = service::patch_epoch;
  30. /** Activity message type 3 starts the client join transaction. */
  31. constexpr std::uint32_t kJoinRequestMessageType = 3;
  32. /** One row per Client-sent message this route accepts but has no state to change for. */
  33. struct AcceptedMessage {
  34. std::uint32_t type;
  35. const char* name;
  36. };
  37. /**
  38. * The Client senders that carry no work for this host. Each is one-way, so accepting is the whole
  39. * contract. The names are the binary's own, so a log line says what arrived.
  40. */
  41. constexpr std::array<AcceptedMessage, 14> kAcceptedMessages{{
  42. {6, "sensor_sense_update"},
  43. {8, "request_activity_host"},
  44. {11, "start_new_activity"},
  45. {13, "request_peer_reservation"},
  46. {14, "release_peer_reservation"},
  47. {15, "peer_leave_request"},
  48. {34, "process_debug_command"},
  49. {37, "connectivity_failure"},
  50. {39, "send_client_heartbeat"},
  51. {43, "bug_claw"},
  52. {46, "report_lag_switch"},
  53. {47, "connection_quality_report"},
  54. {48, "speculative_migration"},
  55. {50, "refresh_inspirations"},
  56. }};
  57. /** @return The binary name for one accepted message type, or nullptr when it is not one. */
  58. [[nodiscard]] const char* accepted_name(std::uint32_t messageType) noexcept {
  59. const auto row = std::find_if(kAcceptedMessages.begin(),
  60. kAcceptedMessages.end(),
  61. [messageType](const AcceptedMessage& candidate) noexcept {
  62. return candidate.type == messageType;
  63. });
  64. return row == kAcceptedMessages.end() ? nullptr : row->name;
  65. }
  66. /**
  67. * Records one accepted message that changes no host state.
  68. * @param messageType Activity message type from the envelope.
  69. * @param name Binary name for that type.
  70. * @param payloadSize Declared payload bytes, which is the only thing that varies here.
  71. */
  72. void report_accepted(std::uint32_t messageType,
  73. const char* name,
  74. std::size_t payloadSize) noexcept {
  75. std::array<char, core::log::kLineCapacity> line{};
  76. const int written = std::snprintf(line.data(),
  77. line.size(),
  78. "ev=activity stage=message result=accept type=%u name=%s "
  79. "bytes=%zu",
  80. messageType,
  81. name,
  82. payloadSize);
  83. if (written > 0) {
  84. core::log::write(core::log::Channel::server,
  85. core::log::Level::debug,
  86. {line.data(), static_cast<std::size_t>(written)});
  87. }
  88. }
  89. /**
  90. * Checks one incident and reports its verdict. Nothing relays msg 19 yet, so a pass changes
  91. * nothing. A failure is named because a bad target index would crash the Client if it were sent on.
  92. * @param request Validated owned svc8 envelope.
  93. */
  94. void report_incident(const service::Request& request) noexcept {
  95. namespace incident = service::incident;
  96. incident::Incident parsed;
  97. const incident::Verdict verdict = incident::validate(request.payload, parsed);
  98. std::array<char, core::log::kLineCapacity> line{};
  99. const int written = std::snprintf(line.data(),
  100. line.size(),
  101. "ev=activity stage=incident result=%s target=%u extra=%u "
  102. "selector=%u payload=%u",
  103. incident::verdict_name(verdict),
  104. parsed.primaryTarget,
  105. parsed.extraTargetCount,
  106. static_cast<unsigned>(parsed.hasCompressedSelector),
  107. parsed.payloadLength);
  108. if (written <= 0) {
  109. return;
  110. }
  111. const auto level =
  112. verdict == incident::Verdict::accepted ? core::log::Level::debug : core::log::Level::warn;
  113. core::log::write(
  114. core::log::Channel::server, level, {line.data(), static_cast<std::size_t>(written)});
  115. }
  116. /**
  117. * Reports one activity message the route did not stage, naming its type.
  118. * Every inbound activity message is one-way, so nothing here can jam the Client's reply ring. An
  119. * unnamed drop is invisible, and membership waits on the identity message.
  120. * @param messageType Activity message type from the envelope.
  121. * @param accountHandle Handle the envelope carried.
  122. * @param reason Short name of the step that declined.
  123. */
  124. void report_message(std::uint32_t messageType,
  125. std::uint64_t accountHandle,
  126. const char* reason) noexcept {
  127. std::array<char, core::log::kLineCapacity> line{};
  128. const int written = std::snprintf(line.data(),
  129. line.size(),
  130. "ev=activity stage=message result=skip type=%u "
  131. "handle=0x%llX reason=%s",
  132. messageType,
  133. static_cast<unsigned long long>(accountHandle),
  134. reason);
  135. if (written > 0) {
  136. core::log::write(core::log::Channel::server,
  137. core::log::Level::warn,
  138. {line.data(), static_cast<std::size_t>(written)});
  139. }
  140. }
  141. /**
  142. * Prepares the joined State and the whole initial lease mask as one mutation.
  143. * @param request Validated owned svc8 envelope.
  144. * @param plan Cleared, then receives join scalars and the chosen lease mask.
  145. * @return True when the fixed join payload and current State can stage together.
  146. */
  147. [[nodiscard]] bool prepare_join(const service::Request& request, ActivityPlan& plan) noexcept {
  148. service::JoinRequest parsed;
  149. // The client takes the low slots and the server keeps the reserve above them.
  150. const std::size_t reserve =
  151. core::settings::server::gameplay::effective_reserve(core::settings::get().server.gameplay);
  152. const std::size_t granted = state::activity::entity_slots::kSlotCount - reserve;
  153. if (!service::join_request::parse_join_request(request.payload, parsed)
  154. || parsed.sessionId != request.accountHandle
  155. || !state::activity::entity_slots::prepare_join(
  156. parsed.sessionId, parsed.memberKey, granted, reserve, plan.entitySlotMutation)) {
  157. return false;
  158. }
  159. plan.correlation = parsed.correlation;
  160. plan.sessionId = parsed.sessionId;
  161. plan.joinCharacterSoid = parsed.characterSoid;
  162. plan.delivery = Delivery::joinNotifications;
  163. plan.mutationDomain = MutationDomain::entitySlots;
  164. return true;
  165. }
  166. /**
  167. * Prepares only currently free slots for one positive client request.
  168. * @param request Validated owned svc8 envelope.
  169. * @param plan Cleared, then receives the chosen lease mask.
  170. * @return True for a valid positive request, including an exhausted zero-mask grant.
  171. */
  172. [[nodiscard]] bool prepare_grant(const service::Request& request, ActivityPlan& plan) noexcept {
  173. std::int32_t requested = 0;
  174. if (!service::entity_slot_request::parse_entity_slot_request(request.payload, requested)
  175. || requested <= 0
  176. || !state::activity::entity_slots::prepare_grant(
  177. request.accountHandle, static_cast<std::size_t>(requested), plan.entitySlotMutation)) {
  178. return false;
  179. }
  180. plan.sessionId = request.accountHandle;
  181. plan.delivery = Delivery::entitySlotNotification;
  182. plan.mutationDomain = MutationDomain::entitySlots;
  183. return true;
  184. }
  185. /** @return How many slots one authority mask names. */
  186. [[nodiscard]] std::size_t
  187. mask_slot_count(const service::entity_slots::EntitySlotMask& mask) noexcept {
  188. std::size_t slots = 0;
  189. for (const std::byte value : mask) {
  190. slots += static_cast<std::size_t>(std::popcount(std::to_integer<unsigned char>(value)));
  191. }
  192. return slots;
  193. }
  194. /**
  195. * Reports one msg 26 or msg 33. Neither returns a lease. Msg 21 does.
  196. * @param request Validated owned svc8 envelope.
  197. * @param expectReason True for msg 26, which trails a 3-bit reason after the mask.
  198. * @return True when the fixed body for that message type decodes.
  199. */
  200. [[nodiscard]] bool report_authority_release(const service::Request& request,
  201. bool expectReason) noexcept {
  202. authority::Release decoded;
  203. const bool parsed = expectReason ? authority::parse_abandon(request.payload, decoded)
  204. : authority::parse_abdicate(request.payload, decoded);
  205. if (!parsed) {
  206. return false;
  207. }
  208. std::array<char, core::log::kLineCapacity> line{};
  209. const int written = std::snprintf(line.data(),
  210. line.size(),
  211. "ev=activity stage=authority result=noted type=%u "
  212. "selector=%u reason=%d slots=%zu",
  213. request.messageType,
  214. static_cast<unsigned>(decoded.selector),
  215. decoded.hasReason ? decoded.reason : 0,
  216. mask_slot_count(decoded.mask));
  217. if (written > 0) {
  218. core::log::write(core::log::Channel::server,
  219. core::log::Level::debug,
  220. {line.data(), static_cast<std::size_t>(written)});
  221. }
  222. return true;
  223. }
  224. /**
  225. * Reports one msg 29, 31 or 32 answer. This host sends no msg 28 or msg 30, so an answer here is
  226. * the Client reconciling on its own. Nothing is staged.
  227. * @param request Validated owned svc8 envelope.
  228. * @return True when the body for that message type decodes.
  229. */
  230. [[nodiscard]] bool report_query_answer(const service::Request& request) noexcept {
  231. namespace authority = service::entity_authority;
  232. authority::QueryAnswer answer;
  233. if (!authority::parse_query_answer(request.messageType, request.payload, answer)) {
  234. return false;
  235. }
  236. std::array<char, core::log::kLineCapacity> line{};
  237. const int written = std::snprintf(line.data(),
  238. line.size(),
  239. "ev=activity stage=authority result=ok type=%u corr=0x%08X "
  240. "selector=%d mask=%u",
  241. request.messageType,
  242. answer.correlation,
  243. answer.hasSelector ? static_cast<int>(answer.selector) : -1,
  244. static_cast<unsigned>(answer.hasMask));
  245. if (written > 0) {
  246. core::log::write(core::log::Channel::server,
  247. core::log::Level::debug,
  248. {line.data(), static_cast<std::size_t>(written)});
  249. }
  250. return true;
  251. }
  252. /**
  253. * Reports one msg 27 purge request. The host does not answer it: the reply is msg 25, whose
  254. * consumer asserts unless the epoch is one above the Client's own, and nothing here tracks that.
  255. * @param request Validated owned svc8 envelope.
  256. * @return True when the fixed body is present.
  257. */
  258. [[nodiscard]] bool report_request_purge(const service::Request& request) noexcept {
  259. std::int32_t reason = 0;
  260. if (!service::entity_authority::parse_request_purge(request.payload, reason)) {
  261. return false;
  262. }
  263. std::array<char, core::log::kLineCapacity> line{};
  264. const int written = std::snprintf(
  265. line.data(), line.size(), "ev=activity stage=purge result=noted reason=%d", reason);
  266. if (written > 0) {
  267. core::log::write(core::log::Channel::server,
  268. core::log::Level::debug,
  269. {line.data(), static_cast<std::size_t>(written)});
  270. }
  271. return true;
  272. }
  273. /**
  274. * Prepares only the slots that are both held and in the returned mask.
  275. * @param request Validated owned svc8 envelope.
  276. * @param plan Cleared, then receives the chosen release mask.
  277. * @return True when the exact mask decodes and its session can stage a release.
  278. */
  279. [[nodiscard]] bool prepare_release(const service::Request& request, ActivityPlan& plan) noexcept {
  280. service::entity_slots::EntitySlotMask decoded{};
  281. if (!service::entity_slots::decode_entity_slots(request.payload, decoded)) {
  282. return false;
  283. }
  284. state::activity::entity_slots::LeaseMask returned{};
  285. std::copy(decoded.begin(), decoded.end(), returned.begin());
  286. if (!state::activity::entity_slots::prepare_release(
  287. request.accountHandle, returned, plan.entitySlotMutation)) {
  288. return false;
  289. }
  290. plan.sessionId = request.accountHandle;
  291. plan.delivery = Delivery::none;
  292. plan.mutationDomain = MutationDomain::entitySlots;
  293. return true;
  294. }
  295. } // namespace
  296. /** Routes one svc8 activity message and prepares any supported push transaction. */
  297. bool process(std::uint64_t boundSessionId,
  298. std::span<const std::byte> requestBody,
  299. ActivityPlan& plan,
  300. bool& hasTransaction) noexcept {
  301. plan = {};
  302. hasTransaction = false;
  303. service::Request request;
  304. if (!service::parse_request(requestBody, request)) {
  305. report_message(0, 0, "parse");
  306. return false;
  307. }
  308. // Dispatch is on message type alone, and every handler keys off the envelope's own handle, so
  309. // nothing has to be bound first. The join request carries the session in the first place, and
  310. // it arrives on a link that has allocated nothing.
  311. bool prepared = false;
  312. if (request.messageType == epoch_message::kMessageType) {
  313. // Type 52 alone carries a zero handle, so its session is the one this link allocated.
  314. prepared = patch_epoch::prepare(boundSessionId, request, plan);
  315. } else if (request.messageType == high_water::kMessageType
  316. || request.messageType == client_keepalive::kMessageType) {
  317. // Both are one-way notices with nothing to answer.
  318. return true;
  319. } else if (request.messageType == kJoinRequestMessageType) {
  320. prepared = prepare_join(request, plan);
  321. } else if (request.messageType == service::entity_slot_request::kMessageType) {
  322. prepared = prepare_grant(request, plan);
  323. } else if (request.messageType == service::entity_slots::kRequestMessageType) {
  324. prepared = prepare_release(request, plan);
  325. } else if (request.messageType == service::state_refresh::kMessageType) {
  326. prepared = membership::prepare_refresh(request, plan);
  327. } else if (request.messageType == service::client_identity::kMessageType) {
  328. prepared = membership::prepare_identity(request, plan);
  329. } else if (request.messageType == service::client_authoritative_data::kMessageType) {
  330. prepared = membership::prepare_authoritative(request, plan);
  331. } else if (request.messageType == service::membership_acknowledgement::kMessageType) {
  332. prepared = membership::prepare_acknowledgement(request, plan);
  333. } else if (request.messageType == authority::kAbandonMessageType) {
  334. if (!report_authority_release(request, true)) {
  335. report_message(request.messageType, request.accountHandle, "parse");
  336. }
  337. return true;
  338. } else if (request.messageType == authority::kAbdicateMessageType) {
  339. if (!report_authority_release(request, false)) {
  340. report_message(request.messageType, request.accountHandle, "parse");
  341. }
  342. return true;
  343. } else if (request.messageType == service::incident::kMessageType) {
  344. report_incident(request);
  345. return true;
  346. } else if (request.messageType == authority::kRequestPurgeMessageType) {
  347. if (!report_request_purge(request)) {
  348. report_message(request.messageType, request.accountHandle, "parse");
  349. }
  350. return true;
  351. } else if (request.messageType == authority::kResetAcknowledgementMessageType
  352. || request.messageType == authority::kQueryPerBubbleMessageType
  353. || request.messageType == authority::kQueryResponseMessageType) {
  354. if (!report_query_answer(request)) {
  355. report_message(request.messageType, request.accountHandle, "parse");
  356. }
  357. return true;
  358. } else if (const char* name = accepted_name(request.messageType); name != nullptr) {
  359. // One-way with nothing to change here. Accepting is the whole contract.
  360. report_accepted(request.messageType, name, request.payload.size());
  361. return true;
  362. } else {
  363. // Later message handlers are independent. An owned envelope is a safe no-op.
  364. report_message(request.messageType, request.accountHandle, "unhandled");
  365. return true;
  366. }
  367. // A message that cannot be staged is reported and dropped. Failing the frame would leave the
  368. // Client's pending ring jammed.
  369. if (!prepared) {
  370. report_message(request.messageType, request.accountHandle, "prepare");
  371. plan = {};
  372. return true;
  373. }
  374. hasTransaction = true;
  375. return true;
  376. }
  377. } // namespace sunrise::server::bap::encrypted::activity_message