use super::*;
use bacnet_encoding::apdu::ConfirmedRequest;
use bacnet_services::device_mgmt::ReinitializeDeviceRequest;
use bacnet_types::constructed::BACnetAddress;
use bacnet_types::enums::ReinitializedState;
use std::sync::atomic::{AtomicU8, Ordering};
const FAMILIES: [(ReinitializedState, Option<AuditOperation>); 8] = [
(
ReinitializedState::COLDSTART,
Some(AuditOperation::DEVICE_RESET),
),
(
ReinitializedState::WARMSTART,
Some(AuditOperation::DEVICE_RESET),
),
(
ReinitializedState::START_BACKUP,
Some(AuditOperation::DEVICE_BACKUP),
),
(
ReinitializedState::END_BACKUP,
Some(AuditOperation::DEVICE_BACKUP),
),
(ReinitializedState::ACTIVATE_CHANGES, None),
(
ReinitializedState::START_RESTORE,
Some(AuditOperation::DEVICE_RESTORE),
),
(
ReinitializedState::END_RESTORE,
Some(AuditOperation::DEVICE_RESTORE),
),
(
ReinitializedState::ABORT_RESTORE,
Some(AuditOperation::DEVICE_RESTORE),
),
];
const ACCEPT: u8 = 0;
const REFUSE: u8 = 1;
const PANIC: u8 = 2;
const FAIL: u8 = 3;
async fn session_mac(
session: &EndpointSession<BipTransport>,
peer: &NetworkLayer<BipTransport>,
replies: &mut bacnet_network::layer::ApduReceiver,
) -> MacAddr {
session
.egress
.as_ref()
.unwrap()
.send_apdu(
vec![0x10, 8],
bacnet_endpoint_core::endpoint_ingress::EndpointApduDestination::Direct {
destination_mac: MacAddr::from_slice(peer.local_mac()),
},
false,
NetworkPriority::Normal,
Vec::new(),
)
.await
.unwrap();
receive(replies).await.source_mac
}
async fn reinitialize(
peer: &NetworkLayer<BipTransport>,
session: &[u8],
replies: &mut bacnet_network::layer::ApduReceiver,
state: ReinitializedState,
password: Option<&str>,
invoke_id: u8,
) -> Apdu {
let mut service = BytesMut::new();
ReinitializeDeviceRequest {
reinitialized_state: state,
password: password.map(str::to_owned),
}
.encode(&mut service)
.unwrap();
send(
peer,
session,
Apdu::ConfirmedRequest(ConfirmedRequest {
segmented: false,
more_follows: false,
segmented_response_accepted: false,
max_segments: None,
max_apdu_length: 1476,
invoke_id,
sequence_number: None,
proposed_window_size: None,
service_choice: ConfirmedServiceChoice::REINITIALIZE_DEVICE,
service_request: service.freeze(),
}),
)
.await;
decode_apdu(receive(replies).await.apdu).unwrap()
}
#[tokio::test]
async fn endpoint_records_accepted_reinitialize_requests_only() {
let (mut sink, mut records) = network().await;
let (mut peer, mut replies) = network().await;
let mut db = database(false);
db.set_clock_reader(None);
let mut operations = AuditOperationFlags::empty();
for operation in [
AuditOperation::DEVICE_RESET,
AuditOperation::DEVICE_BACKUP,
AuditOperation::DEVICE_RESTORE,
] {
operations.insert(operation);
}
db.get_mut(&selected())
.unwrap()
.configure_audit_reporter_internal(
AuditLevel::AUDIT_ALL,
operations,
false,
None,
BACnetPriorityFilter::empty(),
None,
)
.unwrap();
let mode = Arc::new(AtomicU8::new(ACCEPT));
let answer = Arc::clone(&mode);
let mut session = session(db, SessionRole::Both, &sink)
.with_reinitialize(move |_, _| match answer.load(Ordering::SeqCst) {
ACCEPT => Ok(()),
REFUSE => Err(Error::protocol(
ErrorClass::DEVICE,
ErrorCode::CONFIGURATION_IN_PROGRESS,
)),
PANIC => panic!("reinitialize handler panic"),
_ => Err(Error::Encoding("disk full".into())),
})
.with_reinit_password("pw");
session.start().await.unwrap();
let mac = session_mac(&session, &peer, &mut replies).await;
let requester = BACnetRecipient::Address(BACnetAddress {
network_number: 0,
mac_address: MacAddr::from_slice(peer.local_mac()),
});
let (mut invoke, mut sequence) = (0u8, 0u16);
for (state, operation) in FAMILIES {
for (answer, password) in [
(REFUSE, Some("pw")),
(PANIC, Some("pw")),
(FAIL, Some("pw")),
(ACCEPT, None),
(ACCEPT, Some("wrong")),
] {
mode.store(answer, Ordering::SeqCst);
invoke += 1;
let reply = reinitialize(&peer, &mac, &mut replies, state, password, invoke).await;
assert!(matches!(reply, Apdu::Error(_)), "{state:?}: {reply:?}");
}
mode.store(ACCEPT, Ordering::SeqCst);
invoke += 1;
let reply = reinitialize(&peer, &mac, &mut replies, state, Some("pw"), invoke).await;
assert!(matches!(reply, Apdu::SimpleAck(_)), "{state:?}: {reply:?}");
let Some(operation) = operation else {
continue;
};
let (record, _) = notification(&receive(&mut records).await, false);
assert_eq!(record.operation, operation, "{state:?}");
assert_eq!(record.source_device, requester);
assert_eq!(record.invoke_id, Some(invoke));
assert_eq!(
record.target_device,
BACnetRecipient::Device(oid(ObjectType::DEVICE, 123))
);
assert_eq!(
record.target_timestamp,
Some(BACnetTimeStamp::SequenceNumber(sequence)),
"{state:?}: no record before this one since the last"
);
assert_eq!(record.source_timestamp, None);
assert_eq!(record.target_object, None);
assert_eq!(record.target_property, None);
assert_eq!(record.target_value, None);
assert_eq!(record.result, None);
sequence += 1;
}
session.stop().await.unwrap();
sink.stop().await.unwrap();
peer.stop().await.unwrap();
}