blob: 297719dacf26eff4781e4054e660dd47b2e6ac63 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
fidl_fuchsia_bluetooth_snoop::SnoopPacket,
std::{
collections::{
HashMap,
vec_deque::{Iter as VecDequeIter, VecDeque},
},
time::Duration,
},
};
use crate::{bounded_queue::{BoundedQueue, CreatedAt, SizeOf}, DeviceId};
// Size for SnoopPacket must be implemented here because SnoopPacket is defined in generated code.
impl SizeOf for SnoopPacket {
fn size_of(&self) -> usize {
std::mem::size_of::<Self>() + self.payload.len()
}
}
// CreatedAt for SnoopPacket must be implemented here because SnoopPacket is defined in generated
// code.
impl CreatedAt for SnoopPacket {
fn created_at(&self) -> Duration {
Duration::new(self.timestamp.seconds, self.timestamp.subsec_nanos)
}
}
/// Alias for a queue of snoop packets.
pub(crate) type PacketLog = BoundedQueue<SnoopPacket>;
/// A container for packet logs for each snoop channel.
pub(crate) struct PacketLogs {
max_device_count: usize,
log_size_bytes: usize,
log_age: Duration,
inner: HashMap<DeviceId, PacketLog>,
insertion_order: VecDeque<DeviceId>,
}
impl PacketLogs {
/// Create a new `PacketLogs` struct. `max_device_count` sets the number of hci devices the
/// logger will store packets for. `log_size_bytes` sets the size limit associated with each
/// device (see `BoundedQueue` documentation for more information). `log_age` sets the age limit
/// associated with each device (see `BoundedQueue` documentation for more information).
///
/// Note that the `log_size_bytes` and `log_age` values are set on a _per device_ basis.
///
/// Panics if `max_device_count` is 0.
pub fn new(max_device_count: usize, log_size_bytes: usize, log_age: Duration) -> PacketLogs {
assert!(max_device_count != 0, "Cannot create a `PacketLog` with a max_device_count of 0");
PacketLogs {
max_device_count,
log_size_bytes,
log_age,
inner: HashMap::new(),
insertion_order: VecDeque::new(),
}
}
/// Add a log to record packets for a new device. Return the `DeviceId` of the oldest log if it
/// was removed to make room for the new device log.
/// If the device is already being recorded, this method does nothing.
pub fn add_device(&mut self, device: DeviceId) -> Option<DeviceId> {
if self.inner.contains_key(&device) {
return None;
}
// Add log and update insertion order metadata
self.insertion_order.push_back(device.clone());
self.inner.insert(device, BoundedQueue::new(self.log_size_bytes, self.log_age));
// Remove old log and its insertion order metadata if there are too many logs
//
// TODO (belgum): This is a first pass at an algorithm to determine which log to drop in
// the case of too many logs. Alternatives that can be explored in the future include
// variations of LRU or LFU caching which may or may not account for empty device logs.
if self.inner.len() > self.max_device_count {
let oldest_key = self.insertion_order.pop_front()
.expect("insertion list shouldn't be empty");
self.inner.remove(&oldest_key);
Some(oldest_key)
} else {
None
}
}
/// Get a mutable reference to a single log by `DeviceId`
pub fn get_log_mut(&mut self, device: &DeviceId) -> Option<&mut PacketLog> {
self.inner.get_mut(device)
}
/// Iterator over the device ids which `PacketLogs` is currently recording packets for.
pub fn device_ids<'a>(&'a self) -> VecDequeIter<'a, DeviceId> {
self.insertion_order.iter()
}
/// Log a packet for a given device. If the device is not being logged, the packet is
/// dropped.
pub fn log_packet(&mut self, device: &DeviceId, packet: SnoopPacket) {
// If the packet log has been removed, there's not much we can do with the packet.
if let Some(packet_log) = self.inner.get_mut(device) {
packet_log.insert(packet);
}
}
}