xref: /aosp_15_r20/external/uwb/src/rust/uwb_core/src/uci/pcapng_uci_logger_factory.rs (revision e0df40009cb5d71e642272d38ba1bb7ffccfce41)
1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, item 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! This file defines PcapngUciLoggerFactory, which implements UciLoggerFactory
16 //! trait and logging UCI packets into PCAPNG format.
17 
18 use std::fs;
19 use std::io::Write;
20 use std::path::{Path, PathBuf};
21 
22 use log::{debug, error};
23 use tokio::runtime::Handle;
24 use tokio::sync::mpsc;
25 
26 use crate::uci::pcapng_block::{
27     BlockBuilder, BlockOption, HeaderBlockBuilder, InterfaceDescriptionBlockBuilder,
28 };
29 use crate::uci::uci_logger_factory::UciLoggerFactory;
30 use crate::uci::uci_logger_pcapng::UciLoggerPcapng;
31 use crate::utils::consuming_builder_field;
32 
33 const DEFAULT_LOG_DIR: &str = "/var/log/uwb";
34 const DEFAULT_FILE_PREFIX: &str = "uwb_uci";
35 const DEFAULT_BUFFER_SIZE: usize = 10240; // 10 KB
36 const DEFAUL_FILE_SIZE: usize = 1048576; // 1 MB
37 
38 /// The PCAPNG log file factory.
39 pub struct PcapngUciLoggerFactory {
40     /// log_writer references to LogWriterActor.
41     log_writer: LogWriter,
42     /// Maps recording chip-id to interface-id for UciLoggerPcapng.
43     ///
44     /// Map is forwarded LogWriterActor, the "actor" that log_writer owns which performs
45     /// actual writing of files which needs this map to build the InterfaceDescriptionBlock.
46     /// Since PCAPNG format defines the interface ID by the order of appearance of IDB inside file,
47     /// the "map" is a vector whose index coincides with the interface ID.
48     chip_interface_id_map: Vec<String>,
49 }
50 
51 impl UciLoggerFactory for PcapngUciLoggerFactory {
52     type Logger = UciLoggerPcapng;
53 
54     /// PcapngUciLoggerFactory builds UciLoggerPcapng.
build_logger(&mut self, chip_id: &str) -> Option<UciLoggerPcapng>55     fn build_logger(&mut self, chip_id: &str) -> Option<UciLoggerPcapng> {
56         let chip_interface_id = match self.chip_interface_id_map.iter().position(|c| c == chip_id) {
57             Some(id) => id as u32,
58             None => {
59                 let id = self.chip_interface_id_map.len() as u32;
60                 self.chip_interface_id_map.push(chip_id.to_owned());
61                 if self.log_writer.send_chip(chip_id.to_owned(), id).is_none() {
62                     error!("UCI log: associated LogWriterActor is dead");
63                     return None;
64                 }
65                 id
66             }
67         };
68         Some(UciLoggerPcapng::new(self.log_writer.clone(), chip_interface_id))
69     }
70 }
71 
72 /// Builder for PCAPNG log file factory.
73 pub struct PcapngUciLoggerFactoryBuilder {
74     /// Buffer size.
75     buffer_size: usize,
76     /// Max file size:
77     file_size: usize,
78     /// Filename prefix for log file.
79     filename_prefix: String,
80     /// Directory for log file.
81     log_path: PathBuf,
82     /// Range for the rotating index of log files.
83     rotate_range: usize,
84     /// Tokio Runtime Handle for driving Log.
85     runtime_handle: Option<Handle>,
86 }
87 impl Default for PcapngUciLoggerFactoryBuilder {
default() -> Self88     fn default() -> Self {
89         Self {
90             buffer_size: DEFAULT_BUFFER_SIZE,
91             file_size: DEFAUL_FILE_SIZE,
92             filename_prefix: DEFAULT_FILE_PREFIX.to_owned(),
93             log_path: PathBuf::from(DEFAULT_LOG_DIR),
94             rotate_range: 8,
95             runtime_handle: None,
96         }
97     }
98 }
99 
100 impl PcapngUciLoggerFactoryBuilder {
101     /// Constructor.
new() -> Self102     pub fn new() -> Self {
103         PcapngUciLoggerFactoryBuilder::default()
104     }
105 
106     // Setter methods of each field.
107     consuming_builder_field!(runtime_handle, Handle, Some);
108     consuming_builder_field!(filename_prefix, String);
109     consuming_builder_field!(rotate_range, usize);
110     consuming_builder_field!(log_path, PathBuf);
111     consuming_builder_field!(buffer_size, usize);
112     consuming_builder_field!(file_size, usize);
113 
114     /// Builds PcapngUciLoggerFactory
build(self) -> Option<PcapngUciLoggerFactory>115     pub fn build(self) -> Option<PcapngUciLoggerFactory> {
116         let file_factory = FileFactory::new(
117             self.log_path,
118             self.filename_prefix,
119             self.buffer_size,
120             self.rotate_range,
121         );
122         let log_writer = LogWriter::new(file_factory, self.file_size, self.runtime_handle?)?;
123         let manager = PcapngUciLoggerFactory { log_writer, chip_interface_id_map: Vec::new() };
124         Some(manager)
125     }
126 }
127 
128 #[derive(Clone, Debug)]
129 pub(crate) enum PcapngLoggerMessage {
130     ByteStream(Vec<u8>),
131     NewChip((String, u32)),
132     Flush(mpsc::UnboundedSender<bool>),
133 }
134 
135 /// LogWriterActor performs the log writing and file operations asynchronously.
136 struct LogWriterActor {
137     /// Maps chip id to interface id. The content follows the content of the component in
138     /// PcapngUciLoggerFactory with the same name.
139     chip_interface_id_map: Vec<String>,
140     current_file: Option<BufferedFile>,
141     file_factory: FileFactory,
142     file_size_limit: usize,
143     log_receiver: mpsc::UnboundedReceiver<PcapngLoggerMessage>,
144 }
145 
146 impl LogWriterActor {
147     /// write data to file.
write_once(&mut self, data: Vec<u8>) -> Option<()>148     fn write_once(&mut self, data: Vec<u8>) -> Option<()> {
149         // Create new file if the file is not created, or does not fit incoming data:
150         if self.current_file.is_none()
151             || data.len() + self.current_file.as_ref().unwrap().file_size() > self.file_size_limit
152         {
153             self.current_file = Some(
154                 self.file_factory
155                     .build_file_with_metadata(&self.chip_interface_id_map, self.file_size_limit)?,
156             );
157         }
158         self.current_file.as_mut().unwrap().buffered_write(data)
159     }
160 
161     /// Handle single new chip: stores chip in chip_interface_id_map and:
162     ///
163     /// a. Nothing extra if current_file is not created yet.
164     /// b. If current file exists:
165     ///    Insert IDB in current file if it fits, otherwise switch to new file.
handle_new_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()>166     fn handle_new_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()> {
167         if self.chip_interface_id_map.contains(&chip_id)
168             || self.chip_interface_id_map.len() as u32 != interface_id
169         {
170             error!(
171                 "UCI log: unexpected chip_id {} with associated interface id {}",
172                 &chip_id, interface_id
173             );
174             return None;
175         }
176         self.chip_interface_id_map.push(chip_id.clone());
177 
178         if let Some(current_file) = &mut self.current_file {
179             let idb_data = into_interface_description_block(chip_id)?;
180             if idb_data.len() + current_file.file_size() <= self.file_size_limit {
181                 current_file.buffered_write(idb_data)?;
182             } else {
183                 self.current_file =
184                     Some(self.file_factory.build_file_with_metadata(
185                         &self.chip_interface_id_map,
186                         self.file_size_limit,
187                     )?);
188             }
189         }
190         Some(())
191     }
192 
run(&mut self)193     async fn run(&mut self) {
194         debug!("UCI log: LogWriterActor started");
195         loop {
196             match self.log_receiver.recv().await {
197                 Some(PcapngLoggerMessage::NewChip((chip_id, interface_id))) => {
198                     if self.handle_new_chip(chip_id.clone(), interface_id).is_none() {
199                         error!("UCI log: failed logging new chip {}", &chip_id);
200                         break;
201                     }
202                 }
203                 Some(PcapngLoggerMessage::ByteStream(data)) => {
204                     if self.write_once(data).is_none() {
205                         match &self.current_file {
206                             Some(current_file) => {
207                                 error!(
208                                     "UCI log: failed writting packet to log file {:?}",
209                                     current_file.file
210                                 );
211                             }
212                             None => {
213                                 error!("UCI log: failed writting packet to log file: no log file.");
214                             }
215                         }
216                         break;
217                     }
218                 }
219                 Some(PcapngLoggerMessage::Flush(flush_sender)) => {
220                     if self.current_file.is_some() {
221                         match self.current_file.as_mut().unwrap().flush_file() {
222                             Some(_) => {
223                                 let _ = flush_sender.send(true);
224                             }
225                             None => {
226                                 error!("UCI log: failed flushing the file");
227                                 let _ = flush_sender.send(false);
228                             }
229                         }
230                     } else {
231                         error!("UCI log: current_file not present");
232                         let _ = flush_sender.send(false);
233                     }
234                 }
235                 None => {
236                     debug!("UCI log: LogWriterActor dropping.");
237                     break;
238                 }
239             }
240         }
241     }
242 }
243 
244 /// Handle to LogWriterActor.
245 #[derive(Clone)]
246 pub(crate) struct LogWriter {
247     log_sender: Option<mpsc::UnboundedSender<PcapngLoggerMessage>>,
248 }
249 
250 impl LogWriter {
251     /// Constructs LogWriter and its actor.
252     ///
253     /// runtime_handle must be a Handle to a multithread runtime that outlives LogWriterActor
new( file_factory: FileFactory, file_size_limit: usize, runtime_handle: Handle, ) -> Option<Self>254     fn new(
255         file_factory: FileFactory,
256         file_size_limit: usize,
257         runtime_handle: Handle,
258     ) -> Option<Self> {
259         let chip_interface_id_map = Vec::new();
260         let (log_sender, log_receiver) = mpsc::unbounded_channel();
261         let mut log_writer_actor = LogWriterActor {
262             chip_interface_id_map,
263             current_file: None,
264             file_factory,
265             file_size_limit,
266             log_receiver,
267         };
268         runtime_handle.spawn(async move { log_writer_actor.run().await });
269         Some(LogWriter { log_sender: Some(log_sender) })
270     }
271 
send_bytes(&mut self, bytes: Vec<u8>) -> Option<()>272     pub fn send_bytes(&mut self, bytes: Vec<u8>) -> Option<()> {
273         let log_sender = self.log_sender.as_ref()?;
274         match log_sender.send(PcapngLoggerMessage::ByteStream(bytes)) {
275             Ok(_) => Some(()),
276             Err(e) => {
277                 error!("UCI log: LogWriterActor dead unexpectedly, sender error: {:?}", e);
278                 self.log_sender = None;
279                 None
280             }
281         }
282     }
283 
flush(&mut self) -> Option<mpsc::UnboundedReceiver<bool>>284     pub fn flush(&mut self) -> Option<mpsc::UnboundedReceiver<bool>> {
285         let log_sender = self.log_sender.as_ref()?;
286         let (flush_sender, flush_receiver) = mpsc::unbounded_channel();
287         match log_sender.send(PcapngLoggerMessage::Flush(flush_sender)) {
288             Ok(_) => Some(flush_receiver),
289             Err(e) => {
290                 error!("UCI log: LogWriterActor dead unexpectedly, sender error: {:?}", e);
291                 self.log_sender = None;
292                 None
293             }
294         }
295     }
296 
send_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()>297     fn send_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()> {
298         let log_sender = self.log_sender.as_ref()?;
299         match log_sender.send(PcapngLoggerMessage::NewChip((chip_id, interface_id))) {
300             Ok(_) => Some(()),
301             Err(e) => {
302                 error!("UCI log: LogWriterActor dead unexpectedly, sender error: {:?}", e);
303                 self.log_sender = None;
304                 None
305             }
306         }
307     }
308 }
309 
into_interface_description_block(chip_id: String) -> Option<Vec<u8>>310 fn into_interface_description_block(chip_id: String) -> Option<Vec<u8>> {
311     let if_name_option = BlockOption::new(0x2, chip_id.into_bytes());
312     InterfaceDescriptionBlockBuilder::new().append_option(if_name_option).into_le_bytes()
313 }
314 
315 /// FileFactory builds next BufferedFile.
316 ///
317 /// The most recent log file is {fileprefix}.pcapng. The archived log files have their index
318 /// increased: {fileprefix}_{n}.pcapng where n = 0..(rotate_range-1).
319 struct FileFactory {
320     log_directory: PathBuf,
321     filename_prefix: String,
322     rotate_range: usize,
323     buffer_size: usize,
324 }
325 
326 impl FileFactory {
327     /// Constructor.
new( log_directory: PathBuf, filename_prefix: String, buffer_size: usize, rotate_range: usize, ) -> FileFactory328     fn new(
329         log_directory: PathBuf,
330         filename_prefix: String,
331         buffer_size: usize,
332         rotate_range: usize,
333     ) -> FileFactory {
334         Self { log_directory, filename_prefix, rotate_range, buffer_size }
335     }
336 
337     /// Builds pcapng file from a file factory, and prepares it with necessary header and metadata.
build_file_with_metadata( &mut self, chip_interface_id_map: &[String], file_size_limit: usize, ) -> Option<BufferedFile>338     fn build_file_with_metadata(
339         &mut self,
340         chip_interface_id_map: &[String],
341         file_size_limit: usize,
342     ) -> Option<BufferedFile> {
343         let mut current_file = self.build_empty_file()?;
344         let mut metadata = Vec::new();
345         metadata.append(&mut HeaderBlockBuilder::new().into_le_bytes()?);
346         for chip_id in chip_interface_id_map.iter() {
347             metadata.append(&mut into_interface_description_block(chip_id.to_owned())?);
348         }
349         if metadata.len() > file_size_limit {
350             error!(
351                 "UCI log: log file size limit is too small ({}) for file header and metadata ({})",
352                 file_size_limit,
353                 metadata.len()
354             );
355         }
356         current_file.buffered_write(metadata)?;
357         Some(current_file)
358     }
359 
360     /// Builds next file as an empty BufferedFile.
build_empty_file(&mut self) -> Option<BufferedFile>361     fn build_empty_file(&mut self) -> Option<BufferedFile> {
362         self.rotate_file()?;
363         let file_path = self.get_file_path(0);
364         BufferedFile::new(&self.log_directory, &file_path, self.buffer_size)
365     }
366 
367     /// get file path for log files of given index.
get_file_path(&self, index: usize) -> PathBuf368     fn get_file_path(&self, index: usize) -> PathBuf {
369         let file_basename = if index == 0 {
370             format!("{}.pcapng", self.filename_prefix)
371         } else {
372             format!("{}_{}.pcapng", self.filename_prefix, index)
373         };
374         self.log_directory.join(file_basename)
375     }
376 
377     /// Vacates {filename_prefix}_0.pcapng for new log.
rotate_file(&self) -> Option<()>378     fn rotate_file(&self) -> Option<()> {
379         for source_idx in (0..self.rotate_range - 1).rev() {
380             let target_idx = source_idx + 1;
381             let source_path = self.get_file_path(source_idx);
382             let target_path = self.get_file_path(target_idx);
383             if source_path.is_dir() {
384                 error!("UCI log: expect {:?} to be a filename, but is a directory", &source_path);
385                 return None;
386             }
387             if source_path.is_file() && fs::rename(&source_path, &target_path).is_err() {
388                 error!(
389                     "UCI log: failed to rename {} to {} while rotating log file.",
390                     source_path.display(),
391                     target_path.display(),
392                 );
393                 return None;
394             }
395         }
396         Some(())
397     }
398 }
399 
400 struct BufferedFile {
401     file: fs::File,
402     written_size: usize,
403     buffer_size: usize,
404     buffer: Vec<u8>,
405 }
406 
407 impl BufferedFile {
408     /// Constructor.
new(log_dir: &Path, file_path: &Path, buffer_size: usize) -> Option<Self>409     pub fn new(log_dir: &Path, file_path: &Path, buffer_size: usize) -> Option<Self> {
410         if file_path.is_file() {
411             if let Err(e) = fs::remove_file(file_path) {
412                 error!("UCI Log: failed to remove {}: {:?}", file_path.display(), e);
413             };
414         }
415         if !log_dir.is_dir() {
416             if let Err(e) = fs::create_dir_all(log_dir) {
417                 error!(
418                     "UCI Log: failed to create log directory {}. Error: {:?}",
419                     log_dir.display(),
420                     e
421                 );
422             }
423         }
424 
425         let file = match fs::OpenOptions::new().write(true).create_new(true).open(file_path) {
426             Ok(f) => f,
427             Err(e) => {
428                 error!(
429                     "UCI Log: failed to create log file {} for write: {:?}",
430                     file_path.display(),
431                     e
432                 );
433                 return None;
434             }
435         };
436         Some(Self { file, written_size: 0, buffer_size, buffer: Vec::new() })
437     }
438 
439     /// Returns the file size received.
file_size(&self) -> usize440     pub fn file_size(&self) -> usize {
441         self.written_size + self.buffer.len()
442     }
443 
444     /// Writes data to file with buffering.
buffered_write(&mut self, mut data: Vec<u8>) -> Option<()>445     pub fn buffered_write(&mut self, mut data: Vec<u8>) -> Option<()> {
446         if self.buffer.len() + data.len() >= self.buffer_size {
447             self.flush_buffer();
448         }
449         self.buffer.append(&mut data);
450         Some(())
451     }
452 
453     /// Clears buffer.
flush_buffer(&mut self) -> Option<()>454     fn flush_buffer(&mut self) -> Option<()> {
455         self.file.write_all(&self.buffer).ok()?;
456         self.written_size += self.buffer.len();
457         self.buffer.clear();
458 
459         self.file.flush().ok()
460     }
461 
flush_file(&mut self) -> Option<()>462     pub fn flush_file(&mut self) -> Option<()> {
463         // Flush the buffer and then the file to storage.
464         self.flush_buffer();
465         self.file.sync_all().ok();
466         Some(())
467     }
468 }
469 
470 /// Manual Drop implementation.
471 impl Drop for BufferedFile {
drop(&mut self)472     fn drop(&mut self) {
473         // Flush buffer before Closing file.
474         self.flush_buffer();
475     }
476 }
477 
478 #[cfg(test)]
479 mod tests {
480     use super::*;
481 
482     use std::fs;
483 
484     use tempfile::tempdir;
485     use tokio::runtime::Builder;
486     use uwb_uci_packets::UciVendor_A_NotificationBuilder;
487 
488     use crate::uci::uci_logger::UciLogger;
489 
490     /// Gets block info from a little-endian PCAPNG file bytestream.
491     ///
492     /// Returns a vector of (block type, block length) if the bytestream is valid PCAPNG.
get_block_info(datastream: Vec<u8>) -> Option<Vec<(u32, u32)>>493     fn get_block_info(datastream: Vec<u8>) -> Option<Vec<(u32, u32)>> {
494         if datastream.len() % 4 != 0 || datastream.is_empty() {
495             return None;
496         }
497         let mut block_info = Vec::new();
498         let mut offset = 0usize;
499         while offset < datastream.len() - 1 {
500             let (_read, unread) = datastream.split_at(offset);
501             if unread.len() < 8 {
502                 return None;
503             }
504             let (type_bytes, unread) = unread.split_at(4);
505             let block_type = u32::from_le_bytes(type_bytes.try_into().unwrap());
506             let (length_bytes, _unread) = unread.split_at(4);
507             let block_length = u32::from_le_bytes(length_bytes.try_into().unwrap());
508             offset += block_length as usize;
509             if offset > datastream.len() {
510                 return None;
511             }
512             block_info.push((block_type, block_length));
513         }
514         Some(block_info)
515     }
516 
flush_loggers(loggers: Vec<UciLoggerPcapng>)517     async fn flush_loggers(loggers: Vec<UciLoggerPcapng>) {
518         for mut logger in loggers {
519             let flush_receiver = logger.flush();
520             flush_receiver.unwrap().recv().await;
521         }
522     }
523 
524     #[test]
test_no_file_write()525     fn test_no_file_write() {
526         let dir = tempdir().unwrap();
527 
528         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
529         let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
530             .buffer_size(1024)
531             .filename_prefix("log".to_owned())
532             .log_path(dir.as_ref().to_owned())
533             .runtime_handle(runtime.handle().to_owned())
534             .build()
535             .unwrap();
536         let logger_0 = file_manager.build_logger("logger 0").unwrap();
537         let logger_1 = file_manager.build_logger("logger 1").unwrap();
538 
539         // Flush the loggers so that the files are created.
540         runtime.block_on(flush_loggers(vec![logger_0, logger_1]));
541 
542         // Expect no log file created as no packet is received.
543         let log_path = dir.as_ref().to_owned().join("log.pcapng");
544         assert!(fs::read(log_path).is_err());
545     }
546 
547     #[test]
test_no_preexisting_dir_created()548     fn test_no_preexisting_dir_created() {
549         let dir_root = Path::new("./uwb_test_dir_123");
550         let dir = dir_root.join("this/path/doesnt/exist");
551         let log_path = dir.join("log.pcapng");
552 
553         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
554         let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
555             .buffer_size(1024)
556             .filename_prefix("log".to_owned())
557             .log_path(dir.clone())
558             .runtime_handle(runtime.handle().to_owned())
559             .build()
560             .unwrap();
561         let mut logger_0 = file_manager.build_logger("logger 0").unwrap();
562         let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
563         logger_0.log_uci_control_packet(packet_0.into());
564 
565         // Flush all the loggers so that the files are created and all packets written.
566         runtime.block_on(flush_loggers(vec![logger_0]));
567 
568         // Expect the dir was created.
569         assert!(dir.is_dir());
570         // Expect the log file exists.
571         assert!(log_path.is_file());
572         // Clear test dir
573         let _ = fs::remove_dir_all(dir_root);
574     }
575 
576     #[test]
test_single_file_write()577     fn test_single_file_write() {
578         let dir = tempdir().unwrap();
579         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
580         let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
581             .buffer_size(1024)
582             .filename_prefix("log".to_owned())
583             .log_path(dir.as_ref().to_owned())
584             .runtime_handle(runtime.handle().to_owned())
585             .build()
586             .unwrap();
587         let mut logger_0 = file_manager.build_logger("logger 0").unwrap();
588         let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
589         logger_0.log_uci_control_packet(packet_0.into());
590         let mut logger_1 = file_manager.build_logger("logger 1").unwrap();
591         let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
592         logger_1.log_uci_control_packet(packet_1.into());
593         let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
594         logger_0.log_uci_control_packet(packet_2.into());
595 
596         // Flush all the loggers so that the files are created and all packets written.
597         runtime.block_on(flush_loggers(vec![logger_0, logger_1]));
598 
599         // Expect file log.pcapng consist of SHB->IDB(logger 0)->EPB(packet 0)->IDB(logger 1)
600         // ->EPB(packet 1)->EPB(packet 2)
601         let log_path = dir.as_ref().to_owned().join("log.pcapng");
602         assert!(log_path.is_file());
603         let log_content = fs::read(log_path).unwrap();
604         let block_info = get_block_info(log_content).unwrap();
605         assert_eq!(block_info.len(), 6);
606         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
607         assert_eq!(block_info[1].0, 0x1); // IDB
608         assert_eq!(block_info[2].0, 0x6); // EPB
609         assert_eq!(block_info[3].0, 0x1); // IDB
610         assert_eq!(block_info[4].0, 0x6); // EPB
611         assert_eq!(block_info[5].0, 0x6); // EPB
612     }
613 
614     #[test]
test_file_switch_epb_unfit_case()615     fn test_file_switch_epb_unfit_case() {
616         let dir = tempdir().unwrap();
617         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
618         let mut file_manager_140 = PcapngUciLoggerFactoryBuilder::new()
619             .buffer_size(1024)
620             .filename_prefix("log".to_owned())
621             .log_path(dir.as_ref().to_owned())
622             .file_size(140)
623             .runtime_handle(runtime.handle().to_owned())
624             .build()
625             .unwrap();
626         let mut logger_0 = file_manager_140.build_logger("logger 0").unwrap();
627         let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
628         logger_0.log_uci_control_packet(packet_0.into());
629         let mut logger_1 = file_manager_140.build_logger("logger 1").unwrap();
630         let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
631         logger_1.log_uci_control_packet(packet_1.into());
632         let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
633         logger_0.log_uci_control_packet(packet_2.into());
634 
635         // Flush all the loggers so that the files are created and all packets written.
636         runtime.block_on(flush_loggers(vec![logger_0, logger_1]));
637 
638         // Expect (Old to new):
639         // File 2: SHB->IDB->EPB->IDB (cannot fit next)
640         // File 1: SHB->IDB->IDB->EPB (cannot fit next)
641         // File 0: SHB->IDB->IDB->EPB
642         let log_path = dir.as_ref().to_owned().join("log_2.pcapng");
643         assert!(log_path.is_file());
644         let log_content = fs::read(log_path).unwrap();
645         let block_info = get_block_info(log_content).unwrap();
646         assert_eq!(block_info.len(), 4);
647         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
648         assert_eq!(block_info[1].0, 0x1); // IDB
649         assert_eq!(block_info[2].0, 0x6); // EPB
650         assert_eq!(block_info[3].0, 0x1); // IDB
651         let log_path = dir.as_ref().to_owned().join("log_1.pcapng");
652         assert!(log_path.is_file());
653         let log_content = fs::read(log_path).unwrap();
654         let block_info = get_block_info(log_content).unwrap();
655         assert_eq!(block_info.len(), 4);
656         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
657         assert_eq!(block_info[1].0, 0x1); // IDB
658         assert_eq!(block_info[2].0, 0x1); // IDB
659         assert_eq!(block_info[3].0, 0x6); // EPB
660         let log_path = dir.as_ref().to_owned().join("log.pcapng");
661         assert!(log_path.is_file());
662         let log_content = fs::read(log_path).unwrap();
663         let block_info = get_block_info(log_content).unwrap();
664         assert_eq!(block_info.len(), 4);
665         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
666         assert_eq!(block_info[1].0, 0x1); // IDB
667         assert_eq!(block_info[2].0, 0x1); // IDB
668         assert_eq!(block_info[3].0, 0x6); // EPB
669     }
670 
671     #[test]
test_file_switch_idb_unfit_case()672     fn test_file_switch_idb_unfit_case() {
673         let dir = tempdir().unwrap();
674         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
675         let mut file_manager_144 = PcapngUciLoggerFactoryBuilder::new()
676             .buffer_size(1024)
677             .filename_prefix("log".to_owned())
678             .log_path(dir.as_ref().to_owned())
679             .file_size(144)
680             .runtime_handle(runtime.handle().to_owned())
681             .build()
682             .unwrap();
683         let mut logger_0 = file_manager_144.build_logger("logger 0").unwrap();
684         let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
685         logger_0.log_uci_control_packet(packet_0.into());
686         let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
687         logger_0.log_uci_control_packet(packet_2.into());
688         let mut logger_1 = file_manager_144.build_logger("logger 1").unwrap();
689         let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
690         logger_1.log_uci_control_packet(packet_1.into());
691 
692         // Flush all the loggers so that the files are created and all packets written.
693         runtime.block_on(flush_loggers(vec![logger_0, logger_1]));
694 
695         // Expect (Old to new):
696         // File 1: SHB->IDB->EPB->EPB (cannot fit next)
697         // File 0: SHB->IDB->IDB->EPB
698         let log_path = dir.as_ref().to_owned().join("log_1.pcapng");
699         assert!(log_path.is_file());
700         let log_content = fs::read(log_path).unwrap();
701         let block_info = get_block_info(log_content).unwrap();
702         assert_eq!(block_info.len(), 4);
703         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
704         assert_eq!(block_info[1].0, 0x1); // IDB
705         assert_eq!(block_info[2].0, 0x6); // EPB
706         assert_eq!(block_info[3].0, 0x6); // EPB
707         let log_path = dir.as_ref().to_owned().join("log.pcapng");
708         assert!(log_path.is_file());
709         let log_content = fs::read(log_path).unwrap();
710         let block_info = get_block_info(log_content).unwrap();
711         assert_eq!(block_info.len(), 4);
712         assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
713         assert_eq!(block_info[1].0, 0x1); // IDB
714         assert_eq!(block_info[2].0, 0x1); // IDB
715         assert_eq!(block_info[3].0, 0x6); // EPB
716     }
717 
718     // Program shall not panic even if log writing has failed for some reason.
719     #[test]
test_log_fail_safe()720     fn test_log_fail_safe() {
721         let dir = tempdir().unwrap();
722         let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
723         let mut file_manager_96 = PcapngUciLoggerFactoryBuilder::new()
724             .buffer_size(1024)
725             .filename_prefix("log".to_owned())
726             .log_path(dir.as_ref().to_owned())
727             .file_size(96) // Fails logging, as metadata takes 100
728             .runtime_handle(runtime.handle().to_owned())
729             .build()
730             .unwrap();
731         let mut logger_0 = file_manager_96.build_logger("logger 0").unwrap();
732         let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
733         logger_0.log_uci_control_packet(packet_0.into());
734         let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
735         logger_0.log_uci_control_packet(packet_2.into());
736         let mut logger_1 = file_manager_96.build_logger("logger 1").unwrap();
737         let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
738         logger_1.log_uci_control_packet(packet_1.into());
739 
740         // Flush all the loggers so that the files are created and all packets written.
741         runtime.block_on(flush_loggers(vec![logger_0, logger_1]));
742 
743         // Verify existence of the log files.
744         let log_path = dir.as_ref().to_owned().join("log_1.pcapng");
745         assert!(log_path.is_file());
746         let log_path = dir.as_ref().to_owned().join("log.pcapng");
747         assert!(log_path.is_file());
748     }
749 }
750