use std::collections::HashMap;
use std::sync::Arc;
use crate::apdu::encoding::{ApduDecoder, ApduEncoder};
use crate::apdu::types::{ConfirmedService, ErrorClass, ErrorCode};
use crate::object::property::{BACnetValue, PropertyId};
use crate::object::registry::ObjectRegistry;
use crate::object::traits::ArcObject;
use crate::object::types::{ObjectId, ObjectType};
use super::handler::{ConfirmedServiceHandler, ServiceContext, ServiceResult};
type ConstructorFn = fn(u32, String) -> ArcObject;
pub struct ObjectFactory {
constructors: HashMap<ObjectType, ConstructorFn>,
}
impl ObjectFactory {
pub fn new() -> Self {
Self {
constructors: HashMap::new(),
}
}
pub fn register(&mut self, object_type: ObjectType, constructor: ConstructorFn) {
self.constructors.insert(object_type, constructor);
}
pub fn supports(&self, object_type: ObjectType) -> bool {
self.constructors.contains_key(&object_type)
}
pub fn create(
&self,
object_type: ObjectType,
instance: u32,
name: String,
) -> Option<ArcObject> {
self.constructors
.get(&object_type)
.map(|ctor| ctor(instance, name))
}
pub fn supported_types(&self) -> Vec<ObjectType> {
self.constructors.keys().copied().collect()
}
}
impl Default for ObjectFactory {
fn default() -> Self {
Self::new()
}
}
pub fn default_object_factory() -> ObjectFactory {
use crate::object::event_enrollment::{EventEnrollment, NotificationClass};
use crate::object::file::FileObject;
use crate::object::schedule::{Calendar, Schedule};
use crate::object::standard::{
AnalogInput, AnalogOutput, AnalogValue, BinaryInput, BinaryOutput, BinaryValue,
MultiStateInput, MultiStateOutput, MultiStateValue,
};
use crate::object::trend_log::TrendLog;
let mut factory = ObjectFactory::new();
factory.register(ObjectType::AnalogInput, |inst, name| {
Arc::new(AnalogInput::new(inst, name))
});
factory.register(ObjectType::AnalogOutput, |inst, name| {
Arc::new(AnalogOutput::new(inst, name))
});
factory.register(ObjectType::AnalogValue, |inst, name| {
Arc::new(AnalogValue::new(inst, name))
});
factory.register(ObjectType::BinaryInput, |inst, name| {
Arc::new(BinaryInput::new(inst, name))
});
factory.register(ObjectType::BinaryOutput, |inst, name| {
Arc::new(BinaryOutput::new(inst, name))
});
factory.register(ObjectType::BinaryValue, |inst, name| {
Arc::new(BinaryValue::new(inst, name))
});
factory.register(ObjectType::MultiStateInput, |inst, name| {
Arc::new(MultiStateInput::new(inst, name, 4))
});
factory.register(ObjectType::MultiStateOutput, |inst, name| {
Arc::new(MultiStateOutput::new(inst, name, 4))
});
factory.register(ObjectType::MultiStateValue, |inst, name| {
Arc::new(MultiStateValue::new(inst, name, 4))
});
factory.register(ObjectType::TrendLog, |inst, name| {
Arc::new(TrendLog::new(inst, &name, 1000))
});
factory.register(ObjectType::File, |inst, name| {
Arc::new(FileObject::new(inst, name))
});
factory.register(ObjectType::EventEnrollment, |inst, name| {
use crate::object::event_enrollment::EventType;
Arc::new(EventEnrollment::new(inst, name, EventType::ChangeOfValue))
});
factory.register(ObjectType::NotificationClass, |inst, name| {
Arc::new(NotificationClass::new(inst, name))
});
factory.register(ObjectType::Schedule, |inst, name| {
Arc::new(Schedule::new(inst, name))
});
factory.register(ObjectType::Calendar, |inst, name| {
Arc::new(Calendar::new(inst, name))
});
factory
}
#[derive(Debug, Clone)]
pub struct CreateObjectRequest {
pub object_type: ObjectType,
pub object_instance: Option<u32>,
pub object_name: Option<String>,
pub initial_values: Vec<(PropertyId, BACnetValue)>,
}
pub fn decode_create_object_request(data: &[u8]) -> Result<CreateObjectRequest, &'static str> {
let mut decoder = ApduDecoder::new(data);
if !decoder.is_opening_tag(0) {
return Err("Expected opening tag [0] for object specifier");
}
decoder
.read_u8()
.map_err(|_| "Failed to skip opening tag")?;
let peek = decoder.peek().ok_or("Unexpected end of data")?;
let context_tag = (peek >> 4) & 0x0F;
let (object_type, object_instance) = if context_tag == 0 {
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode type tag")?;
let type_val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode type value")?;
let obj_type = ObjectType::from_u16(type_val as u16).ok_or("Unsupported object type")?;
(obj_type, None)
} else if context_tag == 1 {
let (_, _, _len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode id tag")?;
let obj_id = decoder
.decode_object_identifier()
.map_err(|_| "Failed to decode object identifier")?;
(obj_id.object_type, Some(obj_id.instance))
} else {
return Err("Invalid object specifier tag");
};
if !decoder.is_closing_tag(0) {
return Err("Expected closing tag [0]");
}
decoder
.read_u8()
.map_err(|_| "Failed to skip closing tag")?;
let mut object_name = None;
let mut initial_values = Vec::new();
if !decoder.is_empty() && decoder.is_opening_tag(1) {
decoder
.read_u8()
.map_err(|_| "Failed to skip opening tag [1]")?;
while !decoder.is_empty() && !decoder.is_closing_tag(1) {
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode property tag")?;
let prop_val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode property id")?;
let property_id = match PropertyId::from_u32(prop_val) {
Some(id) => id,
None => {
if !decoder.is_empty() {
let peek = decoder.peek().unwrap_or(0);
let next_tag = (peek >> 4) & 0x0F;
if next_tag == 1 && (peek & 0x08) != 0 {
let (_, _, len) = decoder.decode_tag_info().map_err(|_| "skip")?;
let _ = decoder.decode_unsigned(len);
}
}
if decoder.is_opening_tag(2) {
decoder.read_u8().map_err(|_| "skip")?;
skip_to_closing_tag(&mut decoder, 2)?;
decoder.read_u8().map_err(|_| "skip")?;
}
continue;
}
};
if !decoder.is_empty() {
let peek = decoder.peek().unwrap_or(0);
let next_tag = (peek >> 4) & 0x0F;
if next_tag == 1 && (peek & 0x08) != 0 {
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode array index tag")?;
let _ = decoder.decode_unsigned(len);
}
}
if !decoder.is_opening_tag(2) {
return Err("Expected opening tag [2] for property value");
}
decoder
.read_u8()
.map_err(|_| "Failed to skip opening tag [2]")?;
let value = decode_application_value(&mut decoder)?;
if !decoder.is_closing_tag(2) {
return Err("Expected closing tag [2] for property value");
}
decoder
.read_u8()
.map_err(|_| "Failed to skip closing tag [2]")?;
if property_id == PropertyId::ObjectName {
if let BACnetValue::CharacterString(ref s) = value {
object_name = Some(s.clone());
}
}
initial_values.push((property_id, value));
}
if !decoder.is_closing_tag(1) {
return Err("Expected closing tag [1]");
}
decoder
.read_u8()
.map_err(|_| "Failed to skip closing tag [1]")?;
}
Ok(CreateObjectRequest {
object_type,
object_instance,
object_name,
initial_values,
})
}
fn skip_to_closing_tag(decoder: &mut ApduDecoder<'_>, tag: u8) -> Result<(), &'static str> {
let mut depth = 0u32;
while !decoder.is_empty() {
if depth == 0 && decoder.is_closing_tag(tag) {
return Ok(());
}
let byte = decoder.peek().ok_or("Unexpected end while skipping")?;
if (byte & 0x0F) == 0x0E {
depth += 1;
decoder.read_u8().map_err(|_| "skip")?;
continue;
}
if (byte & 0x0F) == 0x0F {
depth = depth.saturating_sub(1);
decoder.read_u8().map_err(|_| "skip")?;
continue;
}
let (_, _, len) = decoder.decode_tag_info().map_err(|_| "skip")?;
if len > 0 {
let _ = decoder.read_bytes(len);
}
}
Err("Closing tag not found")
}
fn decode_application_value(decoder: &mut ApduDecoder<'_>) -> Result<BACnetValue, &'static str> {
let (tag_number, is_context, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode value tag")?;
if is_context {
let val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode context value")?;
return Ok(BACnetValue::Unsigned(val));
}
match tag_number {
0 => {
Ok(BACnetValue::Null)
}
1 => {
Ok(BACnetValue::Boolean(len != 0))
}
2 => {
let val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode unsigned")?;
Ok(BACnetValue::Unsigned(val))
}
3 => {
let val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode signed")?;
Ok(BACnetValue::Signed(val as i32))
}
4 => {
if len != 4 {
return Err("Invalid real length");
}
let bytes = decoder
.read_bytes(4)
.map_err(|_| "Failed to read float bytes")?;
let val = f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
Ok(BACnetValue::Real(val))
}
5 => {
if len != 8 {
return Err("Invalid double length");
}
let bytes = decoder
.read_bytes(8)
.map_err(|_| "Failed to read double bytes")?;
let val = f64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
Ok(BACnetValue::Double(val))
}
7 => {
let s = decoder
.decode_character_string(len)
.map_err(|_| "Failed to decode string")?;
Ok(BACnetValue::CharacterString(s))
}
8 => {
let bytes = decoder
.read_bytes(len)
.map_err(|_| "Failed to read bitstring")?;
if bytes.is_empty() {
return Ok(BACnetValue::BitString(vec![]));
}
let unused_bits = bytes[0] as usize;
let data = &bytes[1..];
let total_bits = data.len() * 8 - unused_bits;
let mut bits = Vec::with_capacity(total_bits);
for i in 0..total_bits {
let byte_idx = i / 8;
let bit_idx = 7 - (i % 8);
bits.push((data[byte_idx] >> bit_idx) & 1 == 1);
}
Ok(BACnetValue::BitString(bits))
}
9 => {
let val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode enumerated")?;
Ok(BACnetValue::Enumerated(val))
}
12 => {
let obj_id = decoder
.decode_object_identifier()
.map_err(|_| "Failed to decode object id")?;
Ok(BACnetValue::ObjectIdentifier(obj_id))
}
_ => {
let _ = decoder.read_bytes(len);
Ok(BACnetValue::Null)
}
}
}
#[derive(Debug, Clone)]
pub struct DeleteObjectRequest {
pub object_id: ObjectId,
}
pub fn decode_delete_object_request(data: &[u8]) -> Result<DeleteObjectRequest, &'static str> {
let mut decoder = ApduDecoder::new(data);
let (_, _, _len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
let object_id = decoder
.decode_object_identifier()
.map_err(|_| "Failed to decode object identifier")?;
Ok(DeleteObjectRequest { object_id })
}
pub fn encode_create_object_ack(object_id: ObjectId) -> Vec<u8> {
let mut encoder = ApduEncoder::new();
encoder.encode_object_identifier(object_id);
encoder.into_bytes()
}
pub struct CreateObjectHandler {
factory: ObjectFactory,
}
impl CreateObjectHandler {
pub fn new() -> Self {
Self {
factory: default_object_factory(),
}
}
pub fn with_factory(factory: ObjectFactory) -> Self {
Self { factory }
}
fn create_object(
&self,
request: &CreateObjectRequest,
registry: &Arc<ObjectRegistry>,
) -> Result<ObjectId, ServiceResult> {
if !self.factory.supports(request.object_type) {
return Err(ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::UnsupportedObjectType,
});
}
let instance = if let Some(inst) = request.object_instance {
let id = ObjectId::new(request.object_type, inst);
if registry.contains(&id) {
return Err(ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::ObjectIdentifierAlreadyExists,
});
}
inst
} else {
registry
.next_available_instance(request.object_type)
.ok_or_else(|| ServiceResult::Error {
error_class: ErrorClass::Resources,
error_code: ErrorCode::NoSpaceForObject,
})?
};
let name = if let Some(ref n) = request.object_name {
if registry.contains_name(n) {
return Err(ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::DuplicateName,
});
}
n.clone()
} else {
let type_prefix = object_type_prefix(request.object_type);
format!("{}_{}", type_prefix, instance)
};
let object = self
.factory
.create(request.object_type, instance, name)
.ok_or_else(|| ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::UnsupportedObjectType,
})?;
let object_id = object.object_identifier();
for (prop_id, value) in &request.initial_values {
if *prop_id == PropertyId::ObjectName || *prop_id == PropertyId::ObjectIdentifier {
continue; }
let _ = object.write_property(*prop_id, value.clone());
}
registry.register(object);
Ok(object_id)
}
}
impl ConfirmedServiceHandler for CreateObjectHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::CreateObject
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let request = match decode_create_object_request(data) {
Ok(r) => r,
Err(_) => return ServiceResult::missing_required_parameter(),
};
match self.create_object(&request, &ctx.objects) {
Ok(object_id) => ServiceResult::ComplexAck(encode_create_object_ack(object_id)),
Err(result) => result,
}
}
fn name(&self) -> &'static str {
"CreateObject"
}
fn min_data_length(&self) -> usize {
4 }
}
const NON_DELETABLE_TYPES: &[ObjectType] = &[ObjectType::Device];
pub struct DeleteObjectHandler;
impl DeleteObjectHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for DeleteObjectHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::DeleteObject
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let request = match decode_delete_object_request(data) {
Ok(r) => r,
Err(_) => return ServiceResult::missing_required_parameter(),
};
if NON_DELETABLE_TYPES.contains(&request.object_id.object_type) {
return ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::ObjectDeletionNotPermitted,
};
}
if !ctx.objects.contains(&request.object_id) {
return ServiceResult::unknown_object();
}
ctx.objects.unregister(&request.object_id);
ServiceResult::SimpleAck
}
fn name(&self) -> &'static str {
"DeleteObject"
}
fn min_data_length(&self) -> usize {
4 }
}
fn object_type_prefix(object_type: ObjectType) -> &'static str {
match object_type {
ObjectType::AnalogInput => "AI",
ObjectType::AnalogOutput => "AO",
ObjectType::AnalogValue => "AV",
ObjectType::BinaryInput => "BI",
ObjectType::BinaryOutput => "BO",
ObjectType::BinaryValue => "BV",
ObjectType::MultiStateInput => "MSI",
ObjectType::MultiStateOutput => "MSO",
ObjectType::MultiStateValue => "MSV",
ObjectType::TrendLog => "TL",
ObjectType::File => "FILE",
ObjectType::EventEnrollment => "EE",
ObjectType::NotificationClass => "NC",
ObjectType::Schedule => "SCHED",
ObjectType::Calendar => "CAL",
_ => "OBJ",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::registry::ObjectRegistry;
use crate::object::standard::AnalogInput;
fn make_ctx(registry: &Arc<ObjectRegistry>) -> ServiceContext {
ServiceContext {
objects: registry.clone(),
device_instance: 1234,
invoke_id: Some(1),
max_apdu_length: 1476,
source_address: None,
}
}
#[test]
fn test_handler_creation() {
let create = CreateObjectHandler::new();
assert_eq!(create.service_choice(), ConfirmedService::CreateObject);
assert_eq!(create.name(), "CreateObject");
let delete = DeleteObjectHandler::new();
assert_eq!(delete.service_choice(), ConfirmedService::DeleteObject);
assert_eq!(delete.name(), "DeleteObject");
}
#[test]
fn test_object_factory_default() {
let factory = default_object_factory();
assert!(factory.supports(ObjectType::AnalogInput));
assert!(factory.supports(ObjectType::AnalogOutput));
assert!(factory.supports(ObjectType::AnalogValue));
assert!(factory.supports(ObjectType::BinaryInput));
assert!(factory.supports(ObjectType::BinaryOutput));
assert!(factory.supports(ObjectType::BinaryValue));
assert!(factory.supports(ObjectType::MultiStateInput));
assert!(factory.supports(ObjectType::MultiStateOutput));
assert!(factory.supports(ObjectType::MultiStateValue));
assert!(factory.supports(ObjectType::TrendLog));
assert!(factory.supports(ObjectType::File));
assert!(factory.supports(ObjectType::EventEnrollment));
assert!(factory.supports(ObjectType::NotificationClass));
assert!(factory.supports(ObjectType::Schedule));
assert!(factory.supports(ObjectType::Calendar));
assert!(!factory.supports(ObjectType::Device));
}
#[test]
fn test_factory_create_object() {
let factory = default_object_factory();
let obj = factory
.create(ObjectType::AnalogInput, 42, "TestAI".to_string())
.unwrap();
assert_eq!(
obj.object_identifier(),
ObjectId::new(ObjectType::AnalogInput, 42)
);
assert_eq!(obj.object_name(), "TestAI");
}
#[test]
fn test_factory_unsupported_type() {
let factory = default_object_factory();
assert!(factory
.create(ObjectType::Device, 0, "Dev".to_string())
.is_none());
}
#[test]
fn test_create_object_auto_instance() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
registry.register(Arc::new(AnalogInput::new(0, "AI_0")));
let request = CreateObjectRequest {
object_type: ObjectType::AnalogInput,
object_instance: None,
object_name: Some("NewAI".to_string()),
initial_values: vec![],
};
let result = handler.create_object(&request, ®istry);
let obj_id = result.unwrap();
assert_eq!(obj_id.object_type, ObjectType::AnalogInput);
assert_eq!(obj_id.instance, 1); assert!(registry.contains(&obj_id));
assert_eq!(registry.get(&obj_id).unwrap().object_name(), "NewAI");
}
#[test]
fn test_create_object_specific_instance() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let request = CreateObjectRequest {
object_type: ObjectType::BinaryInput,
object_instance: Some(100),
object_name: Some("BI_100".to_string()),
initial_values: vec![],
};
let result = handler.create_object(&request, ®istry);
let obj_id = result.unwrap();
assert_eq!(obj_id, ObjectId::new(ObjectType::BinaryInput, 100));
}
#[test]
fn test_create_object_duplicate_instance() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
registry.register(Arc::new(AnalogInput::new(5, "AI_5")));
let request = CreateObjectRequest {
object_type: ObjectType::AnalogInput,
object_instance: Some(5),
object_name: Some("DuplicateAI".to_string()),
initial_values: vec![],
};
let result = handler.create_object(&request, ®istry);
assert!(result.is_err());
if let Err(ServiceResult::Error { error_code, .. }) = result {
assert_eq!(error_code, ErrorCode::ObjectIdentifierAlreadyExists);
}
}
#[test]
fn test_create_object_duplicate_name() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
registry.register(Arc::new(AnalogInput::new(0, "TakenName")));
let request = CreateObjectRequest {
object_type: ObjectType::AnalogInput,
object_instance: None,
object_name: Some("TakenName".to_string()),
initial_values: vec![],
};
let result = handler.create_object(&request, ®istry);
assert!(result.is_err());
if let Err(ServiceResult::Error { error_code, .. }) = result {
assert_eq!(error_code, ErrorCode::DuplicateName);
}
}
#[test]
fn test_create_object_unsupported_type() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let request = CreateObjectRequest {
object_type: ObjectType::Device,
object_instance: None,
object_name: None,
initial_values: vec![],
};
let result = handler.create_object(&request, ®istry);
assert!(result.is_err());
if let Err(ServiceResult::Error { error_code, .. }) = result {
assert_eq!(error_code, ErrorCode::UnsupportedObjectType);
}
}
#[test]
fn test_create_object_auto_name() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let request = CreateObjectRequest {
object_type: ObjectType::BinaryValue,
object_instance: None,
object_name: None, initial_values: vec![],
};
let result = handler.create_object(&request, ®istry).unwrap();
let obj = registry.get(&result).unwrap();
assert_eq!(obj.object_name(), "BV_0");
}
#[test]
fn test_create_object_with_initial_values() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let request = CreateObjectRequest {
object_type: ObjectType::AnalogInput,
object_instance: None,
object_name: Some("TempSensor".to_string()),
initial_values: vec![
(PropertyId::Units, BACnetValue::Enumerated(62)),
],
};
let result = handler.create_object(&request, ®istry).unwrap();
let obj = registry.get(&result).unwrap();
let units = obj.read_property(PropertyId::Units).unwrap();
assert_eq!(units, BACnetValue::Enumerated(62));
}
#[test]
fn test_create_object_handler_apdu() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let ctx = make_ctx(®istry);
let data = [
0x0E, 0x09, 0x00, 0x0F, ];
let result = handler.handle(&data, &ctx);
match result {
ServiceResult::ComplexAck(ack_data) => {
assert!(!ack_data.is_empty());
}
other => panic!("Expected ComplexAck, got {:?}", other),
}
let obj_id = ObjectId::new(ObjectType::AnalogInput, 0);
assert!(registry.contains(&obj_id));
}
#[test]
fn test_delete_object_success() {
let handler = DeleteObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
registry.register(Arc::new(AnalogInput::new(5, "AI_5")));
let ctx = make_ctx(®istry);
let obj_id = ObjectId::new(ObjectType::AnalogInput, 5);
let encoded = obj_id.encode();
let data = [
0xC4, (encoded >> 24) as u8,
(encoded >> 16) as u8,
(encoded >> 8) as u8,
encoded as u8,
];
let result = handler.handle(&data, &ctx);
assert!(matches!(result, ServiceResult::SimpleAck));
assert!(!registry.contains(&obj_id));
}
#[test]
fn test_delete_device_not_permitted() {
let handler = DeleteObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let ctx = make_ctx(®istry);
let obj_id = ObjectId::new(ObjectType::Device, 1234);
let encoded = obj_id.encode();
let data = [
0xC4,
(encoded >> 24) as u8,
(encoded >> 16) as u8,
(encoded >> 8) as u8,
encoded as u8,
];
let result = handler.handle(&data, &ctx);
match result {
ServiceResult::Error { error_code, .. } => {
assert_eq!(error_code, ErrorCode::ObjectDeletionNotPermitted);
}
other => panic!("Expected Error, got {:?}", other),
}
}
#[test]
fn test_delete_nonexistent_object() {
let handler = DeleteObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let ctx = make_ctx(®istry);
let obj_id = ObjectId::new(ObjectType::AnalogInput, 999);
let encoded = obj_id.encode();
let data = [
0xC4,
(encoded >> 24) as u8,
(encoded >> 16) as u8,
(encoded >> 8) as u8,
encoded as u8,
];
let result = handler.handle(&data, &ctx);
match result {
ServiceResult::Error { error_code, .. } => {
assert_eq!(error_code, ErrorCode::UnknownObject);
}
other => panic!("Expected Error, got {:?}", other),
}
}
#[test]
fn test_next_available_instance() {
let registry = ObjectRegistry::new();
assert_eq!(
registry.next_available_instance(ObjectType::AnalogInput),
Some(0)
);
registry.register(Arc::new(AnalogInput::new(0, "AI_0")));
assert_eq!(
registry.next_available_instance(ObjectType::AnalogInput),
Some(1)
);
registry.register(Arc::new(AnalogInput::new(5, "AI_5")));
assert_eq!(
registry.next_available_instance(ObjectType::AnalogInput),
Some(6)
);
}
#[test]
fn test_create_then_delete_roundtrip() {
let create_handler = CreateObjectHandler::new();
let _delete_handler = DeleteObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
let request = CreateObjectRequest {
object_type: ObjectType::AnalogValue,
object_instance: None,
object_name: Some("TempSetpoint".to_string()),
initial_values: vec![],
};
let obj_id = create_handler.create_object(&request, ®istry).unwrap();
assert!(registry.contains(&obj_id));
assert_eq!(registry.len(), 1);
registry.unregister(&obj_id);
assert!(!registry.contains(&obj_id));
assert_eq!(registry.len(), 0);
let request2 = CreateObjectRequest {
object_type: ObjectType::AnalogValue,
object_instance: None,
object_name: Some("TempSetpoint2".to_string()),
initial_values: vec![],
};
let obj_id2 = create_handler.create_object(&request2, ®istry).unwrap();
assert_eq!(obj_id2.instance, 0); }
#[test]
fn test_decode_create_object_type_only() {
let data = [
0x0E, 0x09, 0x00, 0x0F, ];
let request = decode_create_object_request(&data).unwrap();
assert_eq!(request.object_type, ObjectType::AnalogInput);
assert!(request.object_instance.is_none());
assert!(request.object_name.is_none());
assert!(request.initial_values.is_empty());
}
#[test]
fn test_decode_create_object_with_identifier() {
let obj_id = ObjectId::new(ObjectType::AnalogInput, 42);
let encoded = obj_id.encode();
let data = [
0x0E, 0x1C, (encoded >> 24) as u8,
(encoded >> 16) as u8,
(encoded >> 8) as u8,
encoded as u8,
0x0F, ];
let request = decode_create_object_request(&data).unwrap();
assert_eq!(request.object_type, ObjectType::AnalogInput);
assert_eq!(request.object_instance, Some(42));
}
#[test]
fn test_decode_delete_object() {
let obj_id = ObjectId::new(ObjectType::BinaryOutput, 10);
let encoded = obj_id.encode();
let data = [
0xC4, (encoded >> 24) as u8,
(encoded >> 16) as u8,
(encoded >> 8) as u8,
encoded as u8,
];
let request = decode_delete_object_request(&data).unwrap();
assert_eq!(request.object_id, obj_id);
}
#[test]
fn test_encode_create_object_ack() {
let obj_id = ObjectId::new(ObjectType::AnalogInput, 7);
let ack = encode_create_object_ack(obj_id);
assert!(!ack.is_empty());
assert_eq!(ack[0] & 0xF0, 0xC0); }
#[test]
fn test_create_multiple_objects_sequential() {
let handler = CreateObjectHandler::new();
let registry = Arc::new(ObjectRegistry::new());
for i in 0..5 {
let request = CreateObjectRequest {
object_type: ObjectType::AnalogInput,
object_instance: None,
object_name: Some(format!("AI_{}", i)),
initial_values: vec![],
};
let obj_id = handler.create_object(&request, ®istry).unwrap();
assert_eq!(obj_id.instance, i);
}
assert_eq!(registry.count_by_type(ObjectType::AnalogInput), 5);
}
#[test]
fn test_object_type_prefix() {
assert_eq!(object_type_prefix(ObjectType::AnalogInput), "AI");
assert_eq!(object_type_prefix(ObjectType::BinaryOutput), "BO");
assert_eq!(object_type_prefix(ObjectType::MultiStateValue), "MSV");
assert_eq!(object_type_prefix(ObjectType::TrendLog), "TL");
assert_eq!(object_type_prefix(ObjectType::File), "FILE");
assert_eq!(object_type_prefix(ObjectType::EventEnrollment), "EE");
assert_eq!(object_type_prefix(ObjectType::NotificationClass), "NC");
}
}