use super::network_port_activation_tests::{reinitialize, wp, FALSE, TRUE};
use super::network_port_mode_tests::{port_oid, property_list, rp};
use super::*;
use bacnet_transport::bip::BipMulticast;
use bacnet_types::enums::{PropertyIdentifier as P, ReinitializedState};
const GROUP: [u8; 4] = [239, 255, 95, 1];
const MOVED: [u8; 4] = [239, 255, 95, 2];
fn tagged(group: [u8; 4]) -> Vec<u8> {
let mut octets = vec![0x64];
octets.extend_from_slice(&group);
octets
}
fn free_port() -> u16 {
std::net::UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))
.unwrap()
.local_addr()
.unwrap()
.port()
}
async fn member_server(group_port: u16) -> BACnetServer<BipTransport> {
let mut db = ObjectDatabase::new();
let config = BipPortConfig {
ip_address: [127, 0, 0, 1],
udp_port: group_port,
..Default::default()
};
db.add(Box::new(
NetworkPortObject::new_bip(1, "member", config).unwrap(),
))
.unwrap();
let mut transport = BipTransport::new(Ipv4Addr::LOCALHOST, group_port, Ipv4Addr::LOCALHOST);
let multicast = BipMulticast::new(GROUP.into())
.unwrap()
.with_port(group_port)
.unwrap();
transport.set_multicast(Some(multicast)).unwrap();
let config = ServerConfig {
registered_network_port: Some(port_oid()),
on_reinitialize: Some(Arc::new(|_: &_, _: &mut _| Ok(())) as ReinitializeHandler),
..Default::default()
};
BACnetServer::start(config, db, transport).await.unwrap()
}
fn group_in_use(server: &BACnetServer<BipTransport>) -> Option<[u8; 4]> {
let transport = server.test_network().transport();
transport
.multicast()
.map(|multicast| multicast.group().octets())
}
#[tokio::test]
async fn the_multicast_group_is_served_written_and_activated() {
let group_port = free_port();
let mut server = member_server(group_port).await;
assert!(property_list(&server)
.await
.contains(&P::BACNET_IP_MULTICAST_ADDRESS));
assert_eq!(
rp(&server, P::BACNET_IP_MULTICAST_ADDRESS).await.unwrap(),
tagged(GROUP)
);
let endpoint = server.test_network().transport().bip_broadcast_endpoint();
assert_eq!(
endpoint,
Some(std::net::SocketAddrV4::new(GROUP.into(), group_port))
);
assert_eq!(
wp(
&server,
P::BACNET_IP_MULTICAST_ADDRESS,
tagged([10, 0, 0, 1])
)
.await,
Err((ErrorClass::PROPERTY, ErrorCode::VALUE_OUT_OF_RANGE))
);
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
wp(&server, P::BACNET_IP_MULTICAST_ADDRESS, tagged(MOVED))
.await
.unwrap();
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), TRUE);
assert_eq!(
rp(&server, P::BACNET_IP_MULTICAST_ADDRESS).await.unwrap(),
tagged(MOVED)
);
assert_eq!(group_in_use(&server), Some(GROUP));
assert!(matches!(
reinitialize(&server, ReinitializedState::ACTIVATE_CHANGES).await,
Apdu::SimpleAck(_)
));
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
assert_eq!(group_in_use(&server), Some(MOVED));
assert_eq!(
server.test_network().transport().bip_broadcast_endpoint(),
Some(std::net::SocketAddrV4::new(MOVED.into(), group_port))
);
wp(&server, P::BACNET_IP_MULTICAST_ADDRESS, tagged([0; 4]))
.await
.unwrap();
assert!(matches!(
reinitialize(&server, ReinitializedState::WARMSTART).await,
Apdu::SimpleAck(_)
));
assert_eq!(group_in_use(&server), None);
assert_eq!(
rp(&server, P::BACNET_IP_MULTICAST_ADDRESS).await.unwrap(),
tagged([0; 4])
);
server.stop().await.unwrap();
}
async fn group_bbmd_server(port: u16) -> BACnetServer<BipTransport> {
let mut db = ObjectDatabase::new();
let config = BipPortConfig {
ip_address: [127, 0, 0, 1],
udp_port: port,
..Default::default()
};
db.add(Box::new(
NetworkPortObject::new_bip(1, "group BBMD", config).unwrap(),
))
.unwrap();
let mut transport = BipTransport::new(Ipv4Addr::LOCALHOST, port, Ipv4Addr::LOCALHOST);
let multicast = BipMulticast::new(GROUP.into())
.unwrap()
.with_port(port)
.unwrap();
transport.set_multicast(Some(multicast)).unwrap();
transport.enable_bbmd(Vec::new());
let config = ServerConfig {
registered_network_port: Some(port_oid()),
on_reinitialize: Some(Arc::new(|_: &_, _: &mut _| Ok(())) as ReinitializeHandler),
..Default::default()
};
BACnetServer::start(config, db, transport).await.unwrap()
}
#[tokio::test]
async fn a_group_bbmd_serves_its_group_read_only() {
use bacnet_encoding::constructed::encode_bdt_entry_list;
use bacnet_types::constructed::BACnetBDTEntry;
let port = free_port();
let mut server = group_bbmd_server(port).await;
let listed = property_list(&server).await;
assert!(listed.contains(&P::BACNET_IP_MULTICAST_ADDRESS));
assert!(listed.contains(&P::BBMD_BROADCAST_DISTRIBUTION_TABLE));
assert_eq!(
rp(&server, P::BACNET_IP_MULTICAST_ADDRESS).await.unwrap(),
tagged(GROUP)
);
for group in [MOVED, [0; 4]] {
assert_eq!(
wp(&server, P::BACNET_IP_MULTICAST_ADDRESS, tagged(group)).await,
Err((ErrorClass::PROPERTY, ErrorCode::WRITE_ACCESS_DENIED))
);
}
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
assert_eq!(group_in_use(&server), Some(GROUP));
let pics = server
.generate_pics(&crate::pics::PicsConfig::default())
.await;
let writable = |property| {
pics.supported_object_types
.iter()
.flat_map(|object| &object.supported_properties)
.find(|row| row.property_id == property)
.map(|row| row.access.writable)
};
assert_eq!(writable(P::BACNET_IP_MULTICAST_ADDRESS), Some(false));
assert_eq!(writable(P::BBMD_BROADCAST_DISTRIBUTION_TABLE), Some(true));
let own_one_hop = BACnetBDTEntry {
bbmd_address: super::network_port_mode_tests::host([127, 0, 0, 1], port),
broadcast_mask: Some([255, 0, 0, 0]),
};
let mut octets = BytesMut::new();
encode_bdt_entry_list(&mut octets, &[own_one_hop]).unwrap();
wp(
&server,
P::BBMD_BROADCAST_DISTRIBUTION_TABLE,
octets.to_vec(),
)
.await
.unwrap();
assert!(matches!(
reinitialize(&server, ReinitializedState::ACTIVATE_CHANGES).await,
Apdu::Error(_)
));
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), TRUE);
let transport = server.test_network().transport();
let state = transport.bbmd_state().unwrap();
let own = state.lock().unwrap().bdt();
assert!(own.iter().all(|row| row.broadcast_mask == [0xFF; 4]));
server.stop().await.unwrap();
}