use super::*;
fn segment(invoke: u8, sequence: u8, data: Bytes) -> Apdu {
Apdu::ConfirmedRequest(ConfirmedRequestPdu {
segmented: true,
more_follows: sequence == 0,
segmented_response_accepted: true,
max_segments: None,
max_apdu_length: 1476,
invoke_id: invoke,
sequence_number: Some(sequence),
proposed_window_size: Some(1),
service_choice: ConfirmedServiceChoice::WRITE_PROPERTY,
service_request: data,
})
}
fn abort(invoke: u8) -> Apdu {
Apdu::Abort(AbortPdu {
sent_by_server: false,
invoke_id: invoke,
abort_reason: AbortReason::OTHER,
})
}
async fn ack(f: &mut Fixture, sequence: u8) {
assert!(
matches!(f.response().await, Apdu::SegmentAck(a) if a.sequence_number == sequence && !a.negative_ack)
);
}
async fn isolated_segments(same_leaf: bool, cancel_a: bool) {
let ca = TestCa::new();
let a_tls = ca.tls("a");
let b_tls = if same_leaf {
a_tls.clone()
} else {
ca.tls("b")
};
let seen = Arc::new(StdMutex::new(Vec::new()));
let observer = seen.clone();
let config = ServerConfig {
device: bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
policy: Some(Arc::new(crate::FnPolicy::new().mutation(move |context| {
observer
.lock()
.unwrap()
.push(context.direct_sc_identity().unwrap());
true
}))),
..Default::default()
};
let mut f = Fixture::new(&ca, config, database(Arc::new(AtomicUsize::new(0)))).await;
let mut a = f.peer(a_tls).await;
let a_payload = write_payload("A segmented");
let b_payload = write_payload("B segmented");
let cut = a_payload.len() / 2;
let a0 = f
.capture(&mut a, &segment(31, 0, a_payload.slice(..cut)))
.await;
let identity_a = a0.provenance.direct_sc_identity().unwrap();
let a1 = f
.capture(&mut a, &segment(31, 1, a_payload.slice(cut..)))
.await;
let a_abort = f.capture(&mut a, &abort(31)).await;
f.feed(a0).await;
ack(&mut f, 0).await;
let mut b = f.peer(b_tls).await;
let b1 = f
.capture(&mut b, &segment(31, 1, b_payload.slice(cut..)))
.await;
let identity_b = b1.provenance.direct_sc_identity().unwrap();
assert_ne!(identity_a, identity_b);
f.feed(b1).await;
assert!(
matches!(f.response().await, Apdu::Abort(a) if a.abort_reason == AbortReason::INVALID_APDU_IN_THIS_STATE)
);
let mut unverified_abort = f.capture(&mut b, &abort(31)).await;
unverified_abort.provenance = bacnet_transport::port::TransportProvenance::unverified();
f.feed(unverified_abort).await;
let b0 = f
.capture(&mut b, &segment(31, 0, b_payload.slice(..cut)))
.await;
f.feed(b0).await;
ack(&mut f, 0).await;
let (winner, expected) = if cancel_a {
f.feed(a_abort).await;
f.feed(a1).await;
f.dispatch_barrier(&mut b, 239).await;
assert!(f.responses.try_recv().is_err());
let b1 = f
.capture(&mut b, &segment(31, 1, b_payload.slice(cut..)))
.await;
f.feed(b1).await;
(identity_b, "B segmented")
} else {
let b_abort = f.capture(&mut b, &abort(31)).await;
f.feed(b_abort).await;
let b1 = f
.capture(&mut b, &segment(31, 1, b_payload.slice(cut..)))
.await;
f.feed(b1).await;
assert!(
matches!(f.response().await, Apdu::Abort(a) if a.abort_reason == AbortReason::INVALID_APDU_IN_THIS_STATE)
);
f.feed(a1).await;
(identity_a, "A segmented")
};
if cancel_a {
ack(&mut f, 1).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(_)));
} else {
f.dispatch_barrier(&mut b, 240).await;
}
f.active(0).await;
assert!(f.responses.try_recv().is_err());
assert_eq!(*seen.lock().unwrap(), vec![winner]);
assert_eq!(
f.server
.db
.read()
.await
.get(&csv_oid())
.unwrap()
.read_property(PropertyIdentifier::PRESENT_VALUE, None)
.unwrap(),
PropertyValue::CharacterString(expected.into())
);
f.stop().await;
}
#[tokio::test]
async fn direct_principal_new_leaf_segments_and_abort_preserve_queued_old_context() {
isolated_segments(false, false).await;
}
#[tokio::test]
async fn direct_principal_same_leaf_segments_and_abort_preserve_queued_old_context() {
isolated_segments(true, false).await;
}
#[tokio::test]
async fn direct_principal_queued_old_abort_preserves_new_context() {
isolated_segments(false, true).await;
}
#[tokio::test]
async fn direct_principal_two_admitted_contexts_finish_without_merging() {
let ca = TestCa::new();
let seen = Arc::new(StdMutex::new(Vec::new()));
let observer = seen.clone();
let mut f = Fixture::new(
&ca,
ServerConfig {
device: bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
policy: Some(Arc::new(crate::FnPolicy::new().mutation(move |c| {
observer
.lock()
.unwrap()
.push(c.direct_sc_identity().unwrap());
true
}))),
..Default::default()
},
database(Arc::new(AtomicUsize::new(0))),
)
.await;
let mut a = f.peer(ca.tls("a")).await;
let payload = write_payload("first admitted");
let cut = payload.len() / 2;
let a0 = f
.capture(&mut a, &segment(33, 0, payload.slice(..cut)))
.await;
let id_a = a0.provenance.direct_sc_identity().unwrap();
let a1 = f
.capture(&mut a, &segment(33, 1, payload.slice(cut..)))
.await;
f.feed(a0).await;
ack(&mut f, 0).await;
let mut b = f.peer(ca.tls("b")).await;
let payload = write_payload("second admitted");
let b0 = f
.capture(&mut b, &segment(33, 0, payload.slice(..cut)))
.await;
let id_b = b0.provenance.direct_sc_identity().unwrap();
f.feed(b0).await;
ack(&mut f, 0).await;
f.feed(a1).await;
f.dispatch_barrier(&mut b, 240).await;
f.active(0).await;
assert_eq!(*seen.lock().unwrap(), vec![id_a]);
assert!(f.responses.try_recv().is_err());
let b1 = f
.capture(&mut b, &segment(33, 1, payload.slice(cut..)))
.await;
f.feed(b1).await;
ack(&mut f, 1).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(_)));
f.active(0).await;
assert_eq!(*seen.lock().unwrap(), vec![id_a, id_b]);
assert_eq!(
f.server
.db
.read()
.await
.get(&csv_oid())
.unwrap()
.read_property(PropertyIdentifier::PRESENT_VALUE, None)
.unwrap(),
PropertyValue::CharacterString("second admitted".into())
);
f.stop().await;
}
#[tokio::test]
async fn direct_principal_reconnect_does_not_expand_reassembly_peer_capacity() {
let ca = TestCa::new();
let mut f = Fixture::new(
&ca,
ServerConfig {
device: bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
..Default::default()
},
database(Arc::new(AtomicUsize::new(0))),
)
.await;
let mut a = f.peer(ca.tls("a")).await;
for invoke in 0..16 {
let a0 = f
.capture(&mut a, &segment(invoke, 0, Bytes::from_static(&[12])))
.await;
f.feed(a0).await;
ack(&mut f, 0).await;
}
let a_abort = f.capture(&mut a, &abort(0)).await;
let mut b = f.peer(ca.tls("b")).await;
let b0 = f
.capture(&mut b, &segment(0, 0, Bytes::from_static(&[12])))
.await;
f.feed(b0).await;
assert!(
matches!(f.response().await, Apdu::Abort(a) if a.abort_reason == AbortReason::OUT_OF_RESOURCES)
);
f.feed(a_abort).await;
let b0 = f
.capture(&mut b, &segment(0, 0, Bytes::from_static(&[12])))
.await;
f.feed(b0).await;
ack(&mut f, 0).await;
f.stop().await;
}
#[tokio::test]
async fn direct_response_reassembly_uses_segment_zero_capability_not_final_metadata() {
let ca = TestCa::new();
let mut f = Fixture::new(
&ca,
ServerConfig {
device: bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
..Default::default()
},
database(Arc::new(AtomicUsize::new(0))),
)
.await;
let mut a = f.peer(ca.tls("a")).await;
let payload = write_payload("saved route");
let cut = payload.len() / 2;
let first = f
.capture(&mut a, &segment(61, 0, payload.slice(..cut)))
.await;
f.feed(first).await;
ack(&mut f, 0).await;
let mut final_segment = f
.capture(&mut a, &segment(61, 1, payload.slice(cut..)))
.await;
final_segment.direct_response = None;
f.feed(final_segment).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(ack) if ack.invoke_id == 61));
f.active(0).await;
assert_eq!(
f.server
.db
.read()
.await
.get(&csv_oid())
.unwrap()
.read_property(PropertyIdentifier::PRESENT_VALUE, None)
.unwrap(),
PropertyValue::CharacterString("saved route".into())
);
assert!(f.responses.try_recv().is_err());
f.stop().await;
}
#[tokio::test]
async fn direct_response_receive_segment_gap_nak_and_final_ack_use_original_socket() {
let ca = TestCa::new();
let mut f = Fixture::new(
&ca,
ServerConfig {
device: bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
..Default::default()
},
database(Arc::new(AtomicUsize::new(0))),
)
.await;
let mut a = f.peer(ca.tls("a")).await;
let payload = write_payload("after gap");
let cut = payload.len() / 2;
let first = f
.capture(&mut a, &segment(62, 0, payload.slice(..cut)))
.await;
f.feed(first).await;
ack(&mut f, 0).await;
let gap = f
.capture(&mut a, &segment(62, 2, payload.slice(cut..)))
.await;
f.feed(gap).await;
assert!(
matches!(f.response().await, Apdu::SegmentAck(a) if a.sent_by_server && a.invoke_id == 62 && a.sequence_number == 0 && a.negative_ack)
);
let last = f
.capture(&mut a, &segment(62, 1, payload.slice(cut..)))
.await;
f.feed(last).await;
ack(&mut f, 1).await;
assert!(matches!(f.response().await, Apdu::SimpleAck(a) if a.invoke_id == 62));
f.active(0).await;
assert_eq!(
f.server
.db
.read()
.await
.get(&csv_oid())
.unwrap()
.read_property(PropertyIdentifier::PRESENT_VALUE, None)
.unwrap(),
PropertyValue::CharacterString("after gap".into())
);
f.stop().await;
}