Route lifecycle admission through the control registry

This commit is contained in:
ms2k 2026-07-16 16:08:12 +09:00
commit 510dfccf5a

View file

@ -39,6 +39,9 @@ use sha2::{Digest, Sha256};
use time::{Duration, OffsetDateTime};
use crate::domain_store::{DomainStoreError, DomainStoreMutation, HostCoordinatorDomainStore};
use crate::lifecycle::{
HostCoordinatorLifecycle, LifecycleAction, SessionAdmissionIdentity, SessionLifecycleAdapter,
};
pub const CONTROL_ALPN: &[u8] = b"lazaret-control/1";
pub const CONTROL_SERVER_NAME: &str = "coordinator.lazaret.invalid";
@ -387,6 +390,15 @@ pub struct RegisteredSession {
pub session_anchor_id: SessionAnchorId,
}
#[derive(Clone)]
pub struct ControlSessionAdmission {
pub request: SessionRegistrationRequest,
pub credential: TlsCredential,
}
pub type ControlHostCoordinatorLifecycle<'store, A> =
HostCoordinatorLifecycle<'store, A, Arc<SessionRegistry>>;
#[derive(Default)]
struct RegistryState {
enrolled: BTreeMap<CertificateDigest, TlsCredential>,
@ -478,11 +490,26 @@ impl SessionRegistry {
|| registered.binding.cli_id == binding.cli_id
|| registered.session_anchor_id == session_anchor_id
});
let resumable_waiter = snapshot.sessions.iter().any(|session| {
session.generation_id == binding.generation_identity.generation_id
&& session.session_id == binding.session_id
&& session.cli_id == binding.cli_id
&& session.session_anchor_id == session_anchor_id
&& session.session_phase == SessionPhase::Waiting
});
let duplicate_session = snapshot.sessions.iter().any(|session| {
session.session_id == binding.session_id
|| (is_live_session(session.session_phase)
&& (session.cli_id == binding.cli_id
|| session.session_anchor_id == session_anchor_id))
let same_session_allowed = resumable_waiter
&& session.session_id == binding.session_id
&& (session.session_phase == SessionPhase::Closed
|| (session.generation_id == binding.generation_identity.generation_id
&& session.cli_id == binding.cli_id
&& session.session_anchor_id == session_anchor_id
&& session.session_phase == SessionPhase::Waiting));
!same_session_allowed
&& (session.session_id == binding.session_id
|| (is_live_session(session.session_phase)
&& (session.cli_id == binding.cli_id
|| session.session_anchor_id == session_anchor_id)))
});
if duplicate_in_memory || duplicate_session {
return Err(ControlError::DuplicateIdentity);
@ -580,6 +607,69 @@ impl SessionRegistry {
}
}
impl SessionLifecycleAdapter for Arc<SessionRegistry> {
type Error = ControlError;
type Admission = ControlSessionAdmission;
fn admission_identity(&self, admission: &Self::Admission) -> SessionAdmissionIdentity {
let binding = &admission.request.session_binding;
SessionAdmissionIdentity {
generation_id: binding.generation_identity.generation_id,
session_id: binding.session_id,
configuration_fingerprint: binding.generation_identity.configuration_fingerprint,
}
}
fn register_session(
&self,
_store: &HostCoordinatorDomainStore,
entry_action: LifecycleAction,
admission: Self::Admission,
) -> Result<Session, Self::Error> {
if !matches!(
entry_action,
LifecycleAction::StartReadySession | LifecycleAction::AdvanceWaitingSession
) {
return Err(ControlError::InvalidRequest);
}
let registered = self.register(admission.request, &admission.credential)?;
Ok(Session {
generation_id: registered.binding.generation_identity.generation_id,
session_id: registered.binding.session_id,
cli_id: registered.binding.cli_id,
session_anchor_id: registered.session_anchor_id,
session_phase: SessionPhase::Attached,
})
}
fn disconnect_session(
&self,
_store: &HostCoordinatorDomainStore,
session: &Session,
) -> Result<(), Self::Error> {
let mut state = self.lock()?;
let registered = state.connected.get(&session.session_id);
if let Some(registered) = registered
&& (registered.binding.generation_identity.generation_id != session.generation_id
|| registered.binding.cli_id != session.cli_id
|| registered.session_anchor_id != session.session_anchor_id)
{
return Err(ControlError::InvalidRequest);
}
if session.session_phase == SessionPhase::Attached && registered.is_none() {
return Err(ControlError::InvalidRequest);
}
state.connected.remove(&session.session_id);
drop(state);
self.store
.apply(&[DomainStoreMutation::PutSession(Session {
session_phase: SessionPhase::Closed,
..session.clone()
})])?;
Ok(())
}
}
struct ControlPaths {
root: PathBuf,
root_file: File,