Skip to main content

bacnet_objects/
traits.rs

1//! BACnetObject trait — the interface all BACnet objects implement.
2
3#[path = "object_storage.rs"]
4mod object_storage;
5use crate::prelude::*;
6pub(crate) use object_storage::ObjectStorageAccess;
7
8use alloc::borrow::Cow;
9use alloc::sync::Arc;
10use core::time::Duration;
11
12use bacnet_types::bitstring::EventTransitionBits;
13use bacnet_types::calendar::SpecificDate;
14use bacnet_types::constructed::{
15    BACnetEventLogRecord, BACnetLogMultipleRecord, BACnetLogRecord, BACnetObjectPropertyReference,
16};
17use bacnet_types::enums::{
18    ErrorClass, ErrorCode, EventState, LifeSafetyOperation, ObjectType, PropertyIdentifier,
19    Reliability,
20};
21use bacnet_types::error::Error;
22use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, Time};
23
24use crate::audit::{AuditLogNotificationSink, AuditLogStorage};
25use crate::clock::ClockReader;
26use crate::durable::DurableWrites;
27use crate::event::{
28    EnrollmentSummaryCapability, EventStateChange, EventTransitionCommit,
29    EventTransitionCommitError, TransitionOutcome,
30};
31use crate::event_enrollment::{
32    EventEnrollmentEvalState, EventEnrollmentMonitoredSource, EventEnrollmentReliabilityCommit,
33};
34use crate::file::{FileConfiguration, FileStorage};
35use crate::log_buffer::{LogBufferRecords, LogRecordIdentity};
36use crate::log_reporting::BufferReadyReport;
37use crate::schedule::{ScheduleTargetOutcome, ScheduleWrite};
38
39/// Process-local monotonic time source used by internal object lifecycles.
40#[doc(hidden)]
41pub type MonotonicClock = dyn Fn() -> Duration + Send + Sync;
42
43/// Wakes the server's monotonic task to read object deadlines again (#1384).
44#[doc(hidden)]
45pub type DeadlineWaker = dyn Fn() + Send + Sync;
46
47mod defaults;
48use defaults::{
49    array_property_default, cov_reported_properties_default, historical_writable_default,
50    list_property_default,
51};
52
53/// One property a whole-object (SubscribeCOV) notification reports after
54/// Present_Value and Status_Flags, from the object type's Table 13-1 row.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum CovReportedProperty {
57    /// Carried in each notification; a change of it alone sends none.
58    Value(PropertyIdentifier),
59    /// Carried in each notification, and any change of it sends one.
60    Trigger(PropertyIdentifier),
61}
62
63impl CovReportedProperty {
64    /// The reported property.
65    pub const fn property(self) -> PropertyIdentifier {
66        match self {
67            Self::Value(property) | Self::Trigger(property) => property,
68        }
69    }
70
71    /// Whether any change of the property triggers a notification.
72    pub const fn triggers(self) -> bool {
73        matches!(self, Self::Trigger(_))
74    }
75}
76
77/// Whether the stack's classification table marks `property` as a BACnetARRAY
78/// on `object_type`: the answer the default [`BACnetObject::is_array_property`]
79/// gives, and every built-in object's. The table holds every property a Clause
80/// 12 object table of the 2020 standard types as an array, served or not. A
81/// client can ask it about a remote object, before or without any value, to
82/// learn the property's shape; a vendor-defined array isn't in it.
83pub fn standard_array_property(object_type: ObjectType, property: PropertyIdentifier) -> bool {
84    array_property_default(object_type, property)
85}
86
87/// Whether the stack's classification table marks `property` as a BACnetLIST
88/// on `object_type`: the answer the default [`BACnetObject::is_list_property`]
89/// gives, and every built-in object's, covering every list of the 2020 object
90/// tables. Usable for a remote object as [`standard_array_property`] is.
91pub fn standard_list_property(object_type: ObjectType, property: PropertyIdentifier) -> bool {
92    list_property_default(object_type, property)
93}
94
95/// Result of applying a LifeSafetyOperation to an object.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum LifeSafetyOperationEffect {
98    /// The operation changed object state.
99    Applied,
100    /// The requested idempotent state was already present.
101    AlreadyApplied,
102}
103
104/// Result of applying a `LifeSafetyOperation` to object-owned state.
105///
106/// `changed_properties` contains exact committed readback changes in the object's
107/// stable reporting order, with each property at most once. Implementations own
108/// this projection; the server does not infer custom object mutations.
109/// [`LifeSafetyOperationEffect::AlreadyApplied`] means no state changed and must
110/// carry an empty property list.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct LifeSafetyOperationOutcome {
113    /// Whether the operation committed a change or was already applied.
114    pub effect: LifeSafetyOperationEffect,
115    /// Exact properties whose committed readback changed.
116    pub changed_properties: Vec<PropertyIdentifier>,
117}
118
119/// Result of one object-owned reliability evaluation pass.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum ReliabilityEvaluation {
122    /// The object made no reliability-related mutation.
123    Unchanged,
124    /// The object successfully mutated its `Reliability` value.
125    Changed {
126        /// Reliability before the successful mutation.
127        old_reliability: Reliability,
128        /// Reliability after the successful mutation.
129        new_reliability: Reliability,
130    },
131}
132
133/// The core trait for all BACnet objects.
134///
135/// Stored objects have crate-authorized concrete access; borrowed read views
136/// remain valid implementors. Applications cannot manufacture its access permit:
137/// ```compile_fail
138/// use bacnet_objects::traits::ObjectStorageAccess;
139/// let permit = ObjectStorageAccess(());
140/// ```
141/// Implementors represent a single BACnet object (Device, AnalogInput, etc.)
142/// and provide read/write access to their properties.
143pub trait BACnetObject: Send + Sync + object_storage::StoredObject {
144    /// Typed local Device authority for composition-owned configuration.
145    /// This does not grant network write access to the object database.
146    #[doc(hidden)]
147    fn device_authority_internal(&mut self) -> Option<crate::device::DeviceAuthority<'_>> {
148        None
149    }
150
151    /// Snapshot of object-owned target Audit overrides. Absent fields inherit
152    /// the selected Reporter; source reporting does not inspect remote policies.
153    #[doc(hidden)]
154    fn audit_object_policy_internal(&self) -> crate::audit::ObjectAuditPolicy {
155        crate::audit::ObjectAuditPolicy::default()
156    }
157
158    /// Optional sealed built-in policy assignment authority (AV/BV, Color and
159    /// Color Temperature). Custom objects
160    /// remain on their ordinary writer path; adapters may forward this capability.
161    #[doc(hidden)]
162    fn audit_policy_authority_internal(
163        &mut self,
164    ) -> Option<crate::audit::AuditPolicyAuthority<'_>> {
165        None
166    }
167
168    /// The optional built-in Reporter authority.
169    #[doc(hidden)]
170    fn audit_reporter_internal(&self) -> Option<&crate::audit::AuditReporterObject> {
171        None
172    }
173
174    /// Scoped built-in Reporter mutation authority; absent for other objects.
175    #[doc(hidden)]
176    fn audit_reporter_authority_internal(
177        &mut self,
178    ) -> Option<crate::audit::AuditReporterAuthority<'_>> {
179        None
180    }
181
182    /// Atomically replace all trusted local Reporter settings without a network write.
183    ///
184    /// The default opts out. Supporting implementations must reject invalid
185    /// settings before any mutation. `None` selectors remove Monitored_Objects
186    /// (catch-all); `Some(vec![])` retains the property and selects no ordinary
187    /// targets. An installed target owner prepares required records and lifecycle admission.
188    ///
189    /// Supply every field together, including the optional paired delay capability.
190    /// Active target ownership fixes pair presence; source ownership rejects it.
191    /// `None` omits both delay/control properties; a present zero is immediate.
192    #[doc(hidden)]
193    fn configure_audit_reporter_internal(
194        &mut self,
195        _level: bacnet_types::enums::AuditLevel,
196        _operations: bacnet_types::bitstring::AuditOperationFlags,
197        _confirmed: bool,
198        _selectors: Option<Vec<bacnet_types::constructed::BACnetObjectSelector>>,
199        _priorities: bacnet_types::bitstring::BACnetPriorityFilter,
200        _maximum_send_delay: Option<crate::audit::AuditSendDelay>,
201    ) -> Result<(), Error> {
202        Err(Error::protocol(
203            ErrorClass::OBJECT,
204            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
205        ))
206    }
207
208    /// The object's identifier (type + instance).
209    fn object_identifier(&self) -> ObjectIdentifier;
210
211    /// The object's name.
212    fn object_name(&self) -> &str;
213
214    /// Read a property value.
215    fn read_property(
216        &self,
217        property: PropertyIdentifier,
218        array_index: Option<u32>,
219    ) -> Result<PropertyValue, Error>;
220
221    /// Write a property value.
222    ///
223    /// Returning `Err` MUST leave the object unchanged. WritePropertyMultiple
224    /// retains earlier successful writes and cannot undo a mutation made by the
225    /// currently failing write, including for a write-only property.
226    ///
227    /// Refusing one element of a list value, an implementation may name it:
228    /// `Error::Structured` with `ErrorDetail::FirstFailedElementNumber`
229    /// holding the element's position, from 1, in the list `value` carries.
230    /// The server's AddListElement handler then reports the request element
231    /// at that position in its ChangeList-Error (#1048); a plain
232    /// `Error::protocol` there names the first element the list would gain.
233    ///
234    /// Make the checks that don't depend on the value (the property exists,
235    /// it is writable now, the array index is in range) before judging the
236    /// value's datatype, and refuse a value of the wrong datatype with
237    /// PROPERTY / INVALID_DATA_TYPE. The bundled server reads that refusal of
238    /// a NULL, on a property that isn't commandable and has no NULL in its
239    /// datatype, as a success that leaves the property as it is (Clauses
240    /// 15.9.2 and 19.2.1, #1396), so an access check made after the datatype
241    /// check would never be seen for a NULL.
242    fn write_property(
243        &mut self,
244        property: PropertyIdentifier,
245        array_index: Option<u32>,
246        value: PropertyValue,
247        priority: Option<u8>,
248    ) -> Result<(), Error>;
249
250    /// Write with explicit actual command provenance. Standalone callers assert
251    /// the origin; server boundaries additionally validate ingress or membership.
252    /// Decorators must forward this hook without discarding the origin.
253    /// The default preserves custom/unaffected object behavior. First-party
254    /// source-tracked commands require this hook and deny context-free writing.
255    fn write_property_from(
256        &mut self,
257        property: PropertyIdentifier,
258        array_index: Option<u32>,
259        value: PropertyValue,
260        priority: Option<u8>,
261        _origin: &crate::command_source::CommandOrigin,
262    ) -> Result<(), Error> {
263        self.write_property(property, array_index, value, priority)
264    }
265
266    /// Return canonical metadata for this object's effective property rows.
267    ///
268    /// Migrated implementations return every supported standard row for the
269    /// current instance, including `PROPERTY_LIST`. Borrowed rows cover static
270    /// or object-owned metadata; owned rows support dynamically assembled
271    /// per-instance sets. An empty borrowed default marks an object as unmigrated.
272    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
273        Cow::Borrowed(&[])
274    }
275
276    /// List all properties this object supports in the legacy projection.
277    ///
278    /// For migrated objects this includes Object_Identifier, Object_Name, and
279    /// Object_Type but omits Property_List. Reading the BACnet Property_List
280    /// property applies the additional wire-level universal-property filter.
281    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]>;
282
283    /// Bind or remove the database's shared wall-clock reader.
284    ///
285    /// Objects that do not expose clock-derived state ignore this internal
286    /// lifecycle hook.
287    #[doc(hidden)]
288    fn bind_clock_internal(&mut self, _clock: Option<Arc<dyn ClockReader>>) {}
289
290    /// Advance object-owned operations that use monotonic elapsed time.
291    ///
292    /// The server calls this internal hook from a dedicated lifecycle task.
293    /// `true` means readable state changed and generic COV processing is
294    /// required. The default is a source-compatible no-op for downstream
295    /// object implementations.
296    #[doc(hidden)]
297    fn advance_time_internal(&mut self, _elapsed: Duration) -> bool {
298        false
299    }
300
301    /// Bind the process-local monotonic source used when operations arm.
302    #[doc(hidden)]
303    fn bind_monotonic_clock_internal(&mut self, _clock: Option<Arc<MonotonicClock>>) {}
304
305    /// Bind the waker an object calls when a write arms a monotonic deadline,
306    /// so the server's task looks again sooner than its next routine pass.
307    #[doc(hidden)]
308    fn bind_deadline_waker_internal(&mut self, _waker: Option<Arc<DeadlineWaker>>) {}
309
310    /// The device is starting: drive the object's output as a start does.
311    ///
312    /// The server calls this once for every object in the database as it
313    /// starts, before the database is shared. The Color and Color
314    /// Temperature objects drive an output nothing has driven yet to its
315    /// default (#1526); other objects ignore it.
316    #[doc(hidden)]
317    fn start_output_internal(&mut self) {}
318
319    /// Advance operations to an absolute process-local monotonic instant.
320    #[doc(hidden)]
321    fn advance_monotonic_time_internal(&mut self, _now: Duration) -> bool {
322        false
323    }
324
325    /// Return the next absolute process-local operation deadline, if any.
326    #[doc(hidden)]
327    fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
328        None
329    }
330
331    /// Take the finest COV increment an active property subscription to
332    /// Tracking_Value asks for, or `None` when no such subscription gives
333    /// one (#1510).
334    ///
335    /// The server's monotonic task passes it before each advance, so an
336    /// object whose fade or ramp samples Tracking_Value for COV samples it
337    /// at least that finely, still no more often than its sample grid
338    /// allows. The value is finite and not negative. Objects without such a
339    /// Tracking_Value ignore it.
340    #[doc(hidden)]
341    fn set_tracking_cov_increment_internal(&mut self, _finest: Option<f64>) {}
342
343    /// Freeze COV-readable state while the object's mutation lock is held.
344    #[doc(hidden)]
345    fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
346        None
347    }
348
349    /// Return how many blink-warn notifications a lighting object has
350    /// requested, for objects that model them.
351    ///
352    /// This is an internal conformance-test channel, not a BACnet property,
353    /// host callback, or physical-output claim.
354    #[doc(hidden)]
355    fn lighting_blink_count_internal(&self) -> u64 {
356        0
357    }
358
359    /// Whether a property write route can accept `property` for this object.
360    ///
361    /// PICS generation and runtime dispatch MUST consult this (or
362    /// `write_property_from` where source authority is required) rather than a
363    /// separate heuristic, so the PICS
364    /// writable flags cannot drift from the actual write routes. The default
365    /// reproduces the historical PICS heuristic (see
366    /// `historical_writable_default`) so unmigrated object types keep their
367    /// current PICS output. Object implementations override to mirror their
368    /// real write capabilities, including required command origin and ownership.
369    ///
370    /// Universal read-only properties (`OBJECT_IDENTIFIER`, `OBJECT_TYPE`,
371    /// `PROPERTY_LIST`, `STATUS_FLAGS`) are always non-writable and are
372    /// excluded by the default; overrides should preserve that invariant.
373    ///
374    /// Built-in analog, binary, multi-state, Event Enrollment, and Alert
375    /// Enrollment objects also deny network writes to `ACKED_TRANSITIONS`.
376    /// The property descriptions accompanying ASHRAE 135-2020 Tables 12-2/3/4,
377    /// 12-6/8/10, 12-21/22/23, 12-14, and 12-61 explicitly make it read-only;
378    /// the table's `R`/`O` classification alone is not a write prohibition.
379    /// Clause 13.2.3 assigns acknowledgment-bit maintenance to internal
380    /// transition and acknowledgment processing. Those lifecycle hooks and
381    /// supported AcknowledgeAlarm paths do not use `write_property`, so this
382    /// network restriction does not prevent their internal updates.
383    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
384        let metadata = self.property_metadata();
385        if metadata.is_empty() {
386            historical_writable_default(self.object_identifier().object_type(), property)
387        } else {
388            crate::property_metadata::is_writable_in_metadata(metadata.as_ref(), property)
389        }
390    }
391
392    /// Whether `property` accepts an array index on this object.
393    ///
394    /// Per Clause 12.1.5.1, only BACnetARRAY (and BACnetARRAY of BACnetLIST)
395    /// properties accept an array index; Clause 12.1.5.2 makes ReadRange the
396    /// only positional access to a BACnetLIST. The RP/RPM/WP/WPM service
397    /// handlers gate the request's array index on this query and reject a
398    /// supplied index on a non-array property with PROPERTY /
399    /// PROPERTY_IS_NOT_AN_ARRAY (Clause 15.5.1.3, Clause 15.9.1.3).
400    ///
401    /// The default reproduces the standard's classification (see
402    /// `array_property_default`): identifier-stable arrays are admitted
403    /// without consulting the object type, the identifiers whose datatype
404    /// changes with the object type (ACTION, ALARM_VALUES / FAULT_VALUES,
405    /// LIST_OF_OBJECT_PROPERTY_REFERENCES, LOG_DEVICE_OBJECT_PROPERTY,
406    /// PRESENT_VALUE) classify by `object_identifier().object_type()`, and
407    /// everything else — scalars and BACnetLIST properties — rejects the
408    /// index. The built-in objects keep the default, so clients that classify
409    /// with [`standard_array_property`] agree with them; object
410    /// implementations with vendor array properties override.
411    fn is_array_property(&self, property: PropertyIdentifier) -> bool {
412        array_property_default(self.object_identifier().object_type(), property)
413    }
414
415    /// Whether `property` is a BACnetLIST on this object.
416    ///
417    /// AddListElement and RemoveListElement (Clauses 15.1 and 15.2) edit only
418    /// BACnetLIST properties, and ReadRange (Clause 15.8) reads only them. The
419    /// server's handlers ask this before decoding any element or selecting any
420    /// item, and refuse every other target, including an array element, with
421    /// SERVICES / PROPERTY_IS_NOT_A_LIST. Like
422    /// [`Self::is_array_property`], the answer follows the property's datatype,
423    /// not the shape of a value read: a whole array also reads as a list, and
424    /// constructed single values often read as framed bytes.
425    ///
426    /// The default reproduces the standard's classification (see
427    /// `list_property_default`): identifier-stable lists are admitted on every
428    /// object type, and the identifiers whose datatype changes with the object
429    /// type (ALARM_VALUES / FAULT_VALUES, LIST_OF_OBJECT_PROPERTY_REFERENCES,
430    /// PRESENT_VALUE, MEMBER_OF) classify by `object_identifier().object_type()`.
431    /// The built-in objects keep the default; object implementations with
432    /// vendor list properties override.
433    fn is_list_property(&self, property: PropertyIdentifier) -> bool {
434        list_property_default(self.object_identifier().object_type(), property)
435    }
436
437    /// Whether this object type can be created at runtime via CreateObject.
438    ///
439    /// Default `false`; override `true` only for types the network factory
440    /// (`handle_create_object`) actually constructs, so PICS createability
441    /// matches the runtime factory with no separate list to drift.
442    fn is_createable(&self) -> bool {
443        false
444    }
445
446    /// The properties whose whole value a CreateObject initial value may set
447    /// on this object although WriteProperty can't change it afterwards.
448    ///
449    /// Clause 15.3 doesn't limit initial values to the properties
450    /// WriteProperty can change, so a device may take some of its read-only
451    /// properties at creation. The bundled server hands such an initial value,
452    /// written without an array index, to [`Self::initialize_property`]
453    /// instead of the write route; every other initial value goes the way a
454    /// WriteProperty does. The PICS lists the set for each createable type.
455    /// Default empty: no property is set only at creation.
456    fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
457        &[]
458    }
459
460    /// Apply a CreateObject initial value to one of
461    /// [`Self::creation_only_properties`], written whole.
462    ///
463    /// The server calls this only on the object it is creating, before any
464    /// other request can see it. Check the value as a write would, and on
465    /// `Err` leave the object unchanged: the server then refuses the request,
466    /// naming the initial value, and drops the object. As on the write
467    /// route, an initial value that is one application NULL and that this
468    /// refuses with PROPERTY / INVALID_DATA_TYPE leaves the property as it
469    /// is and counts as applied, so refuse a NULL that way. The default
470    /// refuses every property with PROPERTY / WRITE_ACCESS_DENIED, the
471    /// answer for a property that can't be initialized at creation (Clause
472    /// 15.3.1.3.1).
473    ///
474    /// The bundled server's CreateObject builds only built-in objects today
475    /// (Analog Input and Output, the binary types and the multi-state
476    /// types), so on a custom object this hook is reached only by a caller
477    /// that builds the object itself.
478    fn initialize_property(
479        &mut self,
480        _property: PropertyIdentifier,
481        _value: PropertyValue,
482    ) -> Result<(), Error> {
483        Err(Error::protocol(
484            ErrorClass::PROPERTY,
485            ErrorCode::WRITE_ACCESS_DENIED,
486        ))
487    }
488
489    /// Whether this object type can be deleted at runtime via DeleteObject.
490    ///
491    /// Default `true`; override `false` on object types that are not
492    /// deleteable (e.g. `Device`, `NetworkPort`).
493    fn is_deleteable(&self) -> bool {
494        true
495    }
496
497    /// List the REQUIRED properties for this object type.
498    ///
499    /// Migrated objects derive this set from canonical `R` and `W` rows,
500    /// including Property_List. Unmigrated objects retain the historical four
501    /// universal properties. Service-specific consumers may exclude
502    /// Property_List where their protocol contract requires it.
503    fn required_properties(&self) -> Cow<'static, [PropertyIdentifier]> {
504        let metadata = self.property_metadata();
505        if !metadata.is_empty() {
506            return crate::property_metadata::required_properties_from_metadata(metadata.as_ref());
507        }
508        static UNIVERSAL: [PropertyIdentifier; 4] = [
509            PropertyIdentifier::OBJECT_IDENTIFIER,
510            PropertyIdentifier::OBJECT_NAME,
511            PropertyIdentifier::OBJECT_TYPE,
512            PropertyIdentifier::PROPERTY_LIST,
513        ];
514        Cow::Borrowed(&UNIVERSAL)
515    }
516
517    /// Whether this object type supports COV notifications.
518    ///
519    /// Override to return `true` for object types that can generate COV
520    /// notifications (analog, binary, multi-state I/O/V). Default is `false`.
521    /// This answer admits SubscribeCOV, the whole-object form, and is the
522    /// default for
523    /// [`supports_subscribe_cov_property`](Self::supports_subscribe_cov_property).
524    fn supports_cov(&self) -> bool {
525        false
526    }
527
528    /// Whether SubscribeCOVProperty and SubscribeCOVPropertyMultiple may
529    /// monitor this object's properties.
530    ///
531    /// Defaults to [`supports_cov`](Self::supports_cov). An object that has
532    /// no whole-object COV criteria, because Clause 13.1's Table 13-1 doesn't
533    /// list its type, can still return `true` here while `supports_cov`
534    /// stays `false`: its property subscriptions then follow Table 13-1a and
535    /// SubscribeCOV is refused. The built-in Averaging object does this.
536    /// [`supports_cov_property`](Self::supports_cov_property) still decides
537    /// each property.
538    fn supports_subscribe_cov_property(&self) -> bool {
539        self.supports_cov()
540    }
541
542    /// Take pending local target work from a Staging object.
543    ///
544    /// The default keeps every non-Staging object source-compatible. The
545    /// bundled server calls this only while it owns the database mutation
546    /// guard, then performs the returned writes after releasing that guard.
547    #[doc(hidden)]
548    fn take_staging_write_plan_internal(&mut self) -> Option<crate::staging::StagingWritePlan> {
549        None
550    }
551
552    /// Return the current Staging transition generation, if applicable.
553    #[doc(hidden)]
554    fn staging_generation_internal(&self) -> Option<u64> {
555        None
556    }
557
558    /// Commit one guarded Staging plan's success or failure to Reliability.
559    ///
560    /// Returns whether readable source state changed. Implementations ignore a
561    /// stale generation so older work cannot fault a newer transition.
562    #[doc(hidden)]
563    fn complete_staging_write_plan_internal(&mut self, _generation: u64, _success: bool) -> bool {
564        false
565    }
566
567    /// Take the writes a Command or Channel object's Present_Value write
568    /// queued: a Command's selected list, or a Channel's value for its
569    /// members.
570    ///
571    /// The default keeps every other object source-compatible. The bundled
572    /// server takes it under the guard that committed the write, then makes
573    /// the writes after releasing that guard.
574    #[doc(hidden)]
575    fn take_command_run_internal(&mut self) -> Option<crate::command::CommandRun> {
576        None
577    }
578
579    /// Return the current Command or Channel run generation, if applicable.
580    #[doc(hidden)]
581    fn command_generation_internal(&self) -> Option<u64> {
582        None
583    }
584
585    /// Record how command `command` of a Command's running list fared.
586    ///
587    /// Returns whether the run is still the current one. Implementations
588    /// ignore a stale generation, so older work can't mark a newer run.
589    #[doc(hidden)]
590    fn record_command_write_internal(
591        &mut self,
592        _generation: u64,
593        _command: usize,
594        _success: bool,
595    ) -> bool {
596        false
597    }
598
599    /// Keep `datatype`, learned for a Channel's member in another device, for
600    /// the distributions after this one, or forget the one kept with `None`:
601    /// the member at zero-based `slot` of List_Of_Object_Property_References,
602    /// if that slot still holds `reference`. Writing the member, or the whole
603    /// list, forgets it too.
604    #[doc(hidden)]
605    fn remember_member_datatype_internal(
606        &mut self,
607        _slot: usize,
608        _reference: &bacnet_types::constructed::BACnetDeviceObjectPropertyReference,
609        _datatype: Option<crate::channel::MemberDatatype>,
610    ) {
611    }
612
613    /// End the current run, setting a Command's All_Writes_Successful or a
614    /// Channel's Write_Status and Reliability. `outcome` is `Ok` when every
615    /// write was made and succeeded, otherwise the run's first failure.
616    ///
617    /// Returns whether readable state changed; a stale generation is ignored.
618    #[doc(hidden)]
619    fn complete_command_run_internal(
620        &mut self,
621        _generation: u64,
622        _outcome: Result<(), crate::command::WriteFailure>,
623    ) -> bool {
624        false
625    }
626
627    /// Return this object's GetEnrollmentSummary event capability.
628    ///
629    /// The default opts custom and downstream objects out. Implementations opt
630    /// in only when they own an actual configured or implied event algorithm
631    /// and shared committed-transition history.
632    #[doc(hidden)]
633    fn enrollment_summary_capability_internal(&self) -> Option<EnrollmentSummaryCapability> {
634        None
635    }
636
637    /// Whether a readable property supports property-specific COV.
638    ///
639    /// The default preserves the existing behavior of every other COV-capable
640    /// object family while enforcing the bounded standardized Life Safety
641    /// surface for source-compatible custom Point and Zone implementations.
642    /// Other types answer
643    /// [`supports_subscribe_cov_property`](Self::supports_subscribe_cov_property)
644    /// for every property.
645    fn supports_cov_property(&self, property: PropertyIdentifier) -> bool {
646        match self.object_identifier().object_type() {
647            ObjectType::LIFE_SAFETY_POINT | ObjectType::LIFE_SAFETY_ZONE => matches!(
648                property,
649                PropertyIdentifier::PRESENT_VALUE
650                    | PropertyIdentifier::STATUS_FLAGS
651                    | PropertyIdentifier::TRACKING_VALUE
652                    | PropertyIdentifier::SILENCED
653                    | PropertyIdentifier::OPERATION_EXPECTED
654            ),
655            _ => self.supports_subscribe_cov_property(),
656        }
657    }
658
659    /// COV increment for this object (objects with a COV_Increment property).
660    ///
661    /// Returns `Some(increment)` for objects that use COV_Increment filtering
662    /// (e.g., AnalogInput, AnalogOutput, AnalogValue, Loop, Staging, and the
663    /// Integer, Positive Integer and Large Analog Value types). A notification
664    /// fires only when the numeric Present_Value delta reaches the increment.
665    /// Property COV inherits this increment only for numeric Present_Value;
666    /// other selected properties use their own supplied increment or typed
667    /// change reporting.
668    ///
669    /// The increment is an `f64` so every COV_Increment datatype fits without
670    /// rounding: a REAL widens exactly, a Large Analog Value's Double is kept
671    /// as is, and an Unsigned increment is exact up to 2^53.
672    ///
673    /// Returns `None` for objects that notify on any state change (binary, multi-state).
674    fn cov_increment(&self) -> Option<f64> {
675        None
676    }
677
678    /// Properties a whole-object (SubscribeCOV) notification reports after
679    /// its leading value and Status_Flags, in report order.
680    ///
681    /// The leading value is Present_Value, except on Access Point, whose
682    /// Table 13-1 row leads with Access_Event; the server chooses it by object
683    /// type. The default follows the object type's Table 13-1 row:
684    ///
685    /// - Access Door: Door_Alarm_State, a trigger.
686    /// - Access Point: Access_Event_Tag, Access_Event_Time (a trigger),
687    ///   Access_Event_Credential and Access_Event_Authentication_Factor.
688    /// - Credential Data Input: Update_Time, a trigger.
689    /// - Load Control: Requested_Shed_Level, Start_Time, Shed_Duration and
690    ///   Duty_Window, all triggers.
691    /// - Loop: Setpoint and Controlled_Variable_Value.
692    /// - Pulse Converter: Update_Time.
693    /// - Staging: Present_Stage, a trigger.
694    ///
695    /// A trigger's change sends a notification by itself; any other listed
696    /// value only rides along. Every other type reports nothing more. The
697    /// server leaves out a listed property the object's Property_List lacks.
698    /// Property subscriptions (SubscribeCOVProperty and
699    /// SubscribeCOVPropertyMultiple) report their own property instead.
700    fn cov_reported_properties(&self) -> &'static [CovReportedProperty] {
701        cov_reported_properties_default(self.object_identifier().object_type())
702    }
703
704    /// Set the OVERRIDDEN bit in StatusFlags.
705    ///
706    /// For software-only objects this is always FALSE per spec. Hardware
707    /// integrations can override to set TRUE when present_value is overridden
708    /// by physical means (e.g., a manual switch on an output).
709    fn set_overridden(&mut self, _overridden: bool) {}
710
711    /// Evaluate intrinsic reporting after a present_value change.
712    ///
713    /// This is the per-write entry point: it seeds (or cancels) a pending
714    /// delayed transition and fires immediately only when `Time_Delay == 0`.
715    /// It never advances the `Time_Delay` countdown — repeated writes to the
716    /// same value do not shorten the delay (ASHRAE 135-2020 Clause 13.3 counts
717    /// the delay in seconds, so the countdown advances once per elapsed second
718    /// via [`tick_intrinsic_reporting`](Self::tick_intrinsic_reporting)).
719    ///
720    /// Returns `Some(TransitionOutcome)` whenever a transition is ready to be
721    /// committed, or
722    /// `None` when none did (no change, delay seeded, or the object does not
723    /// support intrinsic reporting). A cleared `Event_Enable` bit sets the
724    /// outcome's `distribute` flag to false rather than withholding the
725    /// transition. Implementations must leave `Event_State`,
726    /// `Acked_Transitions`, event history, and fire-ready detector state
727    /// unchanged until [`commit_event_transition_internal`](Self::commit_event_transition_internal)
728    /// succeeds. Clause 13.2.2.1.4's transition actions run either way, and
729    /// `Event_Enable` disables only external distribution, downstream in the
730    /// notification-distribution process (Clause 13.2.5).
731    fn evaluate_intrinsic_reporting(&mut self) -> Option<TransitionOutcome> {
732        None
733    }
734
735    /// Advance the `Time_Delay` countdown for a pending transition.
736    ///
737    /// Called by the server's one-second intrinsic-reporting task. Fires the
738    /// pending transition when its delay elapses this tick, cancels it if the
739    /// triggering condition reverted, and returns `Some(TransitionOutcome)`
740    /// when a transition is ready. A fire-ready proposal must remain
741    /// retryable until the commit hook succeeds. As with
742    /// [`evaluate_intrinsic_reporting`](Self::evaluate_intrinsic_reporting),
743    /// `Event_Enable` is reported via `distribute`, not by returning `None`.
744    /// Objects without a delayed transition return `None`.
745    fn tick_intrinsic_reporting(&mut self) -> Option<TransitionOutcome> {
746        None
747    }
748
749    /// Atomically commit all object-owned state for one event transition.
750    ///
751    /// The caller supplies an exact state change, its transition coordinate,
752    /// the resolved Notification Class `Ack_Required` value, a typed
753    /// timestamp, and an optional message. Implementations must validate the
754    /// coordinate and source state before changing `Event_State`,
755    /// `Acked_Transitions`, `Event_Time_Stamps`, or stored message state, and
756    /// must leave every value unchanged on error. A successful
757    /// implementation also finalizes the detector's pending and fault-edge
758    /// state; a failed commit leaves that state retryable.
759    ///
760    /// This internal channel is deliberately separate from network property
761    /// writes and notification distribution. The default fails closed so an
762    /// object participates only after implementing this contract. Custom objects
763    /// can own and commit their state directly using the public
764    /// [`EventTransitionCommit`] and [`EventTransitionCommitError`] types;
765    /// no built-in commit kernel is required. Both server intrinsic paths
766    /// require success before distributing a notification.
767    #[doc(hidden)]
768    fn commit_event_transition_internal(
769        &mut self,
770        _commit: EventTransitionCommit,
771    ) -> Result<(), EventTransitionCommitError> {
772        Err(EventTransitionCommitError::Unsupported)
773    }
774
775    /// Atomically commit Event Enrollment Reliability and transition state.
776    ///
777    /// This Event Enrollment-specific channel joins `Reliability` to the
778    /// existing atomic `Event_State`, `Acked_Transitions`, and
779    /// `Event_Time_Stamps` commit. Implementations must stage every supplied
780    /// value before assigning object-owned fields and leave them unchanged on
781    /// error. The default fails closed for custom objects that have not adopted
782    /// the stronger contract.
783    #[doc(hidden)]
784    fn commit_event_enrollment_reliability_internal(
785        &mut self,
786        _commit: EventEnrollmentReliabilityCommit,
787    ) -> Result<(), EventTransitionCommitError> {
788        Err(EventTransitionCommitError::Unsupported)
789    }
790
791    /// Evaluate this object's schedule at `time` on `today` (Clause 12.24.4).
792    ///
793    /// `calendar_active` answers for a special event whose period references
794    /// a Calendar: whether that Calendar is TRUE on `today`. Returns the
795    /// writes owed when Present_Value changed, the object has just entered
796    /// its Effective_Period, or its references or priority changed since the
797    /// last write, with the complete local references, target array indices
798    /// included. Otherwise, while some references refused their last write,
799    /// returns a [`retry`](ScheduleWrite::retry) of the current value to
800    /// those references only (#1436). Only meaningful for Schedule objects;
801    /// default returns `None`.
802    fn tick_schedule(
803        &mut self,
804        _today: SpecificDate,
805        _time: Time,
806        _calendar_active: &dyn Fn(ObjectIdentifier) -> bool,
807    ) -> Option<ScheduleWrite> {
808        None
809    }
810
811    /// Take the writes this schedule owes its targets apart from its
812    /// calculation, each once, in order: a NULL to every priority slot a
813    /// change of List_Of_Object_Property_References or Priority_For_Writing
814    /// left behind (#1088), then the Present_Value owed out of service, for a
815    /// client's write (Clause 12.24.14) or a change of those two properties.
816    ///
817    /// A schedule pass collects them before calling
818    /// [`tick_schedule`](Self::tick_schedule), so they reach the targets ahead
819    /// of any calculated value; they need no clock. Only meaningful for
820    /// Schedule objects; default returns none.
821    fn take_owed_schedule_writes(&mut self) -> Vec<ScheduleWrite> {
822        Vec::new()
823    }
824
825    /// Report how each target took `write`, one of this schedule's writes:
826    /// `outcomes` holds one entry per member of `write.references`, in order.
827    ///
828    /// A Schedule judges the reference half of its Reliability from these
829    /// (Clause 12.24.13, #1086). Returns whether a readable property, such as
830    /// Reliability, changed. Only meaningful for Schedule objects; default
831    /// returns `false`.
832    fn complete_schedule_write(
833        &mut self,
834        _write: &ScheduleWrite,
835        _outcomes: &[ScheduleTargetOutcome],
836    ) -> bool {
837        false
838    }
839
840    /// The [`retry`](ScheduleWrite::retry) of the current value to the
841    /// references whose last write was refused and that name `target`, an
842    /// object just added to the database (#1440), so the refusal can clear
843    /// without waiting for the next pass. `None` when no such refusal
844    /// stands, or when the pass itself would retry nothing: out of service,
845    /// outside Effective_Period, or with a NULL Present_Value. Only
846    /// meaningful for Schedule objects; default returns `None`.
847    fn retry_refusals_naming(&self, _target: ObjectIdentifier) -> Option<ScheduleWrite> {
848        None
849    }
850
851    /// Whether this Calendar's Date_List matches `day`: its Present_Value on
852    /// that day. `None` for an object that does not evaluate a date list; the
853    /// schedule tick then reads its Present_Value instead.
854    #[doc(hidden)]
855    fn calendar_state_internal(&self, _day: SpecificDate) -> Option<bool> {
856        None
857    }
858
859    /// Acknowledge an alarm transition. Sets the `transition_bit` flags in acked_transitions.
860    /// Returns Ok(()) if the object supports event detection, Err otherwise.
861    fn acknowledge_alarm(
862        &mut self,
863        _transition_bit: EventTransitionBits,
864    ) -> Result<(), bacnet_types::error::Error> {
865        Err(bacnet_types::error::Error::protocol(
866            bacnet_types::enums::ErrorClass::OBJECT,
867            bacnet_types::enums::ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
868        ))
869    }
870
871    /// Correlate an acknowledgment with the latest committed transition.
872    #[doc(hidden)]
873    fn acknowledge_alarm_correlated_internal(
874        &mut self,
875        _event_state: EventState,
876        _timestamp: &BACnetTimeStamp,
877    ) -> Result<(), Error> {
878        Err(Error::protocol(
879            ErrorClass::OBJECT,
880            ErrorCode::NO_ALARM_CONFIGURED,
881        ))
882    }
883
884    /// Correlate an acknowledgment and report exact object-owned history.
885    ///
886    /// The default delegates to the source-compatible coarse hook and returns
887    /// no notification context. Custom objects that implement only the coarse
888    /// hook therefore retain their accepted-acknowledgment behavior without the
889    /// bundled server guessing historical states for an ACK notification.
890    #[doc(hidden)]
891    fn acknowledge_alarm_correlated_detailed_internal(
892        &mut self,
893        event_state: EventState,
894        timestamp: &BACnetTimeStamp,
895    ) -> Result<Option<EventStateChange>, Error> {
896        self.acknowledge_alarm_correlated_internal(event_state, timestamp)
897            .map(|()| None)
898    }
899
900    /// Apply a LifeSafetyOperation atomically to this object.
901    ///
902    /// Implementations must leave the object unchanged when returning `Err`.
903    /// They run synchronously under the object-database write lock and must be
904    /// fast, nonblocking, and panic-free. External or irreversible actuation
905    /// also needs an application-owned idempotency/replay contract. The default
906    /// reports that the object does not support this service. Successful
907    /// implementations return an outcome with exact, ordered, deduplicated
908    /// committed property changes. `AlreadyApplied` leaves all state unchanged
909    /// and reports no deltas. The bundled server uses these changes for COV
910    /// after releasing the database lock and dispatching the service response.
911    fn apply_life_safety_operation(
912        &mut self,
913        _operation: LifeSafetyOperation,
914    ) -> Result<LifeSafetyOperationOutcome, Error> {
915        Err(Error::protocol(
916            ErrorClass::OBJECT,
917            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
918        ))
919    }
920
921    /// Set the next LifeSafetyOperation expected by trusted local logic.
922    ///
923    /// This is an application-facing state channel, not a network property
924    /// write. The default reports that the object does not support the state.
925    fn set_life_safety_operation_expected_internal(
926        &mut self,
927        _operation: LifeSafetyOperation,
928    ) -> Result<(), Error> {
929        Err(Error::protocol(
930            ErrorClass::OBJECT,
931            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
932        ))
933    }
934
935    /// Apply an internally-detected `Event_State` transition.
936    ///
937    /// This is the **internal** lifecycle path for the algorithmically-derived
938    /// `Event_State` on objects such as Event Enrollment (ASHRAE 135-2020
939    /// Clause 12.12). It is deliberately distinct from the network
940    /// [`write_property`](Self::write_property) route: `Event_State` is
941    /// read-only over the network, so network writes are rejected while the
942    /// server's evaluator reaches the field through this method. Objects
943    /// without an algorithmic `Event_State` return
944    /// `OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED`.
945    ///
946    /// The **default** returns `Err`, so objects without an algorithmic
947    /// `Event_State` opt out. Objects that do model one (e.g.
948    /// `EventEnrollmentObject`) override this to store the value verbatim:
949    /// the only caller is a trusted internal evaluator that passes a modeled
950    /// [`EventState`], mirroring the inherent `set_event_state` builder and
951    /// the existing read arm. Network-facing validation — rejecting all
952    /// `Event_State` writes — lives in [`write_property`](Self::write_property),
953    /// not here. Implementations must leave `Event_State` unchanged when they
954    /// return `Err`.
955    fn set_event_state_internal(&mut self, _state: EventState) -> Result<(), Error> {
956        Err(Error::protocol(
957            ErrorClass::OBJECT,
958            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
959        ))
960    }
961
962    /// Snapshot this object's Event Enrollment evaluation state, if it models one.
963    ///
964    /// This is the read half of the internal channel the server's Event
965    /// Enrollment evaluator uses to persist per-enrollment algorithm state
966    /// across evaluation cycles: the pending (delayed) transition countdown,
967    /// the CHANGE_OF_VALUE detection baseline (the monitored sample at the
968    /// latest NORMAL indication, per Clause 13.3.3), and
969    /// the monitored value behind the most recent OFFNORMAL transition (Clause
970    /// 13.3.2 condition (c)). Like [`set_event_state_internal`](Self::set_event_state_internal)
971    /// it deliberately bypasses the network property model: none of the three
972    /// slots is a BACnet property, and 135-2020 assigns their initialization
973    /// to local matters.
974    ///
975    /// The default returns `None` — objects without algorithmic event
976    /// detection carry no such state, and the evaluator treats `None` as an
977    /// empty state it cannot write back (delay honoring and the COV baseline
978    /// then stay unavailable, matching this crate's pre-delay behavior).
979    fn enrollment_eval_state_internal(&self) -> Option<EventEnrollmentEvalState> {
980        None
981    }
982
983    /// Store this object's Event Enrollment evaluation state.
984    ///
985    /// The write half of [`enrollment_eval_state_internal`](Self::enrollment_eval_state_internal).
986    /// The only caller is the trusted server evaluator, passing a state it
987    /// derived from a prior snapshot plus the current cycle's evaluation.
988    /// Implementations enforce the Clause 13.2.2.1 invariant by construction:
989    /// transitions are prohibited while `Event_Detection_Enable` is FALSE,
990    /// so a write arriving then is refused rather than queued (and the
991    /// detection-disable reset has already cleared the fields).
992    ///
993    /// The **default** returns `Err`, so objects without enrollment evaluation
994    /// state opt out and the evaluator's write-back is dropped, never stored
995    /// into an object that does not model it.
996    fn set_enrollment_eval_state_internal(
997        &mut self,
998        _state: EventEnrollmentEvalState,
999    ) -> Result<(), Error> {
1000        Err(Error::protocol(
1001            ErrorClass::OBJECT,
1002            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1003        ))
1004    }
1005
1006    /// Snapshot the monitored source that owns Event Enrollment private state.
1007    ///
1008    /// The outer `Option` indicates whether the object supports this channel;
1009    /// the inner `Option` is empty before a source has been established.
1010    /// The server stores source ownership in its object database when an
1011    /// object implements evaluation state but leaves this channel unsupported.
1012    fn enrollment_eval_source_internal(&self) -> Option<Option<EventEnrollmentMonitoredSource>> {
1013        None
1014    }
1015
1016    /// Store or clear the monitored source that owns Event Enrollment state.
1017    fn set_enrollment_eval_source_internal(
1018        &mut self,
1019        _source: Option<EventEnrollmentMonitoredSource>,
1020    ) -> Result<(), Error> {
1021        Err(Error::protocol(
1022            ErrorClass::OBJECT,
1023            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1024        ))
1025    }
1026
1027    /// Set or clear one `Acked_Transitions` bit on a received event-state
1028    /// transition.
1029    ///
1030    /// Implements the alarm-acknowledgment half of Clause 13.2.2.1.4's fourth
1031    /// transition action: pass the transition to alarm-acknowledgment handling.
1032    /// Under Clause 13.2.3, receiving a transition updates its Acked_Transitions
1033    /// bit to the inverse of the corresponding Ack_Required bit. The caller (the
1034    /// server evaluator) resolves `Ack_Required` from the referenced
1035    /// Notification Class object and passes the outcome as `acknowledged`;
1036    /// this method performs only the bit maintenance.
1037    ///
1038    /// `transition_bit` is the transition direction's flag, as returned by
1039    /// [`EventTransition::bit_mask`](crate::event::EventTransition::bit_mask). The set half overlaps the
1040    /// network-reachable [`acknowledge_alarm`](Self::acknowledge_alarm), which
1041    /// also ORs the bit in per Clause 13.2.3's acknowledgment-indication
1042    /// paragraph; the clear half has no network route by design (a property
1043    /// write could fabricate or erase acknowledgments — see the
1044    /// `write_generic_event_properties!` denial comment).
1045    ///
1046    /// The **default** returns `Err`, so objects without an algorithmic
1047    /// `Acked_Transitions` opt out.
1048    fn set_acked_transitions_internal(
1049        &mut self,
1050        _transition_bit: EventTransitionBits,
1051        _acknowledged: bool,
1052    ) -> Result<(), Error> {
1053        Err(Error::protocol(
1054            ErrorClass::OBJECT,
1055            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1056        ))
1057    }
1058
1059    /// Evaluate and, when necessary, mutate this object's `Reliability`.
1060    ///
1061    /// Reliability evaluation is object-owned: an implementation decides
1062    /// whether and how its internal conditions affect `Reliability`, performs
1063    /// the mutation itself, and returns [`ReliabilityEvaluation::Changed`] only
1064    /// after that mutation succeeds. [`ReliabilityEvaluation::Unchanged`] means
1065    /// the object made no mutation. Returning `Err` MUST leave all object state
1066    /// unchanged.
1067    ///
1068    /// The default opts out without changing state, preserving source
1069    /// compatibility for object implementations that do not own a reliability
1070    /// evaluation algorithm.
1071    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
1072        Ok(ReliabilityEvaluation::Unchanged)
1073    }
1074
1075    /// Whether periodic object-owned reliability evaluation is currently
1076    /// inhibited.
1077    ///
1078    /// The default is FALSE so existing and downstream object implementations
1079    /// remain source-compatible. Objects that elect the optional
1080    /// Reliability_Evaluation_Inhibit property override this internal
1081    /// predicate; reporting of an already-applied Reliability transition is
1082    /// intentionally unaffected.
1083    #[doc(hidden)]
1084    fn reliability_evaluation_inhibited_internal(&self) -> bool {
1085        false
1086    }
1087
1088    /// Apply an internally-derived `Reliability` value.
1089    ///
1090    /// This is the **internal** reliability-evaluation path, distinct from the
1091    /// network [`write_property`](Self::write_property) route. Implementations
1092    /// enforce symmetric ownership: clients may write while `Out_Of_Service`
1093    /// is TRUE, and internal evaluation may write while it is FALSE. ASHRAE
1094    /// 135-2020 Clause 3.2 describes reliability evaluation as the object's
1095    /// assessment of its reliability, producing its Reliability property value.
1096    ///
1097    /// The default rejects the operation, so object types without an internal
1098    /// reliability-evaluation process remain unaffected.
1099    fn set_reliability_internal(&mut self, _reliability: Reliability) -> Result<(), Error> {
1100        Err(Error::protocol(
1101            ErrorClass::OBJECT,
1102            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1103        ))
1104    }
1105
1106    /// Apply the logical `Present_Value` supplied by the local application.
1107    ///
1108    /// This narrow internal hook is distinct from the network
1109    /// [`write_property`](Self::write_property) route. Built-in Analog Input,
1110    /// Binary Input, and Multi-state Input objects opt in. Their implementations
1111    /// accept application updates only while in service; rejecting them while
1112    /// `Out_Of_Service` is TRUE is a local ownership policy that protects the
1113    /// client's simulation value, not a requirement imposed by the Standard.
1114    ///
1115    /// Analog, Binary, and Multi-state Value objects opt in unless their
1116    /// [`PresentValueAccess`](crate::present_value_access::PresentValueAccess)
1117    /// is `Commandable`. For them, rejecting updates while `Out_Of_Service` is
1118    /// TRUE is required: their Out_Of_Service clause keeps software local to
1119    /// the device from changing Present_Value.
1120    ///
1121    /// Life Safety Point and Zone opt in with a BACnetLifeSafetyState. Clients
1122    /// never write their Present_Value, so they take the application's value in
1123    /// service and out of service alike, and the hook leaves Tracking_Value,
1124    /// Silenced and Operation_Expected as they are: any latching rule belongs
1125    /// to the application.
1126    ///
1127    /// The default fails closed so commandable and other object families do not
1128    /// acquire privileged `Present_Value` write authority through this hook.
1129    fn set_present_value_internal(&mut self, _value: PropertyValue) -> Result<(), Error> {
1130        Err(Error::protocol(
1131            ErrorClass::OBJECT,
1132            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1133        ))
1134    }
1135
1136    /// [`set_present_value_internal`](Self::set_present_value_internal) on
1137    /// behalf of `origin`, the local Device the application runs in.
1138    ///
1139    /// A noncommandable Analog, Binary or Multi-state Value that tracks its
1140    /// Present_Value's source (#1552) publishes `origin` as Value_Source
1141    /// after the update, as Clause 19.5 asks of a write the local device
1142    /// makes; plain `set_present_value_internal` leaves it NONE. The server's
1143    /// `set_present_value_local` calls this when it knows its Device. The
1144    /// default ignores `origin`, and decorators must forward it.
1145    fn set_present_value_from_internal(
1146        &mut self,
1147        value: PropertyValue,
1148        _origin: &crate::command_source::CommandOrigin,
1149    ) -> Result<(), Error> {
1150        self.set_present_value_internal(value)
1151    }
1152
1153    /// Apply the `Tracking_Value` the local application derived.
1154    ///
1155    /// Only the built-in Life Safety Point and Zone opt in, with a
1156    /// BACnetLifeSafetyState; the property stays read-only over the network in
1157    /// service. While `Out_Of_Service` is TRUE a client's simulated value keeps
1158    /// being served and this one takes over on the return to service. The
1159    /// default fails closed with the same error as
1160    /// [`set_present_value_internal`](Self::set_present_value_internal).
1161    fn set_tracking_value_internal(&mut self, _value: PropertyValue) -> Result<(), Error> {
1162        Err(Error::protocol(
1163            ErrorClass::OBJECT,
1164            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1165        ))
1166    }
1167
1168    /// Apply an input the application reports for an access-control object
1169    /// the server holds (#1132): an Access Point's access event, a
1170    /// Credential Data Input's read or an Access Door's hardware state, each
1171    /// a record whose values change together. See
1172    /// [`AccessControlInput`](crate::access_control::AccessControlInput).
1173    ///
1174    /// Only the built-in Access Point, Credential Data Input and Access Door
1175    /// opt in, each for its own kind of record. While Out_Of_Service is TRUE
1176    /// the point refuses an event with PROPERTY / WRITE_ACCESS_DENIED, since
1177    /// it performs no authentication then, and the door and the reader store
1178    /// the input with their device values put aside, leaving a client's
1179    /// simulated values served. The default, and an object offered another
1180    /// kind of record, fails closed with the same error as
1181    /// [`set_present_value_internal`](Self::set_present_value_internal).
1182    fn report_access_input_internal(
1183        &mut self,
1184        _input: crate::access_control::AccessControlInput,
1185    ) -> Result<(), Error> {
1186        Err(Error::protocol(
1187            ErrorClass::OBJECT,
1188            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1189        ))
1190    }
1191
1192    /// Whether this object opts in to atomic completed-authorization event batches.
1193    /// An implementation returning true must implement the all-or-nothing contract
1194    /// of [`Self::record_access_event_batch_internal`]. The default refuses admission.
1195    #[doc(hidden)]
1196    fn supports_access_event_batch_internal(&self) -> bool {
1197        false
1198    }
1199
1200    /// Install at most four ordered authorization event reports atomically.
1201    /// Validate every report and timestamp before changing any served state. On
1202    /// error, leave all event fields untouched. On success, retain each installed
1203    /// intermediate state and its clock frame for post-commit COV preparation.
1204    /// The native Access Point refuses out-of-service or faulty operation and
1205    /// timestamps omitted times. Custom objects must opt in explicitly; callers
1206    /// must never replace this operation with a fallible per-report loop.
1207    #[doc(hidden)]
1208    fn record_access_event_batch_internal(
1209        &mut self,
1210        _reports: &[crate::access_control::AccessEventReport],
1211    ) -> Result<Vec<crate::access_control::CommittedAccessEvent>, Error> {
1212        Err(Error::protocol(
1213            ErrorClass::OBJECT,
1214            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1215        ))
1216    }
1217
1218    /// Apply the Controlled_Variable_Value measured by the local application.
1219    ///
1220    /// Only the built-in Loop opts in. Its control algorithm runs in the
1221    /// application, which feeds the measured value here while it does so; the
1222    /// property stays read-only over the network. The default fails closed
1223    /// with the same error as [`set_present_value_internal`](Self::set_present_value_internal).
1224    fn set_controlled_variable_value_internal(
1225        &mut self,
1226        _value: PropertyValue,
1227    ) -> Result<(), Error> {
1228        Err(Error::protocol(
1229            ErrorClass::OBJECT,
1230            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1231        ))
1232    }
1233
1234    /// Record one sample for an Averaging object.
1235    ///
1236    /// Only the built-in Averaging object opts in. The value is the reading
1237    /// of the referenced property, or `None` when the attempt produced no
1238    /// value, and the object updates its sample window, statistics and counts
1239    /// together. The application calls it for the samples it takes, and the
1240    /// database for the ones the object's own schedule asks for (see
1241    /// [`take_due_averaging_sample_internal`](Self::take_due_averaging_sample_internal)).
1242    /// The default fails closed with the same error as
1243    /// [`set_present_value_internal`](Self::set_present_value_internal).
1244    fn add_averaging_sample_internal(
1245        &mut self,
1246        _sample: Option<PropertyValue>,
1247    ) -> Result<(), Error> {
1248        Err(Error::protocol(
1249            ErrorClass::OBJECT,
1250            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1251        ))
1252    }
1253
1254    /// Claim the Averaging sample the object's schedule has due at the
1255    /// process-local monotonic instant `now`.
1256    ///
1257    /// When one is due the object steps its schedule past it and returns the
1258    /// property to read; the caller reads it while it still holds exclusive
1259    /// access and records the outcome with
1260    /// [`add_averaging_sample_internal`](Self::add_averaging_sample_internal).
1261    /// An object that answers here reports the due time through
1262    /// [`next_monotonic_deadline_internal`](Self::next_monotonic_deadline_internal),
1263    /// so the server's monotonic task wakes for it. The built-in Averaging
1264    /// object opts in while it holds an Object_Property_Reference and a
1265    /// monotonic clock is bound; the default has nothing to sample.
1266    #[doc(hidden)]
1267    fn take_due_averaging_sample_internal(
1268        &mut self,
1269        _now: Duration,
1270    ) -> Option<BACnetObjectPropertyReference> {
1271        None
1272    }
1273
1274    /// The local property a Pulse Converter counts its input from, its
1275    /// Input_Reference (Clause 12.23.6): `Some(None)` while it holds none,
1276    /// and `None` for an object that counts no referenced input, which the
1277    /// default is.
1278    ///
1279    /// The database judges the reference against its own objects
1280    /// (`ObjectDatabase::check_input_reference`) and reports the verdict
1281    /// through [`set_input_usable_internal`](Self::set_input_usable_internal).
1282    #[doc(hidden)]
1283    fn input_reference_internal(&self) -> Option<Option<&BACnetObjectPropertyReference>> {
1284        None
1285    }
1286
1287    /// Take the database's verdict on the reference
1288    /// [`input_reference_internal`](Self::input_reference_internal) returned:
1289    /// `false` when it names a property the object can't count from, which
1290    /// Clause 12.23.9 reports as a CONFIGURATION_ERROR Reliability. Returns
1291    /// whether a readable property changed, so a caller owes COV. The
1292    /// default has no input to judge.
1293    #[doc(hidden)]
1294    fn set_input_usable_internal(&mut self, _usable: bool) -> bool {
1295        false
1296    }
1297
1298    /// Take a reading of the property
1299    /// [`input_reference_internal`](Self::input_reference_internal) names,
1300    /// or `None` when there is nothing to read. A Pulse Converter counts each
1301    /// increase over the last reading into Count (Clause 12.23.14), across a
1302    /// wrap when the reading carries its bound. Returns whether a readable
1303    /// property changed, so a caller owes COV. The default counts nothing.
1304    #[doc(hidden)]
1305    fn take_input_reading_internal(
1306        &mut self,
1307        _reading: Option<crate::accumulator::InputReading>,
1308    ) -> bool {
1309        false
1310    }
1311
1312    /// Borrow this object's Audit Log query storage, if it has any.
1313    ///
1314    /// This read-only, type-erased channel lets the bundled server execute an
1315    /// AuditLogQuery against an object's already-loaded retained records. It
1316    /// deliberately does not expose persistence, mutation, or object
1317    /// downcasting. The **default** returns `None`; an Audit Log object that
1318    /// does not opt in is reported as SERVICES /
1319    /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
1320    fn audit_log_storage_internal(&self) -> Option<&dyn AuditLogStorage> {
1321        None
1322    }
1323
1324    /// Instance-owned optional Audit Log forwarding profile, never durable pending work.
1325    #[doc(hidden)]
1326    fn audit_log_forwarding_internal(&self) -> Option<Arc<crate::audit::AuditLogForwarding>> {
1327        None
1328    }
1329
1330    /// Set a trusted local Audit Log parent without downcasting or a second object store.
1331    ///
1332    /// The default opts out. This is not a network property-write capability;
1333    /// callers own configuration admission, and the server still validates the route.
1334    #[doc(hidden)]
1335    fn set_audit_log_parent_internal(
1336        &mut self,
1337        _parent: bacnet_types::constructed::BACnetDeviceObjectReference,
1338    ) -> Result<(), Error> {
1339        Err(Error::protocol(
1340            ErrorClass::OBJECT,
1341            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1342        ))
1343    }
1344
1345    /// Mutably borrow this object's Audit notification receiver capability.
1346    ///
1347    /// The default opts out. Implementations own their persistence transaction
1348    /// and must leave memory unchanged when the batch cannot be committed.
1349    fn audit_log_notification_sink_internal(
1350        &mut self,
1351    ) -> Option<&mut dyn AuditLogNotificationSink> {
1352        None
1353    }
1354
1355    /// Mutably borrow this object's staged-write capability (#1270).
1356    ///
1357    /// An object that saves a written state before serving it opts in, so the
1358    /// server can stage the write and run the save without the database
1359    /// guard. The default opts out: the object's writes run as they always
1360    /// do. See [`crate::durable`].
1361    #[doc(hidden)]
1362    fn durable_writes_internal(&mut self) -> Option<&mut dyn DurableWrites> {
1363        None
1364    }
1365
1366    /// Borrow this object's trusted local File configuration capability.
1367    ///
1368    /// The default returns `None`. The built-in [`crate::file::FileObject`]
1369    /// opts in so bindings can configure its pending payload, access method,
1370    /// and growth limits without general object downcasting or a parallel
1371    /// state cache.
1372    #[doc(hidden)]
1373    fn file_configuration_internal(&self) -> Option<&dyn FileConfiguration> {
1374        None
1375    }
1376
1377    /// Mutably borrow this object's trusted local File configuration capability.
1378    ///
1379    /// The default returns `None`; payload mutations remain object-owned and
1380    /// must preserve the same accounting and metadata behavior as the built-in
1381    /// File setters.
1382    #[doc(hidden)]
1383    fn file_configuration_internal_mut(&mut self) -> Option<&mut dyn FileConfiguration> {
1384        None
1385    }
1386
1387    /// Borrow this object's File storage, if it has any.
1388    ///
1389    /// This is the read half of the **internal** channel the server's
1390    /// AtomicReadFile handler uses to reach file contents. Like
1391    /// [`set_event_state_internal`](Self::set_event_state_internal) it
1392    /// bypasses the property model on purpose: Table 12-16 (ASHRAE 135-2020
1393    /// Clause 12.13) defines no File Data property, so file contents are
1394    /// reachable only through the Clause 14 File Access Services.
1395    ///
1396    /// The **default** returns `None`, so object types without a file opt
1397    /// out; the server reports `None` on a File-typed object as SERVICES /
1398    /// FILE_ACCESS_DENIED (Clause 18 covers locked or inaccessible files)
1399    /// rather than reading it as empty.
1400    /// Applications backing a File object with their own storage — a disk
1401    /// file, a firmware partition — implement [`FileStorage`] and return
1402    /// `Some`.
1403    fn file_storage_internal(&self) -> Option<&dyn FileStorage> {
1404        None
1405    }
1406
1407    /// Mutably borrow this object's File storage, if it has any.
1408    ///
1409    /// The write half of
1410    /// [`file_storage_internal`](Self::file_storage_internal), used by the
1411    /// AtomicWriteFile handler after the read-only and access-method gates
1412    /// have passed. The **default** returns `None`.
1413    fn file_storage_internal_mut(&mut self) -> Option<&mut dyn FileStorage> {
1414        None
1415    }
1416
1417    /// Return stable identities aligned with this object's resident log records.
1418    ///
1419    /// Implementing log objects return identities oldest-to-newest, in the
1420    /// same order as their public resident-record view and their
1421    /// [`log_buffer_internal`](Self::log_buffer_internal) records. Sequence
1422    /// numbers are object-owned metadata and never part of an encoded BACnet
1423    /// record. Non-log objects return `None`.
1424    fn log_record_identities_internal(&self) -> Option<Vec<LogRecordIdentity>> {
1425        None
1426    }
1427
1428    /// Borrow this object's Log_Buffer records as ReadRange pages them.
1429    ///
1430    /// The built-in Trend Log, Trend Log Multiple and Event Log objects
1431    /// answer a ReadProperty of Log_Buffer with PROPERTY / READ_ACCESS_DENIED,
1432    /// since their clauses (12.25.14, 12.30.19, 12.27.13) open the buffer to
1433    /// ReadRange only; the server's ReadRange handler reads it through this
1434    /// channel instead, one encoded record per item. The **default** returns
1435    /// `None`, which leaves ReadRange reading the property through
1436    /// [`read_property`](Self::read_property).
1437    fn log_buffer_internal(&self) -> Option<&dyn LogBufferRecords> {
1438        None
1439    }
1440
1441    /// Submit a trend record to an object's log lifecycle.
1442    ///
1443    /// `Ok(())` means the operation was accepted, not that an ordinary record
1444    /// became resident: disabled logs ignore it and zero-capacity logs may only
1445    /// increment their count. A required status transition can replace the
1446    /// ordinary record. Timestamp failures return an error without mutation.
1447    /// Objects without trend insertion return OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
1448    fn add_trend_record(&mut self, _record: BACnetLogRecord) -> Result<(), Error> {
1449        Err(Error::protocol(
1450            ErrorClass::OBJECT,
1451            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1452        ))
1453    }
1454
1455    /// Submit a Trend Log Multiple record, one value per monitored member, to
1456    /// an object's log lifecycle.
1457    ///
1458    /// The outcomes match [`add_trend_record`](Self::add_trend_record).
1459    /// Objects without Trend Log Multiple insertion return
1460    /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
1461    fn add_trend_multiple_record(&mut self, _record: BACnetLogMultipleRecord) -> Result<(), Error> {
1462        Err(Error::protocol(
1463            ErrorClass::OBJECT,
1464            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1465        ))
1466    }
1467
1468    /// Submit an Event Log record to an object's log lifecycle.
1469    ///
1470    /// The server hands the event notifications this device builds to its
1471    /// Event Logs through this hook
1472    /// ([`ObjectDatabase::log_event_notification`](crate::database::ObjectDatabase::log_event_notification)).
1473    /// The outcomes match [`add_trend_record`](Self::add_trend_record).
1474    /// Objects without Event Log insertion return
1475    /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
1476    fn add_event_log_record(&mut self, _record: BACnetEventLogRecord) -> Result<(), Error> {
1477        Err(Error::protocol(
1478            ErrorClass::OBJECT,
1479            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
1480        ))
1481    }
1482
1483    /// Look at a log's Start_Time / Stop_Time window against the bound
1484    /// clock, recording the LOG_DISABLED change when it opened or closed
1485    /// since the last look while Enable is TRUE.
1486    ///
1487    /// The trend poller calls this for every Trend Log, Trend Log Multiple
1488    /// and Event Log on each pass, so a window that moves while no record
1489    /// arrives is still logged within one pass. Returns whether a record was
1490    /// added. The default, for objects without a window, does nothing and
1491    /// returns `false`.
1492    #[doc(hidden)]
1493    fn refresh_log_window_internal(&mut self) -> bool {
1494        false
1495    }
1496
1497    /// Whether a log keeps the ordinary records it is given now: Enable is
1498    /// TRUE and its Start_Time / Stop_Time window admitted the local time at
1499    /// the last look. A Trend Log logging by COV starts over from the
1500    /// current value when collection resumes, so the trend poller asks this
1501    /// before each look (#1480). The default, for objects that aren't logs,
1502    /// says yes and leaves the gate to the insertion hook.
1503    #[doc(hidden)]
1504    fn log_collecting_internal(&self) -> bool {
1505        true
1506    }
1507
1508    /// Whether this Event Log takes the event notifications the device
1509    /// receives, through
1510    /// [`ObjectDatabase::log_received_event_notification`](crate::database::ObjectDatabase::log_received_event_notification).
1511    /// The default, and an Event Log that hasn't opted in, says no.
1512    #[doc(hidden)]
1513    fn logs_received_event_notifications_internal(&self) -> bool {
1514        false
1515    }
1516
1517    /// The counts the last committed BUFFER_READY transition reported, for
1518    /// the server to carry as its event values (Clause 13.3.7). `None` for an
1519    /// object that hasn't reported one, or doesn't run that algorithm.
1520    #[doc(hidden)]
1521    fn buffer_ready_report_internal(&self) -> Option<BufferReadyReport> {
1522        None
1523    }
1524
1525    /// The local property this object's Event_Algorithm_Inhibit follows, its
1526    /// Event_Algorithm_Inhibit_Ref (#1329); `None` while it holds none, and
1527    /// for an object without one, which the default is. The database reads
1528    /// the property and hands the value over through
1529    /// [`follow_event_algorithm_inhibit_internal`](Self::follow_event_algorithm_inhibit_internal).
1530    #[doc(hidden)]
1531    fn event_algorithm_inhibit_reference_internal(&self) -> Option<BACnetObjectPropertyReference> {
1532        None
1533    }
1534
1535    /// Take the value the database read from the property
1536    /// [`event_algorithm_inhibit_reference_internal`](Self::event_algorithm_inhibit_reference_internal)
1537    /// names as Event_Algorithm_Inhibit; returns whether it changed. The
1538    /// default follows nothing.
1539    #[doc(hidden)]
1540    fn follow_event_algorithm_inhibit_internal(&mut self, _inhibit: bool) -> bool {
1541        false
1542    }
1543}