bacnet_objects/access_control/
accompaniment.rs1use super::accompaniment_types::*;
3use super::{AccessRightsDecision, evaluate_access_rights};
4use crate::database::ObjectDatabase;
5use crate::prelude::*;
6use bacnet_types::constructed::BACnetDeviceObjectReference;
7use bacnet_types::enums::{AccessEvent, ObjectType};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::ObjectIdentifier;
10use core::time::Duration;
11
12#[path = "accompaniment_read.rs"]
13mod read;
14#[path = "accompaniment_timing.rs"]
15mod timing;
16
17pub fn evaluate_accompaniment(
97 db: &ObjectDatabase,
98 request: AccompanimentRequest<'_>,
99) -> Result<AccompanimentEvaluation, Error> {
100 let original_rights =
101 evaluate_access_rights(db, request.original.credential, request.original.point)?;
102 Ok(evaluate_with_rights(db, request, original_rights))
103}
104
105pub(super) fn evaluate_with_rights(
108 db: &ObjectDatabase,
109 request: AccompanimentRequest<'_>,
110 original_rights: super::AccessRightsEvaluation,
111) -> AccompanimentEvaluation {
112 let mut result = AccompanimentEvaluation {
113 original: request.original,
114 original_rights,
115 requirement: None,
116 companions: Vec::new(),
117 outcome: AccompanimentOutcome::NotEvaluated,
118 events: Vec::new(),
119 };
120 let selected = match result.original_rights.decision {
121 AccessRightsDecision::Denied { .. } => return result,
122 AccessRightsDecision::Exempt => {
123 result.outcome = AccompanimentOutcome::NotRequired;
124 return result;
125 }
126 AccessRightsDecision::Granted { rule } => rule.access_rights,
127 };
128 result.outcome = match follow(db, &request, &mut result, selected) {
129 Ok(outcome) => outcome,
130 Err(problem) => AccompanimentOutcome::Unresolved(problem),
131 };
132 result
133}
134
135fn witness(
136 request: &AccompanimentRequest<'_>,
137 sequence: u64,
138) -> Result<AccompanimentEvidence, AccompanimentProblem> {
139 let mut entries = request
140 .witnesses
141 .iter()
142 .filter(|witness| witness.original == sequence);
143 let evidence = entries
144 .next()
145 .ok_or(AccompanimentProblem::MissingWitness {
146 presentation: sequence,
147 })?
148 .evidence;
149 if entries.next().is_some() {
150 return Err(AccompanimentProblem::AmbiguousWitness {
151 presentation: sequence,
152 });
153 }
154 Ok(evidence)
155}
156fn deny(
157 result: &mut AccompanimentEvaluation,
158 current: CredentialPresentation,
159 missing: bool,
160) -> AccompanimentOutcome {
161 let missing_original = missing && current == result.original;
162 let event = if missing_original {
163 AccessEvent::DENIED_NO_ACCOMPANIMENT
164 } else {
165 AccessEvent::DENIED_INCORRECT_ACCOMPANIMENT
166 };
167 result.events.push(AccompanimentEventIntent {
168 original: result.original,
169 access_event: event,
170 presentation: result.original,
171 });
172 if missing_original {
173 AccompanimentOutcome::DeniedNoCompanion
174 } else {
175 AccompanimentOutcome::DeniedIncorrectCompanion {
176 presentation: current.sequence,
177 }
178 }
179}
180fn follow(
181 db: &ObjectDatabase,
182 request: &AccompanimentRequest<'_>,
183 result: &mut AccompanimentEvaluation,
184 mut selected: ObjectIdentifier,
185) -> Result<AccompanimentOutcome, AccompanimentProblem> {
186 let mut current = request.original;
187 let mut path = vec![current];
188 let mut period = None;
189 let mut links = 0;
190 loop {
191 let requirement = read::requirement(db, selected)?;
192 if let Some(child) = result.companions.last_mut() {
193 child.requirement = requirement.clone();
194 } else {
195 result.requirement = requirement.clone();
196 }
197 let Some(requirement) = requirement else {
198 return Ok(if links == 0 {
199 AccompanimentOutcome::NotRequired
200 } else {
201 AccompanimentOutcome::Satisfied
202 });
203 };
204 if links >= request.max_links {
205 return Err(AccompanimentProblem::LinkLimit {
206 max_links: request.max_links,
207 });
208 }
209 let period = match period {
210 Some(period) => period,
211 None => {
212 let value = read::period(db, current.point, request.timing.fallback_period)?;
213 period = Some(value);
214 value
215 }
216 };
217 let expired = timing::expired(current, request.now, period, request.timing.deadline)?;
218 let candidate = match witness(request, current.sequence)? {
219 AccompanimentEvidence::Waiting if !expired => {
220 read::available(db, &requirement)?;
221 return Ok(AccompanimentOutcome::Waiting {
222 presentation: current.sequence,
223 });
224 }
225 AccompanimentEvidence::DeadlineReached if !expired => {
226 return Err(timing::problem(
227 current.sequence,
228 AccompanimentPresentationFailure::PrematureDeadline,
229 ));
230 }
231 AccompanimentEvidence::Waiting | AccompanimentEvidence::DeadlineReached => {
232 read::available(db, &requirement)?;
233 return Ok(deny(result, current, true));
234 }
235 AccompanimentEvidence::Presented(candidate) => candidate,
236 };
237 if let Some(previous) = path
238 .iter()
239 .find(|previous| previous.sequence == candidate.sequence)
240 && *previous != candidate
241 {
242 return Err(timing::problem(
243 candidate.sequence,
244 AccompanimentPresentationFailure::ReusedSequence,
245 ));
246 }
247 if !timing::eligible(current, candidate, request, period, &path)? {
248 read::available(db, &requirement)?;
249 return Ok(deny(result, current, true));
250 }
251 if candidate.credential.object_type() != ObjectType::ACCESS_CREDENTIAL {
252 return Err(AccompanimentProblem::Reference {
253 reference: candidate.credential.into(),
254 reason: AccompanimentReferenceFailure::WrongType,
255 });
256 }
257 if links == 0 {
260 result.events.push(AccompanimentEventIntent {
261 original: result.original,
262 access_event: AccessEvent::ACCOMPANIMENT_BY,
263 presentation: candidate,
264 });
265 }
266 read::available(db, &requirement)?;
267 if candidate.credential == current.credential {
268 return Ok(deny(result, current, false));
269 }
270 if !read::matches(db, &requirement, candidate.credential)? {
271 return Ok(deny(result, current, false));
272 }
273 if path.iter().any(|previous| {
276 previous.credential == candidate.credential || previous.sequence == candidate.sequence
277 }) {
278 path.push(candidate);
279 return Err(AccompanimentProblem::CyclicRequirement { path });
280 }
281 if !timing::ordered(current, candidate, request.timing.order) {
282 return Ok(deny(result, current, false));
283 }
284 let rights =
285 evaluate_access_rights(db, candidate.credential, current.point).map_err(|error| {
286 AccompanimentProblem::Rights {
287 presentation: candidate.sequence,
288 detail: error.to_string(),
289 }
290 })?;
291 let decision = rights.decision;
292 result.companions.push(AccompanimentCompanion {
293 presentation: candidate,
294 rights,
295 requirement: None,
296 });
297 match decision {
298 AccessRightsDecision::Denied { .. } => return Ok(deny(result, current, false)),
299 AccessRightsDecision::Exempt => return Ok(AccompanimentOutcome::Satisfied),
300 AccessRightsDecision::Granted { rule } => selected = rule.access_rights,
301 }
302 path.push(candidate);
303 current = candidate;
304 links += 1;
305 }
306}