1use crate::auth_lys::{
13 AccessAction, AccessDecisionError, AccessResource, AdmittedLysPass, DeliberateDecision,
14 GrantReceipt, LysDeliberateConsumer, ResourceKind, SubjectBinding, SubjectBindingError,
15 decide_deliberate,
16};
17use crate::link_handshake::{LocalMachine, MachineIdentity};
18use std::collections::BTreeMap;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum LinkAction {
23 Export,
25 Import,
27}
28
29impl LinkAction {
30 #[must_use]
32 pub const fn access_action(self) -> AccessAction {
33 match self {
34 Self::Export => AccessAction::LinkExport,
35 Self::Import => AccessAction::LinkImport,
36 }
37 }
38
39 #[must_use]
41 pub const fn other(self) -> Self {
42 match self {
43 Self::Export => Self::Import,
44 Self::Import => Self::Export,
45 }
46 }
47}
48
49impl From<liminal::protocol::LinkEnd> for LinkAction {
50 fn from(end: liminal::protocol::LinkEnd) -> Self {
51 match end {
52 liminal::protocol::LinkEnd::Export => Self::Export,
53 liminal::protocol::LinkEnd::Import => Self::Import,
54 }
55 }
56}
57
58#[derive(Debug)]
67pub struct LinkHost {
68 local: LocalMachine,
69 rights: Box<dyn LinkRights>,
70 recorded: BTreeMap<String, MachineIdentity>,
71}
72
73impl LinkHost {
74 #[must_use]
76 pub const fn new(
77 local: LocalMachine,
78 rights: Box<dyn LinkRights>,
79 recorded: BTreeMap<String, MachineIdentity>,
80 ) -> Self {
81 Self {
82 local,
83 rights,
84 recorded,
85 }
86 }
87
88 #[must_use]
90 pub const fn local(&self) -> &LocalMachine {
91 &self.local
92 }
93
94 #[must_use]
96 pub fn rights(&self) -> &dyn LinkRights {
97 self.rights.as_ref()
98 }
99
100 #[must_use]
102 pub fn recorded_machine(&self, holder: &str) -> Option<MachineIdentity> {
103 self.recorded.get(holder).copied()
104 }
105}
106
107pub trait LinkRights: Send + Sync + std::fmt::Debug {
111 fn holds(
116 &self,
117 action: LinkAction,
118 subject: &SubjectBinding,
119 now: u64,
120 ) -> Result<GrantReceipt, AccessDecisionError>;
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
125pub struct Link {
126 network_id: String,
127 id: String,
128 exporter: MachineIdentity,
129 importer: MachineIdentity,
130}
131
132#[derive(Debug, PartialEq, Eq)]
134pub enum LinkError {
135 Identifier(&'static str),
137 SameMachine,
139}
140
141impl std::fmt::Display for LinkError {
142 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::Identifier(member) => write!(formatter, "link {member} is not an identifier"),
145 Self::SameMachine => formatter.write_str("a link joins two different machines"),
146 }
147 }
148}
149
150impl std::error::Error for LinkError {}
151
152#[derive(Debug, PartialEq, Eq)]
154pub enum LinkRefusal {
155 Binding(SubjectBindingError),
157 Export(AccessDecisionError),
159 Import(AccessDecisionError),
161}
162
163impl std::fmt::Display for LinkRefusal {
164 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 match self {
166 Self::Binding(SubjectBindingError(member)) => {
167 write!(formatter, "crossing refused: subject {member}")
168 }
169 Self::Export(error) => write!(formatter, "crossing refused at export: {error}"),
170 Self::Import(error) => write!(formatter, "crossing refused at import: {error}"),
171 }
172 }
173}
174
175impl std::error::Error for LinkRefusal {}
176
177#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct Crossing {
180 pub export: GrantReceipt,
182 pub import: GrantReceipt,
184}
185
186impl Link {
187 pub fn new(
193 network_id: String,
194 id: String,
195 exporter: MachineIdentity,
196 importer: MachineIdentity,
197 ) -> Result<Self, LinkError> {
198 for (member, value) in [("network", &network_id), ("id", &id)] {
199 if value.is_empty() || value.chars().any(char::is_whitespace) {
200 return Err(LinkError::Identifier(member));
201 }
202 }
203 if exporter == importer {
204 return Err(LinkError::SameMachine);
205 }
206 Ok(Self {
207 network_id,
208 id,
209 exporter,
210 importer,
211 })
212 }
213
214 #[must_use]
216 pub fn network_id(&self) -> &str {
217 &self.network_id
218 }
219
220 #[must_use]
222 pub fn resource(&self) -> AccessResource {
223 AccessResource {
224 kind: ResourceKind::Link,
225 id: self.id.clone(),
226 }
227 }
228
229 #[must_use]
231 pub const fn exporter(&self) -> &MachineIdentity {
232 &self.exporter
233 }
234
235 #[must_use]
237 pub const fn importer(&self) -> &MachineIdentity {
238 &self.importer
239 }
240
241 pub fn open<C: LysDeliberateConsumer>(
249 &self,
250 end: LinkAction,
251 consumer: &C,
252 pass: &AdmittedLysPass<C::Pass>,
253 now: u64,
254 ) -> Result<DeliberateDecision, AccessDecisionError> {
255 decide_deliberate(consumer, pass, &self.resource(), end.access_action(), now)
256 }
257
258 pub fn crossing(
264 &self,
265 exporter: &dyn LinkRights,
266 importer: &dyn LinkRights,
267 subject: &str,
268 now: u64,
269 ) -> Result<Crossing, LinkRefusal> {
270 let bound = SubjectBinding::new(
271 self.network_id.clone(),
272 subject.to_owned(),
273 subject.to_owned(),
274 )
275 .map_err(LinkRefusal::Binding)?;
276 let export = exporter
277 .holds(LinkAction::Export, &bound, now)
278 .map_err(LinkRefusal::Export)?;
279 let import = importer
280 .holds(LinkAction::Import, &bound, now)
281 .map_err(LinkRefusal::Import)?;
282 Ok(Crossing { export, import })
283 }
284}