use crate::error::{Error, Result};
pub const PTS_MODULUS_33: u64 = broadcast_common::clock33::WRAP_33BIT;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SnapDirection {
Exact,
Before,
After,
}
impl SnapDirection {
pub fn name(&self) -> &'static str {
match self {
SnapDirection::Exact => "exact",
SnapDirection::Before => "before",
SnapDirection::After => "after",
}
}
}
broadcast_common::impl_spec_display!(SnapDirection);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConditionedSplicePoint {
pub requested_pts: u64,
pub snapped_pts: u64,
pub delta_ticks: u64,
pub direction: SnapDirection,
}
impl ConditionedSplicePoint {
pub fn is_exact(&self) -> bool {
self.delta_ticks == 0
}
}
pub fn condition_splice_point(
requested_pts: u64,
candidates: &[u64],
max_delta_ticks: u64,
) -> Result<ConditionedSplicePoint> {
let mut nearest: Option<(u64, u64)> = None; for &c in candidates {
let delta = c.abs_diff(requested_pts);
if nearest.is_none_or(|(best_c, best)| {
delta < best || (delta == best && c > requested_pts && best_c < requested_pts)
}) {
nearest = Some((c, delta));
}
}
let (snapped_pts, delta_ticks) = nearest.ok_or(Error::NoCandidates)?;
if delta_ticks > max_delta_ticks {
return Err(Error::NoAlignedBoundary {
requested_pts,
tolerance_ticks: max_delta_ticks,
nearest_delta_ticks: delta_ticks,
});
}
let direction = if snapped_pts == requested_pts {
SnapDirection::Exact
} else if snapped_pts < requested_pts {
SnapDirection::Before
} else {
SnapDirection::After
};
Ok(ConditionedSplicePoint {
requested_pts,
snapped_pts,
delta_ticks,
direction,
})
}
pub fn condition_splice_point_wrapping(
requested_pts: u64,
candidates: &[u64],
max_delta_ticks: u64,
modulus: u64,
) -> Result<ConditionedSplicePoint> {
if modulus == 0 || requested_pts >= modulus {
return Err(Error::PtsOutOfRange {
pts: requested_pts,
modulus,
});
}
let m = u128::from(modulus);
let r = u128::from(requested_pts);
let mut nearest: Option<(u64, u64, bool)> = None;
for &c in candidates {
if c >= modulus {
return Err(Error::PtsOutOfRange { pts: c, modulus });
}
let cw = u128::from(c);
let forward = (cw + m - r) % m;
let backward = (r + m - cw) % m;
let (delta, is_forward) = if forward <= backward {
(forward, true)
} else {
(backward, false)
};
let delta = u64::try_from(delta).unwrap_or(u64::MAX);
if nearest.is_none_or(|(_, best, best_forward)| {
delta < best || (delta == best && is_forward && !best_forward)
}) {
nearest = Some((c, delta, is_forward));
}
}
let (snapped_pts, delta_ticks, is_forward) = nearest.ok_or(Error::NoCandidates)?;
if delta_ticks > max_delta_ticks {
return Err(Error::NoAlignedBoundary {
requested_pts,
tolerance_ticks: max_delta_ticks,
nearest_delta_ticks: delta_ticks,
});
}
let direction = if delta_ticks == 0 {
SnapDirection::Exact
} else if is_forward {
SnapDirection::After
} else {
SnapDirection::Before
};
Ok(ConditionedSplicePoint {
requested_pts,
snapped_pts,
delta_ticks,
direction,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snaps_to_nearest_candidate() {
let result = condition_splice_point(1_000, &[500, 1_050, 2_000], 100).unwrap();
assert_eq!(result.snapped_pts, 1_050);
assert_eq!(result.delta_ticks, 50);
assert_eq!(result.direction, SnapDirection::After);
assert!(!result.is_exact());
}
#[test]
fn exact_match_reports_zero_delta() {
let result = condition_splice_point(1_000, &[1_000], 0).unwrap();
assert_eq!(result.delta_ticks, 0);
assert_eq!(result.direction, SnapDirection::Exact);
assert!(result.is_exact());
}
#[test]
fn before_direction_when_candidate_precedes_request() {
let result = condition_splice_point(1_000, &[900], 200).unwrap();
assert_eq!(result.snapped_pts, 900);
assert_eq!(result.direction, SnapDirection::Before);
}
#[test]
fn rejects_a_candidate_outside_tolerance() {
let err = condition_splice_point(1_000, &[2_000], 500).unwrap_err();
match err {
Error::NoAlignedBoundary {
requested_pts,
tolerance_ticks,
nearest_delta_ticks,
} => {
assert_eq!(requested_pts, 1_000);
assert_eq!(tolerance_ticks, 500);
assert_eq!(nearest_delta_ticks, 1_000);
}
other => panic!("expected NoAlignedBoundary, got {other:?}"),
}
}
#[test]
fn rejects_empty_candidates() {
let err = condition_splice_point(1_000, &[], 500).unwrap_err();
assert!(matches!(err, Error::NoCandidates));
}
#[test]
fn wrapping_snaps_across_the_33_bit_boundary() {
let cue = PTS_MODULUS_33 - 100;
assert!(matches!(
condition_splice_point(cue, &[50], 1_000),
Err(Error::NoAlignedBoundary { .. })
));
let r = condition_splice_point_wrapping(cue, &[50], 1_000, PTS_MODULUS_33).unwrap();
assert_eq!(r.snapped_pts, 50);
assert_eq!(r.delta_ticks, 150);
assert_eq!(r.direction, SnapDirection::After);
}
#[test]
fn wrapping_direction_before_across_the_wrap() {
let r = condition_splice_point_wrapping(50, &[PTS_MODULUS_33 - 100], 1_000, PTS_MODULUS_33)
.unwrap();
assert_eq!(r.delta_ticks, 150);
assert_eq!(r.direction, SnapDirection::Before);
}
#[test]
fn wrapping_picks_nearest_among_both_sides_of_the_wrap() {
let cue = PTS_MODULUS_33 - 100;
let r = condition_splice_point_wrapping(
cue,
&[PTS_MODULUS_33 - 500, 90, 4_000],
10_000,
PTS_MODULUS_33,
)
.unwrap();
assert_eq!(r.snapped_pts, 90);
assert_eq!(r.delta_ticks, 190);
let r = condition_splice_point_wrapping(
cue,
&[PTS_MODULUS_33 - 150, 90],
10_000,
PTS_MODULUS_33,
)
.unwrap();
assert_eq!(r.snapped_pts, PTS_MODULUS_33 - 150);
assert_eq!(r.delta_ticks, 50);
assert_eq!(r.direction, SnapDirection::Before);
}
#[test]
fn wrapping_exact_and_tolerance_and_tie() {
let r = condition_splice_point_wrapping(7, &[7], 0, PTS_MODULUS_33).unwrap();
assert_eq!(r.direction, SnapDirection::Exact);
assert!(r.is_exact());
let err = condition_splice_point_wrapping(PTS_MODULUS_33 - 100, &[50], 149, PTS_MODULUS_33)
.unwrap_err();
assert!(matches!(
err,
Error::NoAlignedBoundary {
nearest_delta_ticks: 150,
tolerance_ticks: 149,
..
}
));
let r = condition_splice_point_wrapping(0, &[5], 5, 10).unwrap();
assert_eq!((r.delta_ticks, r.direction), (5, SnapDirection::After));
}
#[test]
fn wrapping_rejects_out_of_range_inputs_and_empty_sets() {
assert!(matches!(
condition_splice_point_wrapping(PTS_MODULUS_33, &[1], 10, PTS_MODULUS_33),
Err(Error::PtsOutOfRange { .. })
));
assert!(matches!(
condition_splice_point_wrapping(1, &[PTS_MODULUS_33], 10, PTS_MODULUS_33),
Err(Error::PtsOutOfRange { .. })
));
assert!(matches!(
condition_splice_point_wrapping(0, &[0], 10, 0),
Err(Error::PtsOutOfRange { .. })
));
assert!(matches!(
condition_splice_point_wrapping(1, &[], 10, PTS_MODULUS_33),
Err(Error::NoCandidates)
));
let r = condition_splice_point_wrapping(u64::MAX - 2, &[1], u64::MAX, u64::MAX).unwrap();
assert_eq!(r.delta_ticks, 3);
}
#[test]
fn an_equidistant_tie_prefers_after_regardless_of_order() {
for candidates in [[900u64, 1_100], [1_100, 900]] {
let r = condition_splice_point(1_000, &candidates, 200).unwrap();
assert_eq!((r.snapped_pts, r.direction), (1_100, SnapDirection::After));
}
for candidates in [[900u64, 1_100], [1_100, 900]] {
let r =
condition_splice_point_wrapping(1_000, &candidates, 200, PTS_MODULUS_33).unwrap();
assert_eq!((r.snapped_pts, r.direction), (1_100, SnapDirection::After));
}
for candidates in [[PTS_MODULUS_33 - 100, 100], [100, PTS_MODULUS_33 - 100]] {
let r = condition_splice_point_wrapping(0, &candidates, 200, PTS_MODULUS_33).unwrap();
assert_eq!((r.snapped_pts, r.direction), (100, SnapDirection::After));
}
}
#[test]
fn direction_labels() {
assert_eq!(SnapDirection::Exact.name(), "exact");
assert_eq!(alloc::format!("{}", SnapDirection::After), "after");
}
}