pub struct SessionManager<S> {
session_initiations: Cache<StartChallenge, MTx<One<Result<StartEstablished<SessionId>, StartErrorType<SessionId>>>>>,
session_notifiers: Arc<OnceLock<(Arc<Mutex<Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>>>, MTx<Array<(SessionId, ClosureReason)>>)>>,
start_protocol_tx: Arc<OnceLock<MTx<Array<(HoprPseudonym, HoprStartProtocol)>>>>,
active_sessions: Arc<AtomicUsize>,
sessions: Cache<SessionId, SessionSlot>,
msg_sender: Arc<OnceLock<S>>,
pix_toolbox: Arc<OnceLock<PixToolbox>>,
cfg: SessionManagerConfig,
slot_allocated: Arc<Mutex<HashMap<SessionId, Vec<Sender<()>>>>>,
}Expand description
Manages lifecycles of Sessions.
Once the manager is started, the SessionManager::dispatch_message
should be called for each [ApplicationData] received by the node.
This way, the SessionManager takes care of proper Start sub-protocol message processing
and correct dispatch of Session-related packets to individual existing Sessions.
Secondly, the manager can initiate new outgoing sessions via SessionManager::new_session,
probe sessions using SessionManager::ping_session
and list them via SessionManager::active_sessions.
Since the SessionManager operates over the HOPR protocol,
the message transport S is required.
Such transport must also be Clone, since it will be cloned into all the created HoprSession objects.
§SURB balancing
The manager also can take care of automatic SURB balancing per Session.
With each packet sent from the session initiator over to the receiving party, zero to 2 SURBs might be delivered. When the receiving party wants to send reply packets back, it must consume 1 SURB per packet. This means that if the difference between the SURBs delivered and SURBs consumed is negative, the receiving party might soon run out of SURBs. If SURBs run out, the reply packets will be dropped, causing likely quality of service degradation.
In an attempt to counter this effect, there are two co-existing automated modes of SURB balancing: local SURB balancing and remote SURB balancing.
§Local SURB balancing
Local SURB balancing is performed on the sessions that were initiated by another party (and are therefore incoming to us). The local SURB balancing mechanism continuously evaluates the rate of SURB consumption and retrieval, and if SURBs are running out, the packet egress shaping takes effect. This by itself does not avoid the depletion of SURBs but slows it down in the hope that the initiating party can deliver more SURBs over time. This might happen either organically by sending effective payloads that allow non-zero number of SURBs in the packet, or non-organically by delivering KeepAlive messages via remote SURB balancing.
The egress shaping is done automatically, unless the Session initiator sets the Capability::NoRateControl
flag during Session initiation.
§Remote SURB balancing
Remote SURB balancing is performed by the Session initiator. The SURB balancer estimates the number of SURBs delivered to the other party, and also the number of SURBs consumed by seeing the amount of traffic received in replies. When enabled, a desired target level of SURBs at the Session counterparty is set. According to measured inflow and outflow of SURBs to/from the counterparty, the production of non-organic SURBs is started via keep-alive messages (sent to counterparty) and is controlled to maintain that target level.
In other words, the Session initiator tries to compensate for the usage of SURBs by the counterparty by sending new ones via the keep-alive messages.
This mechanism is configurable via the surb_management field in SessionClientConfig.
§Possible scenarios
There are 4 different scenarios of local vs. remote SURB balancing configuration, but an equilibrium (= matching the SURB production and consumption) is most likely to be reached only when both are configured (the ideal case below):
§1. Ideal local and remote SURB balancing
- The Session recipient (Exit) set the
initial_return_session_egress_rate,max_surb_buffer_durationandmaximum_surb_buffer_sizevalues in theSessionManagerConfig. - The Session initiator (Entry) sets the
target_surb_buffer_sizewhich matches themaximum_surb_buffer_sizeof the counterparty. - The Session initiator (Entry) does NOT set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) sets
max_surbs_per_secslightly higher than themaximum_surb_buffer_size / max_surb_buffer_durationvalue configured at the counterparty.
In this situation, the maximum Session egress from Exit to the Entry is given by the
maximum_surb_buffer_size / max_surb_buffer_duration ratio. If there is enough bandwidth,
the (remote) SURB balancer sending SURBs to the Exit will stabilize roughly at this rate of SURBs/sec,
and the whole system will be in equilibrium during the Session’s lifetime (under ideal network conditions).
§2. Remote SURB balancing only
- The Session initiator (Entry) DOES set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) sets
max_surbs_per_secandtarget_surb_buffer_sizevalues inSurbBalancerConfig
In this one-sided situation, the Entry node floods the Exit node with SURBs, only based on its estimated consumption of SURBs at the Exit. The Exit’s egress is not rate-limited at all. If the Exit runs out of SURBs at any point in time, it will simply drop egress packets.
This configuration could potentially only lead to an equilibrium
when the SurbBalancer at the Entry can react fast enough to Exit’s demand.
§3. Local SURB balancing only
- The Session recipient (Exit) set the
initial_return_session_egress_rate,max_surb_buffer_durationandmaximum_surb_buffer_sizevalues in theSessionManagerConfig. - The Session initiator (Entry) does NOT set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) does NOT set the
SurbBalancerConfigat all when opening Session.
In this one-sided situation, the Entry node does not provide any additional SURBs at all (except the ones that are naturally carried by the egress packets which have space to hold SURBs). It relies only on the Session egress limiting of the Exit node. The Exit will limit the egress roughly to the rate of natural SURB occurrence in the ingress.
This configuration could potentially only lead to an equilibrium when uploading non-full packets (ones that can carry at least a single SURB), and the Exit’s egress is limiting itself to such a rate. If Exit’s egress reaches low values due to SURB scarcity, the upper layer protocols over Session might break.
§4. No SURB balancing on each side
- The Session initiator (Entry) DOES set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) does NOT set the
SurbBalancerConfigat all when opening Session.
In this situation, no additional SURBs are being produced by the Entry and no Session egress rate-limiting takes place at the Exit.
This configuration can only lead to an equilibrium when Entry sends non-full packets (ones that carry at least a single SURB) and the Exit is consuming the SURBs (Session egress) at a slower or equal rate. Such configuration is very fragile, as any disturbances in the SURB flow might lead to a packet drop at the Exit’s egress.
§SURB decay
In a hypothetical scenario of a non-zero packet loss, the Session initiator (Entry) might send a certain number of SURBs to the Session recipient (Exit), but only a portion of it is actually delivered. The Entry has no way of knowing that and assumes that everything has been delivered. A similar problem happens when the Exit uses SURBs to construct return packets, but only a portion of those packets is actually delivered to the Entry. At this point, the Entry also subtracts fewer SURBs from its SURB estimate at the Exit.
In both situations, the Entry thinks there are more SURBs available at the Exit than there really are.
To compensate for a potential packet loss, the Entry’s estimation of Exit’s SURB buffer is regularly
diminished by a percentage of the target_surb_buffer_size, even if no incoming traffic from the
Exit is detected.
This behavior can be controlled via the surb_decay field of SurbBalancerConfig.
§SURB balance and target notification
The Session recipient (Exit) can notify the Session initiator (Entry) periodically about its estimated
number of SURBs for the Session. This can help the Entry to adjust its approximation of that level so
that its Local SURB balancer can better intervene.
This can be set using the surb_balance_notify_period field of SessionManagerConfig for the Exit.
Likewise, the Entry can inform the Exit about its desired SURB buffer target so that the Exit
can better accommodate its Remote SURB balancing.
This can be set using the surb_target_notify field of the SessionManagerConfig of each new Session.
Both mechanisms leverage the Keep Alive message to report the respective values.
§PIX (Protocol for Incentivization of eXits) Protocol Flow
When a Session is opened with the Capability::UsePIX flag, the following protocol
runs between the Entry (initiator) and Exit (recipient) to provide on-chain payment
guarantees for the data relayed through the Session.
§1. PIX Parameter Negotiation (Session Initiation)
During SessionManager::new_session, the Entry encodes its PIX SSA (Session Stealth
Address) parameters — a PixParams quadruple of polys_per_ssa, shares_per_poly,
surplus_shares and the curve suite — into the upper 32 bits of the
StartSession.additional_data field, via PixParams::into_additional_data. The first two
describe how many polynomials and shares each SSA will use; the third is how many extra shares
per polynomial the Entry emits to absorb losses. Those three define the data quota per SSA, which
is polys × (threshold + surplus) × PAYLOAD_SIZE — the surplus is priced in rather than free,
since a cycle emits it whether or not any share is lost (see pix_params_to_quota).
The fourth is not a dimension and does not enter the quota: it names the elliptic curve the Entry’s build instantiates PIX over, which fixes the width of every curve-sized field later in the handshake. It is fixed at build time on both sides and is therefore not negotiated — the Exit refuses anything but its own, below.
What is announced is built from the installed SsaShareGenerator’s
SsaGeneratorConfig, never from the caller: the
generator is what produces the shares that go on the wire, so advertising anything else would
let the Session proceed while emitting shares the Exit cannot reconstruct. The caller’s
pix_ssa_quota is an assertion about this node’s own PIX configuration, and a disagreement is
refused so a caller whose belief is stale fails loudly rather than silently getting a different
per-SSA quota — and so differently sized deposits — than it sized for. That check runs before
the initiation challenge slot is reserved, so repeated misconfigurations cannot exhaust
challenge slots.
On the Exit side, check_pix_params validates these parameters against:
- The protocol ranges, which
PixParams::try_from_additional_dataenforces as it unpacks. - The configured
IncomingSessionPixConfig::quota_range(by default derived from the default PIX dimensions: ≈162 MiB–649 MiB per SSA). - Optionally,
IncomingSessionPixConfig::enforce_pixrejects Sessions that do not offer PIX. - The Exit only checks the product
polys × (threshold + surplus), not the individual values, so the Entry can split it to suit its computing power. The computation is easily parallelizable in the number of polynomials, but not in threshold. The surplus is inside that product, so redundancy is bought rather than taken: a cycle emitsthreshold + surplusshares per polynomial come what may, and the deposit covers all of them.
If parameters are rejected, a [StartErrorReason::UnacceptablePixParams] error is returned.
§2. Exit SSA Request (SsaRequest → Entry)
Once the PIX parameters are accepted, the Exit calls request_next_ssa
to create a new SSA commitment via the server-side SsaReconstructor. This produces an
Exit commitment (a group element) that is sent back to the Entry as a
[SsaServerCommitmentMessage].
One message can carry a whole batch of them:
IncomingSessionPixConfig::ssas_per_request SSAs at contiguous indices, sharing the single
params field, since every SSA in a Session uses the same negotiated dimensions. The Entry caps
what it will accept at SessionManagerConfig::max_ssas_per_ssa_request, and rejects an over-cap
request in full while replying with an UnacceptablePixParams [StartErrorType] so the Exit does
not have to infer the refusal from its own deposit timeout. The default is a batch of one, which is
byte-for-byte the unbatched exchange.
The Exit also installs a PIX kill switch per requested index — one shared deadline of
ssas_per_request × (max_deposit_wait + max_ssa_delivery_time). Scaling it by the batch size is
what lets an Entry work through a batch in order; any single deposit may be late as long as the
batch lands inside the window. If a deposit is still missing when the deadline passes, the Session
is closed with ClosureReason::UnrealizedDeposit.
§3. Entry SSA Commitment (SsaCommit → Exit)
Upon receiving the [SsaServerCommitmentMessage], the Entry’s handle_ssa_request
generates a client commitment using the shared SsaShareGenerator (which is also
used by the packet pipeline to embed PIX shares into return-path SURBs). The client
commitment is combined with the Exit commitment to derive the on-chain deposit address
via HoprPixSpec::group_to_deposit_address.
The Entry then sends one or more [SsaClientCommitmentMessage]s back to the Exit and
emits a HoprSessionOutPixEvent::ReadyToDeposit to the upper layer, signaling that
funds can be deposited at the computed address.
§4. Deposit Awaiting (Exit Side)
The Exit receives the client commitment messages in handle_ssa_commit, inserts the
coefficient commitments into the SsaReconstructor, and extracts the deposit address.
It emits HoprSessionOutPixEvent::DepositNeeded to the upper layer with the
AgreedSsaQuota and a channel to confirm the deposit.
A deposit awaiter task waits for the deposit confirmation. Once confirmed, the PIX
kill switch is aborted. If the deposit times out, the kill switch closes the Session. The awaiter’s
own timeout is scaled by ssas_per_request too, and has to be: it is the only thing that aborts
the kill switch, so an awaiter that gave up before the widened deadline would let a
legitimately-late deposit go unobserved and the Session be closed for an unrealized deposit that
was in fact realized.
§5. SSA Collection, Recovery and Pipelining
As the Entry sends return-path SURBs during the Session, each SURB can carry a PIX
share generated from the client’s polynomial set. The Exit’s SsaReconstructor
collects these shares.
When the reconstructor reaches the early recovery threshold (≈85%), an
HoprSessionInPixEvent::SsaAlmostRecovered event fires, which triggers
request_next_ssa for the next SSA index — pipelining the costly
commitment exchange with the tail of the share collection for the current SSA.
Once fully recovered, HoprSessionInPixEvent::SsaRecovered fires, allowing the
Exit to unlock and redeem the deposited funds. The deposit awaiter for the next SSA
replaces the kill switch aborted for the previous one.
§6. Unverifiable Shares
Shares are not checked individually. Once a polynomial has collected threshold of them,
the reconstructor interpolates its constant term and compares it against the commitment; if
they disagree, at least one of those shares did not come from the committed polynomial and an
HoprSessionInPixEvent::UnverifiableShare event fires. MAX_ALLOWED_UNVERIFIABLE_PIX_SHARES
is 0, so the first such event closes the Session — the cycle is already unrecoverable, and
closing immediately caps what a malicious Entry is served at threshold packets.
§Configuring PIX at the Exit
The Exit configures PIX via IncomingSessionPixConfig within SessionManagerConfig.
The PixToolbox (holding the SsaShareGenerator and SsaReconstructor) must
be provided via SessionManager::start for PIX to function.
Fields§
§session_initiations: Cache<StartChallenge, MTx<One<Result<StartEstablished<SessionId>, StartErrorType<SessionId>>>>>§session_notifiers: Arc<OnceLock<(Arc<Mutex<Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>>>, MTx<Array<(SessionId, ClosureReason)>>)>>§start_protocol_tx: Arc<OnceLock<MTx<Array<(HoprPseudonym, HoprStartProtocol)>>>>§active_sessions: Arc<AtomicUsize>Authoritative session count for admission control.
Incremented atomically inside allocate_session_slot before the cache insertion,
and decremented at every removal path (explicit close, eviction, guard rollback).
sessions: Cache<SessionId, SessionSlot>§msg_sender: Arc<OnceLock<S>>§pix_toolbox: Arc<OnceLock<PixToolbox>>§cfg: SessionManagerConfig§slot_allocated: Arc<Mutex<HashMap<SessionId, Vec<Sender<()>>>>>Per-SessionId waiters notified when a new session slot is allocated. Lets message handlers that arrive before the slot insertion completes (e.g. SsaRequest vs SessionEstablished) await the slot once instead of busy-looping with sleeps. Keyed by SessionId so that only waiters for the relevant session are woken.
Implementations§
Source§impl<S> SessionManager<S>where
S: Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
S::Error: Error + Send + Sync + Clone + 'static,
impl<S> SessionManager<S>where
S: Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
S::Error: Error + Send + Sync + Clone + 'static,
Sourcepub fn new(cfg: SessionManagerConfig) -> Self
pub fn new(cfg: SessionManagerConfig) -> Self
Creates a new instance given the config.
Sourcepub fn start<T>(
&self,
msg_sender: S,
new_session_notifier: T,
pix: Option<PixToolbox>,
) -> Result<Vec<AbortHandle>>
pub fn start<T>( &self, msg_sender: S, new_session_notifier: T, pix: Option<PixToolbox>, ) -> Result<Vec<AbortHandle>>
Starts the instance with the given msg_sender Sink
and a channel new_session_notifier used to notify when a new incoming session is opened to us.
Optionally, the PIX processor and event sink can be provided for handling PIX protocol.
If not specified, the SessionManager will not handle PIX protocol.
This method must be called prior to any calls to SessionManager::new_session or
SessionManager::dispatch_message.
Sourcepub fn is_started(&self) -> bool
pub fn is_started(&self) -> bool
Check if start has been called and the instance is running.
Sourcefn allocate_session_slot(
&self,
session_id: SessionId,
slot: SessionSlot,
) -> Option<SessionSlotGuard<'_>>
fn allocate_session_slot( &self, session_id: SessionId, slot: SessionSlot, ) -> Option<SessionSlotGuard<'_>>
Atomically allocates a new SessionSlot for session_id and returns an RAII
SessionSlotGuard for it.
Establishing a session involves several fallible steps after the slot has been
inserted. The returned guard rolls the slot back - tearing the partially
established session down via close_session - unless it is
committed.
The active-sessions gauge is incremented here, atomically with the insertion and
the guard creation, precisely so that it is always paired with the guard’s
rollback decrement (performed through close_session). This keeps the gauge
accurate: it is never decremented for a slot that was not counted in the first
place, and every counted slot is decremented exactly once when it leaves the cache.
Returns None if a slot for session_id already exists; in that case nothing is
inserted, the gauge is left untouched, and no guard is produced. The atomic entry
API guarantees that only one concurrent caller can claim the slot for a given
pseudonym (avoiding a TOCTOU race), which also rules out loopback sessions onto
ourselves.
Capacity is enforced by an atomic counter incremented before the cache insertion,
making it impossible for two concurrent callers (with different session IDs) to both
succeed when the cache is already at maximum_sessions.
Sourcepub async fn new_session(
&self,
destination: Address,
target: SessionTarget,
cfg: SessionClientConfig,
) -> Result<HoprSession>
pub async fn new_session( &self, destination: Address, target: SessionTarget, cfg: SessionClientConfig, ) -> Result<HoprSession>
Initiates a new outgoing Session to destination with the given configuration.
If the Session’s counterparty does not respond within
the configured period,
this method returns TransportSessionError::Timeout.
It will also fail if the instance has not been started.
Sourcepub async fn ping_session(&self, id: &SessionId) -> Result<()>
pub async fn ping_session(&self, id: &SessionId) -> Result<()>
Sends a keep-alive packet with the given SessionId.
This currently “fires & forgets” and does not expect nor await any “pong” response.
async fn request_next_ssa( &self, session_id: SessionId, slot: SessionSlot, expected_ssa_index: Option<SsaIndex>, ) -> Result<()>
Sourcepub fn num_active_sessions(&self) -> usize
pub fn num_active_sessions(&self) -> usize
Returns the current number of active sessions.
Sourcepub fn active_sessions(&self) -> Vec<SessionId> ⓘ
pub fn active_sessions(&self) -> Vec<SessionId> ⓘ
Returns SessionIds of all currently active sessions.
Sourcepub fn close_session(&self, id: &SessionId) -> bool
pub fn close_session(&self, id: &SessionId) -> bool
Explicitly closes the session with the given id.
Removes the entry from the internal session cache, closes the data channel,
and aborts any auxiliary tasks. Returns true if a session was found and
closed, false otherwise.
This avoids waiting for the idle timeout (time_to_idle) or the LRU
capacity bound to evict the entry, which is the desired behaviour when
the caller (e.g. REST DELETE /session) knows the session is finished.
Sourcepub fn update_surb_balancer_config(
&self,
id: &SessionId,
config: SurbBalancerConfig,
) -> Result<()>
pub fn update_surb_balancer_config( &self, id: &SessionId, config: SurbBalancerConfig, ) -> Result<()>
Updates the configuration of the SURB balancer on the given SessionId.
Returns an error if the Session with the given id does not exist, or
if it does not use SURB balancing.
Sourcepub fn get_surb_balancer_config(
&self,
id: &SessionId,
) -> Result<Option<SurbBalancerConfig>>
pub fn get_surb_balancer_config( &self, id: &SessionId, ) -> Result<Option<SurbBalancerConfig>>
Retrieves the configuration of SURB balancing for the given Session.
Returns an error if the Session with the given id does not exist.
Sourcepub fn get_surb_level_estimates(&self, id: &SessionId) -> Result<(u64, u64)>
pub fn get_surb_level_estimates(&self, id: &SessionId) -> Result<(u64, u64)>
Gets estimations produced/received and consumed SURBs by the Session.
For an outgoing Session (Entry) the pair is the number of SURBs sent (by us) and used (by the Exit). For an incoming Session (Exit) the pair is the number of SURBs received (from Entry) and used (by us).
Returns an error if the Session with the given id does not exist.
Sourcepub async fn dispatch_pix_event(
&self,
event: HoprSessionInPixEvent,
) -> Result<()>
pub async fn dispatch_pix_event( &self, event: HoprSessionInPixEvent, ) -> Result<()>
Dispatches HoprSessionInPixEvent that notifies the SessionManager about PIX protocol
state update.
Such an event can affect existing Sessions that use the PIX protocol.
Sourcepub fn mark_return_path_degraded(
&self,
destination: &NodeId,
grace: Duration,
) -> usize
pub fn mark_return_path_degraded( &self, destination: &NodeId, grace: Duration, ) -> usize
Marks the return path to destination as degraded on every Session routed there.
The Session layer cannot tell a dead return path from a peer with nothing to say – both
simply stop consuming SURBs – so the judgement is made where sibling paths can be compared
and delivered here. Sessions that did not opt in ignore the mark; the rest stop trusting
their counterparty buffer estimate for grace.
Returns how many Sessions were marked, which is zero when nothing currently routes there.
Sourcepub fn dispatch_message(
&self,
pseudonym: HoprPseudonym,
in_data: ApplicationDataIn,
) -> Result<DispatchResult>
pub fn dispatch_message( &self, pseudonym: HoprPseudonym, in_data: ApplicationDataIn, ) -> Result<DispatchResult>
The main method to be called whenever data are received.
It tries to recognize the message and correctly dispatches either the Session protocol or Start protocol messages.
If the data are not recognized, they are returned as DispatchResult::Unrelated.
Sourcefn check_pix_params(
&self,
req: &StartInitiation<SessionTarget, HoprSessionCapabilities>,
) -> Option<PixParams>
fn check_pix_params( &self, req: &StartInitiation<SessionTarget, HoprSessionCapabilities>, ) -> Option<PixParams>
Checks the PIX parameters offered by the Entry during the Session Initiation.
Returns the validated parameters, or None if the offered parameters were rejected.
async fn handle_incoming_session_initiation( &self, pseudonym: HoprPseudonym, session_req: StartInitiation<SessionTarget, HoprSessionCapabilities>, ) -> Result<()>
async fn handle_session_established( &self, est: StartEstablished<SessionId>, ) -> Result<()>
async fn handle_session_error( &self, error_type: StartErrorType<SessionId>, ) -> Result<()>
async fn handle_keep_alive( &self, msg: KeepAliveMessage<SessionId>, ) -> Result<()>
Sourceasync fn handle_ssa_commit(
&self,
pseudonym: HoprPseudonym,
msg: SsaClientCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixCommitmentProof>,
) -> Result<()>
async fn handle_ssa_commit( &self, pseudonym: HoprPseudonym, msg: SsaClientCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixCommitmentProof>, ) -> Result<()>
Handled by the Exit, when Entry replies with PIX commitment
Sourceasync fn refuse_ssa_request(
&self,
session_id: SessionId,
routing: DestinationRouting,
)
async fn refuse_ssa_request( &self, session_id: SessionId, routing: DestinationRouting, )
Tells the Exit that its [SsaServerCommitmentMessage] was refused, and tears down this half
of the Session.
Without the notification the refusal is invisible to the Exit, and it has no way to recover
from it: it armed one kill switch per requested index before sending, it will never receive
an SsaCommit, and no PIX event can fire to make it re-request — request_next_ssa is only
reached from establishment and from share-recovery events, and no shares are produced for a
cycle the Entry never committed to. So it serves the Session unincentivized for the whole
ssas_per_request × (max_deposit_wait + max_ssa_delivery_time) window and then closes it as
UnrealizedDeposit — a reason that names the deposit rather than the refusal, on the one node
whose operator can act on it. Telling it collapses that to roughly one round trip and puts the
cause in its log where the failure happens.
The Exit’s handle_session_error closes the Session on a SessionId-identified error, which
also retires the reconstructor cycles it registered for the batch rather than leaving them to
their own expiry. It sends nothing back, so there is no error exchange to loop.
No new capability is handed to an attacker by closing on a refusal: an SsaRequest only
reaches here Sphinx-authenticated and with pseudonym == session_id, so only the Exit can
produce one — and the Exit can already close the Session whenever it likes.
Best-effort. A failed send changes nothing, because the Exit’s kill switch remains the backstop; the local close is unconditional because a refused request is terminal for the Session either way (the Exit re-derives every request from state that cannot drift within a Session, so a later one would be refused identically), and leaving the slot up would keep an unusable Session alive until the idle timeout.
Sourceasync fn handle_ssa_request(
&self,
pseudonym: HoprPseudonym,
msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositData>,
) -> Result<()>
async fn handle_ssa_request( &self, pseudonym: HoprPseudonym, msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositData>, ) -> Result<()>
Handled by the Entry, when the Exit sends PIX initiation request
Trait Implementations§
Auto Trait Implementations§
impl<S> !RefUnwindSafe for SessionManager<S>
impl<S> !UnwindSafe for SessionManager<S>
impl<S> Freeze for SessionManager<S>
impl<S> Send for SessionManager<S>
impl<S> Sync for SessionManager<S>
impl<S> Unpin for SessionManager<S>
impl<S> UnsafeUnpin for SessionManager<S>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Conv for T
impl<T> Conv for T
§impl<T> FmtForward for T
impl<T> FmtForward for T
§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
§impl<T> Tap for T
impl<T> Tap for T
§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read more§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read more§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read more§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read more§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read more§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read more§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.