Skip to main content

SessionManager

Struct SessionManager 

Source
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<()>>>>>,
    ssa_request_locks: Cache<HoprPseudonym, Arc<Mutex<()>>>,
    live_cycle_bytes: Arc<AtomicU64>,
    admission_tx: Arc<OnceLock<Mutex<SessionAdmissionSink>>>,
}
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. Both are governed by the initiator’s estimate of our buffer, so signalling SURB distress is what tells it to keep the organic ones coming.

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.

The same estimate also governs organic production: once the counterparty is estimated to be at its target, outgoing Session data packets carry no SURBs at all, and they return to one per packet as soon as the estimate falls back below target. Without that, a balancer could correctly silence its keep-alives while organic SURBs kept pouring into a full buffer — where each one evicts an older SURB and destroys the PIX share it was carrying. A SurbDistress or OutOfSurbs signal from the counterparty overrides the estimate and restores production immediately, which is what makes it safe to act on an estimate that forward-path loss can inflate.

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
  1. The Session recipient (Exit) set the initial_return_session_egress_rate, max_surb_buffer_duration and maximum_surb_buffer_size values in the SessionManagerConfig.
  2. The Session initiator (Entry) sets the target_surb_buffer_size which matches the maximum_surb_buffer_size of the counterparty.
  3. The Session initiator (Entry) does NOT set the Capability::NoRateControl capability flag when opening Session.
  4. The Session initiator (Entry) sets max_surbs_per_sec slightly higher than the maximum_surb_buffer_size / max_surb_buffer_duration value 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
  1. The Session initiator (Entry) DOES set the Capability::NoRateControl capability flag when opening Session.
  2. The Session initiator (Entry) sets max_surbs_per_sec and target_surb_buffer_size values in SurbBalancerConfig

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
  1. The Session recipient (Exit) set the initial_return_session_egress_rate, max_surb_buffer_duration and maximum_surb_buffer_size values in the SessionManagerConfig.
  2. The Session initiator (Entry) does NOT set the Capability::NoRateControl capability flag when opening Session.
  3. The Session initiator (Entry) does NOT set the SurbBalancerConfig at 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
  1. The Session initiator (Entry) DOES set the Capability::NoRateControl capability flag when opening Session.
  2. The Session initiator (Entry) does NOT set the SurbBalancerConfig at 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 64-bit options word carried in the most significant 64 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) × SESSION_MTU — 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 dimensions are therefore not configurable per Session — Capability::UsePIX is the whole per-Session switch, and a node with no PIX toolbox installed refuses the Session. That check runs before the initiation challenge slot is reserved, so a node that cannot serve PIX at all cannot have its challenge slots exhausted by repeated requests for it.

The caller never needs to know the resulting quota in advance: it is computed node-side and handed back per SSA in AgreedSsaQuota::quota_per_ssa, which is what sizes the deposit.

On the Exit side, check_pix_params validates these parameters against:

  • The protocol ranges, which PixParams::try_from_additional_data enforces as it unpacks.
  • The configured IncomingSessionPixConfig::quota_range (by default ≈227 MiB–908 MiB). Dynamic admission checks the smallest batch_size × quota_per_ssa up to the configured maximum; fixed admission checks the per-SSA quota itself and retains the configured exact batch.
  • Optionally, IncomingSessionPixConfig::enforce_pix rejects 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 emits threshold + surplus shares 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 spawns a SessionPixSupervisor, whose opening RequestSsa action has send_ssa_request 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 action, and therefore one message, carries the batch selected during admission: no more than SupervisorConfig::ssas_per_request SSAs at contiguous indices, sharing the single params field, since every SSA in a Session uses the same 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 a deadline. The default is a batch of one, which is byte-for-byte the unbatched exchange.

From here the supervisor owns the cycle’s deadlines: max_ssa_delivery_time for the Entry’s commitment, then max_deposit_wait for the funds, then the recovery deadlines. Missing any of them closes the Session with ClosureReason::PixFailure. The first two are multiplied by the batch size, because a batch asks the Entry for that many commitment sets and that many deposits before any of them can fairly be called late.

§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 SsaClientCommitmentMessages back to the Exit and emits a HoprSessionOutPixEvent::ReadyToDeposit to the upper layer, signaling that funds can be deposited at the computed address.

“One or more” is hundreds at the deployed dimensions — one commitment per polynomial, chunked to packet size — and the Exit can use none of it until every message has landed, since the SSA commitment is the sum of them all. A single lost packet therefore used to cost the whole Session on max_ssa_delivery_time, typically after the Entry had already been told to fund the address.

So a partially delivered commitment is repaired rather than waited out. Each arriving message reports SessionPixEvent::CommitmentProgress, which arms (and re-arms) the supervisor’s commitment_recommit_interval; if delivery then goes quiet with the set incomplete, RequestCommitmentRetransmission has send_commitment_retransmission_request ask the Entry — via an SsaRequest whose missing scope names the polynomial runs the reconstructor never received — to re-send exactly those. The Entry answers in handle_ssa_recommit from the bytes it sent the first time, advancing nothing and announcing no second deposit. A cycle for which nothing arrived never asks: it has no scope to name, and the delivery deadline remains what ends it.

§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 verifiable commitment is what starts the deposit clock, so the supervisor is told (CommitmentVerified) before anything can be reported against it. A PixDepositObserver task then forwards every confirmation arriving on that channel as DepositConfirmed: top-ups accumulate, and the supervisor is what decides when enough has landed. The observer carries no timeout of its own — max_deposit_wait is the deadline, and a second authority racing the first is exactly what the supervisor exists to prevent. If the upper layer drops the sender without ever confirming, the observer reports that (DepositObserverClosed) rather than letting the deadline run out on a deposit that is never coming.

Once the deposit suffices, the SSA moves to recovering. Its recovery deadlines start when it reaches the front, and its deposit releases the egress gate only while it is there. When a paid front leaves—whether recovered or retired after a tolerated failure—an unfunded successor restores its own bounded predeposit allowance; an already funded successor stays open but starts a fresh served-without-progress ceiling.

§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 and reports how far the cycle has got via HoprSessionInPixEvent::RecoveryProgress; dispatch_pix_event forwards those snapshots to the supervisor, which uses them both to reset max_recovery_idle and to keep the gate serving.

When the last cycle of a batch reaches the early recovery threshold (≈85%), an HoprSessionInPixEvent::SsaAlmostRecovered event fires and the supervisor answers with a RequestSsa action for the next batch — pipelining the costly commitment exchange with the tail of share collection. If that cycle is still awaiting its own commitment or deposit, the request waits for funding; if two generations still own cycle state, it waits for one to be fully released. Those checks are independent of the configurable share-order policy.

Once fully recovered, HoprSessionInPixEvent::SsaRecovered fires, allowing the Exit to unlock and redeem the deposited funds. The reconstructor releases the live cycle immediately; the supervisor retains a short tombstone, whose expiry emits RetireSsa to drop that cycle’s SsaCommitmentGuard and abort its deposit observer. The next batch is requested here if SsaAlmostRecovered has not already done so. Observers are keyed by SSA index, so retiring one cycle never cancels a pipelined successor’s.

§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::UnverifiableShares event fires, carrying the reconstructor’s running total for the SSA rather than a delta.

The first such report closes the Session, unconditionally and with nothing to configure. The failed polynomial is never retried, and the SSA is the sum of every polynomial’s constant term, so the cycle is already unrecoverable and can never pay; closing immediately caps what a malicious Entry is served at threshold packets. The running total is logged rather than accumulated. See SessionPixSupervisor::on_unverifiable_shares for why no tolerance is offered.

§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.

§ssa_request_locks: Cache<HoprPseudonym, Arc<Mutex<()>>>

Serialises handle_ssa_request per pseudonym.

Start messages are processed under for_each_concurrent, so without this several SsaRequests for one pseudonym run at once — and each reads the successor gate’s admission state before any of them has advanced it. Every racer passes, and the Entry commits to (and funds) as many batches as the Exit cared to send, which is exactly what the gate exists to stop. The generator’s own monotonic index is the backstop that keeps this from being a correctness hole, but it only rejects equal or lower indices: an Exit numbering its batches upwards races past it.

An async lock rather than a blocking one because the guarded region awaits — commitment generation goes to a blocking pool, and publication awaits the transport.

A moka cache rather than a map, for its idle eviction: a HashMap keyed by pseudonym would otherwise retain one entry per Session for the life of the process.

§live_cycle_bytes: Arc<AtomicU64>

Live reconstructor-cycle bytes this node has committed to, summed over every PIX Session.

The counterpart of active_sessions, and the reason that one is not sufficient: a Session is admitted on a slot count, but what it costs the reconstructor is set by the dimensions its peer offered. Charged in handle_incoming_session_initiation and returned by CycleBudgetReservation’s Drop, so no removal path has to remember to decrement it.

