use crate::lifecycle_reason::{
REASON_DUPLICATE_ACCEPT_SUPERSEDED, REASON_DUPLICATE_DIAL_SUPERSEDED,
REASON_REPLACED_BY_NEW_INBOUND, REASON_REPLACED_BY_NEW_OUTBOUND,
REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT, REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL,
};
pub(crate) const ZOMBIE_REPLACE_AGE_MS: u64 = 2_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ExistingConnectionState {
pub same_stable_id: bool,
pub alive: bool,
pub age_ms: u64,
pub prefer_fresh_duplicate: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IrohConnectionInstallDecision {
Install,
KeepExisting { close_fresh_reason: &'static str },
ReplaceExisting { close_previous_reason: &'static str },
}
pub(crate) fn should_redial_without_precheck(existing_alive: bool) -> bool {
!existing_alive
}
pub(crate) fn decide_inbound_install(
existing: Option<ExistingConnectionState>,
) -> IrohConnectionInstallDecision {
decide_install(existing, InstallDirection::Inbound)
}
pub(crate) fn decide_outbound_install(
existing: Option<ExistingConnectionState>,
) -> IrohConnectionInstallDecision {
decide_install(existing, InstallDirection::Outbound)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InstallDirection {
Inbound,
Outbound,
}
fn decide_install(
existing: Option<ExistingConnectionState>,
direction: InstallDirection,
) -> IrohConnectionInstallDecision {
let Some(existing) = existing else {
return IrohConnectionInstallDecision::Install;
};
if existing.same_stable_id || !existing.alive {
return IrohConnectionInstallDecision::Install;
}
if existing.age_ms < ZOMBIE_REPLACE_AGE_MS {
if existing.prefer_fresh_duplicate {
return IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: match direction {
InstallDirection::Inbound => REASON_REPLACED_BY_NEW_INBOUND,
InstallDirection::Outbound => REASON_REPLACED_BY_NEW_OUTBOUND,
},
};
}
return IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: match direction {
InstallDirection::Inbound => REASON_DUPLICATE_ACCEPT_SUPERSEDED,
InstallDirection::Outbound => REASON_DUPLICATE_DIAL_SUPERSEDED,
},
};
}
IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: match direction {
InstallDirection::Inbound => REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT,
InstallDirection::Outbound => REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_without_existing_or_for_same_stable_id() {
assert_eq!(
decide_inbound_install(None),
IrohConnectionInstallDecision::Install
);
assert_eq!(
decide_outbound_install(Some(ExistingConnectionState {
same_stable_id: true,
alive: true,
age_ms: 1,
prefer_fresh_duplicate: true,
})),
IrohConnectionInstallDecision::Install
);
}
#[test]
fn install_over_dead_existing_connection() {
assert_eq!(
decide_inbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: false,
age_ms: 1,
prefer_fresh_duplicate: true,
})),
IrohConnectionInstallDecision::Install
);
}
#[test]
fn keep_existing_live_connection_inside_symmetric_dial_window() {
assert_eq!(
decide_inbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS - 1,
prefer_fresh_duplicate: false,
})),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_ACCEPT_SUPERSEDED,
}
);
assert_eq!(
decide_outbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS - 1,
prefer_fresh_duplicate: false,
})),
IrohConnectionInstallDecision::KeepExisting {
close_fresh_reason: REASON_DUPLICATE_DIAL_SUPERSEDED,
}
);
}
#[test]
fn tie_break_can_choose_fresh_connection_inside_symmetric_dial_window() {
assert_eq!(
decide_inbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS - 1,
prefer_fresh_duplicate: true,
})),
IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: REASON_REPLACED_BY_NEW_INBOUND,
}
);
assert_eq!(
decide_outbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS - 1,
prefer_fresh_duplicate: true,
})),
IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: REASON_REPLACED_BY_NEW_OUTBOUND,
}
);
}
#[test]
fn replace_old_live_connection_as_zombie() {
assert_eq!(
decide_inbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS,
prefer_fresh_duplicate: false,
})),
IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT,
}
);
assert_eq!(
decide_outbound_install(Some(ExistingConnectionState {
same_stable_id: false,
alive: true,
age_ms: ZOMBIE_REPLACE_AGE_MS,
prefer_fresh_duplicate: false,
})),
IrohConnectionInstallDecision::ReplaceExisting {
close_previous_reason: REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL,
}
);
}
#[test]
fn redial_precheck_only_dials_when_existing_is_not_alive() {
assert!(!should_redial_without_precheck(true));
assert!(should_redial_without_precheck(false));
}
}