Make codec shape authority compile-time and fixed
This commit is contained in:
parent
bfe21dfb3f
commit
54cc167f81
21 changed files with 322 additions and 52 deletions
|
|
@ -300,7 +300,7 @@ impl ControlClient {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns encoding, size, or stream failures.
|
/// Returns encoding, size, or stream failures.
|
||||||
pub fn send<T: serde::Serialize>(
|
pub fn send<T: lazaret_core::records::SchemaType + serde::Serialize>(
|
||||||
stream: &mut impl Write,
|
stream: &mut impl Write,
|
||||||
record: &T,
|
record: &T,
|
||||||
) -> Result<(), ControlClientError> {
|
) -> Result<(), ControlClientError> {
|
||||||
|
|
@ -327,7 +327,7 @@ impl ControlClient {
|
||||||
/// Returns framing, size, decoding, or stream failures.
|
/// Returns framing, size, decoding, or stream failures.
|
||||||
pub fn receive<T>(stream: &mut impl Read) -> Result<T, ControlClientError>
|
pub fn receive<T>(stream: &mut impl Read) -> Result<T, ControlClientError>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + serde::Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
{
|
{
|
||||||
let mut length = [0_u8; 4];
|
let mut length = [0_u8; 4];
|
||||||
stream.read_exact(&mut length)?;
|
stream.read_exact(&mut length)?;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ use std::fmt;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use crate::records::SchemaType;
|
||||||
|
|
||||||
pub const MAX_SCHEMA_RECORD_BYTES: usize = 1024 * 1024;
|
pub const MAX_SCHEMA_RECORD_BYTES: usize = 1024 * 1024;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -45,7 +47,7 @@ impl std::error::Error for CodecError {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error when serialization fails or the encoded value exceeds the generic limit.
|
/// Returns an error when serialization fails or the encoded value exceeds the generic limit.
|
||||||
pub fn encode<T: Serialize>(record: &T) -> Result<Vec<u8>, CodecError> {
|
pub fn encode<T: SchemaType + Serialize>(record: &T) -> Result<Vec<u8>, CodecError> {
|
||||||
let encoded = postcard::to_stdvec(record).map_err(CodecError::MalformedPayload)?;
|
let encoded = postcard::to_stdvec(record).map_err(CodecError::MalformedPayload)?;
|
||||||
if encoded.len() > MAX_SCHEMA_RECORD_BYTES {
|
if encoded.len() > MAX_SCHEMA_RECORD_BYTES {
|
||||||
return Err(CodecError::RecordTooLarge {
|
return Err(CodecError::RecordTooLarge {
|
||||||
|
|
@ -63,7 +65,7 @@ pub fn encode<T: Serialize>(record: &T) -> Result<Vec<u8>, CodecError> {
|
||||||
/// Returns an error for oversized, malformed, trailing, or non-minimal Postcard input.
|
/// Returns an error for oversized, malformed, trailing, or non-minimal Postcard input.
|
||||||
pub fn decode<T>(encoded: &[u8]) -> Result<T, CodecError>
|
pub fn decode<T>(encoded: &[u8]) -> Result<T, CodecError>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + Serialize,
|
T: DeserializeOwned + SchemaType + Serialize,
|
||||||
{
|
{
|
||||||
if encoded.len() > MAX_SCHEMA_RECORD_BYTES {
|
if encoded.len() > MAX_SCHEMA_RECORD_BYTES {
|
||||||
return Err(CodecError::RecordTooLarge {
|
return Err(CodecError::RecordTooLarge {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,20 @@ use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
use crate::codec::{CodecError, encode};
|
use crate::codec::{CodecError, encode};
|
||||||
use crate::identity::{ClientId, RequestId, SessionId};
|
use crate::identity::{ClientId, RequestId, SessionId};
|
||||||
use crate::records::GenerationIdentity;
|
use crate::records::{GenerationIdentity, SchemaType};
|
||||||
|
use serde::Serialize;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct ClientEnrollmentBindingV1<'a> {
|
||||||
|
generation_identity: &'a GenerationIdentity,
|
||||||
|
cli_id: ClientId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaType for ClientEnrollmentBindingV1<'_> {
|
||||||
|
const NAME: &'static str = "ClientEnrollmentBindingV1";
|
||||||
|
}
|
||||||
|
|
||||||
fn identifier_hex(bytes: &[u8]) -> String {
|
fn identifier_hex(bytes: &[u8]) -> String {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
|
|
@ -22,7 +33,10 @@ pub fn client_enrollment_binding(
|
||||||
generation_identity: &GenerationIdentity,
|
generation_identity: &GenerationIdentity,
|
||||||
cli_id: ClientId,
|
cli_id: ClientId,
|
||||||
) -> Result<String, CodecError> {
|
) -> Result<String, CodecError> {
|
||||||
let encoded = encode(&(generation_identity, cli_id))?;
|
let encoded = encode(&ClientEnrollmentBindingV1 {
|
||||||
|
generation_identity,
|
||||||
|
cli_id,
|
||||||
|
})?;
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"lazaret-client-{}",
|
"lazaret-client-{}",
|
||||||
identifier_hex(&Sha256::digest(encoded))
|
identifier_hex(&Sha256::digest(encoded))
|
||||||
|
|
@ -83,3 +97,27 @@ pub fn result_chunk_file_name(request_id: RequestId, sequence: u64) -> OsString
|
||||||
pub fn is_ready_record(name: &OsStr) -> bool {
|
pub fn is_ready_record(name: &OsStr) -> bool {
|
||||||
name.as_encoded_bytes().ends_with(b".ready")
|
name.as_encoded_bytes().ends_with(b".ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::identity::{
|
||||||
|
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, SandboxId,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_enrollment_binding_vector_is_stable() {
|
||||||
|
let identity = GenerationIdentity {
|
||||||
|
agent_profile: "trusted".into(),
|
||||||
|
domain_id: DomainId::new([1; 32]),
|
||||||
|
generation_id: GenerationId::new(2),
|
||||||
|
configuration_fingerprint: ConfigurationFingerprint::new([4; 32]),
|
||||||
|
coordinator_id: CoordinatorId::new([3; 32]),
|
||||||
|
sandbox_id: SandboxId::new([5; 32]),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
client_enrollment_binding(&identity, ClientId::new([6; 32])).unwrap(),
|
||||||
|
"lazaret-client-63c1e3cd11c3acb917053499be99acc57b3e67fdbb332dbf6d03a7b1540d478d"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::codec;
|
use crate::codec;
|
||||||
use crate::identity::{ClientId, ConfigurationFingerprint, DomainId, SessionAnchorId, SessionId};
|
use crate::identity::{ClientId, ConfigurationFingerprint, DomainId, SessionAnchorId, SessionId};
|
||||||
use crate::records::ProtocolVersion;
|
use crate::records::{ProtocolVersion, SchemaType};
|
||||||
|
|
||||||
pub const BOOTSTRAP_WAITER_SOCKET_NAME: &str = "wait.sock";
|
pub const BOOTSTRAP_WAITER_SOCKET_NAME: &str = "wait.sock";
|
||||||
pub const PRIVATE_CLIENT_BOOTSTRAP_ROLE: &str = "--lazaret-private-client-bootstrap-v1";
|
pub const PRIVATE_CLIENT_BOOTSTRAP_ROLE: &str = "--lazaret-private-client-bootstrap-v1";
|
||||||
|
|
@ -44,7 +44,7 @@ impl std::error::Error for BootstrapWaiterFrameError {}
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Refuses oversized or unencodable records and stream write failures.
|
/// Refuses oversized or unencodable records and stream write failures.
|
||||||
pub fn write_waiter_frame<T: Serialize>(
|
pub fn write_waiter_frame<T: SchemaType + Serialize>(
|
||||||
stream: &mut impl Write,
|
stream: &mut impl Write,
|
||||||
record: &T,
|
record: &T,
|
||||||
) -> Result<(), BootstrapWaiterFrameError> {
|
) -> Result<(), BootstrapWaiterFrameError> {
|
||||||
|
|
@ -64,7 +64,7 @@ pub fn write_waiter_frame<T: Serialize>(
|
||||||
/// Refuses oversized, malformed, or incomplete frames and stream read failures.
|
/// Refuses oversized, malformed, or incomplete frames and stream read failures.
|
||||||
pub fn read_waiter_frame<T>(stream: &mut impl Read) -> Result<T, BootstrapWaiterFrameError>
|
pub fn read_waiter_frame<T>(stream: &mut impl Read) -> Result<T, BootstrapWaiterFrameError>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + Serialize,
|
T: SchemaType + serde::de::DeserializeOwned + Serialize,
|
||||||
{
|
{
|
||||||
let mut length = [0_u8; 4];
|
let mut length = [0_u8; 4];
|
||||||
stream.read_exact(&mut length).map_err(failure)?;
|
stream.read_exact(&mut length).map_err(failure)?;
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,30 @@ use super::{
|
||||||
AddressFamily, AddressFamilyDisposition, ArtifactLease, ArtifactState, ClientEnrollmentRequest,
|
AddressFamily, AddressFamilyDisposition, ArtifactLease, ArtifactState, ClientEnrollmentRequest,
|
||||||
ClientEnrollmentResponse, ConfigurationFingerprintRecordV1, ControlRequest, ControlResponse,
|
ClientEnrollmentResponse, ConfigurationFingerprintRecordV1, ControlRequest, ControlResponse,
|
||||||
DefaultAction, DeniedDestinationObservation, DerivedNetworkPolicy, DeveloperDecision,
|
DefaultAction, DeniedDestinationObservation, DerivedNetworkPolicy, DeveloperDecision,
|
||||||
DeveloperDecisionKind, DnsObservation, Domain, DomainStore, EnvironmentReference,
|
DeveloperDecisionKind, DnsObservation, Domain, DomainStore, DomainStoreMetadata,
|
||||||
EscalationOutcome, Generation, GenerationIdentity, GitRoutingOwnership, GuestProcessIdentity,
|
EnvironmentReference, EscalationOutcome, Generation, GenerationIdentity, GitRoutingOwnership,
|
||||||
HelperOwnership, HostProcessIdentity, HostProcessOwnershipSlot, InteractiveSessionBatch,
|
GuestProcessIdentity, HelperOwnership, HostProcessIdentity, HostProcessOwnershipSlot,
|
||||||
InteractiveSessionEvent, InteractiveSessionMessage, InteractiveSessionRequest, Mailbox,
|
InteractiveSessionBatch, InteractiveSessionEvent, InteractiveSessionMessage,
|
||||||
MailboxCancellation, MailboxRequest, MailboxResult, MailboxResultAck, MailboxResultChunk,
|
InteractiveSessionRequest, Mailbox, MailboxCancellation, MailboxRequest, MailboxResult,
|
||||||
MountIdentity, NamespaceIdentity, NetworkObservation, NetworkOwnership,
|
MailboxResultAck, MailboxResultChunk, MountIdentity, NamespaceIdentity, NetworkObservation,
|
||||||
NetworkReachabilityProbe, NetworkReadinessAttestation, NetworkReadinessEvidence,
|
NetworkOwnership, NetworkReachabilityProbe, NetworkReadinessAttestation,
|
||||||
NetworkReadinessIdentity, NonInteractiveOutput, NonInteractiveSessionMessage,
|
NetworkReadinessEvidence, NetworkReadinessIdentity, NonInteractiveOutput,
|
||||||
NonInteractiveSessionRequest, Participant, ParticipantOwnership, ParticipantState,
|
NonInteractiveSessionMessage, NonInteractiveSessionRequest, Participant, ParticipantOwnership,
|
||||||
PermittedDestinationObservation, PersistedHostProcessOwnership, PidfsProcessAuthorityV1,
|
ParticipantState, PermittedDestinationObservation, PersistedHostProcessOwnership,
|
||||||
PidfsProcessIdentityV1, PolicyAction, PolicyDelivery, PrefixRule, PrepareOperation,
|
PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyAction, PolicyDelivery, PrefixRule,
|
||||||
PrepareStageState, PreparedArtifact, PreparedLowerOwnership, ProcessOwnershipCommitAck,
|
PrepareOperation, PrepareStageState, PreparedArtifact, PreparedLowerOwnership,
|
||||||
ProcessOwnershipMessageVersion, ProtocolCredential, ProtocolCredentialRole,
|
ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion, ProtocolCredential,
|
||||||
ProtocolPublicationIdentity, PtyIdentity, PublicationOwnership, ResolvedAutoApprovalRule,
|
ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity, PublicationOwnership,
|
||||||
ResolvedNetworkReadinessV1, ResolvedVolume, Resolver, ResultStream, SandboxCredentialBundle,
|
ResolvedAutoApprovalRule, ResolvedNetworkReadinessV1, ResolvedVolume, Resolver, ResultStream,
|
||||||
SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit, SessionExitOutcome,
|
SandboxCredentialBundle, SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit,
|
||||||
SessionRegistrationRequest, SessionRegistrationResponse, SessionRequestCapability,
|
SessionExitOutcome, SessionRegistrationRequest, SessionRegistrationResponse,
|
||||||
SessionTerminal, SessionTerminalEvent, SessionTerminalMessage, SessionTerminalState,
|
SessionRequestCapability, SessionTerminal, SessionTerminalEvent, SessionTerminalMessage,
|
||||||
SignedProtocolRecord, StagedProcessOwnership, StateAnchorOwnership, StateAnchorReleaseEvidence,
|
SessionTerminalState, SignedProtocolRecord, StagedProcessOwnership, StateAnchorOwnership,
|
||||||
StorageCatalog, StoreMetadata, TerminalInput, TerminalOutput, TerminalPrompt, TerminalResize,
|
StateAnchorReleaseEvidence, StorageCatalog, StorageCatalogMetadata, StoreMetadata,
|
||||||
TerminalSignal, TerminalSignalKind, TlsCredential, TlsRole, TrustManifest,
|
TerminalInput, TerminalOutput, TerminalPrompt, TerminalResize, TerminalSignal,
|
||||||
TrustedHostConfigurationV1,
|
TerminalSignalKind, TlsCredential, TlsRole, TrustManifest, TrustedHostConfigurationV1,
|
||||||
};
|
};
|
||||||
|
use crate::protocol::{BootstrapWaitOutcome, BootstrapWaitRequest};
|
||||||
|
|
||||||
pub trait SchemaType {
|
pub trait SchemaType {
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
|
|
@ -44,6 +45,8 @@ macro_rules! schema_types {
|
||||||
|
|
||||||
schema_types!(
|
schema_types!(
|
||||||
"StoreMetadata" => StoreMetadata,
|
"StoreMetadata" => StoreMetadata,
|
||||||
|
"DomainStoreMetadata" => DomainStoreMetadata,
|
||||||
|
"StorageCatalogMetadata" => StorageCatalogMetadata,
|
||||||
"Domain" => Domain,
|
"Domain" => Domain,
|
||||||
"Generation" => Generation,
|
"Generation" => Generation,
|
||||||
"Participant" => Participant,
|
"Participant" => Participant,
|
||||||
|
|
@ -146,4 +149,6 @@ schema_types!(
|
||||||
"DomainStore" => DomainStore,
|
"DomainStore" => DomainStore,
|
||||||
"StorageCatalog" => StorageCatalog,
|
"StorageCatalog" => StorageCatalog,
|
||||||
"PrepareStageState" => PrepareStageState,
|
"PrepareStageState" => PrepareStageState,
|
||||||
|
"BootstrapWaitRequest" => BootstrapWaitRequest,
|
||||||
|
"BootstrapWaitOutcome" => BootstrapWaitOutcome,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,39 @@ use lazaret_core::identity::{
|
||||||
SessionAnchorId, SessionId, SourceIdentity, StreamDigest, TrustManifestDigest,
|
SessionAnchorId, SessionId, SourceIdentity, StreamDigest, TrustManifestDigest,
|
||||||
};
|
};
|
||||||
use lazaret_core::network::readiness_fingerprint as derive_readiness_fingerprint;
|
use lazaret_core::network::readiness_fingerprint as derive_readiness_fingerprint;
|
||||||
|
use lazaret_core::protocol::{BootstrapWaitOutcome, BootstrapWaitRequest};
|
||||||
use lazaret_core::records::*;
|
use lazaret_core::records::*;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SchemaCorpus {
|
||||||
|
names: HashSet<&'static str>,
|
||||||
|
digest: Sha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaCorpus {
|
||||||
|
fn insert(&mut self, name: &'static str, encoded: &[u8]) -> bool {
|
||||||
|
self.digest.update(name.len().to_be_bytes());
|
||||||
|
self.digest.update(name.as_bytes());
|
||||||
|
self.digest.update(encoded.len().to_be_bytes());
|
||||||
|
self.digest.update(encoded);
|
||||||
|
self.names.insert(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.names.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, name: &&str) -> bool {
|
||||||
|
self.names.contains(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn digest(&self) -> [u8; 32] {
|
||||||
|
self.digest.clone().finalize().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
struct RawParticipant {
|
struct RawParticipant {
|
||||||
|
|
@ -168,6 +198,39 @@ struct RawTrustedHostConfigurationV1 {
|
||||||
profiles: BTreeMap<String, ProfileConfigurationV1>,
|
profiles: BTreeMap<String, ProfileConfigurationV1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct OversizedRecord(Vec<u8>);
|
||||||
|
|
||||||
|
macro_rules! test_schema_types {
|
||||||
|
($($record:ty),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
impl SchemaType for $record {
|
||||||
|
const NAME: &'static str = concat!("test::", stringify!($record));
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test_schema_types!(
|
||||||
|
RawParticipant,
|
||||||
|
RawTlsCredential,
|
||||||
|
RawProtocolCredential,
|
||||||
|
RawSandboxCredentialBundle,
|
||||||
|
RawMetadata,
|
||||||
|
MissingVersion,
|
||||||
|
RawDomainStore,
|
||||||
|
RawStorageCatalog,
|
||||||
|
RawTerminalResize,
|
||||||
|
RawSessionTerminal,
|
||||||
|
InvalidSessionExit,
|
||||||
|
RawDerivedNetworkPolicy,
|
||||||
|
RawNetworkReadinessAttestation,
|
||||||
|
RawNetworkReadinessIdentity,
|
||||||
|
RawMailboxRequest,
|
||||||
|
RawTrustedHostConfigurationV1,
|
||||||
|
OversizedRecord,
|
||||||
|
);
|
||||||
|
|
||||||
fn opaque(byte: u8) -> [u8; 32] {
|
fn opaque(byte: u8) -> [u8; 32] {
|
||||||
[byte; 32]
|
[byte; 32]
|
||||||
}
|
}
|
||||||
|
|
@ -604,12 +667,16 @@ fn participant_edge_snapshot(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_shape<T>(record: T, seen: &mut HashSet<&'static str>)
|
fn verify_shape<T>(record: T, seen: &mut SchemaCorpus)
|
||||||
where
|
where
|
||||||
T: Clone + Debug + DeserializeOwned + Eq + SchemaType + Serialize,
|
T: Clone + Debug + DeserializeOwned + Eq + SchemaType + Serialize,
|
||||||
{
|
{
|
||||||
assert!(seen.insert(T::NAME), "duplicate test for {}", T::NAME);
|
|
||||||
let encoded = encode(&record).expect("representative record encodes");
|
let encoded = encode(&record).expect("representative record encodes");
|
||||||
|
assert!(
|
||||||
|
seen.insert(T::NAME, &encoded),
|
||||||
|
"duplicate test for {}",
|
||||||
|
T::NAME
|
||||||
|
);
|
||||||
assert_eq!(encode(&record).unwrap(), encoded);
|
assert_eq!(encode(&record).unwrap(), encoded);
|
||||||
assert_eq!(decode::<T>(&encoded).unwrap(), record);
|
assert_eq!(decode::<T>(&encoded).unwrap(), record);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,11 +223,11 @@ fn zero_terminal_dimensions_are_rejected() {
|
||||||
fn generic_schema_record_limit_is_enforced_before_decode() {
|
fn generic_schema_record_limit_is_enforced_before_decode() {
|
||||||
let oversized = vec![0; MAX_SCHEMA_RECORD_BYTES + 1];
|
let oversized = vec![0; MAX_SCHEMA_RECORD_BYTES + 1];
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
decode::<Vec<u8>>(&oversized),
|
decode::<StoreMetadata>(&oversized),
|
||||||
Err(CodecError::RecordTooLarge { .. })
|
Err(CodecError::RecordTooLarge { .. })
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
encode(&oversized),
|
encode(&OversizedRecord(oversized)),
|
||||||
Err(CodecError::RecordTooLarge { .. })
|
Err(CodecError::RecordTooLarge { .. })
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,11 @@ fn every_schema_shape_round_trips_and_rejects_malformed_or_unknown_versions() {
|
||||||
inode: InodeId::new(47),
|
inode: InodeId::new(47),
|
||||||
state: ArtifactState::Available,
|
state: ArtifactState::Available,
|
||||||
};
|
};
|
||||||
let mut seen = HashSet::new();
|
let mut seen = SchemaCorpus::default();
|
||||||
|
|
||||||
verify_shape(metadata.clone(), &mut seen);
|
verify_shape(metadata.clone(), &mut seen);
|
||||||
|
verify_shape(DomainStoreMetadata::v1(), &mut seen);
|
||||||
|
verify_shape(StorageCatalogMetadata::v1(), &mut seen);
|
||||||
verify_shape(domain.clone(), &mut seen);
|
verify_shape(domain.clone(), &mut seen);
|
||||||
verify_shape(generation.clone(), &mut seen);
|
verify_shape(generation.clone(), &mut seen);
|
||||||
verify_shape(participant.clone(), &mut seen);
|
verify_shape(participant.clone(), &mut seen);
|
||||||
|
|
@ -669,6 +671,18 @@ image = "example.invalid/agent:v1"
|
||||||
},
|
},
|
||||||
&mut seen,
|
&mut seen,
|
||||||
);
|
);
|
||||||
|
verify_shape(
|
||||||
|
BootstrapWaitRequest {
|
||||||
|
protocol_version: ProtocolVersion::V1,
|
||||||
|
domain_id: domain_id(),
|
||||||
|
configuration_fingerprint: ConfigurationFingerprint::new(opaque(6)),
|
||||||
|
cli_id: ClientId::new(opaque(13)),
|
||||||
|
session_id: session_id(),
|
||||||
|
session_anchor_id: SessionAnchorId::new(opaque(38)),
|
||||||
|
},
|
||||||
|
&mut seen,
|
||||||
|
);
|
||||||
|
verify_shape(BootstrapWaitOutcome::Published, &mut seen);
|
||||||
|
|
||||||
assert_eq!(seen.len(), SERIALIZED_RECORD_TYPE_NAMES.len());
|
assert_eq!(seen.len(), SERIALIZED_RECORD_TYPE_NAMES.len());
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -676,11 +690,18 @@ image = "example.invalid/agent:v1"
|
||||||
.iter()
|
.iter()
|
||||||
.all(|name| seen.contains(name))
|
.all(|name| seen.contains(name))
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
seen.digest(),
|
||||||
|
[
|
||||||
|
255, 220, 6, 56, 49, 12, 147, 182, 148, 61, 218, 228, 124, 211, 136, 128, 186, 186,
|
||||||
|
138, 239, 246, 197, 75, 0, 75, 85, 27, 244, 21, 21, 246, 41,
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_unknown_protocol_version<T>(record: &T)
|
fn assert_unknown_protocol_version<T>(record: &T)
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + Serialize,
|
T: DeserializeOwned + SchemaType + Serialize,
|
||||||
{
|
{
|
||||||
let mut encoded = encode(record).unwrap();
|
let mut encoded = encode(record).unwrap();
|
||||||
encoded[0] = u8::MAX;
|
encoded[0] = u8::MAX;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use lazaret_core::identity::{GenerationId, SessionId};
|
||||||
pub use lazaret_core::records::StateResetScopeV1;
|
pub use lazaret_core::records::StateResetScopeV1;
|
||||||
use lazaret_core::records::{
|
use lazaret_core::records::{
|
||||||
Domain, DomainStore, DomainStoreMetadata, DurableDecision, Generation, Mailbox, Participant,
|
Domain, DomainStore, DomainStoreMetadata, DurableDecision, Generation, Mailbox, Participant,
|
||||||
ParticipantRole, ParticipantState, RecordTransitionError, Session, SessionPhase,
|
ParticipantRole, ParticipantState, RecordTransitionError, SchemaType, Session, SessionPhase,
|
||||||
SessionTerminal, SessionTerminalState,
|
SessionTerminal, SessionTerminalState,
|
||||||
};
|
};
|
||||||
use lazaret_linux::filesystem::ExclusiveFileLock;
|
use lazaret_linux::filesystem::ExclusiveFileLock;
|
||||||
|
|
@ -40,6 +40,10 @@ pub struct StateResetIntentV1 {
|
||||||
scope: StateResetScopeV1,
|
scope: StateResetScopeV1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SchemaType for StateResetIntentV1 {
|
||||||
|
const NAME: &'static str = "StateResetIntentV1";
|
||||||
|
}
|
||||||
|
|
||||||
impl StateResetIntentV1 {
|
impl StateResetIntentV1 {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(domain_id: lazaret_core::identity::DomainId, scope: StateResetScopeV1) -> Self {
|
pub fn new(domain_id: lazaret_core::identity::DomainId, scope: StateResetScopeV1) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,18 @@ fn reset_intent_is_typed_transactional_and_domain_bound() {
|
||||||
domain().domain_id,
|
domain().domain_id,
|
||||||
StateResetScopeV1::Selected(b"/usr".to_vec()),
|
StateResetScopeV1::Selected(b"/usr".to_vec()),
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
encode(&intent).unwrap(),
|
||||||
|
[
|
||||||
|
&[
|
||||||
|
22, 108, 97, 122, 97, 114, 101, 116, 45, 115, 116, 97, 116, 101, 45, 114, 101, 115,
|
||||||
|
101, 116, 45, 118, 49,
|
||||||
|
][..],
|
||||||
|
&[1; 32],
|
||||||
|
&[1, 4, 47, 117, 115, 114],
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(store.state_reset_intent().unwrap(), None);
|
assert_eq!(store.state_reset_intent().unwrap(), None);
|
||||||
store.begin_state_reset(&intent).unwrap();
|
store.begin_state_reset(&intent).unwrap();
|
||||||
|
|
@ -457,6 +469,10 @@ fn metadata_and_typed_row_validation_fail_closed() {
|
||||||
schema_version: u32,
|
schema_version: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SchemaType for RawMetadata<'_> {
|
||||||
|
const NAME: &'static str = "test::RawMetadata";
|
||||||
|
}
|
||||||
|
|
||||||
let directory = TestDirectory::new("metadata");
|
let directory = TestDirectory::new("metadata");
|
||||||
drop(open_store(&directory));
|
drop(open_store(&directory));
|
||||||
let database = Database::open(directory.path("domain.redb")).unwrap();
|
let database = Database::open(directory.path("domain.redb")).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ impl Drop for CancellationSignals {
|
||||||
|
|
||||||
fn read_record<T>(path: &std::path::Path) -> Result<T, Box<dyn std::error::Error>>
|
fn read_record<T>(path: &std::path::Path) -> Result<T, Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + serde::Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
{
|
{
|
||||||
let mut file = std::fs::File::from(rustix::fs::openat(
|
let mut file = std::fs::File::from(rustix::fs::openat(
|
||||||
rustix::fs::CWD,
|
rustix::fs::CWD,
|
||||||
|
|
|
||||||
|
|
@ -499,7 +499,7 @@ impl PublishedControlFace {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns encoding or TLS I/O failures.
|
/// Returns encoding or TLS I/O failures.
|
||||||
pub fn send<Request: serde::Serialize>(
|
pub fn send<Request: lazaret_core::records::SchemaType + serde::Serialize>(
|
||||||
stream: &mut impl Write,
|
stream: &mut impl Write,
|
||||||
request: &Request,
|
request: &Request,
|
||||||
) -> Result<(), ControlError> {
|
) -> Result<(), ControlError> {
|
||||||
|
|
@ -510,7 +510,11 @@ impl PublishedControlFace {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns framing, size, decoding, or TLS I/O failures.
|
/// Returns framing, size, decoding, or TLS I/O failures.
|
||||||
pub fn receive<Response: serde::de::DeserializeOwned + serde::Serialize>(
|
pub fn receive<
|
||||||
|
Response: lazaret_core::records::SchemaType
|
||||||
|
+ serde::de::DeserializeOwned
|
||||||
|
+ serde::Serialize,
|
||||||
|
>(
|
||||||
stream: &mut impl Read,
|
stream: &mut impl Read,
|
||||||
) -> Result<Response, ControlError> {
|
) -> Result<Response, ControlError> {
|
||||||
ControlClient::receive(stream).map_err(ControlError::from)
|
ControlClient::receive(stream).map_err(ControlError::from)
|
||||||
|
|
@ -699,7 +703,11 @@ fn is_live_session(phase: SessionPhase) -> bool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_frame<Record: serde::de::DeserializeOwned + serde::Serialize>(
|
fn read_frame<
|
||||||
|
Record: lazaret_core::records::SchemaType
|
||||||
|
+ serde::de::DeserializeOwned
|
||||||
|
+ serde::Serialize,
|
||||||
|
>(
|
||||||
reader: &mut impl Read,
|
reader: &mut impl Read,
|
||||||
maximum: usize,
|
maximum: usize,
|
||||||
) -> Result<Record, ControlError> {
|
) -> Result<Record, ControlError> {
|
||||||
|
|
@ -714,7 +722,7 @@ fn read_frame<Record: serde::de::DeserializeOwned + serde::Serialize>(
|
||||||
decode(&bytes).map_err(Into::into)
|
decode(&bytes).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_frame<Record: serde::Serialize>(
|
fn write_frame<Record: lazaret_core::records::SchemaType + serde::Serialize>(
|
||||||
writer: &mut impl Write,
|
writer: &mut impl Write,
|
||||||
record: &Record,
|
record: &Record,
|
||||||
) -> Result<(), ControlError> {
|
) -> Result<(), ControlError> {
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ fn send_outcome(
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Refuses oversized or unencodable records and stream write failures.
|
/// Refuses oversized or unencodable records and stream write failures.
|
||||||
pub fn write_frame<T: Serialize>(
|
pub fn write_frame<T: lazaret_core::records::SchemaType + Serialize>(
|
||||||
stream: &mut UnixStream,
|
stream: &mut UnixStream,
|
||||||
record: &T,
|
record: &T,
|
||||||
) -> Result<(), BootstrapWaiterError> {
|
) -> Result<(), BootstrapWaiterError> {
|
||||||
|
|
@ -388,7 +388,7 @@ pub fn write_frame<T: Serialize>(
|
||||||
/// Refuses oversized, malformed, or incomplete frames and stream read failures.
|
/// Refuses oversized, malformed, or incomplete frames and stream read failures.
|
||||||
pub fn read_frame<T>(stream: &mut UnixStream) -> Result<T, BootstrapWaiterError>
|
pub fn read_frame<T>(stream: &mut UnixStream) -> Result<T, BootstrapWaiterError>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + Serialize,
|
||||||
{
|
{
|
||||||
read_waiter_frame(stream).map_err(failure)
|
read_waiter_frame(stream).map_err(failure)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,10 @@ pub fn private_stream_pair() -> std::io::Result<(OwnedFd, OwnedFd)> {
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns an error when a typed private packet is oversized or cannot be sent atomically.
|
/// Returns an error when a typed private packet is oversized or cannot be sent atomically.
|
||||||
pub fn send_packet<T: serde::Serialize>(descriptor: impl AsFd, message: &T) -> io::Result<()> {
|
pub fn send_packet<T: lazaret_core::records::SchemaType + serde::Serialize>(
|
||||||
|
descriptor: impl AsFd,
|
||||||
|
message: &T,
|
||||||
|
) -> io::Result<()> {
|
||||||
let encoded = encode(message).map_err(|error| io::Error::other(error.to_string()))?;
|
let encoded = encode(message).map_err(|error| io::Error::other(error.to_string()))?;
|
||||||
if encoded.is_empty() || encoded.len() > MAX_PRIVATE_PACKET {
|
if encoded.is_empty() || encoded.len() > MAX_PRIVATE_PACKET {
|
||||||
return Err(io::Error::other("private packet has invalid size"));
|
return Err(io::Error::other("private packet has invalid size"));
|
||||||
|
|
@ -55,7 +58,7 @@ pub fn send_packet<T: serde::Serialize>(descriptor: impl AsFd, message: &T) -> i
|
||||||
/// Returns an error when a private packet is empty, oversized, malformed, or unknown-version.
|
/// Returns an error when a private packet is empty, oversized, malformed, or unknown-version.
|
||||||
pub fn receive_packet<T>(descriptor: impl AsFd) -> io::Result<T>
|
pub fn receive_packet<T>(descriptor: impl AsFd) -> io::Result<T>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + serde::Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
{
|
{
|
||||||
let mut encoded = vec![0_u8; MAX_PRIVATE_PACKET + 1];
|
let mut encoded = vec![0_u8; MAX_PRIVATE_PACKET + 1];
|
||||||
let (_, received) = recv(descriptor.as_fd(), &mut encoded, RecvFlags::empty())?;
|
let (_, received) = recv(descriptor.as_fd(), &mut encoded, RecvFlags::empty())?;
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ pub struct NetworkProbeRequest {
|
||||||
pub dns: Vec<DnsProbeTarget>,
|
pub dns: Vec<DnsProbeTarget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl lazaret_core::records::SchemaType for NetworkProbeRequest {
|
||||||
|
const NAME: &'static str = "NetworkProbeRequest";
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct NetworkAttestationContext {
|
pub struct NetworkAttestationContext {
|
||||||
pub domain_id: DomainId,
|
pub domain_id: DomainId,
|
||||||
|
|
@ -663,9 +667,42 @@ mod tests {
|
||||||
DerivedNetworkPolicyFields, PolicyAction, PrefixRule, Resolver,
|
DerivedNetworkPolicyFields, PolicyAction, PrefixRule, Resolver,
|
||||||
};
|
};
|
||||||
use lazaret_linux::filesystem::sealed_memfd;
|
use lazaret_linux::filesystem::sealed_memfd;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn network_probe_request_vector_is_stable() {
|
||||||
|
let request = NetworkProbeRequest {
|
||||||
|
domain_id: DomainId::new([1; 32]),
|
||||||
|
generation_id: GenerationId::new(2),
|
||||||
|
enforcement_namespace: NamespaceIdentity {
|
||||||
|
device: lazaret_core::identity::DeviceId::new(3),
|
||||||
|
inode: lazaret_core::identity::InodeId::new(4),
|
||||||
|
},
|
||||||
|
denied: vec![TcpProbeTarget {
|
||||||
|
address: IpAddress::V4([10, 0, 0, 1]),
|
||||||
|
port: 9,
|
||||||
|
}],
|
||||||
|
permitted: vec![TcpProbeTarget {
|
||||||
|
address: IpAddress::V4([1, 1, 1, 1]),
|
||||||
|
port: 443,
|
||||||
|
}],
|
||||||
|
dns: vec![DnsProbeTarget {
|
||||||
|
resolver: IpAddress::V4([8, 8, 8, 8]),
|
||||||
|
query_name: "example.invalid".into(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let encoded = lazaret_core::codec::encode(&request).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
Sha256::digest(encoded).as_slice(),
|
||||||
|
[
|
||||||
|
190, 221, 207, 18, 23, 85, 102, 200, 111, 129, 14, 45, 225, 55, 146, 147, 223, 162,
|
||||||
|
27, 106, 204, 136, 214, 57, 134, 235, 241, 78, 215, 193, 84, 233,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sealed_policy_requires_exact_canonical_digest() {
|
fn sealed_policy_requires_exact_canonical_digest() {
|
||||||
let policy = policy();
|
let policy = policy();
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,10 @@ pub enum AttachedRunscSignal {
|
||||||
Kill,
|
Kill,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl lazaret_core::records::SchemaType for AttachedRunscSignal {
|
||||||
|
const NAME: &'static str = "AttachedRunscSignal";
|
||||||
|
}
|
||||||
|
|
||||||
impl AttachedRunscSignal {
|
impl AttachedRunscSignal {
|
||||||
const fn signal(self) -> Signal {
|
const fn signal(self) -> Signal {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -112,6 +116,10 @@ pub enum SupervisorEvent {
|
||||||
Refused(String),
|
Refused(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl lazaret_core::records::SchemaType for SupervisorEvent {
|
||||||
|
const NAME: &'static str = "SupervisorEvent";
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
enum CoordinatorMessage {
|
enum CoordinatorMessage {
|
||||||
MountStateAnchor,
|
MountStateAnchor,
|
||||||
|
|
@ -138,6 +146,10 @@ enum CoordinatorMessage {
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl lazaret_core::records::SchemaType for CoordinatorMessage {
|
||||||
|
const NAME: &'static str = "CoordinatorMessage";
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CoordinatorChannel {
|
pub struct CoordinatorChannel {
|
||||||
descriptor: OwnedFd,
|
descriptor: OwnedFd,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ enum RuntimeSupervisorHandoff {
|
||||||
V1(RuntimeSupervisorHandoffV1),
|
V1(RuntimeSupervisorHandoffV1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl lazaret_core::records::SchemaType for RuntimeSupervisorHandoff {
|
||||||
|
const NAME: &'static str = "RuntimeSupervisorHandoff";
|
||||||
|
}
|
||||||
|
|
||||||
fn descriptor(
|
fn descriptor(
|
||||||
number: i32,
|
number: i32,
|
||||||
kind: RuntimeDescriptorKindV1,
|
kind: RuntimeDescriptorKindV1,
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,10 @@ fn lifetime_lost() -> io::Result<bool> {
|
||||||
.intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR))
|
.intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_message<T: Serialize>(descriptor: impl AsFd, message: &T) -> io::Result<()> {
|
fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
|
||||||
|
descriptor: impl AsFd,
|
||||||
|
message: &T,
|
||||||
|
) -> io::Result<()> {
|
||||||
let encoded = encode(message).map_err(|error| io::Error::other(error.to_string()))?;
|
let encoded = encode(message).map_err(|error| io::Error::other(error.to_string()))?;
|
||||||
if encoded.len() > MAX_CONTROL_MESSAGE {
|
if encoded.len() > MAX_CONTROL_MESSAGE {
|
||||||
return Err(io::Error::other("runtime control message is oversized"));
|
return Err(io::Error::other("runtime control message is oversized"));
|
||||||
|
|
@ -557,7 +560,7 @@ fn send_message<T: Serialize>(descriptor: impl AsFd, message: &T) -> io::Result<
|
||||||
|
|
||||||
fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
|
fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + Serialize,
|
||||||
{
|
{
|
||||||
let mut length = [0_u8; 4];
|
let mut length = [0_u8; 4];
|
||||||
receive_exact(descriptor.as_fd(), &mut length)?;
|
receive_exact(descriptor.as_fd(), &mut length)?;
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,51 @@ mod tests {
|
||||||
use lazaret_linux::ipc::private_seqpacket_pair;
|
use lazaret_linux::ipc::private_seqpacket_pair;
|
||||||
use lazaret_linux::process::ChildStandardIoPolicy;
|
use lazaret_linux::process::ChildStandardIoPolicy;
|
||||||
use rustix::pipe::{PipeFlags, pipe_with};
|
use rustix::pipe::{PipeFlags, pipe_with};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn private_control_record_vectors_are_stable() {
|
||||||
|
let namespace = NamespaceIdentity {
|
||||||
|
device: lazaret_core::identity::DeviceId::new(1),
|
||||||
|
inode: lazaret_core::identity::InodeId::new(2),
|
||||||
|
};
|
||||||
|
let mount = MountIdentity {
|
||||||
|
mount_id: lazaret_core::identity::MountId::new(3),
|
||||||
|
device: lazaret_core::identity::DeviceId::new(4),
|
||||||
|
inode: lazaret_core::identity::InodeId::new(5),
|
||||||
|
};
|
||||||
|
let records = [
|
||||||
|
encode(&AttachedRunscSignal::Continue).unwrap(),
|
||||||
|
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
|
||||||
|
user_namespace: namespace,
|
||||||
|
mount_namespace: namespace,
|
||||||
|
mount,
|
||||||
|
observed: mount,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||||
|
user_namespace: namespace,
|
||||||
|
mount_namespace: namespace,
|
||||||
|
mount,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
];
|
||||||
|
let mut corpus = Sha256::new();
|
||||||
|
for record in records {
|
||||||
|
corpus.update(record.len().to_be_bytes());
|
||||||
|
corpus.update(record);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
corpus.finalize().as_slice(),
|
||||||
|
[
|
||||||
|
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250,
|
||||||
|
164, 101, 22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn private_stream_framing_round_trips_typed_events() {
|
fn private_stream_framing_round_trips_typed_events() {
|
||||||
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -863,13 +863,15 @@ fn ensure_absent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, CatalogError> {
|
fn encode<T: lazaret_core::records::SchemaType + serde::Serialize>(
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Vec<u8>, CatalogError> {
|
||||||
codec::encode(value).map_err(CatalogError::Codec)
|
codec::encode(value).map_err(CatalogError::Codec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_row<T>(encoded: &[u8]) -> Result<T, CatalogError>
|
fn decode_row<T>(encoded: &[u8]) -> Result<T, CatalogError>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + serde::Serialize,
|
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
{
|
{
|
||||||
codec::decode(encoded).map_err(CatalogError::Codec)
|
codec::decode(encoded).map_err(CatalogError::Codec)
|
||||||
}
|
}
|
||||||
|
|
@ -889,7 +891,9 @@ fn lease_key(lease: &ArtifactLease) -> [u8; 40] {
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
trait CatalogRecord: Eq + serde::de::DeserializeOwned + serde::Serialize {
|
trait CatalogRecord:
|
||||||
|
Eq + lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize
|
||||||
|
{
|
||||||
const ROW: CatalogRow;
|
const ROW: CatalogRow;
|
||||||
|
|
||||||
fn catalog_key(&self) -> Vec<u8>;
|
fn catalog_key(&self) -> Vec<u8>;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue