mirror of
https://github.com/telemt/telemt.git
synced 2026-09-12 21:44:08 +03:00
Native carrier negotiation + attempt deadline mapping fixed
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -11,7 +11,7 @@ pub(crate) enum CarrierClientClass {
|
||||
Bridge,
|
||||
/// Strict same-origin browser metadata survived while the marker did not.
|
||||
BrowserHint,
|
||||
/// A native iOS client that supports only the serialized HTTPS carrier.
|
||||
/// A native iOS client classified for diagnostics and learning only.
|
||||
Ios,
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl CarrierFailure {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fixed carrier capability set sent by the generated bridge.
|
||||
/// Validated carrier capability set sent by a negotiation-capable client.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct CarrierCapabilities(u8);
|
||||
|
||||
@@ -77,11 +77,6 @@ impl CarrierCapabilities {
|
||||
Self(0b1111)
|
||||
}
|
||||
|
||||
/// Returns the only carrier implemented by the native iOS client.
|
||||
pub(crate) const fn ios() -> Self {
|
||||
Self(0b0001)
|
||||
}
|
||||
|
||||
/// Builds a set from a validated bit representation.
|
||||
pub(crate) const fn from_bits(bits: u8) -> Option<Self> {
|
||||
if bits != 0 && bits & !0b1111 == 0 {
|
||||
@@ -105,7 +100,6 @@ pub(crate) struct CarrierRequest {
|
||||
attempt: Option<u8>,
|
||||
failure: Option<CarrierFailure>,
|
||||
user_agent_hash: [u8; 32],
|
||||
initial_only: bool,
|
||||
}
|
||||
|
||||
impl CarrierRequest {
|
||||
@@ -117,19 +111,17 @@ impl CarrierRequest {
|
||||
attempt: None,
|
||||
failure: None,
|
||||
user_agent_hash,
|
||||
initial_only: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a known fixed-capability client without retry negotiation.
|
||||
/// Constructs a metadata-free native client without inferring capabilities.
|
||||
pub(crate) const fn ios(user_agent_hash: [u8; 32]) -> Self {
|
||||
Self {
|
||||
class: CarrierClientClass::Ios,
|
||||
capabilities: Some(CarrierCapabilities::ios()),
|
||||
capabilities: None,
|
||||
attempt: None,
|
||||
failure: None,
|
||||
user_agent_hash,
|
||||
initial_only: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +139,12 @@ impl CarrierRequest {
|
||||
attempt: Some(attempt),
|
||||
failure,
|
||||
user_agent_hash,
|
||||
initial_only: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this request participates in server-side negotiation.
|
||||
pub(crate) const fn is_automatic(self) -> bool {
|
||||
self.capabilities.is_some() && !self.initial_only
|
||||
self.capabilities.is_some()
|
||||
}
|
||||
|
||||
/// Returns whether server capability filtering applies to this request.
|
||||
@@ -194,7 +185,6 @@ impl CarrierRequest {
|
||||
self.class == other.class
|
||||
&& self.capabilities_bits() == other.capabilities_bits()
|
||||
&& self.user_agent_hash == other.user_agent_hash
|
||||
&& self.initial_only == other.initial_only
|
||||
}
|
||||
|
||||
/// Checks the complete idempotent identity of one exact attempt request.
|
||||
@@ -209,6 +199,21 @@ impl CarrierRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cumulative deadline slot assigned to one carrier attempt.
|
||||
pub(super) const fn carrier_attempt_deadline_index(
|
||||
candidate_count: u8,
|
||||
attempt: u8,
|
||||
) -> Option<usize> {
|
||||
if candidate_count == 0 || candidate_count > 4 || attempt == 0 || attempt > candidate_count {
|
||||
return None;
|
||||
}
|
||||
if attempt == candidate_count {
|
||||
Some(3)
|
||||
} else {
|
||||
Some((attempt - 1) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Secret-independent evidence owner frozen into an automatic session.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct CarrierLearningContext {
|
||||
@@ -225,3 +230,29 @@ pub(crate) struct CarrierLearningContext {
|
||||
/// Whether the authoritative client address is safe to use as learning evidence.
|
||||
pub(crate) ip_learning_eligible: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::carrier_attempt_deadline_index;
|
||||
|
||||
#[test]
|
||||
fn final_candidate_uses_the_final_cumulative_deadline_slot() {
|
||||
assert_eq!(carrier_attempt_deadline_index(1, 1), Some(3));
|
||||
assert_eq!(carrier_attempt_deadline_index(2, 1), Some(0));
|
||||
assert_eq!(carrier_attempt_deadline_index(2, 2), Some(3));
|
||||
assert_eq!(carrier_attempt_deadline_index(3, 1), Some(0));
|
||||
assert_eq!(carrier_attempt_deadline_index(3, 2), Some(1));
|
||||
assert_eq!(carrier_attempt_deadline_index(3, 3), Some(3));
|
||||
assert_eq!(carrier_attempt_deadline_index(4, 1), Some(0));
|
||||
assert_eq!(carrier_attempt_deadline_index(4, 2), Some(1));
|
||||
assert_eq!(carrier_attempt_deadline_index(4, 3), Some(2));
|
||||
assert_eq!(carrier_attempt_deadline_index(4, 4), Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_candidate_or_attempt_counts_have_no_deadline_slot() {
|
||||
for (candidate_count, attempt) in [(0, 1), (5, 1), (1, 0), (1, 2), (3, 4)] {
|
||||
assert_eq!(carrier_attempt_deadline_index(candidate_count, attempt), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use super::state::{
|
||||
CarrierChainPhase, decrement_map, matching_profile, new_unique_token, profile_key,
|
||||
remember_closed_token_locked, remove_expired_locked,
|
||||
};
|
||||
use super::negotiation::carrier_attempt_deadline_index;
|
||||
use super::session_admission::admit_initial;
|
||||
use super::{
|
||||
CarrierLearningContext, CarrierRequest, CreateResult, ManagerError, TokenHash, WebProcessRuntime,
|
||||
@@ -154,7 +155,10 @@ impl WebProcessRuntime {
|
||||
else {
|
||||
return Err(ManagerError::Protocol);
|
||||
};
|
||||
let deadline_index = usize::from(next_attempt.saturating_sub(2));
|
||||
let candidate_count = u8::try_from(entry.carrier_candidates.len())
|
||||
.map_err(|_| ManagerError::Protocol)?;
|
||||
let deadline_index = carrier_attempt_deadline_index(candidate_count, next_attempt)
|
||||
.ok_or(ManagerError::Protocol)?;
|
||||
if entry.carrier_started_at.is_some_and(|started| {
|
||||
now.saturating_duration_since(started)
|
||||
>= Duration::from_secs(
|
||||
|
||||
Reference in New Issue
Block a user