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