Skip to content

Commit

Permalink
Merge branch 'main' into remove-cs-id
Browse files Browse the repository at this point in the history
  • Loading branch information
jdygert-spok authored Feb 12, 2025
2 parents 222caae + ee1f9a9 commit dd62e76
Show file tree
Hide file tree
Showing 22 changed files with 844 additions and 522 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions crates/aranya-afc-util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ std = [
"postcard/use-std",
"serde/std",
"siphasher?/std",
"spin?/std",
"spin/std",
"thiserror/std",
"tracing/std",
]
Expand All @@ -40,19 +40,18 @@ testing = [

"dep:indexmap",
"dep:siphasher",
"dep:spin",
]

[dependencies]
aranya-crypto = { version = "0.2.1", path = "../aranya-crypto", default-features = false, features = ["alloc"] }
aranya-fast-channels = { version = "0.3.0", path = "../aranya-fast-channels", default-features = false, features = ["alloc"] }
aranya-policy-vm = { version = "0.3.0", path = "../aranya-policy-vm", default-features = false, features = ["derive"] }

buggy = { version = "0.1.0", default-features = false }
indexmap = { version = "2.1", default-features = false, optional = true }
postcard = { workspace = true, default-features = false, features = ["heapless"] }
serde = { workspace = true, default-features = false, features = ["derive"] }
siphasher = { version = "1", default-features = false, optional = true }
spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex"], optional = true }
spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex"] }
thiserror = { workspace = true }
tracing = { workspace = true }

Expand Down
46 changes: 33 additions & 13 deletions crates/aranya-afc-util/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use aranya_policy_vm::{
CommandContext, MachineError, MachineErrorType, MachineIOError, Typed, Value,
ValueConversionError,
};
use buggy::Bug;
use spin::Mutex;

use crate::shared::decode_enc_pk;

Expand All @@ -28,15 +30,16 @@ macro_rules! error {
}

/// An [`FfiModule`][aranya_policy_vm::ffi::FfiModule] for AFC.
#[derive(Clone)]
pub struct Ffi<S> {
store: S,
store: Mutex<S>,
}

