use crate::FipsAddress;
use crate::NodeAddr;
use crate::PeerIdentity;
use crate::node::handlers::session::AuthenticatedSessionMessage;
use crate::node::handlers::session::mark_ipv6_ecn_ce;
use crate::node::session::{EpochSlot, FspReceiveSync, FspRecvSessionSnapshot};
use crate::node::session_wire::{
FSP_FLAG_K, FSP_HEADER_SIZE, FSP_PHASE_ESTABLISHED, FSP_PORT_HEADER_SIZE, FSP_PORT_IPV6_SHIM,
FspCommonPrefix, FspEncryptedHeader, fsp_strip_inner_header,
};
use crate::node::{
EndpointDataDelivery, EndpointEventSender, NodeDeliveredPacket, NodeEndpointEvent,
};
use crate::protocol::{LinkMessageType, SessionDatagramRef, SessionMessageType};
use crate::transport::{TransportAddr, TransportId};
use crate::upper::tun::TunTx;
use crossbeam_channel::{Receiver, Sender, TrySendError, bounded};
use ring::aead::{Aad, LessSafeKey, Nonce};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use tokio::sync::mpsc::{
Receiver as TokioReceiver, Sender as TokioSender, error::TrySendError as TokioTrySendError,
};
use tracing::{debug, trace, warn};
use crate::noise::ReplayWindow;
const DEFAULT_DECRYPT_WORKER_BULK_CHANNEL_CAP: usize = 32768;
const DEFAULT_DECRYPT_WORKER_PRIORITY_CHANNEL_CAP: usize = 1024;
const DEFAULT_DECRYPT_FALLBACK_BULK_CHANNEL_CAP: usize = 32768;
const DEFAULT_DECRYPT_FALLBACK_PRIORITY_CHANNEL_CAP: usize = 1024;
pub(crate) const DECRYPT_FALLBACK_BACKLOG_HIGH_WATER: usize = 256;
const DECRYPT_WORKER_PRIORITY_PACKET_MAX_LEN: usize = 512;
const DECRYPT_WORKER_BULK_BURST_BUDGET: usize = 128;
const DECRYPT_WORKER_BULK_BATCH_MAX: usize = 32;
const DECRYPT_WORKER_DIRECT_DELIVERY_BATCH_MAX: usize = DECRYPT_WORKER_BULK_BURST_BUDGET;
const DECRYPT_WORKER_ENDPOINT_DELIVERY_BATCH_MAX: usize = DECRYPT_WORKER_DIRECT_DELIVERY_BATCH_MAX;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum DecryptWorkerLane {
Priority,
Bulk,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct DecryptSessionKey {
transport_id: TransportId,
receiver_idx: u32,
}
impl DecryptSessionKey {
pub(crate) fn new(transport_id: TransportId, receiver_idx: u32) -> Self {
Self {
transport_id,
receiver_idx,
}
}
}
impl From<(TransportId, u32)> for DecryptSessionKey {
fn from((transport_id, receiver_idx): (TransportId, u32)) -> Self {
Self::new(transport_id, receiver_idx)
}
}
#[inline]
fn decrypt_session_fast_hash(session_key: DecryptSessionKey) -> u64 {
let packed =
(u64::from(session_key.transport_id.as_u32()) << 32) | u64::from(session_key.receiver_idx);
mix_decrypt_session_hash(packed ^ 0x9e37_79b9_7f4a_7c15)
}
#[inline]
fn decrypt_fsp_session_fast_hash(source_addr: &NodeAddr) -> u64 {
let bytes = source_addr.as_bytes();
let mut lo = [0u8; 8];
let mut hi = [0u8; 8];
lo.copy_from_slice(&bytes[..8]);
hi.copy_from_slice(&bytes[8..]);
mix_decrypt_session_hash(
u64::from_le_bytes(lo) ^ u64::from_le_bytes(hi).rotate_left(17) ^ 0xa24b_aed4_963e_e407,
)
}
#[inline]
fn mix_decrypt_session_hash(mut value: u64) -> u64 {
value ^= value >> 30;
value = value.wrapping_mul(0xbf58_476d_1ce4_e5b9);
value ^= value >> 27;
value = value.wrapping_mul(0x94d0_49bb_1331_11eb);
value ^ (value >> 31)
}
fn parse_channel_cap(primary: Option<&str>, fallback: Option<&str>, default: usize) -> usize {
primary
.and_then(|raw| raw.trim().parse::<usize>().ok())
.or_else(|| fallback.and_then(|raw| raw.trim().parse::<usize>().ok()))
.unwrap_or(default)
.clamp(1, default)
}
fn bulk_channel_cap() -> usize {
let decrypt_cap = std::env::var("FIPS_DECRYPT_WORKER_CHANNEL_CAP").ok();
let shared_cap = std::env::var("FIPS_WORKER_CHANNEL_CAP").ok();
parse_channel_cap(
decrypt_cap.as_deref(),
shared_cap.as_deref(),
DEFAULT_DECRYPT_WORKER_BULK_CHANNEL_CAP,
)
}
fn priority_channel_cap() -> usize {
let priority_cap = std::env::var("FIPS_DECRYPT_WORKER_PRIORITY_CHANNEL_CAP").ok();
parse_channel_cap(
priority_cap.as_deref(),
None,
DEFAULT_DECRYPT_WORKER_PRIORITY_CHANNEL_CAP,
)
}
fn fallback_bulk_channel_cap() -> usize {
let bulk_cap = std::env::var("FIPS_DECRYPT_FALLBACK_CHANNEL_CAP").ok();
fallback_bulk_channel_cap_from_raw(bulk_cap.as_deref())
}
fn fallback_bulk_channel_cap_from_raw(bulk_cap: Option<&str>) -> usize {
parse_channel_cap(bulk_cap, None, DEFAULT_DECRYPT_FALLBACK_BULK_CHANNEL_CAP)
}
fn fallback_priority_channel_cap() -> usize {
let priority_cap = std::env::var("FIPS_DECRYPT_FALLBACK_PRIORITY_CHANNEL_CAP").ok();
parse_channel_cap(
priority_cap.as_deref(),
None,
DEFAULT_DECRYPT_FALLBACK_PRIORITY_CHANNEL_CAP,
)
}
fn decrypt_worker_packet_lane(len: usize) -> DecryptWorkerLane {
if len <= DECRYPT_WORKER_PRIORITY_PACKET_MAX_LEN {
DecryptWorkerLane::Priority
} else {
DecryptWorkerLane::Bulk
}
}
fn decrypt_job_lane(job: &DecryptJob) -> DecryptWorkerLane {
job.lane()
}
pub(crate) struct OwnedSessionState {
pub fmp_cipher: LessSafeKey,
pub fmp_replay: ReplayWindow,
pub source_peer: PeerIdentity,
}
struct OwnedFspEpochState {
cipher: LessSafeKey,
replay: ReplayWindow,
}
pub(crate) struct OwnedFspSessionState {
source_peer: PeerIdentity,
current_k_bit: bool,
current: OwnedFspEpochState,
pending: Option<OwnedFspEpochState>,
previous: Option<OwnedFspEpochState>,
}
struct FspOpenSuccess {
plaintext: Vec<u8>,
slot: EpochSlot,
}
struct FspOpenInPlaceSuccess {
plaintext_len: usize,
slot: EpochSlot,
}
enum FspOpenError {
Replay,
Aead,
}
impl From<FspRecvSessionSnapshot> for OwnedFspSessionState {
fn from(snapshot: FspRecvSessionSnapshot) -> Self {
Self {
source_peer: snapshot.source_peer,
current_k_bit: snapshot.current_k_bit,
current: OwnedFspEpochState {
cipher: snapshot.current.cipher,
replay: snapshot.current.replay,
},
pending: snapshot.pending.map(|epoch| OwnedFspEpochState {
cipher: epoch.cipher,
replay: epoch.replay,
}),
previous: snapshot.previous.map(|epoch| OwnedFspEpochState {
cipher: epoch.cipher,
replay: epoch.replay,
}),
}
}
}
impl OwnedFspEpochState {
fn open(
&mut self,
ciphertext: &[u8],
counter: u64,
aad: &[u8],
) -> Result<Vec<u8>, FspOpenError> {
if !self.replay.check(counter) {
return Err(FspOpenError::Replay);
}
let mut plaintext = ciphertext.to_vec();
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..12].copy_from_slice(&counter.to_le_bytes());
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
let plaintext_len = self
.cipher
.open_in_place(nonce, Aad::from(aad), &mut plaintext)
.map_err(|_| FspOpenError::Aead)?
.len();
plaintext.truncate(plaintext_len);
self.replay.accept(counter);
Ok(plaintext)
}
fn open_in_place(
&mut self,
ciphertext: &mut [u8],
counter: u64,
aad: &[u8],
) -> Result<usize, FspOpenError> {
if !self.replay.check(counter) {
return Err(FspOpenError::Replay);
}
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..12].copy_from_slice(&counter.to_le_bytes());
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
let plaintext_len = self
.cipher
.open_in_place(nonce, Aad::from(aad), ciphertext)
.map_err(|_| FspOpenError::Aead)?
.len();
self.replay.accept(counter);
Ok(plaintext_len)
}
}
impl OwnedFspSessionState {
fn has_single_current_epoch(&self) -> bool {
self.pending.is_none() && self.previous.is_none()
}
fn open_established_frame(
&mut self,
header: &FspEncryptedHeader,
ciphertext: &[u8],
) -> Result<FspOpenSuccess, FspOpenError> {
let received_k_bit = header.flags & FSP_FLAG_K != 0;
let pending_first = received_k_bit != self.current_k_bit && self.pending.is_some();
let order = if pending_first {
[EpochSlot::Pending, EpochSlot::Current, EpochSlot::Previous]
} else {
[EpochSlot::Current, EpochSlot::Pending, EpochSlot::Previous]
};
let mut saw_replay = false;
for slot in order {
let epoch = match slot {
EpochSlot::Current => Some(&mut self.current),
EpochSlot::Pending => self.pending.as_mut(),
EpochSlot::Previous => self.previous.as_mut(),
};
let Some(epoch) = epoch else {
continue;
};
match epoch.open(ciphertext, header.counter, &header.header_bytes) {
Ok(plaintext) => {
if slot == EpochSlot::Pending {
let old = std::mem::replace(
&mut self.current,
self.pending
.take()
.expect("pending epoch exists for pending slot"),
);
self.previous = Some(old);
self.current_k_bit = !self.current_k_bit;
}
return Ok(FspOpenSuccess { plaintext, slot });
}
Err(FspOpenError::Replay) => saw_replay = true,
Err(FspOpenError::Aead) => {}
}
}
if saw_replay {
Err(FspOpenError::Replay)
} else {
Err(FspOpenError::Aead)
}
}
fn open_current_established_frame_in_place(
&mut self,
header: &FspEncryptedHeader,
ciphertext: &mut [u8],
) -> Result<FspOpenInPlaceSuccess, FspOpenError> {
debug_assert!(self.has_single_current_epoch());
let plaintext_len =
self.current
.open_in_place(ciphertext, header.counter, &header.header_bytes)?;
Ok(FspOpenInPlaceSuccess {
plaintext_len,
slot: EpochSlot::Current,
})
}
}
#[derive(Debug)]
struct FmpOpenOutcome {
plaintext_len: usize,
}
#[derive(Debug, PartialEq, Eq)]
enum FmpOpenError {
Replay,
Aead { fmp_replay_highest: u64 },
}
impl OwnedSessionState {
fn open_fmp_in_place(
&mut self,
packet_data: &mut [u8],
fmp_ciphertext_offset: usize,
fmp_counter: u64,
fmp_header: &[u8; 16],
) -> Result<FmpOpenOutcome, FmpOpenError> {
let fmp_replay_highest = self.fmp_replay.highest();
if !self.fmp_replay.check(fmp_counter) {
return Err(FmpOpenError::Replay);
}
let mut nonce_bytes = [0u8; 12];
nonce_bytes[4..12].copy_from_slice(&fmp_counter.to_le_bytes());
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
let buf = &mut packet_data[fmp_ciphertext_offset..];
let plaintext_len = self
.fmp_cipher
.open_in_place(nonce, Aad::from(fmp_header), buf)
.map_err(|_| FmpOpenError::Aead { fmp_replay_highest })?
.len();
self.fmp_replay.accept(fmp_counter);
Ok(FmpOpenOutcome { plaintext_len })
}
}
pub(crate) struct DecryptJob {
pub packet_data: Vec<u8>,
lane: DecryptWorkerLane,
pub session_key: DecryptSessionKey,
pub _transport_id: TransportId,
pub _remote_addr: TransportAddr,
pub local_node_addr: NodeAddr,
pub timestamp_ms: u64,
pub fmp_counter: u64,
pub fmp_flags: u8,
pub fmp_header: [u8; 16],
pub fmp_ciphertext_offset: usize,
pub fallback_tx: DecryptWorkerFallbackSender,
trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
impl DecryptJob {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
packet_data: Vec<u8>,
session_key: DecryptSessionKey,
transport_id: TransportId,
remote_addr: TransportAddr,
local_node_addr: NodeAddr,
timestamp_ms: u64,
fmp_counter: u64,
fmp_flags: u8,
fmp_header: [u8; 16],
fmp_ciphertext_offset: usize,
fallback_tx: DecryptWorkerFallbackSender,
) -> Self {
let lane = decrypt_worker_packet_lane(packet_data.len());
Self {
packet_data,
lane,
session_key,
_transport_id: transport_id,
_remote_addr: remote_addr,
local_node_addr,
timestamp_ms,
fmp_counter,
fmp_flags,
fmp_header,
fmp_ciphertext_offset,
fallback_tx,
trace_enqueued_at: None,
}
}
fn lane(&self) -> DecryptWorkerLane {
self.lane
}
fn is_bulk_lane(&self) -> bool {
matches!(self.lane(), DecryptWorkerLane::Bulk)
}
fn set_trace_enqueued_at(&mut self, queued_at: Option<crate::perf_profile::TraceStamp>) {
self.trace_enqueued_at = queued_at;
}
fn record_queue_wait(&self) {
let queued_at = self.trace_enqueued_at;
if queued_at.is_none() {
return;
}
let priority_count = u64::from(matches!(self.lane(), DecryptWorkerLane::Priority));
let bulk_count = u64::from(matches!(self.lane(), DecryptWorkerLane::Bulk));
crate::perf_profile::record_since_split_count(
crate::perf_profile::Stage::DecryptWorkerQueueWait,
crate::perf_profile::Stage::DecryptWorkerPriorityQueueWait,
crate::perf_profile::Stage::DecryptWorkerBulkQueueWait,
queued_at,
1,
priority_count,
bulk_count,
);
}
}
#[allow(dead_code)] pub(crate) struct DecryptFallback {
pub source_peer: PeerIdentity,
pub transport_id: TransportId,
pub remote_addr: TransportAddr,
pub timestamp_ms: u64,
pub packet_len: usize,
lane: DecryptWorkerLane,
pub fmp_counter: u64,
pub fmp_flags: u8,
pub packet_data: Vec<u8>,
pub fmp_plaintext_offset: usize,
pub fmp_plaintext_len: usize,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
impl DecryptFallback {
#[allow(clippy::too_many_arguments)]
fn new(
source_peer: PeerIdentity,
transport_id: TransportId,
remote_addr: TransportAddr,
timestamp_ms: u64,
packet_len: usize,
fmp_counter: u64,
fmp_flags: u8,
packet_data: Vec<u8>,
fmp_plaintext_offset: usize,
fmp_plaintext_len: usize,
) -> Self {
let lane = decrypt_worker_packet_lane(packet_len);
Self {
source_peer,
transport_id,
remote_addr,
timestamp_ms,
packet_len,
lane,
fmp_counter,
fmp_flags,
packet_data,
fmp_plaintext_offset,
fmp_plaintext_len,
trace_enqueued_at: None,
}
}
fn lane(&self) -> DecryptWorkerLane {
self.lane
}
}
pub(crate) struct DecryptFailureReport {
pub source_peer: PeerIdentity,
pub fmp_counter: u64,
pub fmp_replay_highest: u64,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
pub(crate) struct DecryptFmpBookkeeping {
pub source_peer: PeerIdentity,
pub transport_id: TransportId,
pub remote_addr: TransportAddr,
pub packet_timestamp_ms: u64,
pub packet_len: usize,
pub fmp_counter: u64,
pub inner_timestamp_ms: u32,
pub fmp_flags: u8,
}
pub(crate) struct DecryptAuthenticatedFmpReceive {
pub fmp: DecryptFmpBookkeeping,
lane: DecryptWorkerLane,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
pub(crate) struct DecryptAuthenticatedSession {
pub fmp: DecryptFmpBookkeeping,
pub source_addr: NodeAddr,
pub previous_hop_peer: PeerIdentity,
pub ce_flag: bool,
pub message: AuthenticatedSessionMessage,
pub receive_sync: FspReceiveSync,
lane: DecryptWorkerLane,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
pub(crate) enum DecryptDirectSessionDelivery {
Ipv6Packet(Vec<u8>),
EndpointData(EndpointDataDelivery),
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DecryptDirectSessionDeliverySink {
tun_tx: Option<TunTx>,
external_packet_tx: Option<TokioSender<NodeDeliveredPacket>>,
endpoint_event_tx: Option<EndpointEventSender>,
}
impl DecryptDirectSessionDeliverySink {
pub(crate) fn new(
tun_tx: Option<TunTx>,
external_packet_tx: Option<TokioSender<NodeDeliveredPacket>>,
endpoint_event_tx: Option<EndpointEventSender>,
) -> Self {
Self {
tun_tx,
external_packet_tx,
endpoint_event_tx,
}
}
fn can_deliver(&self, delivery: &DecryptDirectSessionDelivery) -> bool {
match delivery {
DecryptDirectSessionDelivery::EndpointData(_) => self.endpoint_event_tx.is_some(),
DecryptDirectSessionDelivery::Ipv6Packet(_) => {
self.external_packet_tx.is_some() || self.tun_tx.is_some()
}
}
}
fn same_endpoint_event_channel(&self, other: &Self) -> bool {
match (&self.endpoint_event_tx, &other.endpoint_event_tx) {
(Some(lhs), Some(rhs)) => lhs.same_channels(rhs),
(None, None) => true,
_ => false,
}
}
fn endpoint_event_sender(&self) -> Option<&EndpointEventSender> {
self.endpoint_event_tx.as_ref()
}
fn deliver(
&self,
source_addr: NodeAddr,
source_peer: PeerIdentity,
ce_flag: bool,
delivery: DecryptDirectSessionDelivery,
) {
match delivery {
DecryptDirectSessionDelivery::EndpointData(delivery) => {
let Some(endpoint_event_tx) = &self.endpoint_event_tx else {
return;
};
let _t_deliver =
crate::perf_profile::Timer::start(crate::perf_profile::Stage::EndpointDeliver);
let event = NodeEndpointEvent::Data {
source_peer: delivery.source_peer,
payload: delivery.payload,
queued_at: crate::perf_profile::stamp(),
};
if let Err(error) = endpoint_event_tx.send(event) {
debug!(error = %error, "Failed to deliver worker-decoded endpoint data");
}
}
DecryptDirectSessionDelivery::Ipv6Packet(mut packet) => {
if ce_flag {
mark_ipv6_ecn_ce(&mut packet);
}
if let Some(external_packet_tx) = &self.external_packet_tx {
if packet.len() < 40 {
return;
}
let Ok(destination) = FipsAddress::from_slice(&packet[24..40]) else {
return;
};
let delivered = NodeDeliveredPacket {
source_node_addr: source_addr,
source_npub: Some(source_peer.npub()),
destination,
packet,
};
if let Err(error) = external_packet_tx.try_send(delivered) {
debug!(error = %error, "Failed to deliver worker-decoded packet to external app sink");
}
return;
}
if let Some(tun_tx) = &self.tun_tx {
let _t =
crate::perf_profile::Timer::start(crate::perf_profile::Stage::TunWrite);
if let Err(error) = tun_tx.send(packet) {
debug!(error = %error, "Failed to deliver worker-decoded IPv6 packet to TUN");
}
}
}
}
}
}
struct PendingDirectSessionDelivery {
sink: DecryptDirectSessionDeliverySink,
source_addr: NodeAddr,
source_peer: PeerIdentity,
ce_flag: bool,
delivery: DecryptDirectSessionDelivery,
}
impl PendingDirectSessionDelivery {
fn deliver(self) {
self.sink.deliver(
self.source_addr,
self.source_peer,
self.ce_flag,
self.delivery,
);
}
fn is_endpoint_data(&self) -> bool {
match &self.delivery {
DecryptDirectSessionDelivery::EndpointData(_) => {
self.sink.endpoint_event_sender().is_some()
}
DecryptDirectSessionDelivery::Ipv6Packet(_) => false,
}
}
fn is_ipv6_packet(&self) -> bool {
matches!(&self.delivery, DecryptDirectSessionDelivery::Ipv6Packet(_))
}
#[allow(clippy::result_large_err)]
fn into_endpoint_data(
self,
) -> Result<(DecryptDirectSessionDeliverySink, EndpointDataDelivery), Self> {
match self.delivery {
DecryptDirectSessionDelivery::EndpointData(delivery) => Ok((self.sink, delivery)),
delivery => Err(Self {
sink: self.sink,
source_addr: self.source_addr,
source_peer: self.source_peer,
ce_flag: self.ce_flag,
delivery,
}),
}
}
}
pub(crate) struct DecryptDirectSessionData {
pub fmp: DecryptFmpBookkeeping,
pub source_addr: NodeAddr,
pub previous_hop_peer: PeerIdentity,
pub ce_flag: bool,
pub receive_sync: FspReceiveSync,
pub body_len: usize,
pub delivery: DecryptDirectSessionDelivery,
lane: DecryptWorkerLane,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
impl DecryptDirectSessionData {
#[cfg(test)]
pub(in crate::node) fn for_test(
fmp: DecryptFmpBookkeeping,
source_addr: NodeAddr,
previous_hop_peer: PeerIdentity,
ce_flag: bool,
receive_sync: FspReceiveSync,
body_len: usize,
delivery: DecryptDirectSessionDelivery,
) -> Self {
Self {
fmp,
source_addr,
previous_hop_peer,
ce_flag,
receive_sync,
body_len,
delivery,
lane: DecryptWorkerLane::Bulk,
trace_enqueued_at: None,
}
}
}
pub(crate) struct DecryptDirectSessionCommit {
pub fmp: DecryptFmpBookkeeping,
pub source_addr: NodeAddr,
pub previous_hop_peer: PeerIdentity,
pub ce_flag: bool,
pub receive_sync: FspReceiveSync,
pub body_len: usize,
pub delivered_ipv6: bool,
lane: DecryptWorkerLane,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
impl DecryptDirectSessionCommit {
#[cfg(test)]
pub(in crate::node) fn for_test(
fmp: DecryptFmpBookkeeping,
source_addr: NodeAddr,
previous_hop_peer: PeerIdentity,
ce_flag: bool,
receive_sync: FspReceiveSync,
body_len: usize,
delivered_ipv6: bool,
) -> Self {
Self {
fmp,
source_addr,
previous_hop_peer,
ce_flag,
receive_sync,
body_len,
delivered_ipv6,
lane: DecryptWorkerLane::Bulk,
trace_enqueued_at: None,
}
}
}
pub(crate) struct DecryptFspFailureReport {
pub fmp: DecryptFmpBookkeeping,
pub source_addr: NodeAddr,
pub counter: u64,
pub received_k_bit: bool,
lane: DecryptWorkerLane,
pub(crate) trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
}
pub(crate) enum DecryptWorkerEvent {
Plaintext(DecryptFallback),
PlaintextBatch(Vec<DecryptFallback>),
AuthenticatedFmpReceive(DecryptAuthenticatedFmpReceive),
AuthenticatedSession(DecryptAuthenticatedSession),
DirectSessionCommit(DecryptDirectSessionCommit),
DirectSessionCommitBatch(Vec<DecryptDirectSessionCommit>),
DirectSessionData(DecryptDirectSessionData),
FspDecryptFailure(DecryptFspFailureReport),
DecryptFailure(DecryptFailureReport),
}
impl DecryptWorkerEvent {
fn lane(&self) -> DecryptWorkerLane {
decrypt_worker_event_lane(self)
}
pub(crate) fn packet_count(&self) -> usize {
match self {
Self::Plaintext(_) | Self::DecryptFailure(_) => 1,
Self::AuthenticatedFmpReceive(_) => 1,
Self::AuthenticatedSession(_) => 1,
Self::DirectSessionCommit(_) => 1,
Self::DirectSessionCommitBatch(commits) => commits.len(),
Self::DirectSessionData(_) => 1,
Self::FspDecryptFailure(_) => 1,
Self::PlaintextBatch(fallbacks) => fallbacks.len(),
}
}
fn set_trace_enqueued_at(&mut self, queued_at: Option<crate::perf_profile::TraceStamp>) {
match self {
Self::Plaintext(fallback) => fallback.trace_enqueued_at = queued_at,
Self::PlaintextBatch(fallbacks) => {
for fallback in fallbacks {
fallback.trace_enqueued_at = queued_at;
}
}
Self::AuthenticatedFmpReceive(receive) => receive.trace_enqueued_at = queued_at,
Self::AuthenticatedSession(session) => session.trace_enqueued_at = queued_at,
Self::DirectSessionCommit(commit) => commit.trace_enqueued_at = queued_at,
Self::DirectSessionCommitBatch(commits) => {
for commit in commits {
commit.trace_enqueued_at = queued_at;
}
}
Self::DirectSessionData(direct) => direct.trace_enqueued_at = queued_at,
Self::FspDecryptFailure(report) => report.trace_enqueued_at = queued_at,
Self::DecryptFailure(report) => report.trace_enqueued_at = queued_at,
}
}
fn trace_enqueued_at(&self) -> Option<crate::perf_profile::TraceStamp> {
match self {
Self::Plaintext(fallback) => fallback.trace_enqueued_at,
Self::PlaintextBatch(fallbacks) => fallbacks
.first()
.and_then(|fallback| fallback.trace_enqueued_at),
Self::AuthenticatedFmpReceive(receive) => receive.trace_enqueued_at,
Self::AuthenticatedSession(session) => session.trace_enqueued_at,
Self::DirectSessionCommit(commit) => commit.trace_enqueued_at,
Self::DirectSessionCommitBatch(commits) => {
commits.first().and_then(|commit| commit.trace_enqueued_at)
}
Self::DirectSessionData(direct) => direct.trace_enqueued_at,
Self::FspDecryptFailure(report) => report.trace_enqueued_at,
Self::DecryptFailure(report) => report.trace_enqueued_at,
}
}
fn queue_wait_stages(
&self,
) -> (
crate::perf_profile::Stage,
crate::perf_profile::Stage,
crate::perf_profile::Stage,
) {
match self {
Self::AuthenticatedFmpReceive(_)
| Self::AuthenticatedSession(_)
| Self::DirectSessionCommit(_)
| Self::DirectSessionCommitBatch(_)
| Self::DirectSessionData(_) => (
crate::perf_profile::Stage::DecryptAuthenticatedSessionWait,
crate::perf_profile::Stage::DecryptAuthenticatedSessionPriorityWait,
crate::perf_profile::Stage::DecryptAuthenticatedSessionBulkWait,
),
Self::Plaintext(_)
| Self::PlaintextBatch(_)
| Self::FspDecryptFailure(_)
| Self::DecryptFailure(_) => (
crate::perf_profile::Stage::DecryptFallbackWait,
crate::perf_profile::Stage::DecryptFallbackPriorityWait,
crate::perf_profile::Stage::DecryptFallbackBulkWait,
),
}
}
pub(crate) fn record_queue_wait(&self) {
let queued_at = self.trace_enqueued_at();
if queued_at.is_none() {
return;
}
let count = self.packet_count() as u64;
let (priority_count, bulk_count) = match self.lane() {
DecryptWorkerLane::Priority => (count, 0),
DecryptWorkerLane::Bulk => (0, count),
};
let (total_stage, priority_stage, bulk_stage) = self.queue_wait_stages();
crate::perf_profile::record_since_split_count(
total_stage,
priority_stage,
bulk_stage,
queued_at,
count,
priority_count,
bulk_count,
);
}
}