1use bacnet_encoding::constructed::{ConstructedElement, ElementShape, property_element};
6use bacnet_encoding::primitives::{encode_priority_date_time, encode_timestamp_choice};
7use bacnet_types::bitstring::{EventTransitionBits, LimitEnable, StatusFlags};
8use bacnet_types::enums::{
9 BinaryPV, LifeSafetyState, ObjectType, PropertyIdentifier, ResolvedBits, ResolvedEnum,
10 ServiceSupported,
11};
12use bacnet_types::primitives::{Date, PropertyValue, Time};
13use bytes::BytesMut;
14
15use super::datetime::{parse_date_time, parse_time, parse_timestamp};
16use super::numeric::{Number, coerce, finite, numeric};
17use super::{json, parse_value, split_priority};
18use crate::format::{holds_state, is_binary, is_date_time_value, is_multistate};
19use crate::label::kebab;
20
21const MAX_LISTED: usize = 12;
24
25const NEAREST: usize = 5;
27
28struct Enumeration {
30 type_name: &'static str,
31 names: Vec<(&'static str, u32)>,
32}
33
34struct NamedBits {
36 type_name: &'static str,
37 names: Vec<(&'static str, usize)>,
38 length: usize,
39}
40
41enum Kind {
43 Enum {
46 enumeration: Enumeration,
47 states: bool,
48 },
49 State,
51 Bits(NamedBits),
52 DateTime,
54 PriorityDateTime,
57 Number(Number),
59 TimeStamp,
61 Json {
64 element: ConstructedElement,
65 whole: bool,
66 },
67 Untyped,
69}
70
71fn enumeration(value: ResolvedEnum) -> Option<Enumeration> {
73 value
74 .enumeration()
75 .map(|(type_name, names)| Enumeration { type_name, names })
76}
77
78fn flag_names<I: Iterator<Item = (&'static str, F)>, F>(names: I) -> Vec<(&'static str, usize)> {
81 names
82 .enumerate()
83 .map(|(bit, (name, _))| (name, bit))
84 .collect()
85}
86
87fn named_bits(property: PropertyIdentifier) -> Option<NamedBits> {
89 let (type_name, names) = match ResolvedBits::from_property(property, 0, &[]) {
90 ResolvedBits::StatusFlags(_) => {
91 ("StatusFlags", flag_names(StatusFlags::all().iter_names()))
92 }
93 ResolvedBits::EventTransitionBits(_) => (
94 "EventTransitionBits",
95 flag_names(EventTransitionBits::all().iter_names()),
96 ),
97 ResolvedBits::LimitEnable(_) => {
98 ("LimitEnable", flag_names(LimitEnable::all().iter_names()))
99 }
100 ResolvedBits::ServicesSupported(_) => (
101 "ServicesSupported",
102 ServiceSupported::ALL_NAMED
103 .iter()
104 .map(|&(name, value)| (name, usize::from(value.to_raw())))
105 .collect(),
106 ),
107 ResolvedBits::ObjectTypesSupported(_) => (
108 "ObjectTypesSupported",
109 ObjectType::ALL_NAMED
110 .iter()
111 .map(|&(name, value)| (name, value.to_raw() as usize))
112 .collect(),
113 ),
114 ResolvedBits::Unknown { .. } => return None,
115 };
116 let length = names.iter().map(|&(_, bit)| bit + 1).max().unwrap_or(0);
117 Some(NamedBits {
118 type_name,
119 names,
120 length,
121 })
122}
123
124fn holds_date_time(object_type: ObjectType, property: PropertyIdentifier) -> bool {
127 type O = ObjectType;
128 type P = PropertyIdentifier;
129 match property {
130 P::CHANGE_OF_STATE_TIME | P::TIME_OF_STATE_COUNT_RESET | P::TIME_OF_ACTIVE_TIME_RESET => {
131 is_binary(object_type)
132 }
133 P::START_TIME => [
134 O::TREND_LOG,
135 O::TREND_LOG_MULTIPLE,
136 O::EVENT_LOG,
137 O::LOAD_CONTROL,
138 ]
139 .contains(&object_type),
140 P::STOP_TIME => [O::TREND_LOG, O::TREND_LOG_MULTIPLE, O::EVENT_LOG].contains(&object_type),
141 P::MODIFICATION_DATE => object_type == O::FILE,
142 P::UPDATE_TIME => [O::PULSE_CONVERTER, O::TIMER].contains(&object_type),
143 P::COUNT_CHANGE_TIME => object_type == O::PULSE_CONVERTER,
144 P::EXPIRATION_TIME => [O::TIMER, O::ACCESS_CREDENTIAL].contains(&object_type),
145 P::ACTIVATION_TIME => object_type == O::ACCESS_CREDENTIAL,
146 P::VALUE_CHANGE_TIME => object_type == O::ACCUMULATOR,
147 P::PRESENT_VALUE | P::RELINQUISH_DEFAULT => is_date_time_value(object_type),
148 _ => false,
149 }
150}
151
152fn is_life_safety(object_type: ObjectType) -> bool {
154 [ObjectType::LIFE_SAFETY_POINT, ObjectType::LIFE_SAFETY_ZONE].contains(&object_type)
155}
156
157fn holds_life_safety_state(object_type: ObjectType, property: PropertyIdentifier) -> bool {
159 is_life_safety(object_type)
160 && [
161 PropertyIdentifier::PRESENT_VALUE,
162 PropertyIdentifier::ALARM_VALUES,
163 PropertyIdentifier::FAULT_VALUES,
164 PropertyIdentifier::LIFE_SAFETY_ALARM_VALUES,
165 ]
166 .contains(&property)
167}
168
169fn kind(object_type: ObjectType, property: PropertyIdentifier, index: Option<u32>) -> Kind {
170 if index == Some(0) {
172 return Kind::Untyped;
173 }
174 if let Some(number) = numeric(object_type, property, index) {
175 return Kind::Number(number);
176 }
177 if property == PropertyIdentifier::PRIORITY_ARRAY
178 && index.is_some()
179 && is_date_time_value(object_type)
180 {
181 return Kind::PriorityDateTime;
182 }
183 if holds_state(property) && is_binary(object_type) {
184 let enumeration = enumeration(ResolvedEnum::BinaryPV(BinaryPV::INACTIVE));
185 return enumeration.map_or(Kind::Untyped, |enumeration| Kind::Enum {
186 enumeration,
187 states: true,
188 });
189 }
190 if holds_state(property) && is_multistate(object_type) {
191 return Kind::State;
192 }
193 if holds_life_safety_state(object_type, property) {
194 let state = ResolvedEnum::LifeSafetyState(LifeSafetyState::QUIET);
195 if let Some(enumeration) = enumeration(state) {
196 return Kind::Enum {
197 enumeration,
198 states: false,
199 };
200 }
201 }
202 let tracking = property == PropertyIdentifier::TRACKING_VALUE;
205 if (!tracking || is_life_safety(object_type))
206 && let Some(enumeration) = enumeration(ResolvedEnum::from_property(property, 0))
207 {
208 return Kind::Enum {
209 enumeration,
210 states: false,
211 };
212 }
213 if let Some(bits) = named_bits(property) {
214 return Kind::Bits(bits);
215 }
216 if holds_date_time(object_type, property) {
217 return Kind::DateTime;
218 }
219 match property_element(object_type, property) {
220 Some((ConstructedElement::TimeStamp, shape))
221 if shape == ElementShape::Single || index.is_some() =>
222 {
223 Kind::TimeStamp
224 }
225 Some((element, shape)) if json::takes_json(element) => Kind::Json {
226 element,
227 whole: shape == ElementShape::Collection && index.is_none(),
228 },
229 _ => Kind::Untyped,
230 }
231}
232
233fn explicit(text: &str) -> bool {
236 ["null", "true", "false"]
237 .iter()
238 .any(|word| text.eq_ignore_ascii_case(word))
239 || text.starts_with('"')
240 || ["enumerated:", "date:", "time:", "object:", "bits("]
241 .iter()
242 .any(|prefix| text.starts_with(prefix))
243}
244
245fn folded(name: &str) -> String {
248 name.chars()
249 .filter(|c| *c != '_' && *c != '-')
250 .map(|c| c.to_ascii_lowercase())
251 .collect()
252}
253
254fn distance(a: &str, b: &str) -> usize {
256 let b: Vec<char> = b.chars().collect();
257 let mut row: Vec<usize> = (0..=b.len()).collect();
258 for (i, ca) in a.chars().enumerate() {
259 let mut previous = row[0];
260 row[0] = i + 1;
261 for (j, cb) in b.iter().enumerate() {
262 let substitute = previous + usize::from(ca != *cb);
263 previous = row[j + 1];
264 row[j + 1] = substitute.min(row[j] + 1).min(row[j + 1] + 1);
265 }
266 }
267 row[b.len()]
268}
269
270fn unknown_name<'a>(
273 what: &str,
274 typed: &str,
275 names: impl Iterator<Item = &'a str>,
276 number: &str,
277) -> String {
278 let names: Vec<&str> = names.collect();
279 let (label, shown): (&str, Vec<&str>) = if names.len() <= MAX_LISTED {
280 ("expected one of", names)
281 } else {
282 let typed = folded(typed);
283 let mut ranked: Vec<(bool, usize, &str)> = names
284 .into_iter()
285 .map(|name| {
286 let name_folded = folded(name);
287 (
288 !name_folded.contains(&typed),
289 distance(&typed, &name_folded),
290 name,
291 )
292 })
293 .collect();
294 ranked.sort();
295 (
296 "nearest",
297 ranked
298 .into_iter()
299 .take(NEAREST)
300 .map(|(_, _, n)| n)
301 .collect(),
302 )
303 };
304 let shown: Vec<String> = shown.into_iter().map(kebab).collect();
305 format!(
306 "unknown {what} '{typed}'; {label}: {}{number}",
307 shown.join(", ")
308 )
309}
310
311fn enum_value(enumeration: &Enumeration, text: &str) -> Result<u32, String> {
313 if !text.is_empty() && text.bytes().all(|b| b.is_ascii_digit()) {
314 return text
315 .parse::<u32>()
316 .map_err(|_| format!("{text} is out of an enumeration's range (0-4294967295)"));
317 }
318 let typed = folded(text);
319 enumeration
320 .names
321 .iter()
322 .find(|(name, _)| folded(name) == typed)
323 .map(|&(_, value)| value)
324 .ok_or_else(|| {
325 unknown_name(
326 enumeration.type_name,
327 text,
328 enumeration.names.iter().map(|(name, _)| *name),
329 " (or a number)",
330 )
331 })
332}
333
334fn without_state_text(text: &str) -> &str {
338 match text.find(" (") {
339 Some(at) if text.ends_with(')') => text[..at].trim_end(),
340 _ => text,
341 }
342}
343
344fn list_items(inner: &str) -> Vec<&str> {
347 let mut items = Vec::new();
348 let (mut depth, mut start) = (0usize, 0);
349 for (at, c) in inner.char_indices() {
350 match c {
351 '(' => depth += 1,
352 ')' => depth = depth.saturating_sub(1),
353 ',' if depth == 0 => {
354 items.push(&inner[start..at]);
355 start = at + 1;
356 }
357 _ => {}
358 }
359 }
360 items.push(&inner[start..]);
361 items
362}
363
364fn parse_enum(
367 enumeration: &Enumeration,
368 states: bool,
369 text: &str,
370) -> Result<PropertyValue, String> {
371 let one = |text: &str| {
372 let text = text.trim();
373 let text = if states {
374 without_state_text(text)
375 } else {
376 text
377 };
378 enum_value(enumeration, text.trim()).map(PropertyValue::Enumerated)
379 };
380 match text.strip_prefix('[').and_then(|t| t.strip_suffix(']')) {
381 Some(inner) if inner.trim().is_empty() => Ok(PropertyValue::List(Vec::new())),
382 Some(inner) => list_items(inner)
383 .into_iter()
384 .map(one)
385 .collect::<Result<_, _>>()
386 .map(PropertyValue::List),
387 None => one(text),
388 }
389}
390
391fn parse_bits(bits: &NamedBits, text: &str) -> Result<PropertyValue, String> {
395 let mut set = vec![false; bits.length];
396 if text.is_empty() {
399 return Err(format!(
400 "a {} value is needed: its bits' names, the bits as 0/1 digits, or none",
401 bits.type_name
402 ));
403 }
404 let digits = text.bytes().all(|b| b == b'0' || b == b'1');
405 if digits {
406 set = text.bytes().map(|b| b == b'1').collect();
407 } else if !text.eq_ignore_ascii_case("none") {
408 for name in text.split([',', '|']).map(str::trim) {
409 if name.is_empty() {
410 continue;
411 }
412 let typed = folded(name);
413 let bit = bits
414 .names
415 .iter()
416 .find(|(known, _)| folded(known) == typed)
417 .map(|&(_, bit)| bit)
418 .ok_or_else(|| {
419 unknown_name(
420 &format!("{} bit", bits.type_name),
421 name,
422 bits.names.iter().map(|(known, _)| *known),
423 " (or the bits as 0/1 digits, or none)",
424 )
425 })?;
426 set[bit] = true;
427 }
428 }
429 Ok(bit_string(&set))
430}
431
432pub(super) fn bit_string(set: &[bool]) -> PropertyValue {
434 let mut data = vec![0u8; set.len().div_ceil(8)];
435 for (bit, _) in set.iter().enumerate().filter(|(_, on)| **on) {
436 data[bit / 8] |= 0x80 >> (bit % 8);
437 }
438 PropertyValue::BitString {
439 unused_bits: (data.len() * 8 - set.len()) as u8,
440 data,
441 }
442}
443
444fn parse_date_time_value(text: &str) -> Result<PropertyValue, String> {
446 let (date, time) = parse_date_and_time(text)?;
447 Ok(PropertyValue::List(vec![
448 PropertyValue::Date(date),
449 PropertyValue::Time(time),
450 ]))
451}
452
453fn parse_date_and_time(text: &str) -> Result<(Date, Time), String> {
455 let (date, time) = parse_date_time(text)?;
456 let time: Time = match time {
457 Some(time) => time,
458 None => parse_time("any time")?,
459 };
460 Ok((date, time))
461}
462
463pub fn parse_value_for(
492 object_type: ObjectType,
493 property: PropertyIdentifier,
494 index: Option<u32>,
495 text: &str,
496) -> Result<PropertyValue, String> {
497 let text = text.trim();
498 if explicit(text) {
499 return parse_value(text);
500 }
501 match kind(object_type, property, index) {
502 Kind::Enum {
503 enumeration,
504 states,
505 } => parse_enum(&enumeration, states, text),
506 Kind::State => {
507 let state = without_state_text(text);
508 state.parse::<u64>().map(PropertyValue::Unsigned).map_err(|_| {
509 format!("'{state}' is not a state number; a multi-state state is its number, from 1, its text allowed after it: `2 (Heat)`")
510 })
511 }
512 Kind::Number(Number::Double) => match text.parse::<f64>() {
513 Ok(double) if text.contains(['.', 'e', 'E']) => finite(PropertyValue::Double(double)),
515 _ => coerce(Number::Double, parse_value(text)?),
516 },
517 Kind::Number(number) => coerce(number, parse_value(text)?),
518 Kind::PriorityDateTime => {
519 let (date, time) = parse_date_and_time(text)?;
520 let mut buf = BytesMut::new();
521 encode_priority_date_time(&mut buf, &date, &time);
522 Ok(PropertyValue::ApplicationData(buf.to_vec()))
523 }
524 Kind::Bits(bits) => parse_bits(&bits, text),
525 Kind::DateTime => parse_date_time_value(text),
526 Kind::TimeStamp => {
527 let stamp = parse_timestamp(text)?;
528 let mut buf = BytesMut::new();
529 encode_timestamp_choice(&mut buf, &stamp)
530 .map_err(|e| format!("cannot encode the timestamp: {e}"))?;
531 Ok(PropertyValue::ApplicationData(buf.to_vec()))
532 }
533 Kind::Json { element, whole } if text.starts_with(['[', '{']) => {
534 json::parse(element, whole, text)
535 }
536 Kind::Json { .. } | Kind::Untyped => parse_value(text),
537 }
538}
539
540pub fn parse_value_for_with_priority(
544 object_type: ObjectType,
545 property: PropertyIdentifier,
546 index: Option<u32>,
547 text: &str,
548) -> Result<(PropertyValue, Option<u8>), String> {
549 let (value, priority) = split_priority(text)?;
550 Ok((
551 parse_value_for(object_type, property, index, value)?,
552 priority,
553 ))
554}