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 }
133
134 /// LogWriterActor performs the log writing and file operations asynchronously.
135 struct LogWriterActor {
136 /// Maps chip id to interface id. The content follows the content of the component in
137 /// PcapngUciLoggerFactory with the same name.
138 chip_interface_id_map: Vec<String>,
139 current_file: Option<BufferedFile>,
140 file_factory: FileFactory,
141 file_size_limit: usize,
142 log_receiver: mpsc::UnboundedReceiver<PcapngLoggerMessage>,
143 }
144
145 impl LogWriterActor {
146 /// write data to file.
write_once(&mut self, data: Vec<u8>) -> Option<()>147 fn write_once(&mut self, data: Vec<u8>) -> Option<()> {
148 // Create new file if the file is not created, or does not fit incoming data:
149 if self.current_file.is_none()
150 || data.len() + self.current_file.as_ref().unwrap().file_size() > self.file_size_limit
151 {
152 self.current_file = Some(
153 self.file_factory
154 .build_file_with_metadata(&self.chip_interface_id_map, self.file_size_limit)?,
155 );
156 }
157 self.current_file.as_mut().unwrap().buffered_write(data)
158 }
159
160 /// Handle single new chip: stores chip in chip_interface_id_map and:
161 ///
162 /// a. Nothing extra if current_file is not created yet.
163 /// b. If current file exists:
164 /// 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<()>165 fn handle_new_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()> {
166 if self.chip_interface_id_map.contains(&chip_id)
167 || self.chip_interface_id_map.len() as u32 != interface_id
168 {
169 error!(
170 "UCI log: unexpected chip_id {} with associated interface id {}",
171 &chip_id, interface_id
172 );
173 return None;
174 }
175 self.chip_interface_id_map.push(chip_id.clone());
176
177 if let Some(current_file) = &mut self.current_file {
178 let idb_data = into_interface_description_block(chip_id)?;
179 if idb_data.len() + current_file.file_size() <= self.file_size_limit {
180 current_file.buffered_write(idb_data)?;
181 } else {
182 self.current_file =
183 Some(self.file_factory.build_file_with_metadata(
184 &self.chip_interface_id_map,
185 self.file_size_limit,
186 )?);
187 }
188 }
189 Some(())
190 }
191
run(&mut self)192 async fn run(&mut self) {
193 debug!("UCI log: LogWriterActor started");
194 loop {
195 match self.log_receiver.recv().await {
196 Some(PcapngLoggerMessage::NewChip((chip_id, interface_id))) => {
197 if self.handle_new_chip(chip_id.clone(), interface_id).is_none() {
198 error!("UCI log: failed logging new chip {}", &chip_id);
199 break;
200 }
201 }
202 Some(PcapngLoggerMessage::ByteStream(data)) => {
203 if self.write_once(data).is_none() {
204 match &self.current_file {
205 Some(current_file) => {
206 error!(
207 "UCI log: failed writting packet to log file {:?}",
208 current_file.file
209 );
210 }
211 None => {
212 error!("UCI log: failed writting packet to log file: no log file.");
213 }
214 }
215 break;
216 }
217 }
218 None => {
219 debug!("UCI log: LogWriterActor dropping.");
220 break;
221 }
222 }
223 }
224 }
225 }
226
227 /// Handle to LogWriterActor.
228 #[derive(Clone)]
229 pub(crate) struct LogWriter {
230 log_sender: Option<mpsc::UnboundedSender<PcapngLoggerMessage>>,
231 }
232
233 impl LogWriter {
234 /// Constructs LogWriter and its actor.
235 ///
236 /// 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>237 fn new(
238 file_factory: FileFactory,
239 file_size_limit: usize,
240 runtime_handle: Handle,
241 ) -> Option<Self> {
242 let chip_interface_id_map = Vec::new();
243 let (log_sender, log_receiver) = mpsc::unbounded_channel();
244 let mut log_writer_actor = LogWriterActor {
245 chip_interface_id_map,
246 current_file: None,
247 file_factory,
248 file_size_limit,
249 log_receiver,
250 };
251 runtime_handle.spawn(async move { log_writer_actor.run().await });
252 Some(LogWriter { log_sender: Some(log_sender) })
253 }
254
send_bytes(&mut self, bytes: Vec<u8>) -> Option<()>255 pub fn send_bytes(&mut self, bytes: Vec<u8>) -> Option<()> {
256 let log_sender = self.log_sender.as_ref()?;
257 match log_sender.send(PcapngLoggerMessage::ByteStream(bytes)) {
258 Ok(_) => Some(()),
259 Err(e) => {
260 error!("UCI log: LogWriterActor dead unexpectedly, sender error: {:?}", e);
261 self.log_sender = None;
262 None
263 }
264 }
265 }
266
send_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()>267 fn send_chip(&mut self, chip_id: String, interface_id: u32) -> Option<()> {
268 let log_sender = self.log_sender.as_ref()?;
269 match log_sender.send(PcapngLoggerMessage::NewChip((chip_id, interface_id))) {
270 Ok(_) => Some(()),
271 Err(e) => {
272 error!("UCI log: LogWriterActor dead unexpectedly, sender error: {:?}", e);
273 self.log_sender = None;
274 None
275 }
276 }
277 }
278 }
279
into_interface_description_block(chip_id: String) -> Option<Vec<u8>>280 fn into_interface_description_block(chip_id: String) -> Option<Vec<u8>> {
281 let if_name_option = BlockOption::new(0x2, chip_id.into_bytes());
282 InterfaceDescriptionBlockBuilder::new().append_option(if_name_option).into_le_bytes()
283 }
284
285 /// FileFactory builds next BufferedFile.
286 ///
287 /// The most recent log file is {fileprefix}.pcapng. The archived log files have their index
288 /// increased: {fileprefix}_{n}.pcapng where n = 0..(rotate_range-1).
289 struct FileFactory {
290 log_directory: PathBuf,
291 filename_prefix: String,
292 rotate_range: usize,
293 buffer_size: usize,
294 }
295
296 impl FileFactory {
297 /// Constructor.
new( log_directory: PathBuf, filename_prefix: String, buffer_size: usize, rotate_range: usize, ) -> FileFactory298 fn new(
299 log_directory: PathBuf,
300 filename_prefix: String,
301 buffer_size: usize,
302 rotate_range: usize,
303 ) -> FileFactory {
304 Self { log_directory, filename_prefix, rotate_range, buffer_size }
305 }
306
307 /// 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>308 fn build_file_with_metadata(
309 &mut self,
310 chip_interface_id_map: &[String],
311 file_size_limit: usize,
312 ) -> Option<BufferedFile> {
313 let mut current_file = self.build_empty_file()?;
314 let mut metadata = Vec::new();
315 metadata.append(&mut HeaderBlockBuilder::new().into_le_bytes()?);
316 for chip_id in chip_interface_id_map.iter() {
317 metadata.append(&mut into_interface_description_block(chip_id.to_owned())?);
318 }
319 if metadata.len() > file_size_limit {
320 error!(
321 "UCI log: log file size limit is too small ({}) for file header and metadata ({})",
322 file_size_limit,
323 metadata.len()
324 );
325 }
326 current_file.buffered_write(metadata)?;
327 Some(current_file)
328 }
329
330 /// Builds next file as an empty BufferedFile.
build_empty_file(&mut self) -> Option<BufferedFile>331 fn build_empty_file(&mut self) -> Option<BufferedFile> {
332 self.rotate_file()?;
333 let file_path = self.get_file_path(0);
334 BufferedFile::new(&self.log_directory, &file_path, self.buffer_size)
335 }
336
337 /// get file path for log files of given index.
get_file_path(&self, index: usize) -> PathBuf338 fn get_file_path(&self, index: usize) -> PathBuf {
339 let file_basename = if index == 0 {
340 format!("{}.pcapng", self.filename_prefix)
341 } else {
342 format!("{}_{}.pcapng", self.filename_prefix, index)
343 };
344 self.log_directory.join(file_basename)
345 }
346
347 /// Vacates {filename_prefix}_0.pcapng for new log.
rotate_file(&self) -> Option<()>348 fn rotate_file(&self) -> Option<()> {
349 for source_idx in (0..self.rotate_range - 1).rev() {
350 let target_idx = source_idx + 1;
351 let source_path = self.get_file_path(source_idx);
352 let target_path = self.get_file_path(target_idx);
353 if source_path.is_dir() {
354 error!("UCI log: expect {:?} to be a filename, but is a directory", &source_path);
355 return None;
356 }
357 if source_path.is_file() && fs::rename(&source_path, &target_path).is_err() {
358 error!(
359 "UCI log: failed to rename {} to {} while rotating log file.",
360 source_path.display(),
361 target_path.display(),
362 );
363 return None;
364 }
365 }
366 Some(())
367 }
368 }
369
370 struct BufferedFile {
371 file: fs::File,
372 written_size: usize,
373 buffer_size: usize,
374 buffer: Vec<u8>,
375 }
376
377 impl BufferedFile {
378 /// Constructor.
new(log_dir: &Path, file_path: &Path, buffer_size: usize) -> Option<Self>379 pub fn new(log_dir: &Path, file_path: &Path, buffer_size: usize) -> Option<Self> {
380 if file_path.is_file() {
381 if let Err(e) = fs::remove_file(file_path) {
382 error!("UCI Log: failed to remove {}: {:?}", file_path.display(), e);
383 };
384 }
385 if !log_dir.is_dir() {
386 if let Err(e) = fs::create_dir_all(log_dir) {
387 error!(
388 "UCI Log: failed to create log directory {}. Error: {:?}",
389 log_dir.display(),
390 e
391 );
392 }
393 }
394 let file = match fs::OpenOptions::new().write(true).create_new(true).open(file_path) {
395 Ok(f) => f,
396 Err(e) => {
397 error!(
398 "UCI Log: failed to create log file {} for write: {:?}",
399 file_path.display(),
400 e
401 );
402 return None;
403 }
404 };
405 Some(Self { file, written_size: 0, buffer_size, buffer: Vec::new() })
406 }
407
408 /// Returns the file size received.
file_size(&self) -> usize409 pub fn file_size(&self) -> usize {
410 self.written_size + self.buffer.len()
411 }
412
413 /// Writes data to file with buffering.
buffered_write(&mut self, mut data: Vec<u8>) -> Option<()>414 pub fn buffered_write(&mut self, mut data: Vec<u8>) -> Option<()> {
415 if self.buffer.len() + data.len() >= self.buffer_size {
416 self.flush_buffer();
417 }
418 self.buffer.append(&mut data);
419 Some(())
420 }
421
422 /// Clears buffer.
flush_buffer(&mut self) -> Option<()>423 fn flush_buffer(&mut self) -> Option<()> {
424 self.file.write_all(&self.buffer).ok()?;
425 self.written_size += self.buffer.len();
426 self.buffer.clear();
427
428 self.file.flush().ok()
429 }
430 }
431
432 /// Manual Drop implementation.
433 impl Drop for BufferedFile {
drop(&mut self)434 fn drop(&mut self) {
435 // Flush buffer before Closing file.
436 self.flush_buffer();
437 }
438 }
439
440 #[cfg(test)]
441 mod tests {
442 use super::*;
443
444 use std::{fs, thread, time};
445
446 use tempfile::tempdir;
447 use tokio::runtime::Builder;
448 use uwb_uci_packets::UciVendor_A_NotificationBuilder;
449
450 use crate::uci::uci_logger::UciLogger;
451
452 /// Gets block info from a little-endian PCAPNG file bytestream.
453 ///
454 /// Returns a vector of (block type, block length) if the bytestream is valid PCAPNG.
get_block_info(datastream: Vec<u8>) -> Option<Vec<(u32, u32)>>455 fn get_block_info(datastream: Vec<u8>) -> Option<Vec<(u32, u32)>> {
456 if datastream.len() % 4 != 0 || datastream.is_empty() {
457 return None;
458 }
459 let mut block_info = Vec::new();
460 let mut offset = 0usize;
461 while offset < datastream.len() - 1 {
462 let (_read, unread) = datastream.split_at(offset);
463 if unread.len() < 8 {
464 return None;
465 }
466 let (type_bytes, unread) = unread.split_at(4);
467 let block_type = u32::from_le_bytes(type_bytes.try_into().unwrap());
468 let (length_bytes, _unread) = unread.split_at(4);
469 let block_length = u32::from_le_bytes(length_bytes.try_into().unwrap());
470 offset += block_length as usize;
471 if offset > datastream.len() {
472 return None;
473 }
474 block_info.push((block_type, block_length));
475 }
476 Some(block_info)
477 }
478
479 #[test]
test_no_file_write()480 fn test_no_file_write() {
481 let dir = tempdir().unwrap();
482 {
483 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
484 let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
485 .buffer_size(1024)
486 .filename_prefix("log".to_owned())
487 .log_path(dir.as_ref().to_owned())
488 .runtime_handle(runtime.handle().to_owned())
489 .build()
490 .unwrap();
491 let _logger_0 = file_manager.build_logger("logger 0").unwrap();
492 let _logger_1 = file_manager.build_logger("logger 1").unwrap();
493 // Sleep needed to guarantee handling pending logs before runtime goes out of scope.
494 thread::sleep(time::Duration::from_millis(10));
495 }
496 // Expect no log file created as no packet is received.
497 let log_path = dir.as_ref().to_owned().join("log.pcapng");
498 assert!(fs::read(log_path).is_err());
499 }
500
501 #[test]
test_no_preexisting_dir_created()502 fn test_no_preexisting_dir_created() {
503 let dir_root = Path::new("./uwb_test_dir_123");
504 let dir = dir_root.join("this/path/doesnt/exist");
505 {
506 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
507 let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
508 .buffer_size(1024)
509 .filename_prefix("log".to_owned())
510 .log_path(dir.clone())
511 .runtime_handle(runtime.handle().to_owned())
512 .build()
513 .unwrap();
514 let mut logger_0 = file_manager.build_logger("logger 0").unwrap();
515 let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
516 logger_0.log_uci_control_packet(packet_0.into());
517 // Sleep needed to guarantee handling pending logs before runtime goes out of scope.
518 thread::sleep(time::Duration::from_millis(10));
519 }
520 // Expect the dir was created.
521 assert!(dir.is_dir());
522 // Expect the log file exists.
523 let log_path = dir.join("log.pcapng");
524 assert!(log_path.is_file());
525 // Clear test dir
526 let _ = fs::remove_dir_all(dir_root);
527 }
528
529 #[test]
test_single_file_write()530 fn test_single_file_write() {
531 let dir = tempdir().unwrap();
532 {
533 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
534 let mut file_manager = PcapngUciLoggerFactoryBuilder::new()
535 .buffer_size(1024)
536 .filename_prefix("log".to_owned())
537 .log_path(dir.as_ref().to_owned())
538 .runtime_handle(runtime.handle().to_owned())
539 .build()
540 .unwrap();
541 let mut logger_0 = file_manager.build_logger("logger 0").unwrap();
542 let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
543 logger_0.log_uci_control_packet(packet_0.into());
544 let mut logger_1 = file_manager.build_logger("logger 1").unwrap();
545 let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
546 logger_1.log_uci_control_packet(packet_1.into());
547 let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
548 logger_0.log_uci_control_packet(packet_2.into());
549 // Sleep needed to guarantee handling pending logs before runtime goes out of scope.
550 thread::sleep(time::Duration::from_millis(10));
551 }
552 // Expect file log.pcapng consist of SHB->IDB(logger 0)->EPB(packet 0)->IDB(logger 1)
553 // ->EPB(packet 1)->EPB(packet 2)
554 let log_path = dir.as_ref().to_owned().join("log.pcapng");
555 let log_content = fs::read(log_path).unwrap();
556 let block_info = get_block_info(log_content).unwrap();
557 assert_eq!(block_info.len(), 6);
558 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
559 assert_eq!(block_info[1].0, 0x1); // IDB
560 assert_eq!(block_info[2].0, 0x6); // EPB
561 assert_eq!(block_info[3].0, 0x1); // IDB
562 assert_eq!(block_info[4].0, 0x6); // EPB
563 assert_eq!(block_info[5].0, 0x6); // EPB
564 }
565
566 #[test]
test_file_switch_epb_unfit_case()567 fn test_file_switch_epb_unfit_case() {
568 let dir = tempdir().unwrap();
569 let last_file_expected = dir.as_ref().to_owned().join("log_2.pcapng");
570 {
571 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
572 let mut file_manager_140 = PcapngUciLoggerFactoryBuilder::new()
573 .buffer_size(1024)
574 .filename_prefix("log".to_owned())
575 .log_path(dir.as_ref().to_owned())
576 .file_size(140)
577 .runtime_handle(runtime.handle().to_owned())
578 .build()
579 .unwrap();
580 let mut logger_0 = file_manager_140.build_logger("logger 0").unwrap();
581 let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
582 logger_0.log_uci_control_packet(packet_0.into());
583 let mut logger_1 = file_manager_140.build_logger("logger 1").unwrap();
584 let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
585 logger_1.log_uci_control_packet(packet_1.into());
586 let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
587 logger_0.log_uci_control_packet(packet_2.into());
588 // Sleep needed to guarantee handling pending logs before runtime goes out of scope.
589 let mut timeout = 100;
590 let timeout_slice = 10;
591 loop {
592 if last_file_expected.exists() || timeout == 0 {
593 break;
594 }
595 thread::sleep(time::Duration::from_millis(timeout_slice));
596 timeout -= timeout_slice;
597 }
598 }
599 // Expect (Old to new):
600 // File 2: SHB->IDB->EPB->IDB (cannot fit next)
601 // File 1: SHB->IDB->IDB->EPB (cannot fit next)
602 // File 0: SHB->IDB->IDB->EPB
603 let log_path = dir.as_ref().to_owned().join("log_2.pcapng");
604 let log_content = fs::read(log_path).unwrap();
605 let block_info = get_block_info(log_content).unwrap();
606 assert_eq!(block_info.len(), 4);
607 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
608 assert_eq!(block_info[1].0, 0x1); // IDB
609 assert_eq!(block_info[2].0, 0x6); // EPB
610 assert_eq!(block_info[3].0, 0x1); // IDB
611 let log_path = dir.as_ref().to_owned().join("log_1.pcapng");
612 let log_content = fs::read(log_path).unwrap();
613 let block_info = get_block_info(log_content).unwrap();
614 assert_eq!(block_info.len(), 4);
615 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
616 assert_eq!(block_info[1].0, 0x1); // IDB
617 assert_eq!(block_info[2].0, 0x1); // IDB
618 assert_eq!(block_info[3].0, 0x6); // EPB
619 let log_path = dir.as_ref().to_owned().join("log.pcapng");
620 let log_content = fs::read(log_path).unwrap();
621 let block_info = get_block_info(log_content).unwrap();
622 assert_eq!(block_info.len(), 4);
623 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
624 assert_eq!(block_info[1].0, 0x1); // IDB
625 assert_eq!(block_info[2].0, 0x1); // IDB
626 assert_eq!(block_info[3].0, 0x6); // EPB
627 }
628
629 #[test]
test_file_switch_idb_unfit_case()630 fn test_file_switch_idb_unfit_case() {
631 let dir = tempdir().unwrap();
632 {
633 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
634 let mut file_manager_144 = PcapngUciLoggerFactoryBuilder::new()
635 .buffer_size(1024)
636 .filename_prefix("log".to_owned())
637 .log_path(dir.as_ref().to_owned())
638 .file_size(144)
639 .runtime_handle(runtime.handle().to_owned())
640 .build()
641 .unwrap();
642 let mut logger_0 = file_manager_144.build_logger("logger 0").unwrap();
643 let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
644 logger_0.log_uci_control_packet(packet_0.into());
645 let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
646 logger_0.log_uci_control_packet(packet_2.into());
647 let mut logger_1 = file_manager_144.build_logger("logger 1").unwrap();
648 let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
649 logger_1.log_uci_control_packet(packet_1.into());
650 // Sleep needed to guarantee handling pending logs before runtime goes out of scope.
651 thread::sleep(time::Duration::from_millis(10));
652 }
653 // Expect (Old to new):
654 // File 1: SHB->IDB->EPB->EPB (cannot fit next)
655 // File 0: SHB->IDB->IDB->EPB
656 let log_path = dir.as_ref().to_owned().join("log_1.pcapng");
657 let log_content = fs::read(log_path).unwrap();
658 let block_info = get_block_info(log_content).unwrap();
659 assert_eq!(block_info.len(), 4);
660 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
661 assert_eq!(block_info[1].0, 0x1); // IDB
662 assert_eq!(block_info[2].0, 0x6); // EPB
663 assert_eq!(block_info[3].0, 0x6); // EPB
664 let log_path = dir.as_ref().to_owned().join("log.pcapng");
665 let log_content = fs::read(log_path).unwrap();
666 let block_info = get_block_info(log_content).unwrap();
667 assert_eq!(block_info.len(), 4);
668 assert_eq!(block_info[0].0, 0x0A0D_0D0A); // SHB
669 assert_eq!(block_info[1].0, 0x1); // IDB
670 assert_eq!(block_info[2].0, 0x1); // IDB
671 assert_eq!(block_info[3].0, 0x6); // EPB
672 }
673
674 // Program shall not panic even if log writing has failed for some reason.
675 #[test]
test_log_fail_safe()676 fn test_log_fail_safe() {
677 let dir = tempdir().unwrap();
678 {
679 let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
680 let mut file_manager_96 = PcapngUciLoggerFactoryBuilder::new()
681 .buffer_size(1024)
682 .filename_prefix("log".to_owned())
683 .log_path(dir.as_ref().to_owned())
684 .file_size(96) // Fails logging, as metadata takes 100
685 .runtime_handle(runtime.handle().to_owned())
686 .build()
687 .unwrap();
688 let mut logger_0 = file_manager_96.build_logger("logger 0").unwrap();
689 let packet_0 = UciVendor_A_NotificationBuilder { opcode: 0, payload: None }.build();
690 logger_0.log_uci_control_packet(packet_0.into());
691 let packet_2 = UciVendor_A_NotificationBuilder { opcode: 2, payload: None }.build();
692 logger_0.log_uci_control_packet(packet_2.into());
693 let mut logger_1 = file_manager_96.build_logger("logger 1").unwrap();
694 let packet_1 = UciVendor_A_NotificationBuilder { opcode: 1, payload: None }.build();
695 logger_1.log_uci_control_packet(packet_1.into());
696 }
697 }
698 }
699