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