activity_message_framing.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <span>
  6. #include "../../../../middleware/bap/activity_message/cinematic_incident.h"
  7. #include "../../../../middleware/bap/activity_message/entity_authority.h"
  8. #include "../../../../middleware/bap/activity_message/incident.h"
  9. #include "../../../../middleware/bap/activity_message/player_trigger_incident.h"
  10. #include "../../../../middleware/bap/activity_message/sense_update.h"
  11. #include "../../../../state/activity_sdk/runtime.h"
  12. #include "activity_message_route_internal.h"
  13. namespace sunrise::server::bap::encrypted::activity_message {
  14. namespace {
  15. namespace sense_update = middleware::bap::activity_message::sense_update;
  16. namespace cinematic_incident = middleware::bap::activity_message::cinematic_incident;
  17. namespace player_trigger_incident = middleware::bap::activity_message::player_trigger_incident;
  18. namespace activity_sdk = state::activity_sdk;
  19. /** Borrowed exact connection and SDK state used only during one msg-6 route call. */
  20. struct SenseResolverContext final {
  21. const ActivityClientBinding* binding{};
  22. const RosterDecodeMap* roster{};
  23. const activity_sdk::Catalog* catalog{};
  24. };
  25. /** Resolves the group one sense registry key names, against this connection's roster. */
  26. [[nodiscard]] sense_update::TargetStatus resolve_sense_group(
  27. const void* raw, std::uint32_t registryKey, sense_update::GroupTarget& output) noexcept {
  28. output = {};
  29. const auto* const context = static_cast<const SenseResolverContext*>(raw);
  30. if (context == nullptr || context->binding == nullptr || context->roster == nullptr
  31. || context->catalog == nullptr) {
  32. return sense_update::TargetStatus::targetUnavailable;
  33. }
  34. const RosterDecodeEntry* const entry = find_roster_decode_entry(
  35. *context->roster, context->binding->bindingGeneration, registryKey);
  36. if (entry == nullptr || entry->objectTag == 0) {
  37. return sense_update::TargetStatus::targetUnavailable;
  38. }
  39. const auto objects = context->catalog->objects();
  40. const activity_sdk::format::Object* match = nullptr;
  41. for (const activity_sdk::format::Object& object : objects) {
  42. if (object.objectTag != entry->objectTag) {
  43. continue;
  44. }
  45. if (match != nullptr) {
  46. return sense_update::TargetStatus::targetUnavailable;
  47. }
  48. match = &object;
  49. }
  50. if (match == nullptr || match->objectKey != registryKey) {
  51. return sense_update::TargetStatus::targetUnavailable;
  52. }
  53. output.objectTag = entry->objectTag;
  54. output.objectRow = static_cast<std::uint32_t>(match - objects.data());
  55. return sense_update::TargetStatus::resolved;
  56. }
  57. /** A Sense slot must resolve within the exact published object group. */
  58. [[nodiscard]] sense_update::TargetStatus
  59. resolve_sense_slot(const void* raw,
  60. const sense_update::GroupTarget& group,
  61. std::uint8_t slotType,
  62. std::uint16_t slotIndex,
  63. sense_update::SlotTarget& output) noexcept {
  64. output = {};
  65. const auto* const context = static_cast<const SenseResolverContext*>(raw);
  66. if (context == nullptr || context->catalog == nullptr
  67. || group.objectRow >= context->catalog->objects().size()) {
  68. return sense_update::TargetStatus::targetUnavailable;
  69. }
  70. const activity_sdk::format::Object& object = context->catalog->objects()[group.objectRow];
  71. if (object.objectTag != group.objectTag) {
  72. return sense_update::TargetStatus::targetUnavailable;
  73. }
  74. const auto allSlots = context->catalog->slots();
  75. const activity_sdk::format::Slot* match = nullptr;
  76. for (const activity_sdk::format::Slot& slot :
  77. activity_sdk::object_slots(*context->catalog, object)) {
  78. if (slot.slotType != slotType || slot.slotIndex != slotIndex) {
  79. continue;
  80. }
  81. if (match != nullptr) {
  82. return sense_update::TargetStatus::targetUnavailable;
  83. }
  84. match = &slot;
  85. }
  86. if (match == nullptr || match->objectIndex != group.objectRow) {
  87. return sense_update::TargetStatus::targetUnavailable;
  88. }
  89. output.slotRow = static_cast<std::uint32_t>(match - allSlots.data());
  90. output.senseSchema = match->senseSchema;
  91. if (match->componentClass == activity_sdk::format::kAbsentIndex || match->senseSchema == 0
  92. || match->senseSchema == activity_sdk::format::kAbsentIndex
  93. || (match->flags & activity_sdk::format::kSlotSchemaJoinExact) == 0) {
  94. return sense_update::TargetStatus::schemaUnavailable;
  95. }
  96. // Native Sense codecs dispatch by the authored schema handle. No SDK reflection row exists.
  97. output.schemaRow = match->senseSchema;
  98. return sense_update::TargetStatus::resolved;
  99. }
  100. /** One framing-only adapter and the handler that reads its body. */
  101. struct FramingRoute {
  102. IngressAdapter adapter;
  103. receipts::Framed (*frame)(const service::Request&) noexcept;
  104. };
  105. /** Frames one abandon, which trails a reason after the mask. */
  106. [[nodiscard]] receipts::Framed frame_abandon(const service::Request& request) noexcept {
  107. return receipts::frame_authority_release(request, true);
  108. }
  109. /** Frames one abdicate, which carries no reason. */
  110. [[nodiscard]] receipts::Framed frame_abdicate(const service::Request& request) noexcept {
  111. return receipts::frame_authority_release(request, false);
  112. }
  113. /** Every adapter this route frames and records without changing State. */
  114. constexpr std::array<FramingRoute, 19> kFramingRoutes{{
  115. {IngressAdapter::routeMisuseReceipt, receipts::frame_route_misuse},
  116. {IngressAdapter::reservationRequest, receipts::frame_reservation_request},
  117. {IngressAdapter::reservationRelease, receipts::frame_reservation_release},
  118. {IngressAdapter::peerLeave, receipts::frame_peer_leave},
  119. {IngressAdapter::clientKeepalive, receipts::frame_client_keepalive},
  120. {IngressAdapter::incidentHostIncident, receipts::frame_incident},
  121. {IngressAdapter::authorityAbandon, frame_abandon},
  122. {IngressAdapter::authorityAbdicate, frame_abdicate},
  123. {IngressAdapter::authorityRequestPurge, receipts::frame_request_purge},
  124. {IngressAdapter::authorityResetAcknowledgement, receipts::frame_query_answer},
  125. {IngressAdapter::authorityQueryAnswer, receipts::frame_query_answer},
  126. {IngressAdapter::debugCommandQuarantine, receipts::frame_debug_command},
  127. {IngressAdapter::connectivityFailure, receipts::frame_connectivity_failure},
  128. {IngressAdapter::heartbeat, receipts::frame_heartbeat},
  129. {IngressAdapter::opaqueScalar, receipts::frame_opaque_scalar},
  130. {IngressAdapter::lagSwitch, receipts::frame_lag_switch},
  131. {IngressAdapter::connectionQuality, receipts::frame_connection_quality},
  132. {IngressAdapter::migration, receipts::frame_migration},
  133. {IngressAdapter::highWater, receipts::frame_high_water},
  134. }};
  135. } // namespace
  136. /**
  137. * Frames one message and records its receipt.
  138. * @param binding Exact ActivityClient generation owned by this link.
  139. * @param rosterDecode Last complete msg-5 identity map delivered on this same link.
  140. * @param adapter Ingress adapter the communication route named for this message type.
  141. * @param request Validated envelope.
  142. * @return Always true: a framing-only message can never fail the transport frame.
  143. */
  144. bool frame_only(const ActivityClientBinding& binding,
  145. const RosterDecodeMap& rosterDecode,
  146. IngressAdapter adapter,
  147. const service::Request& request) noexcept {
  148. const auto row = std::find_if(
  149. kFramingRoutes.begin(),
  150. kFramingRoutes.end(),
  151. [adapter](const FramingRoute& candidate) noexcept { return candidate.adapter == adapter; });
  152. service::incident::Incident parsedIncident{};
  153. sense_update::SenseUpdate parsedSense{};
  154. const bool isIncident = adapter == IngressAdapter::incidentHostIncident;
  155. const bool isSense = adapter == IngressAdapter::senseUpdateHostSense;
  156. receipts::Framed framed{};
  157. if (isSense) {
  158. const activity_sdk::Snapshot catalog = activity_sdk::snapshot();
  159. const SenseResolverContext context{&binding, &rosterDecode, catalog.get()};
  160. const sense_update::Resolver resolver{&context, resolve_sense_group, resolve_sense_slot};
  161. std::size_t consumedBits = 0;
  162. static_cast<void>(sense_update::decode_sense_update(
  163. request.payload, resolver, parsedSense, consumedBits));
  164. framed = receipts::frame_sense_update(request, parsedSense);
  165. } else {
  166. framed = isIncident ? receipts::frame_incident_copy(request, parsedIncident)
  167. : row != kFramingRoutes.end() ? row->frame(request)
  168. : receipts::frame_unknown(request);
  169. }
  170. DiagnosticBody diagnostic{};
  171. diagnostic.consumedBits = framed.consumedBits;
  172. diagnostic.status = diagnostic_status(framed, isIncident);
  173. diagnostic.sense = isSense ? &parsedSense.decoded : nullptr;
  174. if (isSense && parsedSense.decoded.status == sense_update::DecodeStatus::partial) {
  175. diagnostic.status = server::activity::host::ClientMessageStatus::decodedPartial;
  176. }
  177. const std::uint64_t clientMessageSequence = record(request,
  178. framed.verdict,
  179. framed.consumedBits,
  180. &binding.session,
  181. binding.bindingGeneration,
  182. diagnostic);
  183. if (isSense && parsedSense.decoded.status != sense_update::DecodeStatus::malformed) {
  184. server::activity::host::SenseInput input{};
  185. input.binding = binding.session;
  186. input.sourceGeneration = binding.bindingGeneration;
  187. input.clientMessageSequence = clientMessageSequence;
  188. input.epochFirst = parsedSense.epoch.first;
  189. input.epochSecond = parsedSense.epoch.second;
  190. input.payloadBytes = static_cast<std::uint32_t>(request.payload.size());
  191. input.peerHeardMask = request.peerHeardMask;
  192. input.tailBits = parsedSense.tailBits;
  193. input.consumedBits = static_cast<std::uint32_t>(framed.consumedBits);
  194. input.firstGroupBits = parsedSense.firstGroupBits;
  195. input.firstRegistryKey = parsedSense.firstRegistryKey;
  196. input.groupsSeen = parsedSense.decoded.groupsSeen;
  197. input.groupsDecoded = parsedSense.decoded.groupsDecoded;
  198. input.groupsSkipped = parsedSense.decoded.groupsSkipped;
  199. input.objectsSeen = parsedSense.decoded.objectsSeen;
  200. input.objectsDecoded = parsedSense.decoded.objectsDecoded;
  201. input.firstSlotIndex = parsedSense.firstSlotIndex;
  202. input.firstSlotType = parsedSense.firstSlotType;
  203. input.decodeStatus = parsedSense.decoded.status;
  204. input.verdict = framed.verdict;
  205. input.decoded = parsedSense.decoded;
  206. input.hasFirstObject = parsedSense.hasFirstObject;
  207. const auto refusal = server::activity::host::submit_sense(input);
  208. if (refusal != server::activity::host::IngressRefusal::none) {
  209. report_message(request.messageType,
  210. request.sessionId,
  211. server::activity::host::ingress_refusal_name(refusal));
  212. }
  213. } else if (isIncident && framed.verdict == store::Verdict::framed) {
  214. server::activity::host::IncidentInput input{};
  215. input.binding = binding.session;
  216. input.incident = parsedIncident;
  217. input.sourceGeneration = binding.bindingGeneration;
  218. input.clientMessageSequence = clientMessageSequence;
  219. input.payloadBytes = static_cast<std::uint32_t>(request.payload.size());
  220. if (parsedIncident.primaryTarget == player_trigger_incident::kPrimaryTarget
  221. && parsedIncident.payloadLength == player_trigger_incident::kPayloadBytes) {
  222. input.hasPlayerTrigger = player_trigger_incident::decode(
  223. std::span(parsedIncident.payload).first(parsedIncident.payloadLength),
  224. input.playerTrigger);
  225. }
  226. if (parsedIncident.payloadLength == cinematic_incident::kPayloadBytes
  227. && cinematic_incident::signal_for_target(parsedIncident.primaryTarget,
  228. input.cinematicSignal)) {
  229. input.hasCinematic = cinematic_incident::decode(
  230. std::span(parsedIncident.payload).first(parsedIncident.payloadLength),
  231. input.cinematic);
  232. }
  233. const auto refusal = server::activity::host::submit_incident(input);
  234. if (refusal != server::activity::host::IngressRefusal::none) {
  235. report_message(request.messageType,
  236. request.sessionId,
  237. server::activity::host::ingress_refusal_name(refusal));
  238. }
  239. }
  240. return true;
  241. }
  242. /** Retains one exact msg-31 or msg-32 answer until its authenticated frame commits. */
  243. bool prepare_authority_query_answer(const ActivityClientBinding& binding,
  244. const RosterDecodeMap& rosterDecode,
  245. IngressAdapter adapter,
  246. const service::Request& request,
  247. ActivityPlan& plan,
  248. bool& hasTransaction) noexcept {
  249. service::entity_authority::QueryAnswer answer{};
  250. const bool parsed =
  251. service::entity_authority::parse_query_answer(request.messageType, request.payload, answer);
  252. if (!frame_only(binding, rosterDecode, adapter, request)) {
  253. return false;
  254. }
  255. if (!parsed
  256. || (request.messageType != service::entity_authority::kQueryPerBubbleMessageType
  257. && request.messageType != service::entity_authority::kQueryResponseMessageType)) {
  258. return true;
  259. }
  260. plan.sessionId = request.sessionId;
  261. plan.authorityQuery.answer = answer;
  262. plan.authorityQuery.sourceGeneration = binding.bindingGeneration;
  263. plan.authorityQuery.pending = true;
  264. plan.delivery = Delivery::none;
  265. plan.mutationDomain = MutationDomain::authorityQuery;
  266. hasTransaction = true;
  267. return true;
  268. }
  269. /** The client reported it is leaving, so the roster it holds is owed its leave delta. */
  270. bool prepare_peer_leave(const ActivityClientBinding& binding,
  271. const RosterDecodeMap& rosterDecode,
  272. IngressAdapter adapter,
  273. const service::Request& request,
  274. ActivityPlan& plan,
  275. bool& hasTransaction) noexcept {
  276. if (!frame_only(binding, rosterDecode, adapter, request)) {
  277. return false;
  278. }
  279. // With no delivered roster the client registered nothing on this link, so nothing is owed.
  280. if (!rosterDecode.valid || rosterDecode.bindingGeneration != binding.bindingGeneration) {
  281. return true;
  282. }
  283. plan.sessionId = request.sessionId;
  284. plan.delivery = Delivery::leaveNotification;
  285. plan.mutationDomain = MutationDomain::none;
  286. hasTransaction = true;
  287. return true;
  288. }
  289. /** A purge answer preserves the requesting client's complete mask and reason. */
  290. bool prepare_authority_purge(const ActivityClientBinding& binding,
  291. const RosterDecodeMap& rosterDecode,
  292. IngressAdapter adapter,
  293. const service::Request& request,
  294. ActivityPlan& plan,
  295. bool& hasTransaction) noexcept {
  296. service::entity_authority::PurgeRequest purge{};
  297. const bool parsed = service::entity_authority::parse_request_purge(request.payload, purge);
  298. if (!frame_only(binding, rosterDecode, adapter, request)) {
  299. return false;
  300. }
  301. if (!parsed) {
  302. return true;
  303. }
  304. plan.sessionId = request.sessionId;
  305. plan.authorityPurge.body.slots = purge.mask;
  306. plan.authorityPurge.body.reason = static_cast<std::int8_t>(purge.reason);
  307. plan.authorityPurge.body.epoch = static_cast<std::uint8_t>(binding.replicationEpoch + 1U);
  308. plan.authorityPurge.sourceGeneration = binding.bindingGeneration;
  309. plan.authorityPurge.pending = true;
  310. plan.delivery = Delivery::purgeNotification;
  311. plan.mutationDomain = MutationDomain::authorityPurge;
  312. hasTransaction = true;
  313. return true;
  314. }
  315. /** A valid abdication changes ownership only after its frame commits. */
  316. bool prepare_authority_abdication(const ActivityClientBinding& binding,
  317. const RosterDecodeMap& rosterDecode,
  318. IngressAdapter adapter,
  319. const service::Request& request,
  320. ActivityPlan& plan,
  321. bool& hasTransaction) noexcept {
  322. service::entity_authority::Release release{};
  323. const bool parsed = service::entity_authority::parse_abdicate(request.payload, release);
  324. if (!frame_only(binding, rosterDecode, adapter, request)) {
  325. return false;
  326. }
  327. if (!parsed) {
  328. return true;
  329. }
  330. plan.sessionId = request.sessionId;
  331. plan.authorityAbdication.sourceGeneration = binding.bindingGeneration;
  332. plan.authorityAbdication.entities = release.mask;
  333. plan.authorityAbdication.bubble = release.selector;
  334. plan.authorityAbdication.pending = true;
  335. plan.delivery = Delivery::none;
  336. plan.mutationDomain = MutationDomain::authorityAbdication;
  337. hasTransaction = true;
  338. return true;
  339. }
  340. /** Retains one exact msg-29 acknowledgement until its authenticated frame commits. */
  341. bool prepare_authority_reset_acknowledgement(const ActivityClientBinding& binding,
  342. const RosterDecodeMap& rosterDecode,
  343. IngressAdapter adapter,
  344. const service::Request& request,
  345. ActivityPlan& plan,
  346. bool& hasTransaction) noexcept {
  347. service::entity_authority::QueryAnswer answer{};
  348. const bool parsed =
  349. service::entity_authority::parse_query_answer(request.messageType, request.payload, answer);
  350. if (!frame_only(binding, rosterDecode, adapter, request)) {
  351. return false;
  352. }
  353. if (!parsed
  354. || request.messageType != service::entity_authority::kResetAcknowledgementMessageType) {
  355. return true;
  356. }
  357. plan.sessionId = request.sessionId;
  358. plan.authorityReset.answer = answer;
  359. plan.authorityReset.sourceGeneration = binding.bindingGeneration;
  360. plan.authorityReset.pending = true;
  361. plan.delivery = Delivery::none;
  362. plan.mutationDomain = MutationDomain::authorityReset;
  363. hasTransaction = true;
  364. return true;
  365. }
  366. } // namespace sunrise::server::bap::encrypted::activity_message