use crate::auth_lys::{
AccessAction, AccessDecisionError, AccessResource, AdmittedLysPass, DeliberateDecision,
GrantReceipt, LysDeliberateConsumer, ResourceKind, SubjectBinding, SubjectBindingError,
decide_deliberate,
};
use crate::link_handshake::{LocalMachine, MachineIdentity};
use std::collections::BTreeMap;
mod life;
pub use life::LinkLifeCounters;
pub(crate) use life::{CloseLinks, LinkLife, LinkRegistry};
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum LinkEnd {
#[error("peer_gone")]
PeerGone,
#[error("revoked")]
Revoked,
#[error("refused: {0}")]
Refused(String),
#[error("replaced")]
Replaced,
#[error("shutdown")]
Shutdown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LinkAction {
Export,
Import,
}
impl LinkAction {
#[must_use]
pub const fn access_action(self) -> AccessAction {
match self {
Self::Export => AccessAction::LinkExport,
Self::Import => AccessAction::LinkImport,
}
}
#[must_use]
pub const fn other(self) -> Self {
match self {
Self::Export => Self::Import,
Self::Import => Self::Export,
}
}
}
impl From<liminal::protocol::LinkEnd> for LinkAction {
fn from(end: liminal::protocol::LinkEnd) -> Self {
match end {
liminal::protocol::LinkEnd::Export => Self::Export,
liminal::protocol::LinkEnd::Import => Self::Import,
}
}
}
#[derive(Debug)]
pub struct LinkHost {
local: LocalMachine,
rights: Box<dyn LinkRights>,
recorded: BTreeMap<String, MachineIdentity>,
}
impl LinkHost {
#[must_use]
pub const fn new(
local: LocalMachine,
rights: Box<dyn LinkRights>,
recorded: BTreeMap<String, MachineIdentity>,
) -> Self {
Self {
local,
rights,
recorded,
}
}
#[must_use]
pub const fn local(&self) -> &LocalMachine {
&self.local
}
#[must_use]
pub fn rights(&self) -> &dyn LinkRights {
self.rights.as_ref()
}
#[must_use]
pub fn recorded_machine(&self, holder: &str) -> Option<MachineIdentity> {
self.recorded.get(holder).copied()
}
}
pub trait LinkRights: Send + Sync + std::fmt::Debug {
fn holds(
&self,
action: LinkAction,
subject: &SubjectBinding,
now: u64,
) -> Result<GrantReceipt, AccessDecisionError>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Link {
network_id: String,
id: String,
exporter: MachineIdentity,
importer: MachineIdentity,
}
#[derive(Debug, PartialEq, Eq)]
pub enum LinkError {
Identifier(&'static str),
SameMachine,
}
impl std::fmt::Display for LinkError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Identifier(member) => write!(formatter, "link {member} is not an identifier"),
Self::SameMachine => formatter.write_str("a link joins two different machines"),
}
}
}
impl std::error::Error for LinkError {}
#[derive(Debug, PartialEq, Eq)]
pub enum LinkRefusal {
Binding(SubjectBindingError),
Export(AccessDecisionError),
Import(AccessDecisionError),
}
impl std::fmt::Display for LinkRefusal {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Binding(SubjectBindingError(member)) => {
write!(formatter, "crossing refused: subject {member}")
}
Self::Export(error) => write!(formatter, "crossing refused at export: {error}"),
Self::Import(error) => write!(formatter, "crossing refused at import: {error}"),
}
}
}
impl std::error::Error for LinkRefusal {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Crossing {
pub export: GrantReceipt,
pub import: GrantReceipt,
}
impl Link {
pub fn new(
network_id: String,
id: String,
exporter: MachineIdentity,
importer: MachineIdentity,
) -> Result<Self, LinkError> {
for (member, value) in [("network", &network_id), ("id", &id)] {
if value.is_empty() || value.chars().any(char::is_whitespace) {
return Err(LinkError::Identifier(member));
}
}
if exporter == importer {
return Err(LinkError::SameMachine);
}
Ok(Self {
network_id,
id,
exporter,
importer,
})
}
#[must_use]
pub fn network_id(&self) -> &str {
&self.network_id
}
#[must_use]
pub fn resource(&self) -> AccessResource {
AccessResource {
kind: ResourceKind::Link,
id: self.id.clone(),
}
}
#[must_use]
pub const fn exporter(&self) -> &MachineIdentity {
&self.exporter
}
#[must_use]
pub const fn importer(&self) -> &MachineIdentity {
&self.importer
}
pub fn open<C: LysDeliberateConsumer>(
&self,
end: LinkAction,
consumer: &C,
pass: &AdmittedLysPass<C::Pass>,
now: u64,
) -> Result<DeliberateDecision, AccessDecisionError> {
decide_deliberate(consumer, pass, &self.resource(), end.access_action(), now)
}
pub fn crossing(
&self,
exporter: &dyn LinkRights,
importer: &dyn LinkRights,
subject: &str,
now: u64,
) -> Result<Crossing, LinkRefusal> {
let bound =
SubjectBinding::link(self.network_id.clone(), self.id.clone(), subject.to_owned())
.map_err(LinkRefusal::Binding)?;
let export = exporter
.holds(LinkAction::Export, &bound, now)
.map_err(LinkRefusal::Export)?;
let import = importer
.holds(LinkAction::Import, &bound, now)
.map_err(LinkRefusal::Import)?;
Ok(Crossing { export, import })
}
}