| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- #include "activity_transaction_notifications.h"
- #include "../../../../core/logging/log.h"
- #include "../../../gameplay/gameplay_advertisement.h"
- #include "../push/activity/activity_arrival.h"
- #include "../push/activity/activity_global_state_push.h"
- #include "../push/activity/activity_membership_push.h"
- #include "../push/activity/activity_message_push.h"
- #include "../push/activity/activity_roster_push.h"
- namespace sunrise::server::bap::encrypted::activity_transaction {
- namespace {
- /**
- * Reports whether the citizen advertisement this membership body would carry is still coming.
- * The client applies one membership update per revision, so a body sent before the region's host
- * session exists spends that revision on a record no later push can fill. Holding costs one
- * keepalive.
- * @param activity Prepared activity transaction, whose region this body publishes.
- * @return True when the push has to wait.
- */
- [[nodiscard]] bool advertisement_pending(const activity_message::ActivityPlan& activity) noexcept {
- // Take the delta's region, not the committed one. Staging runs before the commit, so the
- // committed value still names the region the player has left.
- const server::gameplay::AdvertisementState state = server::gameplay::advertisement_state(
- push::activity::planned_region(activity.membershipMutation, activity.sessionId).index);
- if (state != server::gameplay::AdvertisementState::pending) {
- return false;
- }
- core::log::write(core::log::Channel::server,
- core::log::Level::debug,
- "ev=gameplay stage=membership result=held reason=no_host_session");
- return true;
- }
- /**
- * Stages the whole host snapshot the client's state-refresh request asks for.
- * The order matches the keepalive: the global state, then membership, then the roster, because the
- * roster's participation key binds to the player the membership publishes.
- * @param session Connection-owned roster counters, advanced only by a staged roster.
- * @param scratch Lock-owned transform buffers.
- * @param activity Prepared activity transaction carrying the membership snapshot.
- * @param key Active AES-GCM session key.
- * @param nonce Local send nonce advanced only by complete staged notifications.
- * @param response Lock-owned complete-frame staging storage.
- * @param written Existing staged byte count, updated only by complete notifications.
- * @return True when at least one of the three notifications was staged.
- */
- [[nodiscard]] bool stage_refresh(Session& session,
- Scratch& scratch,
- const activity_message::ActivityPlan& activity,
- std::span<const std::byte, state::kAesKeySize> key,
- std::array<std::byte, state::kBapNonceSize>& nonce,
- std::span<std::byte> response,
- std::size_t& written) noexcept {
- bool staged = push::activity::append_global_state_notification(
- scratch, activity.sessionId, key, nonce, response, written);
- if (activity.membershipMutation.hasSnapshot && !advertisement_pending(activity)) {
- staged = push::activity::append_membership_notification(
- scratch, activity, key, nonce, response, written)
- || staged;
- }
- return push::activity::append_roster_notification(
- session, scratch, key, nonce, response, written, false)
- || staged;
- }
- /**
- * Stages what one client-authoritative delta owes: membership, the roster, or both.
- * The roster follows membership, because its participation key binds to the player membership
- * publishes.
- * @param session Connection-owned roster counters, advanced only by a staged roster.
- * @param scratch Lock-owned transform buffers.
- * @param activity Prepared activity transaction and its region-move flag.
- * @param key Active AES-GCM session key.
- * @param nonce Local send nonce advanced only by complete staged notifications.
- * @param response Lock-owned complete-frame staging storage.
- * @param written Existing staged byte count, updated only by complete notifications.
- * @return True when every notification the delta owed was staged.
- */
- [[nodiscard]] bool stage_authoritative(Session& session,
- Scratch& scratch,
- const activity_message::ActivityPlan& activity,
- std::span<const std::byte, state::kAesKeySize> key,
- std::array<std::byte, state::kBapNonceSize>& nonce,
- std::span<std::byte> response,
- std::size_t& written) noexcept {
- bool staged = false;
- bool held = false;
- if (activity.membershipMutation.hasSnapshot) {
- held = advertisement_pending(activity);
- if (!held) {
- staged = push::activity::append_membership_notification(
- scratch, activity, key, nonce, response, written);
- }
- }
- if (activity.regionMoved) {
- staged = push::activity::append_roster_notification(
- session, scratch, key, nonce, response, written, false)
- || staged;
- }
- // A held membership is not a failed staging. A false here drops the very commit that moved
- // the region the held body waits for. The keepalive publishes it on a later slice.
- return staged || held;
- }
- } // namespace
- /**
- * Stages the notifications one activity transaction requests.
- * @param session Connection-owned roster counters, advanced only by a staged roster.
- * @param scratch Lock-owned transform buffers.
- * @param activity Prepared activity transaction and delivery selection.
- * @param key Active AES-GCM session key.
- * @param nonce Local send nonce advanced only by complete staged notifications.
- * @param response Lock-owned complete-frame staging storage.
- * @param written Existing staged byte count, updated only by complete notifications.
- * @return True when every requested notification is staged.
- */
- bool stage_notifications(Session& session,
- Scratch& scratch,
- const activity_message::ActivityPlan& activity,
- std::span<const std::byte, state::kAesKeySize> key,
- std::array<std::byte, state::kBapNonceSize>& nonce,
- std::span<std::byte> response,
- std::size_t& written) noexcept {
- // Each encoder refuses an absent session itself, so a plan that delivers nothing needs no
- // session at all. Message type 52 is the one that arrives on an unallocated link.
- if (activity.delivery == activity_message::Delivery::joinNotifications) {
- return push::activity::append_join_notifications(
- scratch, activity, key, nonce, response, written);
- }
- if (activity.delivery == activity_message::Delivery::entitySlotNotification) {
- return push::activity::append_entity_slot_notification(scratch,
- activity.sessionId,
- activity.entitySlotMutation.mask,
- key,
- nonce,
- response,
- written);
- }
- if (activity.delivery == activity_message::Delivery::membershipNotification) {
- return push::activity::append_membership_notification(
- scratch, activity, key, nonce, response, written);
- }
- if (activity.delivery == activity_message::Delivery::refreshNotifications) {
- return stage_refresh(session, scratch, activity, key, nonce, response, written);
- }
- if (activity.delivery == activity_message::Delivery::authoritativeNotifications) {
- return stage_authoritative(session, scratch, activity, key, nonce, response, written);
- }
- return activity.delivery == activity_message::Delivery::none;
- }
- } // namespace sunrise::server::bap::encrypted::activity_transaction
|