§admission_tx: Arc<OnceLock<Mutex<SessionAdmissionSink>>>

Where incoming Sessions are put to the session server for an admission decision.

Unset on a node that runs no session server — an Entry, or a relay — where there is nobody to ask and every Session is admitted on this node’s own terms. Behind a Mutex so every request goes through one sender.

futures::channel::mpsc gives each cloned Sender its own guaranteed slot on top of the buffer, so sending through a fresh clone each time would let the queue grow with the number of concurrent initiations — the bound would not bound. try_send never blocks, so the lock is held only for the enqueue itself.

Implementations§

Source§

impl<S> SessionManager<S>
where S: Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static, S::Error: Error + Send + Sync + Clone + 'static,

Source

pub fn new(cfg: SessionManagerConfig) -> Self

Creates a new instance given the config.

Source

pub fn start<T>( &self, msg_sender: S, new_session_notifier: T, pix: Option<PixToolbox>, admission: Option<SessionAdmissionSink>, ) -> Result<Vec<AbortHandle>>
where T: Sink<IncomingSession> + Send + 'static, T::Error: Error + Send + Sync + 'static,

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.

admission is where incoming Sessions are put to the session server for a per-Session decision before they are established. Without it — an Entry, or a relay, or any caller that installed no session server — every Session is admitted on this node’s own configured terms, which is the behaviour of a node that does not distinguish between targets.

This method must be called prior to any calls to SessionManager::new_session or SessionManager::dispatch_message.

Source

pub fn is_started(&self) -> bool

Check if start has been called and the instance is running.

Source

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.

Source

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.

Source

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.

Source

async fn request_deposit_data( pix_toolbox: &PixToolbox, session_id: SessionId, ssa_indices: &[SsaIndex], ) -> Result<Vec<PixDepositData>>

Asks the deposit pool for the data to attach to a batch of SSAs, and collects its answers.

Source

fn spawn_pix_action_driver( &self, session_id: SessionId, slot: &SessionSlot, action_rx: AsyncRx<Array<SessionPixAction>>, reply_routing: DestinationRouting, ) -> AbortHandle

Spawns the task that carries out one Session’s supervisor actions.

The supervisor decides; this executes. Keeping the two apart is what lets the state machine be pure and exhaustively tested, and it means every side effect a PIX Session can have is visible in one match.

The task owns an SsaCommitmentGuard per SSA it has requested and one Session retirement scope. Terminal actions retire through the frontier before releasing their guard; aborting the task drops every remaining guard and then the Session’s compact frontier. No state grows with the number of cycles the Session completed.

Source

fn spawn_exit_keep_alive( &self, session_id: SessionId, slot: &SessionSlot, msg_sender: S, is_pix: bool, announced_surb_target: u64, )

Starts the Exit’s single Exit → Entry keep-alive stream and installs the handle that steers it.

Called for every PIX Session, and for any Session with a configured surb_balance_notify_period — the SURB-level notification and PIX fill are the same packet sent for different reasons, so they share one stream and one rate controller. See PixFillControl.

Returns without spawning anything when neither producer applies, which is an Exit Session that is not PIX on a node that has switched the notification off. Such a Session originates nothing, which is what it did before this existed.

Deliberately not passed through the PIX egress gate. These packets carry no payload; gating them would let an exhausted predeposit budget silence the very signal the Entry needs in order to fund the Session, turning a stall into a teardown. The bound that does apply is PixFillControl::admit’s SURB reserve, which is about this node’s own ability to send at all rather than about what the peer has paid for.

announced_surb_target is the SURB buffer target the Entry asked for, and it is what that reserve is derived against — see effective_surb_reserve. It has to be, because the configured reserve is an absolute count while the buffer it is measured against is the Entry’s choice; a Session whose Entry asks for a shallower buffer than the configured reserve could otherwise never be filled at all.

Source

fn stop_pix_fill(&self, session_id: &SessionId)

Silences a Session’s PIX fill, leaving its SURB-level notification alone.

Called on every path that is about to tear a Session down. close_session aborts the stream outright, so this is not what stops it — it is what stops the extra traffic in the window between a supervisor deciding the Session is over and the teardown reaching the stream. Every packet originated in that window is return-routed to a pseudonym whose SURBs are gone, and one packet the routing resolver cannot resolve holds up every packet this node originates.

Source

async fn report_ssa_request( &self, session_id: &SessionId, slot: Option<&SessionSlot>, action: SessionPixAction, ok: bool, )

