1use crate::object::{
8 BacnetObject, ObjectError, ObjectIdentifier, ObjectType, PropertyIdentifier, PropertyValue,
9 Result,
10};
11
12#[cfg(not(feature = "std"))]
13use alloc::{string::String, vec::Vec};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[repr(u32)]
18pub enum FileAccessMethod {
19 RecordAccess = 0,
20 StreamAccess = 1,
21}
22
23#[derive(Debug, Clone)]
25pub struct File {
26 pub identifier: ObjectIdentifier,
28 pub object_name: String,
30 pub file_type: String,
32 pub file_size: u32,
34 pub modification_date: crate::object::Date,
36 pub archive: bool,
38 pub read_only: bool,
40 pub file_access_method: FileAccessMethod,
42 pub record_count: Option<u32>,
44 pub description: String,
46 pub file_data: Vec<u8>,
48}
49
50impl File {
51 pub fn new(instance: u32, object_name: String, file_type: String) -> Self {
53 Self {
54 identifier: ObjectIdentifier::new(ObjectType::File, instance),
55 object_name,
56 file_type,
57 file_size: 0,
58 modification_date: crate::object::Date {
59 year: 2024,
60 month: 1,
61 day: 1,
62 weekday: 1,
63 },
64 archive: false,
65 read_only: false,
66 file_access_method: FileAccessMethod::StreamAccess,
67 record_count: None,
68 description: String::new(),
69 file_data: Vec::new(),
70 }
71 }
72
73 pub fn set_file_data(&mut self, data: Vec<u8>) {
75 self.file_data = data;
76 self.file_size = self.file_data.len() as u32;
77 }
80
81 pub fn get_file_data(&self) -> &[u8] {
83 &self.file_data
84 }
85
86 pub fn read_data(&self, start_position: u32, requested_count: u32) -> Result<Vec<u8>> {
88 let start = start_position as usize;
89 let end = (start_position + requested_count) as usize;
90
91 if start >= self.file_data.len() {
92 return Ok(Vec::new()); }
94
95 let actual_end = end.min(self.file_data.len());
96 Ok(self.file_data[start..actual_end].to_vec())
97 }
98
99 pub fn write_data(&mut self, start_position: u32, data: &[u8]) -> Result<()> {
101 if self.read_only {
102 return Err(ObjectError::WriteAccessDenied);
103 }
104
105 let start = start_position as usize;
106 let data_len = data.len();
107 let required_len = start + data_len;
108
109 if required_len > self.file_data.len() {
111 self.file_data.resize(required_len, 0);
112 }
113
114 self.file_data[start..start + data_len].copy_from_slice(data);
116 self.file_size = self.file_data.len() as u32;
117
118 Ok(())
119 }
120
121 pub fn read_records(&self, start_record: u32, record_count: u32) -> Result<Vec<Vec<u8>>> {
123 if self.file_access_method != FileAccessMethod::RecordAccess {
124 return Err(ObjectError::InvalidValue(
125 "File is not configured for record access".to_string(),
126 ));
127 }
128
129 let mut records = Vec::new();
132
133 let file_str = String::from_utf8_lossy(&self.file_data);
135 let lines: Vec<&str> = file_str.lines().collect();
136
137 let start_idx = start_record as usize;
138 let end_idx = (start_record + record_count) as usize;
139
140 for line in lines.iter().take(end_idx.min(lines.len())).skip(start_idx) {
141 records.push(line.as_bytes().to_vec());
142 }
143
144 Ok(records)
145 }
146
147 pub fn write_records(&mut self, start_record: u32, records: &[Vec<u8>]) -> Result<()> {
149 if self.read_only {
150 return Err(ObjectError::WriteAccessDenied);
151 }
152
153 if self.file_access_method != FileAccessMethod::RecordAccess {
154 return Err(ObjectError::InvalidValue(
155 "File is not configured for record access".to_string(),
156 ));
157 }
158
159 let file_str = String::from_utf8_lossy(&self.file_data);
162 let mut lines: Vec<String> = file_str.lines().map(|s| s.to_string()).collect();
163
164 let start_idx = start_record as usize;
165
166 while lines.len() < start_idx + records.len() {
168 lines.push(String::new());
169 }
170
171 for (i, record) in records.iter().enumerate() {
173 let record_str = String::from_utf8_lossy(record);
174 lines[start_idx + i] = record_str.to_string();
175 }
176
177 let new_data = lines.join("\n");
179 self.file_data = new_data.into_bytes();
180 self.file_size = self.file_data.len() as u32;
181 self.record_count = Some(lines.len() as u32);
182
183 Ok(())
184 }
185}
186
187impl BacnetObject for File {
188 fn identifier(&self) -> ObjectIdentifier {
189 self.identifier
190 }
191
192 fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
193 match property {
194 PropertyIdentifier::ObjectIdentifier => {
195 Ok(PropertyValue::ObjectIdentifier(self.identifier))
196 }
197 PropertyIdentifier::ObjectName => {
198 Ok(PropertyValue::CharacterString(self.object_name.clone()))
199 }
200 PropertyIdentifier::ObjectType => {
201 Ok(PropertyValue::Enumerated(u32::from(ObjectType::File)))
202 }
203 PropertyIdentifier::Archive => Ok(PropertyValue::Boolean(self.archive)),
204 _ => Err(ObjectError::UnknownProperty),
205 }
206 }
207
208 fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
209 match property {
210 PropertyIdentifier::ObjectName => {
211 if let PropertyValue::CharacterString(name) = value {
212 self.object_name = name;
213 Ok(())
214 } else {
215 Err(ObjectError::InvalidPropertyType)
216 }
217 }
218 PropertyIdentifier::Archive => {
219 if let PropertyValue::Boolean(archive) = value {
220 self.archive = archive;
221 Ok(())
222 } else {
223 Err(ObjectError::InvalidPropertyType)
224 }
225 }
226 _ => Err(ObjectError::PropertyNotWritable),
227 }
228 }
229
230 fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
231 matches!(
232 property,
233 PropertyIdentifier::ObjectName | PropertyIdentifier::Archive
234 )
235 }
236
237 fn property_list(&self) -> Vec<PropertyIdentifier> {
238 vec![
239 PropertyIdentifier::ObjectIdentifier,
240 PropertyIdentifier::ObjectName,
241 PropertyIdentifier::ObjectType,
242 PropertyIdentifier::Archive,
243 ]
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_file_creation() {
253 let file = File::new(1, "config.txt".to_string(), "text/plain".to_string());
254 assert_eq!(file.identifier.instance, 1);
255 assert_eq!(file.object_name, "config.txt");
256 assert_eq!(file.file_type, "text/plain");
257 assert_eq!(file.file_size, 0);
258 }
259
260 #[test]
261 fn test_file_data_operations() {
262 let mut file = File::new(
263 1,
264 "test.dat".to_string(),
265 "application/octet-stream".to_string(),
266 );
267
268 let data = b"Hello, BACnet File!".to_vec();
270 file.set_file_data(data.clone());
271 assert_eq!(file.file_size, data.len() as u32);
272 assert_eq!(file.get_file_data(), data.as_slice());
273
274 let read_data = file.read_data(0, 5).unwrap();
276 assert_eq!(read_data, b"Hello");
277
278 let read_data = file.read_data(7, 6).unwrap();
279 assert_eq!(read_data, b"BACnet");
280
281 file.write_data(7, b"Rust ").unwrap();
283 let expected = b"Hello, Rust File!";
284 assert_eq!(file.get_file_data(), expected);
285 }
286
287 #[test]
288 fn test_file_record_operations() {
289 let mut file = File::new(1, "records.txt".to_string(), "text/plain".to_string());
290 file.file_access_method = FileAccessMethod::RecordAccess;
291
292 let initial_data = "Line 1\nLine 2\nLine 3\nLine 4".as_bytes().to_vec();
294 file.set_file_data(initial_data);
295
296 let records = file.read_records(1, 2).unwrap();
298 assert_eq!(records.len(), 2);
299 assert_eq!(records[0], b"Line 2");
300 assert_eq!(records[1], b"Line 3");
301
302 let new_records = vec![b"New Line 2".to_vec(), b"New Line 3".to_vec()];
304 file.write_records(1, &new_records).unwrap();
305
306 let updated_records = file.read_records(0, 4).unwrap();
307 assert_eq!(updated_records[0], b"Line 1");
308 assert_eq!(updated_records[1], b"New Line 2");
309 assert_eq!(updated_records[2], b"New Line 3");
310 assert_eq!(updated_records[3], b"Line 4");
311 }
312
313 #[test]
314 fn test_file_properties() {
315 let mut file = File::new(1, "test.txt".to_string(), "text/plain".to_string());
316
317 let name = file.get_property(PropertyIdentifier::ObjectName).unwrap();
319 if let PropertyValue::CharacterString(n) = name {
320 assert_eq!(n, "test.txt");
321 } else {
322 panic!("Expected CharacterString");
323 }
324
325 file.set_property(PropertyIdentifier::Archive, PropertyValue::Boolean(true))
327 .unwrap();
328 assert!(file.archive);
329 }
330
331 #[test]
332 fn test_read_only_protection() {
333 let mut file = File::new(1, "readonly.txt".to_string(), "text/plain".to_string());
334 file.read_only = true;
335
336 assert!(file.write_data(0, b"test").is_err());
338
339 file.file_access_method = FileAccessMethod::RecordAccess;
341 let records = vec![b"test".to_vec()];
342 assert!(file.write_records(0, &records).is_err());
343 }
344}