impl<S: KeyStore> Ffi<S> {
/// Creates a new FFI module.
pub const fn new(store: S) -> Self {
Self { store }
Self {
store: Mutex::new(store),
}
}

/// Decodes a [`EncryptionPublicKey`].
Expand Down Expand Up @@ -75,7 +78,7 @@ function create_bidi_channel(
) struct AfcBidiChannel
"#)]
pub(crate) fn create_bidi_channel<E: Engine>(
&mut self,
&self,
_ctx: &CommandContext<'_>,
eng: &mut E,
parent_cmd_id: Id,
Expand All @@ -89,6 +92,7 @@ function create_bidi_channel(

let our_sk = &self
.store
.lock()
.get_key(eng, our_enc_key_id.into())
.map_err(|_| FfiError::KeyStore)?
.ok_or(FfiError::KeyNotFound)?;
Expand All @@ -105,10 +109,13 @@ function create_bidi_channel(

let key_id = peer.id().into();
let wrapped = eng.wrap(author)?;
self.store.try_insert(key_id, wrapped).map_err(|err| {
error!("unable to insert `BidiAuthorSecret` into KeyStore: {err}");
FfiError::KeyStore
})?;
self.store
.lock()
.try_insert(key_id, wrapped)
.map_err(|err| {
error!("unable to insert `BidiAuthorSecret` into KeyStore: {err}");
FfiError::KeyStore
})?;

Ok(AfcBidiChannel {
peer_encap: peer.as_bytes().to_vec(),
Expand All @@ -128,7 +135,7 @@ function create_uni_channel(
) struct AfcUniChannel
"#)]
pub(crate) fn create_uni_channel<E: Engine>(
&mut self,
&self,
_ctx: &CommandContext<'_>,
eng: &mut E,
parent_cmd_id: Id,
Expand All @@ -142,6 +149,7 @@ function create_uni_channel(

let our_sk = &self
.store
.lock()
.get_key(eng, author_enc_key_id.into())
.map_err(|_| FfiError::KeyStore)?
.ok_or(FfiError::KeyNotFound)?;
Expand All @@ -158,10 +166,13 @@ function create_uni_channel(

let key_id = peer.id().into();
let wrapped = eng.wrap(author)?;
self.store.try_insert(key_id, wrapped).map_err(|err| {
error!("unable to insert `UniAuthorSecret` into KeyStore: {err}");
FfiError::KeyStore
})?;
self.store
.lock()
.try_insert(key_id, wrapped)
.map_err(|err| {
error!("unable to insert `UniAuthorSecret` into KeyStore: {err}");
FfiError::KeyStore
})?;

Ok(AfcUniChannel {
peer_encap: peer.as_bytes().to_vec(),
Expand Down Expand Up @@ -194,6 +205,9 @@ pub(crate) enum FfiError {
/// The keystore failed.
#[error("keystore failure")]
KeyStore,
/// Bug
#[error("bug: {0}")]
Bug(Bug),
}

impl From<FfiError> for MachineError {
Expand All @@ -218,6 +232,12 @@ impl From<UnwrapError> for FfiError {
}
}

impl From<Bug> for FfiError {
fn from(bug: Bug) -> Self {
Self::Bug(bug)
}
}

/// An AFC label.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) struct Label(u32);
Expand Down
29 changes: 29 additions & 0 deletions crates/aranya-model/src/tests/basic-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,33 @@ command StoreSessionData {
}
}
}
// `Relationship` is an effect that will be emitted from `Link` commands
// in order to show parent-child relationships between commands
effect Relationship {
parent_id id,
command_id id
}
// Emits `Relationship` effects
command Link {
// Local variables for command
fields {}
seal { return envelope::seal(serialize(this)) }
open { return deserialize(envelope::open(envelope)) }
policy {
finish {
emit Relationship{parent_id: envelope.parent_id, command_id: envelope.command_id}
}
}
}
// Publishes multiple `Link` commands
action publish_multiple_commands() {
publish Link{}
publish Link{}
publish Link{}
}
```
80 changes: 77 additions & 3 deletions crates/aranya-model/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use aranya_policy_compiler::Compiler;
use aranya_policy_lang::lang::parse_policy_document;
use aranya_policy_vm::{
ffi::{FfiModule, ModuleSchema},
Machine,
Machine, Value,
};
use aranya_runtime::{
memory::MemStorageProvider,
storage::linear,
vm_action, vm_effect,
vm_policy::{testing::TestFfiEnvelope, VmPolicy},
ClientState, Engine, FfiCallable, StorageProvider,
ClientState, Engine, FfiCallable, StorageProvider, VmEffect,
};
use tempfile::tempdir;
use test_log::test;
Expand Down Expand Up @@ -1414,8 +1414,10 @@ fn should_create_clients_with_args() {
assert_eq!(effects, expected);
}

// If there are no facts when writing a fact perspective, we skip writing it and just return its prior.
// When starting a perspective from the middle of a segment with no facts, we previously would grab the wrong fact index, by taking that prior's prior to apply 0 fact updates to it.
#[test]
fn test_storage_fact() {
fn test_storage_fact_creturns_correct_index() {
let basic_clients = BasicClientFactory::new(BASIC_POLICY).unwrap();
let mut test_model = RuntimeModel::new(basic_clients);

Expand Down Expand Up @@ -1445,3 +1447,75 @@ fn test_storage_fact() {
test_model.sync(Graph::X, User::B, User::A).unwrap();
}
}

// Ensure multiple commands are all published, and each command has the correct parent ID.
#[test]
fn should_create_client_with_ffi_and_publish_chain_of_commands() -> Result<(), &'static str> {
// Create our client factory, this will be responsible for creating all our clients.
let basic_clients =
BasicClientFactory::new(BASIC_POLICY).expect("should create client factory");
// Create a new model with our client factory.
let mut test_model = RuntimeModel::new(basic_clients);

// Create our first client.
test_model
.add_client(User::A)
.expect("Should create a client");

let nonce = 1;
// Create a graph for client A.
test_model
.new_graph(Graph::X, User::A, vm_action!(init(nonce)))
.expect("Should create a graph");

// Issue the 'publish_multiple_commands' action. It will publish 3 Link commands
// that each will emit a 'Relationship' effect
let effects = test_model
.action(User::A, Graph::X, vm_action!(publish_multiple_commands()))
.expect("Should return effect");

// Ensure that only 'Relationship' effects are emitted as a result of issuing
// a 'publish_multiple_commands' action
assert!(effects.iter().all(|eff| eff.name == "Relationship"));

let [ref eff1, ref eff2, ref eff3] = effects[..] else {
return Err(
"Three effects are not emitted as a result of calling 'publish_multiple_commands'",
);
};

let retrieve_id = |field_name, eff: &VmEffect| {
eff.fields
.iter()
.find_map(|pair| {
let (k, v) = (pair.key(), pair.value());

if k == field_name {
match v {
Value::Id(id) => Some(*id),
_ => None,
}
} else {
None
}
})
.ok_or("Relationship effect is missing a field")
};
let mut expected_parent_id = eff1.command.into_id();
for eff in [eff2, eff3] {
// command's id and its parent_id must be different
assert_ne!(eff.command.into_id(), retrieve_id("parent_id", eff)?);

// Observe that the actual 'parent_id' of the command that created this effect
// is equal to the expected 'parent_id'
let actual_parent_id = retrieve_id("parent_id", eff)?;
assert_eq!(expected_parent_id, actual_parent_id);

// Update the expected 'parent_id' with the 'command_id' of the command that created the
// current effect so that it can be used in the next loop iteration
let current_command_id = retrieve_id("command_id", eff)?;
expected_parent_id = current_command_id;
}

Ok(())
}
2 changes: 1 addition & 1 deletion crates/aranya-perspective-ffi/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl FfiPerspective {
/// Returns the ID of the command at the head of the perspective. Only valid for `Seal` and `Action` contexts.
#[ffi_export(def = r#"function head_id() id"#)]
pub(crate) fn head_id<E: aranya_crypto::Engine>(
&mut self,
&self,
ctx: &CommandContext<'_>,
_eng: &mut E,
) -> Result<Id, MachineError> {
Expand Down
2 changes: 1 addition & 1 deletion crates/aranya-perspective-ffi/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::FfiPerspective;
#[test]
fn test_head_id() {
let (mut eng, _) = DefaultEngine::<_>::from_entropy(Rng);
let mut perspective = FfiPerspective {};
let perspective = FfiPerspective {};
let head_id = Id::default();

{
Expand Down
2 changes: 1 addition & 1 deletion crates/aranya-policy-derive/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> syn::Result<TokenSt
#[doc(hidden)]
#[allow(non_snake_case)]
fn call<__E: #crypto::engine::Engine>(
&mut self,
&self,
__proc: usize,
__stack: &mut impl #vm::Stack,
__ctx: &#vm::CommandContext<'_>,
Expand Down
3 changes: 3 additions & 0 deletions crates/aranya-policy-module/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use crate::{data::Value, Label};
pub enum ExitReason {
/// Execution completed without errors.
Normal,
/// Execution is paused to return a result, which is at the top of the stack. Call `RunState::run()` again to resume.
Yield,
/// Execution was aborted gracefully, due an error.
Check,
/// Execution was aborted due to an unhandled error.
Expand All @@ -35,6 +37,7 @@ impl Display for ExitReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => f.write_str("normal"),
Self::Yield => f.write_str("yield"),
Self::Check => f.write_str("check"),
Self::Panic => f.write_str("panic"),
}
Expand Down
Loading

0 comments on commit dd62e76

Please sign in to comment.