Reports an action outcome to a Session’s supervisor, if it still has one.

Source

async fn notify_session_error( &self, session_id: SessionId, routing: DestinationRouting, reason: StartErrorReason, context: &'static str, )

Best-effort SessionError for an already-established Session, identified by its SessionId.

Best-effort in the strict sense: the outcome is logged and discarded. Every caller is on a path that has already decided the Session is over, so there is nothing a failed send could change — and the peer has a deadline of its own as the backstop either way. What the notice buys is promptness and attribution: the peer tears down in about a round trip, with the reason in its log, instead of waiting out a timer that names the timer rather than the cause.

The peer’s handle_session_error closes the Session on a SessionId-identified error and sends nothing back, so there is no error exchange to loop.

Source

async fn notify_pix_failure( &self, session_id: SessionId, reply_routing: DestinationRouting, )

Best-effort notice to the Entry that this Session died for PIX reasons.

Source

async fn send_ssa_request( &self, session_id: SessionId, slot: &SessionSlot, ssa_ids: &[SsaId<HoprPseudonym>], params: PixParams, ) -> Result<Vec<SsaCommitmentGuard<HoprPixSpec>>>

Registers an Exit commitment for every index in ssa_indices and asks the Entry to commit to the matching SSAs — all in a single SsaServerCommitmentMessage.

Driven by the supervisor’s RequestSsa action, which is what allocates the indices and fixes the dimensions — this only carries it out. Taking them as arguments rather than re-reading them from the slot keeps one source of truth for what was negotiated: the commitments registered here and the parameters the message advertises cannot disagree with what the supervisor is timing.

One message for the whole batch, and one params field covering all of it, which is correct: every SSA in a Session uses the same negotiated dimensions. The Entry enforces its own ceiling on how many it will accept (SessionManagerConfig::max_ssas_per_ssa_request) and refuses an over-cap batch in full, so a batch larger than the peer allows loses the Session — see SupervisorConfig::ssas_per_request.

Installs no deadline of its own: every timeout that used to be armed here now belongs to the supervisor, which can see the whole cycle rather than just this one step.

The returned SsaCommitmentGuards own their registrations until the caller transfers or drops them. Handing them out rather than retaining them here is what makes the failure path safe: this function has fallible steps after the registrations exist — including partway through the batch — and an early return from any of them releases every one of them. That matters more for a batch than for a single SSA: without it one failed send would strand every index it had registered, and since the supervisor does not reuse an index, the Session could never recover.

Source

async fn send_commitment_retransmission_request( &self, session_id: SessionId, slot: &SessionSlot, ssa_id: SsaId<HoprPseudonym>, params: PixParams, ) -> Result<()>

Asks the Entry to re-send the parts of one SSA’s commitment that never arrived.

The Entry’s answer to an SsaRequest is hundreds of packets and the reconstructor can use none of it until every one has landed, so a single drop used to cost the Session on max_ssa_delivery_time — after the Entry had already been told to fund the cycle. This is the repair: the reconstructor says which polynomial runs are missing, and the Entry re-sends exactly those.

Deliberately not built on send_ssa_request, whose every step is wrong here. It registers no Exit commitment — the cycle already has one, and registering a second for the same index is refused as a duplicate — allocates no index, and asks the deposit pool for nothing, since the deposit data of this cycle travelled with the request that created it. Nothing it does can earn the Entry a deposit, which is what makes it safe to repeat.

The scope is read here rather than carried in the action: what is missing changes as the burst continues to arrive, and the freshest answer is both smaller and the one that converges.

Source

pub fn num_active_sessions(&self) -> usize

Returns the current number of active sessions.

Source

pub fn active_sessions(&self) -> Vec<SessionId> ⓘ

Returns SessionIds of all currently active sessions.

Source

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.

One case departs from that: an Exit-side PIX Session with fill.drain_after_close on and spendable SURBs left starts a SURB drain instead of being torn down (see begin_surb_drain). The data path still ends at once, but the session slot and the keep-alive stream stay in place until the supervisor closes the Session — whether because the funded cycle recovered or because the drain was refused — and true then means “found, and now draining” rather than “found and closed”.

Source

fn begin_surb_drain( &self, id: &SessionId, reason: ClosureReason, ) -> Option<bool>

Answers an Exit-side close by draining the Session’s buffered SURBs, when that is worth doing.

A PIX Session can end long before the cycle it was funded for does. The deposit that cycle was paid is released only once its whole emission has ridden back to the Entry, and the address it sits at derives from both nodes’ commitments — so tearing the Session down mid-cycle strands money both sides have already parted with. When the Exit still holds enough SURBs to finish, it keeps the keep-alive stream running instead, and the Session’s slot with it, until the supervisor closes the Session for good.

Returns None when this close was not answered here, which is every case the caller must handle exactly as it always has; Some(v) is the value close_session_with_reason should return. Some currently always carries true — a drain is only ever started for a Session that was found — but the value is returned rather than assumed so that the two outcomes stay distinguishable if a future refusal path wants to report one.

The order of the checks is load-bearing:

  1. the configuration, so that with drain_after_close (or fill.enabled) off nothing below runs and every close path is byte-for-byte what it was;
  2. the slot, by get rather than remove — the lookup also refreshes the idle timer the drain lives on;
  3. an Exit-side PIX Session, so an Entry-side or non-PIX Session falls straight through;
  4. an already-draining Session: a WriteClosed/EmptyRead is the end-of-stream our own ingress abort produces and is absorbed, while anything else — a second operator close, or a peer SessionError — forces the hard teardown, which is the operator’s way to overrule a drain;
  5. a PIX failure already tearing the Session down, which nothing here should interfere with;
  6. the SURB estimate, which is the one half of the predicate this layer can answer — see the comment on it for why answering it here rather than leaving it all to the supervisor matters;
  7. and only then the stream is put into drain mode, before the supervisor is told, so that no packet can be classified as the reserve-exempt notification in the window between the supervisor deciding and the stream learning.

What it does not do is decrement active_sessions or remove the slot: the supervisor’s own Close teardown does both, whether the drain completes or is refused a channel hop later. Balancer, KeepAlive, SurbNotifyDelay, PixActionDriver and the deposit observers are all left running; only the ingress is aborted, so the session server sees its end of the stream as promptly as it would have.

Source

fn close_session_with_reason( &self, id: &SessionId, reason: ClosureReason, ) -> bool

close_session with the reason spelled out.

The reason is what the operator reads when a PIX Session stops, and the failures are hard to tell apart from the outside, so a caller that knows which one it is says so.

true means the Session was found and dealt with. Since begin_surb_drain, “dealt with” also covers “found, and now draining its remaining SURBs into a funded cycle” — in which case the slot is deliberately left in place until the supervisor’s own teardown removes it.

Source

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.

Source

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.

Source

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.

Source

pub async fn dispatch_pix_event( &self, event: HoprSessionInPixEvent, ) -> Result<()>

Forwards a PIX protocol observation to the Session’s supervisor.

The manager no longer interprets these. Deciding what an early-recovery signal or a failed share means for a Session’s lifecycle needs the whole picture — which SSAs are in flight, what phase each is in, how much service has been consumed — and that lives in the supervisor.

A NonExistingSession error is expected traffic rather than a fault: acknowledgements outlive their Session by up to the reconstructor’s ack window, so events for a just-closed Session are routine. Callers distinguish it for exactly that reason.

Source

pub fn mark_return_path_degraded( &self, destination: &NodeId, grace: Duration, ) -> Vec<HoprPseudonym> ⓘ

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 the pseudonyms of the Sessions that were marked (empty when nothing routes there).

The caller uses these to advance each Session’s SURB generation: the return path to destination has just been re-planned, so the fresh SURB batch must supersede the SURBs the counterparty still holds for the dead path.

Source

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.

Source

fn reserve_cycle_budget( &self, params: &PixParams, ssas_per_request: usize, ) -> Option<Arc<CycleBudgetReservation>>

Charges params’ worth of reconstructor state against the node-wide budget.

Returns None if the node has already committed to as much as IncomingSessionPixConfig::max_live_cycle_bytes allows — the caller must then refuse the Session, because nothing later in establishment can give the memory back.

A CAS loop rather than an unconditional fetch_add with a rollback: an add that is provisionally over the ceiling is briefly visible to every concurrent initiation, and with enough of them arriving at once the budget would appear exhausted to Sessions that do fit.

Source

async fn request_admission( &self, session_id: SessionId, req: &StartInitiation<SessionTarget, HoprSessionCapabilities>, offered: Option<PixParams>, ) -> Result<SessionAdmissionDecision, StartErrorReason>

Asks the session server on what terms this Session may be admitted.

Fails closed. A refusal, a server that does not answer in time, a full request channel and a dropped reply all end the Session here rather than falling back to the node’s own terms: falling back would mean an overloaded or wedged server silently disables every rule the operator wrote, and the peer would be served on terms nobody chose.

The two failure kinds are reported differently because they call for different responses. A server that answered Err has decided, and re-asking will not change the answer, so the peer is told StartErrorReason::TargetNotAdmitted. Everything else is this node being unable to ask right now, which is transient, so the peer is told StartErrorReason::Busy and may retry.

A node with no session server has nobody to ask, and admits on its own terms.

Source

fn check_pix_params( &self, challenge: StartChallenge, offered: Option<PixParams>, policy: &EffectivePixAdmission, ) -> Option<(PixParams, usize)>

Checks the offer against the terms this Session was admitted on.

Judged against policy — the node’s configuration as narrowed by the session server’s decision for this Session — rather than against the node’s configuration directly, which is what makes the accepted quota a property of the target as well as of the node.

offered is what decode_pix_offer read, so this is only the policy question; the wire has already been judged.

Returns the validated parameters and selected SSA batch size, or None if the offer cannot satisfy that policy.

Source

async fn handle_incoming_session_initiation( &self, pseudonym: HoprPseudonym, session_req: StartInitiation<SessionTarget, HoprSessionCapabilities>, ) -> Result<()>

Source

async fn handle_session_established( &self, est: StartEstablished<SessionId>, ) -> Result<()>

Source

async fn handle_session_error( &self, error_type: StartErrorType<SessionId>, ) -> Result<()>

Source

async fn handle_keep_alive( &self, msg: KeepAliveMessage<SessionId>, ) -> Result<()>

Source

async fn handle_ssa_commit( &self, pseudonym: HoprPseudonym, msg: SsaClientCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixCommitmentProof>, ) -> Result<()>

Handled by the Exit, when Entry replies with PIX commitment

Source

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 notice the refusal is invisible to the Exit, and it has no way to recover from it. Its supervisor registered an Exit commitment per requested index and armed each cycle’s commitment deadline the moment the request went out; it will never receive an SsaCommit, and nothing can make it re-ask, because a new RequestSsa only comes from share-recovery events and no shares exist for a cycle the Entry never committed to. So it serves the Session unincentivized for the whole batch-scaled max_ssa_delivery_time and then closes it as CommitmentTimeout — a reason that names the clock rather than the refusal, on the one node whose operator can act on it.

Closing this half too is what stops the Entry sitting on a Session that can never make PIX progress: a refusal is terminal either way, since the Exit re-derives every request from state that cannot drift within a Session, so a later one would be refused identically. Left alone the slot would survive until the idle timeout.

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.

Source

async fn handle_ssa_recommit( &self, pseudonym: HoprPseudonym, session_slot: &SessionSlot, msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>, ) -> Result<()>

Handled by the Entry, when the Exit says it never received parts of a commitment this node already published.

Re-sends the named polynomial commitments and nothing else. Emits no ReadyToDeposit: the deposit address of a cycle is fixed when it is first committed, and the caller was told it then, so announcing it again would invite a second deposit against one quota.

A failure here is reported to the caller of the Start-message dispatcher and otherwise dropped. It is deliberately not refuse_ssa_request, which closes the Session: an Exit that asks for a cycle this node cannot serve is asking for something already lost, and its own commitment deadline ends the Session either way — whereas closing on a request that merely raced our own state would throw away a Session that was about to work.

Two separate bounds keep this from being a packet-amplification lever, because one request may name a whole cycle’s polynomials and the answer to that is the entire commitment burst again. The generator bounds how often it answers for one cycle (MAX_COMMITMENT_RETRANSMISSIONS); this function bounds how many cycles one request may set going. Neither stands in for the other — the first bounds repeats, the second bounds breadth.

Source

async fn handle_ssa_request( &self, pseudonym: HoprPseudonym, msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>, ) -> Result<()>

Handled by the Entry, when the Exit sends PIX initiation request

Trait Implementations§

Source§

impl<S> Clone for SessionManager<S>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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>
where S: Sync + Send,

§

impl<S> Sync for SessionManager<S>
where S: Sync + Send,

§

impl<S> Unpin for SessionManager<S>

§

impl<S> UnsafeUnpin for SessionManager<S>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> Conv for T

§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> DynClone for T
where T: Clone,

§

impl<T> FmtForward for T

§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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 T
where T: ?Sized,

§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows 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) -> R
where R: 'a,

Mutably borrows 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
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows 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
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows 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
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T> Tap for T

§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .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
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .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
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
§

impl<T> TryConv for T

§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more