use bacnet_encoding::tags::{Tag, TagClass, decode_tag};
use bacnet_types::enums::ConfirmedServiceChoice as Confirmed;
use super::{Doc, Field, Params};
#[derive(Debug, Clone, Copy)]
pub(super) struct Secret {
tag: u8,
before: &'static [(u8, u32)],
}
impl Params {
pub(super) fn secret(self) -> Option<Secret> {
match self {
Self::Request(Confirmed::DEVICE_COMMUNICATION_CONTROL) => Some(Secret {
tag: 2,
before: &[(0, 4), (1, 1)],
}),
Self::Request(Confirmed::REINITIALIZE_DEVICE) => Some(Secret {
tag: 1,
before: &[(0, 1)],
}),
_ => None,
}
}
}
pub(super) const PASSWORD: &str = "Password: (hidden)";
const MAYBE_PASSWORD: &str = "Hidden: may hold a password";
pub(super) fn hide_passwords(fields: &mut [Field], doc: &Doc<'_>, secret: Secret) {
let mut next = 0;
let mut open = true;
for field in fields {
let header = header(field, doc);
if open
&& let Some(header) = header
&& header.class == TagClass::Context
&& !header.is_opening
&& !header.is_closing
&& let Some(skipped) = secret.before[next..]
.iter()
.position(|&(number, most)| number == header.number && header.length <= most)
{
next += skipped + 1;
continue;
}
open = false;
let password = header.is_some_and(|header| {
header.class == TagClass::Context
&& header.number == secret.tag
&& !header.is_opening
&& !header.is_closing
});
if password {
field.label = PASSWORD.into();
field.device_text = false;
} else {
hide(field, doc);
}
}
}
fn header(field: &Field, doc: &Doc<'_>) -> Option<Tag> {
field
.range
.start
.checked_sub(doc.at)
.and_then(|at| decode_tag(doc.data, at).ok())
.map(|(header, _)| header)
}
fn hide(field: &mut Field, doc: &Doc<'_>) {
if !field.children.is_empty() {
for child in &mut field.children {
hide(child, doc);
}
return;
}
if header(field, doc).is_some_and(|header| header.is_opening || header.is_closing) {
return;
}
field.label = MAYBE_PASSWORD.into();
field.device_text = false;
}