use super::*;
use bacnet_encoding::{apdu::decode_apdu, npdu::decode_npdu};
use bacnet_services::device_mgmt::ReinitializeDeviceRequest;
use bacnet_types::enums::ReinitializedState;
const STATES: [ReinitializedState; 10] = [
ReinitializedState::COLDSTART,
ReinitializedState::WARMSTART,
ReinitializedState::START_BACKUP,
ReinitializedState::END_BACKUP,
ReinitializedState::START_RESTORE,
ReinitializedState::END_RESTORE,
ReinitializedState::ABORT_RESTORE,
ReinitializedState::ACTIVATE_CHANGES,
ReinitializedState::from_raw(8),
ReinitializedState::from_raw(u32::MAX),
];
fn request_data(state: ReinitializedState, password: Option<&str>) -> Bytes {
let mut data = BytesMut::new();
ReinitializeDeviceRequest {
reinitialized_state: state,
password: password.map(str::to_owned),
}
.encode(&mut data)
.unwrap();
data.freeze()
}
async fn dispatch(service_request: Bytes, password: Option<&str>, initial: DccState) -> Apdu {
let network = Arc::new(NetworkLayer::new(BipTransport::new(
Ipv4Addr::LOCALHOST,
0,
Ipv4Addr::BROADCAST,
)));
let comm_state = Arc::new(CommState::default());
comm_state.set_for_test(initial);
let dcc_timer = Arc::new(Mutex::new(crate::server::dcc_timer::TimerSlot::default()));
let config = ServerConfig {
reinit_password: password.map(str::to_owned),
..Default::default()
};
let request = ConfirmedRequestPdu {
segmented: false,
more_follows: false,
segmented_response_accepted: false,
max_segments: None,
max_apdu_length: 480,
invoke_id: 42,
sequence_number: None,
proposed_window_size: None,
service_choice: ConfirmedServiceChoice::REINITIALIZE_DEVICE,
service_request,
};
let (tx, rx) = oneshot::channel();
BACnetServer::<BipTransport>::handle_confirmed_request(
&RequestServices {
comm_state: Arc::clone(&comm_state),
dcc_timer: Arc::clone(&dcc_timer),
..RequestServices::for_test(Arc::clone(&network), config.clone())
},
&Arc::new(ConfirmedRequestTracker::default()),
&Arc::new(crate::server::request_tasks::RequestTasks::default()).spawner(),
&[127, 0, 0, 1, 0xba, 0xc0],
None,
request,
Some(tx),
)
.await;
assert_eq!(comm_state.get(), initial);
assert!(dcc_timer.lock().await.is_none());
let npdu = decode_npdu(rx.await.unwrap()).unwrap();
decode_apdu(npdu.payload).unwrap()
}
fn assert_error(apdu: Apdu, class: ErrorClass, code: ErrorCode) {
let Apdu::Error(error) = apdu else {
panic!("expected Error, never SimpleACK, got {apdu:?}")
};
assert_eq!(error.invoke_id, 42);
assert_eq!(
error.service_choice,
ConfirmedServiceChoice::REINITIALIZE_DEVICE
);
assert_eq!(error.error_class, class);
assert_eq!(error.error_code, code);
assert!(error.error_data.is_empty());
}
#[tokio::test(start_paused = true)]
async fn reinitialize_device_refuses_all_states_after_password_validation() {
for state in STATES {
for (configured, supplied) in [
(Some("reinit-pw"), Some("reinit-pw")),
(None, None),
(None, Some("anything")),
] {
for initial in [DccState::Enable, DccState::DisableInitiation] {
assert_error(
dispatch(request_data(state, supplied), configured, initial).await,
ErrorClass::SERVICES,
ErrorCode::SERVICE_REQUEST_DENIED,
);
}
}
}
}
#[tokio::test(start_paused = true)]
async fn reinitialize_device_password_failure_precedes_refusal() {
for state in STATES {
for supplied in [None, Some("wrong")] {
for initial in [DccState::Enable, DccState::DisableInitiation] {
assert_error(
dispatch(request_data(state, supplied), Some("reinit-pw"), initial).await,
ErrorClass::SECURITY,
ErrorCode::PASSWORD_FAILURE,
);
}
}
}
}
#[tokio::test(start_paused = true)]
async fn reinitialize_device_malformed_request_precedes_password_and_refusal() {
for data in [
&[][..], &[0x09][..], &[0x19, 0x00][..], &[0x09, 0x01, 0x1a, 0][..], ] {
assert!(ReinitializeDeviceRequest::decode(data).is_err());
for configured in [None, Some("reinit-pw")] {
for initial in [DccState::Enable, DccState::DisableInitiation] {
let response = dispatch(Bytes::copy_from_slice(data), configured, initial).await;
let Apdu::Reject(reject) = response else {
panic!("expected a Reject, got {response:?}")
};
assert_eq!(
(reject.invoke_id, reject.reject_reason),
(42, RejectReason::MISSING_REQUIRED_PARAMETER),
"{data:02X?}"
);
}
}
}
}