use super::*;
pub fn handle_read_range(
db: &ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
use bacnet_services::read_range::{RangeSpec, ReadRangeAck, ReadRangeRequest};
let request = ReadRangeRequest::decode(service_data)?;
let object = db.get(&request.object_identifier).ok_or(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
})?;
let value = object.read_property(request.property_identifier, request.property_array_index)?;
let items = match value {
PropertyValue::List(items) => items,
_ => {
return Err(Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::PROPERTY_IS_NOT_A_LIST.to_raw() as u32,
});
}
};
let total = items.len();
let (selected, first_item, last_item) = match &request.range {
None => (items, true, true),
Some(RangeSpec::ByPosition {
reference_index,
count,
}) => {
let ref_idx = *reference_index as usize;
let cnt = *count;
if cnt == 0 || total == 0 || ref_idx == 0 || ref_idx > total {
(Vec::new(), true, true)
} else if cnt > 0 {
let start = ref_idx - 1; let end = (start + cnt as usize).min(total);
let first = start == 0;
let last = end >= total;
(items[start..end].to_vec(), first, last)
} else {
let abs_count = cnt.unsigned_abs() as usize;
let end = ref_idx; let start = end.saturating_sub(abs_count);
let first = start == 0;
let last = end >= total;
(items[start..end].to_vec(), first, last)
}
}
Some(RangeSpec::BySequenceNumber {
reference_seq,
count,
}) => {
let ref_idx = *reference_seq as usize;
let cnt = *count;
if cnt == 0 || total == 0 {
(Vec::new(), true, true)
} else if cnt > 0 {
let start = ref_idx.min(total).saturating_sub(1);
let end = (start + cnt as usize).min(total);
let first = start == 0;
let last = end >= total;
(items[start..end].to_vec(), first, last)
} else {
let abs_count = cnt.unsigned_abs() as usize;
let end = ref_idx.min(total);
let start = end.saturating_sub(abs_count);
let first = start == 0;
let last = end >= total;
(items[start..end].to_vec(), first, last)
}
}
Some(RangeSpec::ByTime { .. }) => {
return Err(Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::SERVICE_REQUEST_DENIED.to_raw() as u32,
});
}
};
let mut item_data = BytesMut::new();
let mut encoded_count: u32 = 0;
for item in &selected {
if encode_property_value(&mut item_data, item).is_err() {
continue;
}
encoded_count += 1;
}
let item_count = encoded_count;
let ack = ReadRangeAck {
object_identifier: request.object_identifier,
property_identifier: request.property_identifier,
property_array_index: request.property_array_index,
result_flags: (first_item, last_item, false),
item_count,
item_data: item_data.to_vec(),
first_sequence_number: None,
};
ack.encode(buf);
Ok(())
}
pub fn handle_atomic_read_file(
db: &ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
use bacnet_services::file::{
AtomicReadFileAck, AtomicReadFileRequest, FileAccessMethod, FileReadAckMethod,
};
let request = AtomicReadFileRequest::decode(service_data)?;
let object = db.get(&request.file_identifier).ok_or(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
})?;
if request.file_identifier.object_type() != ObjectType::FILE {
return Err(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNSUPPORTED_OBJECT_TYPE.to_raw() as u32,
});
}
let file_size = object
.read_property(PropertyIdentifier::FILE_SIZE, None)
.ok()
.and_then(|v| match v {
PropertyValue::Unsigned(n) => Some(n),
_ => None,
})
.unwrap_or(0);
match request.access {
FileAccessMethod::Stream {
file_start_position,
requested_octet_count,
} => {
let start = file_start_position.max(0) as u64;
let count = requested_octet_count as u64;
let end_of_file = start + count >= file_size;
let file_data = object
.read_property(PropertyIdentifier::from_raw(PROP_FILE_DATA), None)
.ok()
.and_then(|v| match v {
PropertyValue::OctetString(d) => Some(d),
_ => None,
})
.unwrap_or_default();
let s = start as usize;
let e = (s + count as usize).min(file_data.len());
let data = if s < file_data.len() {
file_data[s..e].to_vec()
} else {
Vec::new()
};
let ack = AtomicReadFileAck {
end_of_file,
access: FileReadAckMethod::Stream {
file_start_position,
file_data: data,
},
};
ack.encode(buf);
Ok(())
}
FileAccessMethod::Record {
file_start_record,
requested_record_count,
} => {
let start = file_start_record.max(0) as usize;
let count = requested_record_count as usize;
let record_count = object
.read_property(PropertyIdentifier::RECORD_COUNT, None)
.ok()
.and_then(|v| match v {
PropertyValue::Unsigned(n) => Some(n as usize),
_ => None,
})
.unwrap_or(0);
let end = (start + count).min(record_count);
let end_of_file = end >= record_count;
let records_data: Vec<Vec<u8>> = (start..end).map(|_| Vec::new()).collect();
let ack = AtomicReadFileAck {
end_of_file,
access: FileReadAckMethod::Record {
file_start_record,
returned_record_count: records_data.len() as u32,
file_record_data: records_data,
},
};
ack.encode(buf);
Ok(())
}
}
}
pub fn handle_atomic_write_file(
db: &mut ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
use bacnet_services::file::{
AtomicWriteFileAck, AtomicWriteFileRequest, FileWriteAccessMethod, FileWriteAckMethod,
};
let request = AtomicWriteFileRequest::decode(service_data)?;
let object = db.get(&request.file_identifier).ok_or(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
})?;
if request.file_identifier.object_type() != ObjectType::FILE {
return Err(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNSUPPORTED_OBJECT_TYPE.to_raw() as u32,
});
}
let read_only = object
.read_property(PropertyIdentifier::READ_ONLY, None)
.ok()
.and_then(|v| match v {
PropertyValue::Boolean(b) => Some(b),
_ => None,
})
.unwrap_or(false);
if read_only {
return Err(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::FILE_ACCESS_DENIED.to_raw() as u32,
});
}
match request.access {
FileWriteAccessMethod::Stream {
file_start_position,
file_data,
} => {
let object = db
.get_mut(&request.file_identifier)
.ok_or(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
})?;
let mut existing = object
.read_property(PropertyIdentifier::from_raw(PROP_FILE_DATA), None)
.ok()
.and_then(|v| match v {
PropertyValue::OctetString(d) => Some(d),
_ => None,
})
.unwrap_or_default();
let start = file_start_position.max(0) as usize;
if start + file_data.len() > existing.len() {
existing.resize(start + file_data.len(), 0);
}
existing[start..start + file_data.len()].copy_from_slice(&file_data);
object.write_property(
PropertyIdentifier::from_raw(PROP_FILE_DATA),
None,
PropertyValue::OctetString(existing),
None,
)?;
let ack = AtomicWriteFileAck {
access: FileWriteAckMethod::Stream {
file_start_position,
},
};
ack.encode(buf);
Ok(())
}
FileWriteAccessMethod::Record {
file_start_record, ..
} => {
let ack = AtomicWriteFileAck {
access: FileWriteAckMethod::Record { file_start_record },
};
ack.encode(buf);
Ok(())
}
}
}