use std::sync::Arc;
use sim_codec_binary::BinaryCodecLib;
use std::time::Duration;
use sim_kernel::{
Consistency, Cx, DefaultFactory, EagerPolicy, EvalMode, EvalRequest, Expr, ObjectEncoding,
StrictNames, Symbol,
capability::{
eval_remote_capability, table_db_capability, table_db_mkdir_capability,
table_db_read_capability, table_db_rmdir_capability, table_db_write_capability,
table_remote_capability,
},
read_construct_capability,
};
use sim_lib_server::{EvalSite, LocalEvalSite, ServerAddress, server_frame_from_request};
use sim_table_db::install_db_dir_lib;
use crate::{
RemoteDirDescriptor, remote_dir_class_symbol, remote_dir_value, wrap_remote_table_site,
};
fn cx() -> Cx {
let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
install_codecs(&mut cx);
cx
}
fn strict_name_cx() -> Cx {
let mut cx = Cx::new(Arc::new(StrictNames(EagerPolicy)), Arc::new(DefaultFactory));
install_codecs(&mut cx);
cx
}
fn install_codecs(cx: &mut Cx) {
let binary = BinaryCodecLib::new(cx.registry_mut().fresh_codec_id());
cx.load_lib(&binary).unwrap();
}
fn grant(cx: &mut Cx, capabilities: &[sim_kernel::CapabilityName]) {
for capability in capabilities {
cx.grant(capability.clone());
}
}
fn remote_site(cx: &mut Cx) -> Arc<dyn EvalSite> {
grant(
cx,
&[
table_db_capability(),
table_db_read_capability(),
table_db_write_capability(),
table_db_mkdir_capability(),
table_db_rmdir_capability(),
],
);
let root = install_db_dir_lib(cx).unwrap();
let inner: Arc<dyn EvalSite> = Arc::new(LocalEvalSite::new(
ServerAddress::Local,
vec![Symbol::qualified("codec", "binary")],
));
wrap_remote_table_site(inner, root)
}
#[test]
fn remote_dir_roundtrips_against_in_process_site() {
let mut cx = cx();
let site = remote_site(&mut cx);
grant(
&mut cx,
&[table_remote_capability(), eval_remote_capability()],
);
let remote = remote_dir_value(&mut cx, site, Symbol::qualified("codec", "binary")).unwrap();
let table = remote.object().as_table_impl().unwrap();
let dir = remote.object().as_dir().unwrap();
let root_value = cx.factory().string("root".to_owned()).unwrap();
table.set(&mut cx, Symbol::new("x"), root_value).unwrap();
assert_eq!(
table
.get(&mut cx, Symbol::new("x"))
.unwrap()
.object()
.as_expr(&mut cx)
.unwrap(),
Expr::String("root".to_owned())
);
assert_eq!(table.keys(&mut cx).unwrap(), vec![Symbol::new("x")]);
assert_eq!(table.len(&mut cx).unwrap(), 1);
let sub = dir.mkdir(&mut cx, Symbol::new("sub")).unwrap();
let sub_dir = sub.object().as_dir().unwrap();
assert!(dir.is_dir(&mut cx, Symbol::new("sub")).unwrap());
assert!(dir.opendir(&mut cx, Symbol::new("sub")).unwrap().is_some());
let child_value = cx.factory().string("child".to_owned()).unwrap();
sub_dir
.as_table_impl()
.unwrap()
.set(&mut cx, Symbol::new("y"), child_value)
.unwrap();
let opened = dir.opendir(&mut cx, Symbol::new("sub")).unwrap().unwrap();
assert_eq!(
opened
.object()
.as_table_impl()
.unwrap()
.get(&mut cx, Symbol::new("y"))
.unwrap()
.object()
.as_expr(&mut cx)
.unwrap(),
Expr::String("child".to_owned())
);
}
fn table_request(op: &str, args: Vec<Expr>) -> EvalRequest {
let mut call_args = vec![Expr::List(Vec::new())];
call_args.extend(args);
EvalRequest {
expr: Expr::Call {
operator: Box::new(Expr::Symbol(Symbol::qualified("table", op))),
args: call_args,
},
mode: EvalMode::Eval,
result_shape: None,
answer_limit: None,
stream_buffer: None,
stream: false,
required_capabilities: Vec::new(),
deadline: Some(Duration::from_secs(5)),
consistency: Consistency::RemoteOnly,
trace: false,
}
}
#[test]
fn server_rejects_malformed_and_unknown_table_ops() {
let mut cx = strict_name_cx();
let site = remote_site(&mut cx);
let codec = Symbol::qualified("codec", "binary");
let frame =
server_frame_from_request(&mut cx, &codec, table_request("get", Vec::new())).unwrap();
let err = site.answer(&mut cx, frame).unwrap_err();
assert!(err.to_string().contains("table/get expects path and key"));
let frame = server_frame_from_request(
&mut cx,
&codec,
table_request("del", vec![Expr::String("x".to_owned())]),
)
.unwrap();
assert!(site.answer(&mut cx, frame).is_err());
let frame =
server_frame_from_request(&mut cx, &codec, table_request("bogus", Vec::new())).unwrap();
assert!(site.answer(&mut cx, frame).is_err());
}
#[test]
fn remote_dir_requires_table_remote_for_construction_and_eval_remote_for_ops() {
let mut cx = cx();
let site = remote_site(&mut cx);
let err =
remote_dir_value(&mut cx, site.clone(), Symbol::qualified("codec", "binary")).unwrap_err();
assert!(matches!(
err,
sim_kernel::Error::CapabilityDenied { capability } if capability == table_remote_capability()
));
cx.grant(table_remote_capability());
let remote = remote_dir_value(&mut cx, site, Symbol::qualified("codec", "binary")).unwrap();
let table = remote.object().as_table_impl().unwrap();
let err = table.get(&mut cx, Symbol::new("x")).unwrap_err();
assert!(matches!(
err,
sim_kernel::Error::CapabilityDenied { capability } if capability == eval_remote_capability()
));
}
#[test]
fn remote_dir_all_ops_require_eval_remote() {
let mut cx = cx();
let site = remote_site(&mut cx);
cx.grant(table_remote_capability());
let remote = remote_dir_value(&mut cx, site, Symbol::qualified("codec", "binary")).unwrap();
let table = remote.object().as_table_impl().unwrap();
let dir = remote.object().as_dir().unwrap();
let value = cx.factory().string("value".to_owned()).unwrap();
assert!(matches!(
table.set(&mut cx, Symbol::new("x"), value),
Err(sim_kernel::Error::CapabilityDenied { capability })
if capability == eval_remote_capability()
));
assert!(matches!(
table.keys(&mut cx),
Err(sim_kernel::Error::CapabilityDenied { capability })
if capability == eval_remote_capability()
));
assert!(matches!(
table.len(&mut cx),
Err(sim_kernel::Error::CapabilityDenied { capability })
if capability == eval_remote_capability()
));
assert!(matches!(
dir.mkdir(&mut cx, Symbol::new("sub")),
Err(sim_kernel::Error::CapabilityDenied { capability })
if capability == eval_remote_capability()
));
assert!(matches!(
dir.rmdir(&mut cx, Symbol::new("sub")),
Err(sim_kernel::Error::CapabilityDenied { capability })
if capability == eval_remote_capability()
));
}
#[test]
fn remote_dir_citizen_round_trips_as_descriptor_only() {
let mut cx = cx();
let site = remote_site(&mut cx);
cx.load_lib(&sim_citizen::CitizenLib::all()).unwrap();
grant(
&mut cx,
&[table_remote_capability(), eval_remote_capability()],
);
cx.grant(read_construct_capability());
let remote = remote_dir_value(&mut cx, site, Symbol::qualified("codec", "binary")).unwrap();
let child = remote
.object()
.as_dir()
.unwrap()
.mkdir(&mut cx, Symbol::new("child"))
.unwrap();
sim_citizen::check_value_fixture(&mut cx, child.clone()).unwrap();
let ObjectEncoding::Constructor { args, .. } = child
.object()
.as_object_encoder()
.unwrap()
.object_encoding(&mut cx)
.unwrap()
else {
panic!("expected constructor encoding");
};
let args = args
.iter()
.map(|arg| sim_citizen::value_from_expr(&mut cx, arg))
.collect::<sim_kernel::Result<Vec<_>>>()
.unwrap();
let decoded = cx.read_construct(&remote_dir_class_symbol(), args).unwrap();
let descriptor = decoded
.object()
.as_any()
.downcast_ref::<RemoteDirDescriptor>()
.expect("expected remote descriptor");
assert_eq!(descriptor.site_kind, "remote-table");
assert_eq!(descriptor.codec, Symbol::qualified("codec", "binary"));
assert_eq!(descriptor.path, vec!["child".to_owned()]);
assert!(decoded.object().as_table_impl().is_none());
assert!(decoded.object().as_dir().is_none());
}
#[test]
fn remote_dir_citizen_rejects_malformed_path() {
let mut cx = cx();
cx.load_lib(&sim_citizen::CitizenLib::all()).unwrap();
cx.grant(read_construct_capability());
let args = [
Expr::Symbol(Symbol::new("v0")),
Expr::String("local".to_owned()),
Expr::Symbol(Symbol::qualified("codec", "binary")),
Expr::List(vec![Expr::String("a/b".to_owned())]),
]
.iter()
.map(|arg| sim_citizen::value_from_expr(&mut cx, arg))
.collect::<sim_kernel::Result<Vec<_>>>()
.unwrap();
let err = cx
.read_construct(&remote_dir_class_symbol(), args)
.unwrap_err();
assert!(err.to_string().contains("illegal segment"));
}