1extern crate alloc;
118
119use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, sync::Arc, vec::Vec};
120use core::fmt;
121
122use aranya_buggy::bug;
123use aranya_policy_vm::{
124 ActionContext, CommandContext, ExitReason, KVPair, Machine, MachineIO, MachineStack,
125 OpenContext, PolicyContext, RunState, SealContext, Struct, Value,
126};
127use spin::Mutex;
128use tracing::{error, info, instrument};
129
130use crate::{
131 command::{Command, CommandId},
132 engine::{EngineError, NullSink, Policy, Sink},
133 CommandRecall, FactPerspective, MergeIds, Perspective, Prior,
134};
135
136mod error;
137mod io;
138mod protocol;
139pub mod testing;
140
141pub use error::*;
142pub use io::*;
143pub use protocol::*;
144
145#[macro_export]
157macro_rules! vm_action {
158 ($name:ident($($arg:expr),* $(,)?)) => {
159 $crate::VmAction {
160 name: stringify!($name),
161 args: [$(::aranya_policy_vm::Value::from($arg)),*].as_slice().into(),
162 }
163 };
164}
165
166#[macro_export]
180macro_rules! vm_effect {
181 ($name:ident { $($field:ident : $val:expr),* $(,)? }) => {
182 $crate::VmEffectData {
183 name: stringify!($name).into(),
184 fields: vec![$(
185 ::aranya_policy_vm::KVPair::new(stringify!($field), $val.into())
186 ),*],
187 }
188 };
189}
190
191pub struct VmPolicy<E> {
193 machine: Machine,
194 engine: Mutex<E>,
195 ffis: Mutex<Vec<Box<dyn FfiCallable<E> + Send + 'static>>>,
196 priority_map: Arc<BTreeMap<String, u32>>,
198}
199
200impl<E> VmPolicy<E> {
201 pub fn new(
203 machine: Machine,
204 engine: E,
205 ffis: Vec<Box<dyn FfiCallable<E> + Send + 'static>>,
206 ) -> Result<Self, VmPolicyError> {
207 let priority_map = VmPolicy::<E>::get_command_priorities(&machine)?;
208 Ok(Self {
209 machine,
210 engine: Mutex::from(engine),
211 ffis: Mutex::from(ffis),
212 priority_map: Arc::new(priority_map),
213 })
214 }
215
216 fn source_location<M>(&self, rs: &RunState<'_, M>) -> String
217 where
218 M: MachineIO<MachineStack>,
219 {
220 rs.source_location()
221 .unwrap_or(String::from("(unknown location)"))
222 }
223
224 fn get_command_priorities(machine: &Machine) -> Result<BTreeMap<String, u32>, VmPolicyError> {
226 let mut priority_map = BTreeMap::new();
227 for (name, attrs) in &machine.command_attributes {
228 if let Some(Value::Int(p)) = attrs.get("priority") {
229 let pv = (*p).try_into().map_err(|e| {
230 error!(
231 ?e,
232 "Priority out of range in {name}: {p} does not fit in u32"
233 );
234 VmPolicyError::Unknown
235 })?;
236 priority_map.insert(name.clone(), pv);
237 }
238 }
239 Ok(priority_map)
240 }
241}
242
243impl<E: aranya_crypto::Engine> VmPolicy<E> {
244 #[allow(clippy::too_many_arguments)]
245 #[instrument(skip_all, fields(name = name))]
246 fn evaluate_rule<'a, P>(
247 &self,
248 name: &str,
249 fields: &[KVPair],
250 envelope: Envelope<'_>,
251 facts: &'a mut P,
252 sink: &'a mut impl Sink<VmEffect>,
253 ctx: &CommandContext<'_>,
254 recall: CommandRecall,
255 ) -> Result<(), EngineError>
256 where
257 P: FactPerspective,
258 {
259 let mut ffis = self.ffis.lock();
260 let mut eng = self.engine.lock();
261 let mut io = VmPolicyIO::new(facts, sink, &mut *eng, &mut ffis);
262 let mut rs = self.machine.create_run_state(&mut io, ctx);
263 let self_data = Struct::new(name, fields);
264 match rs.call_command_policy(&self_data.name, &self_data, envelope.clone().into()) {
265 Ok(reason) => match reason {
266 ExitReason::Normal => Ok(()),
267 ExitReason::Check => {
268 info!("Check {}", self.source_location(&rs));
269 let CommandContext::Policy(policy_ctx) = ctx else {
271 error!("Non-policy context while evaluating rule: {ctx:?}");
272 return Err(EngineError::InternalError);
273 };
274 let recall_ctx = CommandContext::Recall(policy_ctx.clone());
275 rs.set_context(&recall_ctx);
276 self.recall_internal(recall, &mut rs, name, &self_data, envelope)
277 }
278 ExitReason::Panic => {
279 info!("Panicked {}", self.source_location(&rs));
280 Err(EngineError::Panic)
281 }
282 },
283 Err(e) => {
284 error!("\n{e}");
285 Err(EngineError::InternalError)
286 }
287 }
288 }
289
290 fn recall_internal<M>(
291 &self,
292 recall: CommandRecall,
293 rs: &mut RunState<'_, M>,
294 name: &str,
295 self_data: &Struct,
296 envelope: Envelope<'_>,
297 ) -> Result<(), EngineError>
298 where
299 M: MachineIO<MachineStack>,
300 {
301 match recall {
302 CommandRecall::None => Err(EngineError::Check),
303 CommandRecall::OnCheck => {
304 match rs.call_command_recall(name, self_data, envelope.into()) {
305 Ok(ExitReason::Normal) => Err(EngineError::Check),
306 Ok(ExitReason::Check) => {
307 info!("Recall failed: {}", self.source_location(rs));
308 Err(EngineError::Check)
309 }
310 Ok(ExitReason::Panic) | Err(_) => {
311 info!("Recall panicked: {}", self.source_location(rs));
312 Err(EngineError::Panic)
313 }
314 }
315 }
316 }
317 }
318
319 #[instrument(skip_all, fields(name = name))]
320 fn open_command<P>(
321 &self,
322 name: &str,
323 envelope: Envelope<'_>,
324 facts: &mut P,
325 ) -> Result<Struct, EngineError>
326 where
327 P: FactPerspective,
328 {
329 let mut sink = NullSink;
330 let mut ffis = self.ffis.lock();
331 let mut eng = self.engine.lock();
332 let mut io = VmPolicyIO::new(facts, &mut sink, &mut *eng, &mut ffis);
333 let ctx = CommandContext::Open(OpenContext { name });
334 let mut rs = self.machine.create_run_state(&mut io, &ctx);
335 let status = rs.call_open(name, envelope.into());
336 match status {
337 Ok(reason) => match reason {
338 ExitReason::Normal => {
339 let v = rs.consume_return().map_err(|e| {
340 error!("Could not pull envelope from stack: {e}");
341 EngineError::InternalError
342 })?;
343 Ok(v.try_into().map_err(|e| {
344 error!("Envelope is not a struct: {e}");
345 EngineError::InternalError
346 })?)
347 }
348 ExitReason::Check => {
349 info!("Check {}", self.source_location(&rs));
350 Err(EngineError::Check)
351 }
352 ExitReason::Panic => {
353 info!("Panicked {}", self.source_location(&rs));
354 Err(EngineError::Check)
355 }
356 },
357 Err(e) => {
358 error!("\n{e}");
359 Err(EngineError::InternalError)
360 }
361 }
362 }
363
364 #[instrument(skip_all, fields(name = name))]
365 fn seal_command(
366 &self,
367 name: &str,
368 fields: impl IntoIterator<Item = impl Into<(String, Value)>>,
369 ctx_parent: CommandId,
370 facts: &mut impl FactPerspective,
371 ) -> Result<Envelope<'static>, EngineError> {
372 let mut sink = NullSink;
373 let mut ffis = self.ffis.lock();
374 let mut eng = self.engine.lock();
375 let mut io = VmPolicyIO::new(facts, &mut sink, &mut *eng, &mut ffis);
376 let ctx = CommandContext::Seal(SealContext {
377 name,
378 head_id: ctx_parent.into(),
379 });
380 let mut rs = self.machine.create_run_state(&mut io, &ctx);
381 let command_struct = Struct::new(name, fields);
382 let status = rs.call_seal(name, &command_struct);
383 match status {
384 Ok(reason) => match reason {
385 ExitReason::Normal => {
386 let v = rs.consume_return().map_err(|e| {
387 error!("Could not pull envelope from stack: {e}");
388 EngineError::InternalError
389 })?;
390 let strukt = Struct::try_from(v).map_err(|e| {
391 error!("Envelope is not a struct: {e}");
392 EngineError::InternalError
393 })?;
394 let envelope = Envelope::try_from(strukt).map_err(|e| {
395 error!("Malformed Envelope: {e}");
396 EngineError::InternalError
397 })?;
398 Ok(envelope)
399 }
400 ExitReason::Check => {
401 info!("Check {}", self.source_location(&rs));
402 Err(EngineError::Check)
403 }
404 ExitReason::Panic => {
405 info!("Panicked {}", self.source_location(&rs));
406 Err(EngineError::Panic)
407 }
408 },
409 Err(e) => {
410 error!("\n{e}");
411 Err(EngineError::InternalError)
412 }
413 }
414 }
415}
416
417#[derive(Clone, Debug, PartialEq, Eq)]
419pub struct VmAction<'a> {
420 pub name: &'a str,
422 pub args: Cow<'a, [Value]>,
424}
425
426#[derive(Debug)]
430pub struct VmEffectData {
431 pub name: String,
433 pub fields: Vec<KVPair>,
435}
436
437impl PartialEq<VmEffect> for VmEffectData {
438 fn eq(&self, other: &VmEffect) -> bool {
439 self.name == other.name && self.fields == other.fields
440 }
441}
442
443impl PartialEq<VmEffectData> for VmEffect {
444 fn eq(&self, other: &VmEffectData) -> bool {
445 self.name == other.name && self.fields == other.fields
446 }
447}
448
449#[derive(Clone, Debug, PartialEq, Eq)]
451pub struct VmEffect {
452 pub name: String,
454 pub fields: Vec<KVPair>,
456 pub command: CommandId,
458 pub recalled: bool,
460}
461
462impl<E: aranya_crypto::Engine> Policy for VmPolicy<E> {
463 type Action<'a> = VmAction<'a>;
464 type Effect = VmEffect;
465 type Command<'a> = VmProtocol<'a>;
466
467 fn serial(&self) -> u32 {
468 0u32
470 }
471
472 #[instrument(skip_all)]
473 fn call_rule(
474 &self,
475 command: &impl Command,
476 facts: &mut impl FactPerspective,
477 sink: &mut impl Sink<Self::Effect>,
478 recall: CommandRecall,
479 ) -> Result<(), EngineError> {
480 let unpacked: VmProtocolData<'_> = postcard::from_bytes(command.bytes()).map_err(|e| {
481 error!("Could not deserialize: {e:?}");
482 EngineError::Read
483 })?;
484 match unpacked {
485 VmProtocolData::Init {
486 author_id,
487 kind,
488 serialized_fields,
489 signature,
490 ..
491 } => {
492 let envelope = Envelope {
493 parent_id: CommandId::default(),
494 author_id,
495 command_id: command.id(),
496 payload: Cow::Borrowed(serialized_fields),
497 signature: Cow::Borrowed(signature),
498 };
499 let command_struct = self.open_command(kind, envelope.clone(), facts)?;
500 let fields: Vec<KVPair> = command_struct
501 .fields
502 .into_iter()
503 .map(|(k, v)| KVPair::new(&k, v))
504 .collect();
505 let ctx = CommandContext::Policy(PolicyContext {
506 name: kind,
507 id: command.id().into(),
508 author: author_id,
509 version: CommandId::default().into(),
510 });
511 self.evaluate_rule(kind, fields.as_slice(), envelope, facts, sink, &ctx, recall)?
512 }
513 VmProtocolData::Basic {
514 parent,
515 kind,
516 author_id,
517 serialized_fields,
518 signature,
519 } => {
520 let envelope = Envelope {
521 parent_id: parent.id,
522 author_id,
523 command_id: command.id(),
524 payload: Cow::Borrowed(serialized_fields),
525 signature: Cow::Borrowed(signature),
526 };
527 let command_struct = self.open_command(kind, envelope.clone(), facts)?;
528 let fields: Vec<KVPair> = command_struct
529 .fields
530 .into_iter()
531 .map(|(k, v)| KVPair::new(&k, v))
532 .collect();
533 let ctx = CommandContext::Policy(PolicyContext {
534 name: kind,
535 id: command.id().into(),
536 author: author_id,
537 version: CommandId::default().into(),
538 });
539 self.evaluate_rule(kind, fields.as_slice(), envelope, facts, sink, &ctx, recall)?
540 }
541 _ => (),
543 }
544
545 Ok(())
546 }
547
548 #[instrument(skip_all, fields(name = action.name))]
549 fn call_action(
550 &self,
551 action: Self::Action<'_>,
552 facts: &mut impl Perspective,
553 sink: &mut impl Sink<Self::Effect>,
554 ) -> Result<(), EngineError> {
555 let VmAction { name, args } = action;
556
557 let parent = match facts.head_address()? {
558 Prior::None => None,
559 Prior::Single(id) => Some(id),
560 Prior::Merge(_, _) => bug!("cannot have a merge parent in call_action"),
561 };
562 let ctx_parent = parent.unwrap_or_default();
565
566 let publish_stack = {
567 let mut ffis = self.ffis.lock();
568 let mut eng = self.engine.lock();
569 let mut io = VmPolicyIO::new(facts, sink, &mut *eng, &mut ffis);
570 let ctx = CommandContext::Action(ActionContext {
571 name,
572 head_id: ctx_parent.id.into(),
573 });
574 {
575 let mut rs = self.machine.create_run_state(&mut io, &ctx);
576 let exit_reason = match args {
577 Cow::Borrowed(args) => rs.call_action(name, args.iter().cloned()),
578 Cow::Owned(args) => rs.call_action(name, args),
579 }
580 .map_err(|e| {
581 error!("\n{e}");
582 EngineError::InternalError
583 })?;
584 match exit_reason {
585 ExitReason::Normal => {}
586 ExitReason::Check => {
587 info!("Check {}", self.source_location(&rs));
588 return Err(EngineError::Check);
589 }
590 ExitReason::Panic => {
591 info!("Panicked {}", self.source_location(&rs));
592 return Err(EngineError::Panic);
593 }
594 };
595 }
596 io.into_publish_stack()
597 };
598
599 for (name, fields) in publish_stack {
600 let envelope = self.seal_command(&name, fields, ctx_parent.id, facts)?;
601 let data = match parent {
602 None => VmProtocolData::Init {
603 policy: 0u64.to_le_bytes(),
605 author_id: envelope.author_id,
606 kind: &name,
607 serialized_fields: &envelope.payload,
608 signature: &envelope.signature,
609 },
610 Some(parent) => VmProtocolData::Basic {
611 author_id: envelope.author_id,
612 parent,
613 kind: &name,
614 serialized_fields: &envelope.payload,
615 signature: &envelope.signature,
616 },
617 };
618 let wrapped = postcard::to_allocvec(&data)?;
619 let new_command = VmProtocol::new(
620 &wrapped,
621 envelope.command_id,
622 data,
623 Arc::clone(&self.priority_map),
624 );
625
626 self.call_rule(&new_command, facts, sink, CommandRecall::None)?;
627 facts.add_command(&new_command).map_err(|e| {
628 error!("{e}");
629 EngineError::Write
630 })?;
631 }
632
633 Ok(())
634 }
635
636 fn merge<'a>(
637 &self,
638 target: &'a mut [u8],
639 ids: MergeIds,
640 ) -> Result<Self::Command<'a>, EngineError> {
641 let (left, right) = ids.into();
642 let c = VmProtocolData::Merge { left, right };
643 let data = postcard::to_slice(&c, target).map_err(|e| {
644 error!("{e}");
645 EngineError::Write
646 })?;
647 let id = CommandId::hash_for_testing_only(data);
648 Ok(VmProtocol::new(data, id, c, Arc::clone(&self.priority_map)))
649 }
650}
651
652impl fmt::Display for VmAction<'_> {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 let mut d = f.debug_tuple(self.name);
655 for arg in self.args.as_ref() {
656 d.field(&DebugViaDisplay(arg));
657 }
658 d.finish()
659 }
660}
661
662impl fmt::Display for VmEffect {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 let mut d = f.debug_struct(&self.name);
665 for field in &self.fields {
666 d.field(field.key(), &DebugViaDisplay(field.value()));
667 }
668 d.finish()
669 }
670}
671
672struct DebugViaDisplay<T>(T);
674
675impl<T: fmt::Display> fmt::Debug for DebugViaDisplay<T> {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 write!(f, "{}", self.0)
678 }
679}