use sha2::{Digest, Sha256};
use sim_kernel::{ContentId, Symbol};
use crate::{InstructionPolicy, LocatedCode};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AdmissionLimits {
pub instructions: usize,
pub operand_units: usize,
pub slots: usize,
pub frames: usize,
pub work: usize,
}
impl AdmissionLimits {
fn encode(self, digest: &mut Sha256) {
for value in [
self.instructions,
self.operand_units,
self.slots,
self.frames,
self.work,
] {
digest.update(value.to_le_bytes());
}
}
}
pub struct MachineDescription<'a, P: InstructionPolicy, M> {
code: &'a LocatedCode<P>,
limits: AdmissionLimits,
metadata: &'a M,
}
impl<'a, P: InstructionPolicy, M> MachineDescription<'a, P, M> {
pub fn new(code: &'a LocatedCode<P>, limits: AdmissionLimits, metadata: &'a M) -> Self {
Self {
code,
limits,
metadata,
}
}
pub fn code(&self) -> &LocatedCode<P> {
self.code
}
pub fn limits(&self) -> AdmissionLimits {
self.limits
}
pub fn metadata(&self) -> &M {
self.metadata
}
}
pub trait AdmissionPolicy<P: InstructionPolicy, M> {
type Refusal;
fn validate_description(
description: &MachineDescription<'_, P, M>,
) -> Result<(), Self::Refusal>;
fn validate_instruction(
instruction: &P::Instruction,
metadata: &M,
) -> Result<(), Self::Refusal>;
fn encode_metadata(metadata: &M, output: &mut Vec<u8>);
fn encode_instruction(instruction: &P::Instruction, output: &mut Vec<u8>);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AdmissionError<R> {
ZeroLimit {
limit: &'static str,
},
InstructionLimit {
actual: usize,
limit: usize,
},
Policy(R),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MachinePermit {
content_id: ContentId,
}
impl MachinePermit {
pub fn admit<P, M, A>(
description: &MachineDescription<'_, P, M>,
) -> Result<Self, AdmissionError<A::Refusal>>
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
{
validate_limits(description)?;
A::validate_description(description).map_err(AdmissionError::Policy)?;
for located in description.code.instructions() {
A::validate_instruction(located.instruction(), description.metadata)
.map_err(AdmissionError::Policy)?;
}
Ok(Self {
content_id: content_id::<P, M, A>(description),
})
}
pub fn content_id(&self) -> &ContentId {
&self.content_id
}
pub fn accepts<P, M, A>(&self, description: &MachineDescription<'_, P, M>) -> bool
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
{
self.content_id == content_id::<P, M, A>(description)
}
}
fn validate_limits<P: InstructionPolicy, M, R>(
description: &MachineDescription<'_, P, M>,
) -> Result<(), AdmissionError<R>> {
for (limit, value) in [
("instructions", description.limits.instructions),
("operand_units", description.limits.operand_units),
("slots", description.limits.slots),
("frames", description.limits.frames),
("work", description.limits.work),
] {
if value == 0 {
return Err(AdmissionError::ZeroLimit { limit });
}
}
if description.code.len() > description.limits.instructions {
return Err(AdmissionError::InstructionLimit {
actual: description.code.len(),
limit: description.limits.instructions,
});
}
Ok(())
}
fn content_id<P, M, A>(description: &MachineDescription<'_, P, M>) -> ContentId
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
{
let mut digest = Sha256::new();
digest.update(b"sim-lib-machine/admission/v1\0");
description.limits.encode(&mut digest);
let mut encoded = Vec::new();
A::encode_metadata(description.metadata, &mut encoded);
hash_field(&mut digest, &encoded);
description
.code
.hash_structure(&mut digest, |instruction, output| {
A::encode_instruction(instruction, output);
});
ContentId::from_bytes(
Symbol::qualified("core", "sha256"),
digest.finalize().into(),
)
}
fn hash_field(digest: &mut Sha256, bytes: &[u8]) {
digest.update(bytes.len().to_le_bytes());
digest.update(bytes);
}