Make external codec boundaries explicit and exhaustive
This commit is contained in:
parent
76de24c75c
commit
d7de87fb8f
6 changed files with 319 additions and 25 deletions
|
|
@ -33,6 +33,8 @@ const STATE_RESET: TableDefinition<u8, &[u8]> = TableDefinition::new("state_rese
|
||||||
const SINGLETON_KEY: u8 = 0;
|
const SINGLETON_KEY: u8 = 0;
|
||||||
const STATE_RESET_SCHEMA_V1: &str = "lazaret-state-reset-v1";
|
const STATE_RESET_SCHEMA_V1: &str = "lazaret-state-reset-v1";
|
||||||
|
|
||||||
|
pub const DOMAIN_STATE_SERIALIZED_RECORD_TYPE_NAMES: &[&str] = &["StateResetIntentV1"];
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub struct StateResetIntentV1 {
|
pub struct StateResetIntentV1 {
|
||||||
schema: String,
|
schema: String,
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,10 @@ 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!(
|
||||||
|
DOMAIN_STATE_SERIALIZED_RECORD_TYPE_NAMES,
|
||||||
|
&[<StateResetIntentV1 as SchemaType>::NAME]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
encode(&intent).unwrap(),
|
encode(&intent).unwrap(),
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -673,6 +673,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn network_probe_request_vector_is_stable() {
|
fn network_probe_request_vector_is_stable() {
|
||||||
|
assert!(
|
||||||
|
crate::supervision::RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES
|
||||||
|
.contains(&<NetworkProbeRequest as lazaret_core::records::SchemaType>::NAME)
|
||||||
|
);
|
||||||
let request = NetworkProbeRequest {
|
let request = NetworkProbeRequest {
|
||||||
domain_id: DomainId::new([1; 32]),
|
domain_id: DomainId::new([1; 32]),
|
||||||
generation_id: GenerationId::new(2),
|
generation_id: GenerationId::new(2),
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,14 @@ const MAX_RUNTIME_HANDOFF_BYTES: usize = 64 * 1024;
|
||||||
const READINESS_TIMEOUT: Duration = Duration::from_secs(30);
|
const READINESS_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
const TERMINATION_GRACE: Duration = Duration::from_secs(2);
|
const TERMINATION_GRACE: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
|
pub const RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES: &[&str] = &[
|
||||||
|
"RuntimeSupervisorHandoff",
|
||||||
|
"NetworkProbeRequest",
|
||||||
|
"AttachedRunscSignal",
|
||||||
|
"SupervisorEvent",
|
||||||
|
"CoordinatorMessage",
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub enum AttachedRunscSignal {
|
pub enum AttachedRunscSignal {
|
||||||
Stop,
|
Stop,
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,10 @@ mod handoff_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v1_vector_and_non_utf8_runsc_arguments_are_stable() {
|
fn v1_vector_and_non_utf8_runsc_arguments_are_stable() {
|
||||||
|
assert!(
|
||||||
|
crate::supervision::RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES
|
||||||
|
.contains(&<RuntimeSupervisorHandoff as lazaret_core::records::SchemaType>::NAME)
|
||||||
|
);
|
||||||
let directory = |number, identity| RuntimeDescriptorV1 {
|
let directory = |number, identity| RuntimeDescriptorV1 {
|
||||||
number,
|
number,
|
||||||
device: identity,
|
device: identity,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,241 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
|
use lazaret_core::identity::{
|
||||||
use lazaret_core::records::HostProcessOwnershipSlot;
|
BootId, ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, DomainId, GenerationId,
|
||||||
|
HostPidLocator, NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1,
|
||||||
|
PidfsProcessIdentityV1, ProcessStartTimeTicks,
|
||||||
|
};
|
||||||
|
use lazaret_core::records::{
|
||||||
|
HostProcessOwnershipSlot, NetworkObservation, NetworkReachabilityProbe,
|
||||||
|
NetworkReadinessAttestation, NetworkReadinessAttestationFields, PolicyDelivery,
|
||||||
|
ProcessOwnershipCommitAck, SchemaType, StagedProcessOwnership,
|
||||||
|
};
|
||||||
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 sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
use crate::network::NetworkProbeRequest;
|
||||||
|
|
||||||
use super::runtime::{attached_runsc_standard_io, runsc_delete_target, runsc_target};
|
use super::runtime::{attached_runsc_standard_io, runsc_delete_target, runsc_target};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn process() -> PidfsProcessIdentityV1 {
|
||||||
|
PidfsProcessIdentityV1::new(
|
||||||
|
PidfsProcessAuthorityV1 {
|
||||||
|
boot_id: BootId::new([6; 16]),
|
||||||
|
pidfs_st_dev: PidfsDeviceId::new(7),
|
||||||
|
pidfs_st_ino: PidfsInodeId::new(8),
|
||||||
|
},
|
||||||
|
HostPidLocator::new(9),
|
||||||
|
ProcessStartTimeTicks::new(10),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn staged() -> StagedProcessOwnership {
|
||||||
|
StagedProcessOwnership::v1(
|
||||||
|
DomainId::new([1; 32]),
|
||||||
|
GenerationId::new(2),
|
||||||
|
CoordinatorId::new([3; 32]),
|
||||||
|
HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||||
|
process(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_tag(event: &SupervisorEvent) -> &'static str {
|
||||||
|
match event {
|
||||||
|
SupervisorEvent::StateAnchorStaged(_) => "StateAnchorStaged",
|
||||||
|
SupervisorEvent::StateAnchorReady { .. } => "StateAnchorReady",
|
||||||
|
SupervisorEvent::GitRoutingStaged(_) => "GitRoutingStaged",
|
||||||
|
SupervisorEvent::GitRoutingReady { .. } => "GitRoutingReady",
|
||||||
|
SupervisorEvent::SidecarStarted => "SidecarStarted",
|
||||||
|
SupervisorEvent::HelperStaged(_) => "HelperStaged",
|
||||||
|
SupervisorEvent::HelperReady(_) => "HelperReady",
|
||||||
|
SupervisorEvent::NetworkAttestation(_) => "NetworkAttestation",
|
||||||
|
SupervisorEvent::NetworkProbe(_) => "NetworkProbe",
|
||||||
|
SupervisorEvent::HelperExited => "HelperExited",
|
||||||
|
SupervisorEvent::Staged(_) => "Staged",
|
||||||
|
SupervisorEvent::Ready { .. } => "Ready",
|
||||||
|
SupervisorEvent::SignalApplied(_) => "SignalApplied",
|
||||||
|
SupervisorEvent::SandboxStopped => "SandboxStopped",
|
||||||
|
SupervisorEvent::GitRoutingStopped { .. } => "GitRoutingStopped",
|
||||||
|
SupervisorEvent::SidecarStopped => "SidecarStopped",
|
||||||
|
SupervisorEvent::StateAnchorUnmounted { .. } => "StateAnchorUnmounted",
|
||||||
|
SupervisorEvent::StateAnchorAbsenceObserved { .. } => "StateAnchorAbsenceObserved",
|
||||||
|
SupervisorEvent::Exited => "Exited",
|
||||||
|
SupervisorEvent::AttachedRunscExited => "AttachedRunscExited",
|
||||||
|
SupervisorEvent::Stopped => "Stopped",
|
||||||
|
SupervisorEvent::Refused(_) => "Refused",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_tag(message: &CoordinatorMessage) -> &'static str {
|
||||||
|
match message {
|
||||||
|
CoordinatorMessage::MountStateAnchor => "MountStateAnchor",
|
||||||
|
CoordinatorMessage::StateAnchorStagedAck(_) => "StateAnchorStagedAck",
|
||||||
|
CoordinatorMessage::StartGitRouting => "StartGitRouting",
|
||||||
|
CoordinatorMessage::GitRoutingStagedAck(_) => "GitRoutingStagedAck",
|
||||||
|
CoordinatorMessage::StartSidecar => "StartSidecar",
|
||||||
|
CoordinatorMessage::HelperStagedAck(_) => "HelperStagedAck",
|
||||||
|
CoordinatorMessage::NetworkAttestationAccepted(_) => "NetworkAttestationAccepted",
|
||||||
|
CoordinatorMessage::NetworkReadinessCommitted => "NetworkReadinessCommitted",
|
||||||
|
CoordinatorMessage::StagedAck(_) => "StagedAck",
|
||||||
|
CoordinatorMessage::SandboxCommitted => "SandboxCommitted",
|
||||||
|
CoordinatorMessage::SignalAttachedRunsc(_) => "SignalAttachedRunsc",
|
||||||
|
CoordinatorMessage::StopSandbox => "StopSandbox",
|
||||||
|
CoordinatorMessage::StopGitRouting => "StopGitRouting",
|
||||||
|
CoordinatorMessage::StopSidecar => "StopSidecar",
|
||||||
|
CoordinatorMessage::UnmountStateAnchor => "UnmountStateAnchor",
|
||||||
|
CoordinatorMessage::ObserveStateAnchorAbsence { .. } => "ObserveStateAnchorAbsence",
|
||||||
|
CoordinatorMessage::Exit => "Exit",
|
||||||
|
CoordinatorMessage::Stop => "Stop",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn representative_events(
|
||||||
|
namespace: NamespaceIdentity,
|
||||||
|
mount: MountIdentity,
|
||||||
|
staged: StagedProcessOwnership,
|
||||||
|
attestation: NetworkReadinessAttestation,
|
||||||
|
probe: NetworkReachabilityProbe,
|
||||||
|
) -> Vec<SupervisorEvent> {
|
||||||
|
vec![
|
||||||
|
SupervisorEvent::StateAnchorStaged(staged),
|
||||||
|
SupervisorEvent::StateAnchorReady {
|
||||||
|
staged,
|
||||||
|
user_namespace: namespace,
|
||||||
|
mount_namespace: namespace,
|
||||||
|
mount,
|
||||||
|
},
|
||||||
|
SupervisorEvent::GitRoutingStaged(staged),
|
||||||
|
SupervisorEvent::GitRoutingReady { staged, mount },
|
||||||
|
SupervisorEvent::SidecarStarted,
|
||||||
|
SupervisorEvent::HelperStaged(staged),
|
||||||
|
SupervisorEvent::HelperReady(staged),
|
||||||
|
SupervisorEvent::NetworkAttestation(attestation),
|
||||||
|
SupervisorEvent::NetworkProbe(probe),
|
||||||
|
SupervisorEvent::HelperExited,
|
||||||
|
SupervisorEvent::Staged(staged),
|
||||||
|
SupervisorEvent::Ready {
|
||||||
|
staged,
|
||||||
|
sentry_pid_inventory: 14,
|
||||||
|
},
|
||||||
|
SupervisorEvent::SignalApplied(AttachedRunscSignal::Continue),
|
||||||
|
SupervisorEvent::SandboxStopped,
|
||||||
|
SupervisorEvent::GitRoutingStopped { mount },
|
||||||
|
SupervisorEvent::SidecarStopped,
|
||||||
|
SupervisorEvent::StateAnchorUnmounted { mount },
|
||||||
|
SupervisorEvent::StateAnchorAbsenceObserved {
|
||||||
|
user_namespace: namespace,
|
||||||
|
mount_namespace: namespace,
|
||||||
|
mount,
|
||||||
|
observed: mount,
|
||||||
|
},
|
||||||
|
SupervisorEvent::Exited,
|
||||||
|
SupervisorEvent::AttachedRunscExited,
|
||||||
|
SupervisorEvent::Stopped,
|
||||||
|
SupervisorEvent::Refused("refused".into()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn representative_messages(
|
||||||
|
namespace: NamespaceIdentity,
|
||||||
|
mount: MountIdentity,
|
||||||
|
acknowledgment: ProcessOwnershipCommitAck,
|
||||||
|
request: NetworkProbeRequest,
|
||||||
|
) -> Vec<CoordinatorMessage> {
|
||||||
|
vec![
|
||||||
|
CoordinatorMessage::MountStateAnchor,
|
||||||
|
CoordinatorMessage::StateAnchorStagedAck(acknowledgment),
|
||||||
|
CoordinatorMessage::StartGitRouting,
|
||||||
|
CoordinatorMessage::GitRoutingStagedAck(acknowledgment),
|
||||||
|
CoordinatorMessage::StartSidecar,
|
||||||
|
CoordinatorMessage::HelperStagedAck(acknowledgment),
|
||||||
|
CoordinatorMessage::NetworkAttestationAccepted(request),
|
||||||
|
CoordinatorMessage::NetworkReadinessCommitted,
|
||||||
|
CoordinatorMessage::StagedAck(acknowledgment),
|
||||||
|
CoordinatorMessage::SandboxCommitted,
|
||||||
|
CoordinatorMessage::SignalAttachedRunsc(AttachedRunscSignal::Kill),
|
||||||
|
CoordinatorMessage::StopSandbox,
|
||||||
|
CoordinatorMessage::StopGitRouting,
|
||||||
|
CoordinatorMessage::StopSidecar,
|
||||||
|
CoordinatorMessage::UnmountStateAnchor,
|
||||||
|
CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||||
|
user_namespace: namespace,
|
||||||
|
mount_namespace: namespace,
|
||||||
|
mount,
|
||||||
|
},
|
||||||
|
CoordinatorMessage::Exit,
|
||||||
|
CoordinatorMessage::Stop,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_event_variant_coverage(events: &[SupervisorEvent]) {
|
||||||
|
assert_eq!(
|
||||||
|
events.iter().map(event_tag).collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
"StateAnchorStaged",
|
||||||
|
"StateAnchorReady",
|
||||||
|
"GitRoutingStaged",
|
||||||
|
"GitRoutingReady",
|
||||||
|
"SidecarStarted",
|
||||||
|
"HelperStaged",
|
||||||
|
"HelperReady",
|
||||||
|
"NetworkAttestation",
|
||||||
|
"NetworkProbe",
|
||||||
|
"HelperExited",
|
||||||
|
"Staged",
|
||||||
|
"Ready",
|
||||||
|
"SignalApplied",
|
||||||
|
"SandboxStopped",
|
||||||
|
"GitRoutingStopped",
|
||||||
|
"SidecarStopped",
|
||||||
|
"StateAnchorUnmounted",
|
||||||
|
"StateAnchorAbsenceObserved",
|
||||||
|
"Exited",
|
||||||
|
"AttachedRunscExited",
|
||||||
|
"Stopped",
|
||||||
|
"Refused",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_message_variant_coverage(messages: &[CoordinatorMessage]) {
|
||||||
|
assert_eq!(
|
||||||
|
messages.iter().map(message_tag).collect::<Vec<_>>(),
|
||||||
|
[
|
||||||
|
"MountStateAnchor",
|
||||||
|
"StateAnchorStagedAck",
|
||||||
|
"StartGitRouting",
|
||||||
|
"GitRoutingStagedAck",
|
||||||
|
"StartSidecar",
|
||||||
|
"HelperStagedAck",
|
||||||
|
"NetworkAttestationAccepted",
|
||||||
|
"NetworkReadinessCommitted",
|
||||||
|
"StagedAck",
|
||||||
|
"SandboxCommitted",
|
||||||
|
"SignalAttachedRunsc",
|
||||||
|
"StopSandbox",
|
||||||
|
"StopGitRouting",
|
||||||
|
"StopSidecar",
|
||||||
|
"UnmountStateAnchor",
|
||||||
|
"ObserveStateAnchorAbsence",
|
||||||
|
"Exit",
|
||||||
|
"Stop",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn corpus_digest<T: SchemaType + serde::Serialize>(records: &[T]) -> [u8; 32] {
|
||||||
|
let mut corpus = Sha256::new();
|
||||||
|
for record in records {
|
||||||
|
let record = encode(record).unwrap();
|
||||||
|
corpus.update(record.len().to_be_bytes());
|
||||||
|
corpus.update(record);
|
||||||
|
}
|
||||||
|
corpus.finalize().into()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn private_control_record_vectors_are_stable() {
|
fn private_control_record_vectors_are_stable() {
|
||||||
let namespace = NamespaceIdentity {
|
let namespace = NamespaceIdentity {
|
||||||
|
|
@ -21,32 +247,78 @@ fn private_control_record_vectors_are_stable() {
|
||||||
device: lazaret_core::identity::DeviceId::new(4),
|
device: lazaret_core::identity::DeviceId::new(4),
|
||||||
inode: lazaret_core::identity::InodeId::new(5),
|
inode: lazaret_core::identity::InodeId::new(5),
|
||||||
};
|
};
|
||||||
let records = [
|
let staged = staged();
|
||||||
encode(&AttachedRunscSignal::Continue).unwrap(),
|
let acknowledgment = ProcessOwnershipCommitAck::v1(staged);
|
||||||
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
|
let attestation = NetworkReadinessAttestation::v1(NetworkReadinessAttestationFields {
|
||||||
user_namespace: namespace,
|
domain_id: DomainId::new([1; 32]),
|
||||||
mount_namespace: namespace,
|
generation_id: GenerationId::new(2),
|
||||||
mount,
|
configuration_fingerprint: ConfigurationFingerprint::new([11; 32]),
|
||||||
observed: mount,
|
network_sidecar_id: NetworkSidecarId::new([12; 32]),
|
||||||
})
|
derived_policy_digest: DerivedPolicyDigest::new([13; 32]),
|
||||||
.unwrap(),
|
enforcement_namespace: namespace,
|
||||||
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
|
runtime_supervisor: process(),
|
||||||
user_namespace: namespace,
|
connectivity_helper: process(),
|
||||||
mount_namespace: namespace,
|
observation: NetworkObservation {
|
||||||
mount,
|
policy_delivery: PolicyDelivery::SealedMemfd,
|
||||||
})
|
helper_ready: true,
|
||||||
.unwrap(),
|
observed_routes: Vec::new(),
|
||||||
|
routes_attested: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let probe = NetworkReachabilityProbe {
|
||||||
|
domain_id: DomainId::new([1; 32]),
|
||||||
|
generation_id: GenerationId::new(2),
|
||||||
|
enforcement_namespace: namespace,
|
||||||
|
denied_destinations: Vec::new(),
|
||||||
|
permitted_destinations: Vec::new(),
|
||||||
|
dns_observations: Vec::new(),
|
||||||
|
};
|
||||||
|
let request = NetworkProbeRequest {
|
||||||
|
domain_id: DomainId::new([1; 32]),
|
||||||
|
generation_id: GenerationId::new(2),
|
||||||
|
enforcement_namespace: namespace,
|
||||||
|
denied: Vec::new(),
|
||||||
|
permitted: Vec::new(),
|
||||||
|
dns: Vec::new(),
|
||||||
|
};
|
||||||
|
let signals = [
|
||||||
|
AttachedRunscSignal::Stop,
|
||||||
|
AttachedRunscSignal::Continue,
|
||||||
|
AttachedRunscSignal::Kill,
|
||||||
];
|
];
|
||||||
let mut corpus = Sha256::new();
|
let events = representative_events(namespace, mount, staged, attestation, probe);
|
||||||
for record in records {
|
let messages = representative_messages(namespace, mount, acknowledgment, request);
|
||||||
corpus.update(record.len().to_be_bytes());
|
assert_eq!(
|
||||||
corpus.update(record);
|
RUNTIME_SUPERVISOR_SERIALIZED_RECORD_TYPE_NAMES,
|
||||||
}
|
&[
|
||||||
|
"RuntimeSupervisorHandoff",
|
||||||
|
<NetworkProbeRequest as SchemaType>::NAME,
|
||||||
|
<AttachedRunscSignal as SchemaType>::NAME,
|
||||||
|
<SupervisorEvent as SchemaType>::NAME,
|
||||||
|
<CoordinatorMessage as SchemaType>::NAME,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_event_variant_coverage(&events);
|
||||||
|
assert_message_variant_coverage(&messages);
|
||||||
assert_eq!(
|
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,
|
corpus_digest(&signals),
|
||||||
22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
|
corpus_digest(&events),
|
||||||
|
corpus_digest(&messages),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
81, 139, 171, 185, 206, 78, 197, 44, 238, 115, 83, 251, 40, 178, 113, 209, 41, 53,
|
||||||
|
251, 52, 93, 195, 13, 90, 91, 33, 97, 9, 204, 249, 127, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
46, 250, 76, 205, 43, 212, 214, 67, 162, 9, 72, 170, 22, 44, 123, 161, 14, 206, 29,
|
||||||
|
244, 137, 251, 60, 194, 96, 96, 92, 216, 50, 97, 244, 47,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
227, 133, 56, 78, 199, 235, 191, 196, 191, 7, 154, 135, 223, 104, 221, 62, 240,
|
||||||
|
253, 202, 161, 22, 254, 133, 59, 22, 126, 218, 254, 81, 114, 240, 120,
|
||||||
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue