liminal-server 0.24.5

Standalone server for the liminal messaging bus
Documentation
//! A seat retired by its own Leave re-enrols with a new token and attaches with the new
//! credential (Cambium CORE-049 and CORE-051, Apollo, 18 Oct 18:28): the attach is answered,
//! bound or refused by name, never a bare close.

use std::error::Error;

use liminal_protocol::wire::{
    AttachAttemptToken, ClientRequest, CredentialAttachRequest, DetachAttemptToken, DetachRequest,
    EnrollmentRequest, EnrollmentToken, Generation, LeaveAttemptToken, LeaveRequest,
    RecordAdmission, RecordAdmissionAttemptToken, ServerValue,
};

use super::e2e_cold_all_shapes_fixture::{ColdMember, expect_enrolled};
use super::e2e_tests::SocketFixture;

const CONVERSATION: u64 = 777;

#[test]
fn a_seat_retired_by_its_own_leave_re_enrols_and_its_attach_on_a_new_connection_is_answered()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x71; 16]),
        }))?,
        "first identity",
    )?);
    let left = first.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x72; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");

    // The next open: a token the conversation has not seen, so the next identity.
    let mut reopened = first.spawn_peer()?;
    let successor = ColdMember::enrolled(&expect_enrolled(
        reopened.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x73; 16]),
        }))?,
        "successor",
    )?);
    assert_ne!(successor.participant_id, retired.participant_id);

    // It attaches with the new credential on a fresh connection, as the door does.
    let mut attaching = first.spawn_peer()?;
    let attached = attaching.request(ClientRequest::CredentialAttach(CredentialAttachRequest {
        conversation_id: CONVERSATION,
        participant_id: successor.participant_id,
        capability_generation: successor.generation,
        attach_secret: successor.secret,
        attach_attempt_token: AttachAttemptToken::new([0x74; 16]),
        accept_marker_delivery_seq: None,
    }))?;
    let ServerValue::AttachBound(_) = attached else {
        return Err(format!("the successor's attach was not bound: {attached:?}").into());
    };
    Ok(())
}

#[test]
fn a_seat_retired_by_its_own_leave_re_enrols_and_its_attach_on_the_same_connection_is_answered()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x75; 16]),
        }))?,
        "first identity",
    )?);
    let left = first.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x76; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");

    let mut reopened = first.spawn_peer()?;
    let successor = ColdMember::enrolled(&expect_enrolled(
        reopened.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x77; 16]),
        }))?,
        "successor",
    )?);
    // The connection the enrolment bound presents the credential again: answered, not closed.
    let attached = reopened.request(ClientRequest::CredentialAttach(CredentialAttachRequest {
        conversation_id: CONVERSATION,
        participant_id: successor.participant_id,
        capability_generation: successor.generation,
        attach_secret: successor.secret,
        attach_attempt_token: AttachAttemptToken::new([0x78; 16]),
        accept_marker_delivery_seq: None,
    }))?;
    assert!(
        matches!(
            attached,
            ServerValue::AttachBound(_) | ServerValue::ConnectionConversationBindingOccupied(_)
        ),
        "the attach on the enrolling connection was neither bound nor refused by name: {attached:?}"
    );
    Ok(())
}

/// The door's exact shape (cambium-door `substrate_recovery`, Apollo 18 Oct 18:28): the seat
/// detaches, leaves while detached, and its next open is ONE connection that presents the
/// dead credential, re-enrols on the answer, and attaches with the new credential.
#[test]
fn the_doors_next_open_after_a_detached_leave_presents_re_enrols_and_attaches_on_one_connection()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x79; 16]),
        }))?,
        "first identity",
    )?);
    let detached = first.request(ClientRequest::Detach(DetachRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        detach_attempt_token: DetachAttemptToken::new([0x7a; 16]),
    }))?;
    assert!(
        matches!(detached, ServerValue::DetachCommitted(_)),
        "{detached:?}"
    );
    let mut leaving = first.spawn_peer()?;
    let left = leaving.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x7b; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");

    // The next open, on one connection: the dead credential first.
    let mut next = first.spawn_peer()?;
    let dead = next.request(ClientRequest::CredentialAttach(CredentialAttachRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        attach_attempt_token: AttachAttemptToken::new([0x7c; 16]),
        accept_marker_delivery_seq: None,
    }))?;
    assert!(
        !matches!(dead, ServerValue::AttachBound(_)),
        "the retired credential attached: {dead:?}"
    );
    let successor = ColdMember::enrolled(&expect_enrolled(
        next.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x7d; 16]),
        }))?,
        "successor",
    )?);
    assert_ne!(successor.participant_id, retired.participant_id);
    let attached = next
        .request(ClientRequest::CredentialAttach(CredentialAttachRequest {
            conversation_id: CONVERSATION,
            participant_id: successor.participant_id,
            capability_generation: successor.generation,
            attach_secret: successor.secret,
            attach_attempt_token: AttachAttemptToken::new([0x7e; 16]),
            accept_marker_delivery_seq: None,
        }))
        .map_err(|error| format!("the successor's attach was not answered (dead credential answer was {dead:?}): {error}"))?;
    assert!(
        matches!(
            attached,
            ServerValue::AttachBound(_) | ServerValue::ConnectionConversationBindingOccupied(_)
        ),
        "the successor's attach was neither bound nor refused by name: {attached:?}"
    );
    Ok(())
}

/// The door's line on the 205 (cambium-door `substrate_recovery` at 878a9b63): the successor
/// attaches as "participant 1 generation 2" when the enrolment bound generation 1, and the
/// broker closes the connection before a frame. A generation the participant never reached
/// is a refusal by name, never a close.
#[test]
fn an_attach_with_a_generation_the_participant_never_reached_is_refused_by_name()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x81; 16]),
        }))?,
        "first identity",
    )?);
    let left = first.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x82; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");
    let mut next = first.spawn_peer()?;
    let successor = ColdMember::enrolled(&expect_enrolled(
        next.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x83; 16]),
        }))?,
        "successor",
    )?);
    let beyond = Generation::new(successor.generation.get() + 1).ok_or("no next generation")?;
    let answer = next
        .request(ClientRequest::CredentialAttach(CredentialAttachRequest {
            conversation_id: CONVERSATION,
            participant_id: successor.participant_id,
            capability_generation: beyond,
            attach_secret: successor.secret,
            attach_attempt_token: AttachAttemptToken::new([0x84; 16]),
            accept_marker_delivery_seq: None,
        }))
        .map_err(|error| {
            format!(
                "the attach with generation {} was not answered: {error}",
                beyond.get()
            )
        })?;
    assert!(
        !matches!(answer, ServerValue::AttachBound(_)),
        "a generation the participant never reached was bound: {answer:?}"
    );
    // The connection is still answered afterwards: the refusal was an answer, not a close.
    let again = next.request(ClientRequest::Enrollment(EnrollmentRequest {
        conversation_id: CONVERSATION,
        enrollment_token: EnrollmentToken::new([0x83; 16]),
    }))?;
    assert!(!matches!(again, ServerValue::AttachBound(_)), "{again:?}");
    Ok(())
}

/// The other member the door's attach can carry: a marker delivery sequence from the
/// retired identity's last acknowledgement, presented on the successor's first attach. A
/// marker the successor was never delivered is a refusal by name, never a close.
#[test]
fn a_marker_bearing_attach_by_a_successor_never_delivered_a_marker_is_refused_by_name()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x85; 16]),
        }))?,
        "first identity",
    )?);
    let left = first.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x86; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");
    let mut next = first.spawn_peer()?;
    let successor = ColdMember::enrolled(&expect_enrolled(
        next.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x87; 16]),
        }))?,
        "successor",
    )?);
    let answer = next
        .request(ClientRequest::CredentialAttach(CredentialAttachRequest {
            conversation_id: CONVERSATION,
            participant_id: successor.participant_id,
            capability_generation: successor.generation,
            attach_secret: successor.secret,
            attach_attempt_token: AttachAttemptToken::new([0x88; 16]),
            accept_marker_delivery_seq: Some(1),
        }))
        .map_err(|error| format!("the marker-bearing attach was not answered: {error}"))?;
    assert!(
        !matches!(answer, ServerValue::AttachBound(_)),
        "a marker the successor was never delivered was accepted: {answer:?}"
    );
    Ok(())
}

/// The door test's whole sequence (`substrate_recovery`, 878a9b63): the first identity detaches
/// and leaves while detached; the next open re-enrols, attaches (the attach rotates the
/// credential to generation 2), commits one record and closes; the open after that presents
/// the rotated credential, "participant 1 generation 2", and the broker closed before a frame.
#[test]
fn a_re_enrolled_seats_reattach_with_its_rotated_credential_after_a_record_is_answered()
-> Result<(), Box<dyn Error>> {
    let data_dir = tempfile::tempdir()?;
    let mut first = SocketFixture::start(data_dir.path())?;
    let retired = ColdMember::enrolled(&expect_enrolled(
        first.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x89; 16]),
        }))?,
        "first identity",
    )?);
    let detached = first.request(ClientRequest::Detach(DetachRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        detach_attempt_token: DetachAttemptToken::new([0x8a; 16]),
    }))?;
    assert!(
        matches!(detached, ServerValue::DetachCommitted(_)),
        "{detached:?}"
    );
    let mut leaving = first.spawn_peer()?;
    let left = leaving.request(ClientRequest::Leave(LeaveRequest {
        conversation_id: CONVERSATION,
        participant_id: retired.participant_id,
        capability_generation: retired.generation,
        attach_secret: retired.secret,
        leave_attempt_token: LeaveAttemptToken::new([0x8b; 16]),
    }))?;
    assert!(matches!(left, ServerValue::LeaveCommitted(_)), "{left:?}");
    drop(leaving);

    // The next open: enrol, attach (rotating the credential), commit one record, close.
    let mut reopened = first.spawn_peer()?;
    let successor = ColdMember::enrolled(&expect_enrolled(
        reopened.request(ClientRequest::Enrollment(EnrollmentRequest {
            conversation_id: CONVERSATION,
            enrollment_token: EnrollmentToken::new([0x8c; 16]),
        }))?,
        "successor",
    )?);
    let mut attaching = first.spawn_peer()?;
    let attached = attaching.request(ClientRequest::CredentialAttach(CredentialAttachRequest {
        conversation_id: CONVERSATION,
        participant_id: successor.participant_id,
        capability_generation: successor.generation,
        attach_secret: successor.secret,
        attach_attempt_token: AttachAttemptToken::new([0x8d; 16]),
        accept_marker_delivery_seq: None,
    }))?;
    let ServerValue::AttachBound(bound) = attached else {
        return Err(format!("the successor's first attach was not bound: {attached:?}").into());
    };
    let rotated = (bound.capability_generation(), bound.attach_secret());
    let committed = attaching.request(ClientRequest::RecordAdmission(RecordAdmission {
        conversation_id: CONVERSATION,
        participant_id: successor.participant_id,
        capability_generation: rotated.0,
        record_admission_attempt_token: RecordAdmissionAttemptToken::new([0x8e; 16]),
        payload: vec![0xD4],
    }))?;
    assert!(
        matches!(committed, ServerValue::RecordCommitted(_)),
        "{committed:?}"
    );
    drop(attaching);
    drop(reopened);

    // The open after that: the rotated credential on a fresh connection.
    let mut again = first.spawn_peer()?;
    let answer = again
        .request(ClientRequest::CredentialAttach(CredentialAttachRequest {
            conversation_id: CONVERSATION,
            participant_id: successor.participant_id,
            capability_generation: rotated.0,
            attach_secret: rotated.1,
            attach_attempt_token: AttachAttemptToken::new([0x8f; 16]),
            accept_marker_delivery_seq: None,
        }))
        .map_err(|error| {
            format!(
                "the reattach as participant {} generation {} was not answered: {error}",
                successor.participant_id,
                rotated.0.get()
            )
        })?;
    assert!(
        matches!(answer, ServerValue::AttachBound(_)),
        "the reattach with the rotated credential was not bound: {answer:?}"
    );
    Ok(())
}