use super::*;
#[derive(Debug)]
struct HostRights {
import: &'static [&'static str],
}
impl LinkRights for HostRights {
fn holds(
&self,
action: LinkAction,
subject: &SubjectBinding,
_now: u64,
) -> Result<GrantReceipt, AccessDecisionError> {
let reached =
action == LinkAction::Import && self.import.contains(&subject.resource().id.as_str());
if reached {
return Ok(GrantReceipt {
grant: format!("host-import-{}", subject.resource().id),
});
}
Err(AccessDecisionError {
cause: RefusalCause::MissingGrant,
refusal: AccessRefusal::new(
subject.resource().clone(),
action.access_action(),
"https://lys.example/ask/host".to_owned(),
),
})
}
}
const LINK_STREAM: u32 = 4;
fn machine(seed: u8) -> LocalMachine {
LocalMachine::new(Ed25519Identity::from_seed(&Zeroizing::new([seed; 32])))
}
fn export_offer(
remote: &LocalMachine,
host: &LocalMachine,
link_id: &str,
) -> Result<(LinkHandshake, LinkOffer), Failure> {
let mut initiator = LinkHandshake::initiator(remote, host.machine())?;
let mut first = vec![0_u8; NOISE_OVERHEAD];
let written = initiator.write(&mut first)?;
first.truncate(written);
Ok((
initiator,
LinkOffer {
link_id: link_id.to_owned(),
end: LinkEnd::Export,
machine: *remote.machine().ed25519_public(),
handshake: first,
},
))
}
fn link_open(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
offer: LinkOffer,
) -> Result<LinkOpenOutcome, Failure> {
match apply_frame(
PID,
runtime,
state,
Frame::LinkOpen {
flags: 0,
stream_id: LINK_STREAM,
offer,
},
) {
FrameAction::Respond(Frame::LinkOpenAck {
stream_id: LINK_STREAM,
outcome,
..
}) => Ok(outcome),
other => Err(format!("the open was not acknowledged on its stream: {other:?}").into()),
}
}
fn link_message(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
subject: &str,
sealed: Vec<u8>,
) -> FrameAction {
apply_frame(
PID,
runtime,
state,
Frame::LinkMessage {
flags: 0,
stream_id: LINK_STREAM,
subject: subject.to_owned(),
sealed,
},
)
}
fn crossing_refusal(action: FrameAction) -> Result<String, Failure> {
match action {
FrameAction::Respond(Frame::PublishError {
stream_id: LINK_STREAM,
reason_code: crate::server::connection::link_route::LINK_CROSSING_REFUSED_CODE,
message: Some(message),
..
}) => Ok(message),
other => Err(format!("the crossing was not refused by name: {other:?}").into()),
}
}
#[test]
fn a_link_opens_on_the_holders_recorded_key_when_lys_allows_the_end() -> Result<(), Failure> {
let grants = Arc::new(Grants::default());
let door = Arc::new(Door::default());
let (runtime, mut state) = admitted(&grants, &door)?;
let remote = machine(1);
let host = machine(2);
let (_, offer) = export_offer(&remote, &host, "link-7")?;
let LinkOpenOutcome::Refused { reason } = link_open(&runtime, &mut state, offer.clone())?
else {
return Err("a liminal without a machine identity opened a link".into());
};
assert!(reason.contains("[auth.lys.machine]"), "{reason}");
runtime
.install_link_host(Arc::new(LinkHost::new(
machine(2),
Box::new(HostRights {
import: &["link-7"],
}),
[("fixture-holder".to_owned(), remote.machine())].into(),
)))
.map_err(|_| "a link host was already installed")?;
let (_, stranger) = export_offer(&machine(3), &host, "link-7")?;
let LinkOpenOutcome::Refused { reason } = link_open(&runtime, &mut state, stranger)? else {
return Err("a stranger's key opened a link".into());
};
assert!(reason.contains("recorded key"), "{reason}");
let LinkOpenOutcome::Refused { reason } = link_open(&runtime, &mut state, offer)? else {
return Err("an end Lys refused opened a link".into());
};
assert!(reason.contains("\"link_export\""), "{reason}");
assert!(reason.contains("liminal.link:link-7"), "{reason}");
grants.allow_open("link-7", AccessAction::LinkExport)?;
let (mut initiator, offer) = export_offer(&remote, &host, "link-7")?;
let LinkOpenOutcome::Opened { handshake } = link_open(&runtime, &mut state, offer)? else {
return Err("an allowed end did not open".into());
};
initiator.read(&handshake)?;
assert!(initiator.is_finished());
assert!(state.links.contains_key(LINK_STREAM)?);
Ok(())
}
#[test]
fn a_crossing_is_decided_on_both_passes_before_it_is_published() -> Result<(), Failure> {
let grants = Arc::new(Grants::default());
let door = Arc::new(Door::default());
let (runtime, mut state) = admitted(&grants, &door)?;
let remote = machine(1);
runtime
.install_link_host(Arc::new(LinkHost::new(
machine(2),
Box::new(HostRights {
import: &["link-7"],
}),
[("fixture-holder".to_owned(), remote.machine())].into(),
)))
.map_err(|_| "a link host was already installed")?;
let refused = crossing_refusal(link_message(&runtime, &mut state, "orders", vec![1, 2, 3]))?;
assert!(refused.contains("no link is open on stream"), "{refused}");
grants.allow_open("link-7", AccessAction::LinkExport)?;
let (mut initiator, offer) = export_offer(&remote, &machine(2), "link-7")?;
let LinkOpenOutcome::Opened { handshake } = link_open(&runtime, &mut state, offer)? else {
return Err("the link did not open".into());
};
initiator.read(&handshake)?;
let mut transport = initiator.finish()?;
let envelope = MessageEnvelope::new(
SchemaId::new([1; SchemaId::WIRE_LEN]),
CausalContext::independent(),
b"crossing".to_vec(),
);
let mut plain = 1_u64.to_be_bytes().to_vec();
plain.extend(envelope.to_wire_bytes()?);
let seal = |transport: &mut crate::link_handshake::LinkTransport| -> Result<Vec<u8>, Failure> {
let mut sealed = vec![0_u8; plain.len() + NOISE_OVERHEAD];
let written = transport.seal(&plain, &mut sealed)?;
sealed.truncate(written);
Ok(sealed)
};
let refused = crossing_refusal(link_message(
&runtime,
&mut state,
"orders",
seal(&mut transport)?,
))?;
assert!(
refused.starts_with("crossing refused at export:"),
"{refused}"
);
assert!(refused.contains("\"link_export\""), "{refused}");
grants.grant_link("link-7", LinkAction::Export)?;
grants.grant_link("invoices-link", LinkAction::Export)?;
grants.allow_open("invoices-link", AccessAction::LinkExport)?;
let (invoice_runtime, mut invoice_state) = admitted(&grants, &door)?;
invoice_runtime
.install_link_host(Arc::new(LinkHost::new(
machine(2),
Box::new(HostRights {
import: &["link-7"],
}),
[("fixture-holder".to_owned(), remote.machine())].into(),
)))
.map_err(|_| "invoice host already installed")?;
let (mut invoice_initiator, invoice_offer) =
export_offer(&remote, &machine(2), "invoices-link")?;
let LinkOpenOutcome::Opened { handshake } =
link_open(&invoice_runtime, &mut invoice_state, invoice_offer)?
else {
return Err("invoice link did not open".into());
};
invoice_initiator.read(&handshake)?;
let mut invoice_transport = invoice_initiator.finish()?;
let refused = crossing_refusal(link_message(
&invoice_runtime,
&mut invoice_state,
"invoices",
seal(&mut invoice_transport)?,
))?;
assert!(
refused.starts_with("crossing refused at import:"),
"{refused}"
);
let crossed = link_message(&runtime, &mut state, "orders", seal(&mut transport)?);
match crossed {
FrameAction::Respond(Frame::PublishError {
reason_code,
message: Some(message),
..
}) => {
assert_ne!(
reason_code,
crate::server::connection::link_route::LINK_CROSSING_REFUSED_CODE
);
assert!(
message.contains("publish is not a route under test"),
"{message}"
);
}
other => return Err(format!("the crossing did not reach publish: {other:?}").into()),
}
let refused = crossing_refusal(link_message(&runtime, &mut state, "orders", vec![0; 40]))?;
assert!(refused.starts_with("link frame refused:"), "{refused}");
Ok(())
}