use crate::lifecycle_reason::{
REASON_DUPLICATE_ACCEPT_SUPERSEDED, REASON_DUPLICATE_DIAL_SUPERSEDED,
};
pub(crate) const NONCANONICAL_OUTBOUND_DIAL_GRACE_MS: u64 = 250;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IrohConnectionDirection {
Inbound,
Outbound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ExistingConnectionState {
pub same_stable_id: bool,
pub alive: bool,
pub direction: IrohConnectionDirection,
pub age_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IrohConnectionInstallDecision {
Install,
ReplaceExisting { close_existing_reason: &'static str },
KeepExisting { close_fresh_reason: &'static str },
}
pub(crate) fn should_start_outbound_dial(
existing: Option<(bool, IrohConnectionDirection)>,
local_prefers_outbound: bool,
) -> bool {
let Some((alive, direction)) = existing else {
return true;
};
!alive || (local_prefers_outbound && direction != IrohConnectionDirection::Outbound)
}
pub(crate) fn should_wait_for_canonical_inbound(
existing: Option<(bool, IrohConnectionDirection)>,
local_prefers_outbound: bool,
) -> bool {
if local_prefers_outbound {
return false;
}
match existing {
None => true,
Some((alive, _)) => !alive,
}
}
pub(crate) fn decide_inbound_install(
existing: Option<ExistingConnectionState>,
local_prefers_outbound: bool,
) -> IrohConnectionInstallDecision {
decide_install(
existing,
IrohConnectionDirection::Inbound,
local_prefers_outbound,
)
}
pub(crate) fn decide_outbound_install(
existing: Option<ExistingConnectionState>,
local_prefers_outbound: bool,
) -> IrohConnectionInstallDecision {
decide_install(
existing,
IrohConnectionDirection::Outbound,
local_prefers_outbound,
)
}
fn replace_existing_decision(
fresh_direction: IrohConnectionDirection,
) -> IrohConnectionInstallDecision {
IrohConnectionInstallDecision::ReplaceExisting {
close_existing_reason: match fresh_direction {
IrohConnectionDirection::Inbound => {
crate::lifecycle_reason::REASON_REPLACED_BY_NEW_INBOUND
}
IrohConnectionDirection::Outbound => {
crate::lifecycle_reason::REASON_REPLACED_BY_NEW_OUTBOUND
}
},
}
}
fn decide_install(
existing: Option<ExistingConnectionState>,
fresh_direction: IrohConnectionDirection,
local_prefers_outbound: bool,
) -> IrohConnectionInstallDecision {
let Some(existing) = existing else {
return IrohConnectionInstallDecision::Install;
};
if existing.same_stable_id || !existing.alive {
return IrohConnectionInstallDecision::Install;
}
let canonical_direction = if local_prefers_outbound {
IrohConnectionDirection::Outbound
} else {
IrohConnectionDirection::Inbound
};
if fresh_direction != canonical_direction {
return IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: match fresh_direction {
IrohConnectionDirection::Inbound => REASON_DUPLICATE_ACCEPT_SUPERSEDED,
IrohConnectionDirection::Outbound => REASON_DUPLICATE_DIAL_SUPERSEDED,
},
};
}
if existing.direction != canonical_direction {
return replace_existing_decision(fresh_direction);
}
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: match fresh_direction {
IrohConnectionDirection::Inbound => REASON_DUPLICATE_ACCEPT_SUPERSEDED,
IrohConnectionDirection::Outbound => REASON_DUPLICATE_DIAL_SUPERSEDED,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_without_existing_or_for_same_stable_id() {
assert_eq!(
decide_inbound_install(None, false),
IrohConnectionInstallDecision::Install
);
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: true,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: 0,
}),
true
),
IrohConnectionInstallDecision::Install
);
}
#[test]
fn install_over_dead_existing_connection() {
assert_eq!(
decide_inbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: false,
direction: IrohConnectionDirection::Outbound,
age_ms: 0,
}),
false
),
IrohConnectionInstallDecision::Install
);
}
#[test]
fn recent_same_direction_canonical_duplicate_keeps_existing_connection() {
assert_eq!(
decide_inbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: 1_999,
}),
false
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_ACCEPT_SUPERSEDED,
}
);
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Outbound,
age_ms: 0,
}),
true
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_DIAL_SUPERSEDED,
}
);
}
#[test]
fn elapsed_time_cannot_replace_a_live_same_direction_connection() {
assert_eq!(
decide_inbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: u64::MAX,
}),
false
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_ACCEPT_SUPERSEDED,
}
);
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Outbound,
age_ms: u64::MAX,
}),
true
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_DIAL_SUPERSEDED,
}
);
}
#[test]
fn canonical_fresh_leg_replaces_the_non_canonical_live_leg() {
assert_eq!(
decide_inbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Outbound,
age_ms: 0,
}),
false
),
IrohConnectionInstallDecision::ReplaceExisting {
close_existing_reason: crate::lifecycle_reason::REASON_REPLACED_BY_NEW_INBOUND,
}
);
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: 0,
}),
true
),
IrohConnectionInstallDecision::ReplaceExisting {
close_existing_reason: crate::lifecycle_reason::REASON_REPLACED_BY_NEW_OUTBOUND,
}
);
}
#[test]
fn noncanonical_fresh_leg_cannot_replace_an_existing_live_leg() {
assert_eq!(
decide_inbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Outbound,
age_ms: u64::MAX,
}),
true
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_ACCEPT_SUPERSEDED,
}
);
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: u64::MAX,
}),
false
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_DIAL_SUPERSEDED,
}
);
}
#[test]
fn connection_age_never_overrides_noncanonical_direction() {
assert_eq!(
decide_outbound_install(
Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
direction: IrohConnectionDirection::Inbound,
age_ms: u64::MAX,
}),
false,
),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_DIAL_SUPERSEDED,
}
);
}
#[test]
fn outbound_precheck_is_idempotent_and_repairs_only_noncanonical_live_legs() {
assert!(should_start_outbound_dial(None, true));
assert!(should_start_outbound_dial(
Some((false, IrohConnectionDirection::Outbound)),
true,
));
assert!(!should_start_outbound_dial(
Some((true, IrohConnectionDirection::Outbound)),
true,
));
assert!(should_start_outbound_dial(
Some((true, IrohConnectionDirection::Inbound)),
true,
));
assert!(!should_start_outbound_dial(
Some((true, IrohConnectionDirection::Outbound)),
false,
));
}
#[test]
fn noncanonical_endpoint_waits_only_when_no_live_leg_exists() {
assert!(should_wait_for_canonical_inbound(None, false));
assert!(should_wait_for_canonical_inbound(
Some((false, IrohConnectionDirection::Outbound)),
false,
));
assert!(!should_wait_for_canonical_inbound(
Some((true, IrohConnectionDirection::Inbound)),
false,
));
assert!(!should_wait_for_canonical_inbound(None, true));
}
}