blob: ae9a949c73833b11aa3f48ada89c03e6849b9d64 [file] [edit]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use netsim_proto::frontend::{ListDeviceResponse, ListLinkResponse};
use netsim_proto::model::{
self,
chip::ble_beacon::advertise_settings,
chip::ble_beacon::{AdvertiseData, AdvertiseSettings},
};
use protobuf::MessageField;
use std::fmt;
const INDENT_WIDTH: usize = 2;
/// Displayer for model protobufs. Implements fmt::Display.
/// # Invariants
/// Displayed values **do not** end in a newline.
pub struct Displayer<T> {
value: T,
verbose: bool,
indent: usize,
}
impl<T> Displayer<T> {
/// Returns a new displayer for values of the provided type.
pub fn new(value: T, verbose: bool) -> Self {
Displayer { value, verbose, indent: 0 }
}
/// Indent the displayed string by a given amount. Returns `self`.
pub fn indent(&mut self, current_indent: usize) -> &Self {
self.indent = current_indent + INDENT_WIDTH;
self
}
}
impl fmt::Display for Displayer<ListDeviceResponse> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let mut devices = self.value.devices.iter().peekable();
while let Some(device) = devices.next() {
write!(f, "{:indent$}{}", "", Displayer::new(device, self.verbose))?;
if devices.peek().is_some() {
// We print the newline here instead of in the Device displayer because we don't want a newline before the very first device.
writeln!(f)?;
}
}
Ok(())
}
}
impl fmt::Display for Displayer<&model::Device> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 9;
write!(
f,
"{:indent$}{:width$} {}",
"",
self.value.name,
Displayer::new(self.value.position.as_ref().unwrap_or_default(), self.verbose)
)?;
for chip in self.value.chips.iter() {
write!(f, "{:indent$}{}", "", Displayer::new(chip, self.verbose).indent(self.indent))?;
}
Ok(())
}
}
impl fmt::Display for Displayer<&model::Chip> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 9;
match self.value.chip.as_ref() {
Some(model::chip::Chip::BleBeacon(ble_beacon)) => {
let radio = ble_beacon.bt.low_energy.as_ref().unwrap_or_default();
let beacon_width = 16 + width;
if self.verbose || !radio.state.unwrap_or_default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:beacon_width$} {}",
"",
format!("beacon-ble ({}):", self.value.name),
Displayer::new(radio, self.verbose),
)?;
}
if self.verbose {
writeln!(f)?;
write!(f, "{}", Displayer::new(ble_beacon, self.verbose).indent(self.indent))?;
}
}
Some(model::chip::Chip::Bt(bt)) => {
if let Some(ble) = bt.low_energy.as_ref() {
if self.verbose || !ble.state.unwrap_or_default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}{}",
"",
"ble: ",
Displayer::new(ble, self.verbose),
)?;
}
};
if let Some(classic) = bt.classic.as_ref() {
if self.verbose || !classic.state.unwrap_or_default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}{}",
"",
"classic: ",
Displayer::new(classic, self.verbose),
)?;
}
};
}
Some(model::chip::Chip::Wifi(wifi)) => {
if self.verbose || !wifi.state.unwrap_or_default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}{}",
"",
"wifi: ",
Displayer::new(wifi, self.verbose)
)?;
}
}
Some(model::chip::Chip::Uwb(uwb)) => {
if self.verbose || !uwb.state.unwrap_or_default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}{}",
"",
"uwb: ",
Displayer::new(uwb, self.verbose)
)?;
}
}
_ => {
if self.verbose {
writeln!(f)?;
write!(f, "{:indent$}Unknown chip", "")?
}
}
}
Ok(())
}
}
impl fmt::Display for Displayer<&model::chip::BleBeacon> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let address_width = 16;
write!(f, "{:indent$}address: {:address_width$}", "", self.value.address)?;
if self.value.settings.is_some()
&& self.value.settings != MessageField::some(AdvertiseSettings::default())
{
writeln!(f)?;
write!(
f,
"{:indent$}advertise settings:{}",
"",
Displayer::new(self.value.settings.as_ref().unwrap_or_default(), self.verbose)
.indent(self.indent)
)?;
}
if self.value.adv_data.is_some()
&& self.value.adv_data != MessageField::some(AdvertiseData::default())
{
writeln!(f)?;
write!(
f,
"{:indent$}advertise packet data:{}",
"",
Displayer::new(self.value.adv_data.as_ref().unwrap_or_default(), self.verbose)
.indent(self.indent)
)?;
}
// TODO(jmes): Add scan response data.
Ok(())
}
}
impl fmt::Display for Displayer<&model::Position> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let precision = 2;
if self.verbose
|| (self.value.x != f32::default()
|| self.value.y != f32::default()
|| self.value.z != f32::default())
{
write!(
f,
"{:indent$} position: {:.precision$}, {:.precision$}, {:.precision$}",
"", self.value.x, self.value.y, self.value.z,
)?;
}
Ok(())
}
}
impl fmt::Display for Displayer<&model::chip::ble_beacon::AdvertiseSettings> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 25;
if let Some(tx_power) = self.value.tx_power.as_ref() {
writeln!(f)?;
write!(f, "{:indent$}{}", "", Displayer::new(tx_power, self.verbose))?;
}
if let Some(interval) = self.value.interval.as_ref() {
writeln!(f)?;
write!(f, "{:indent$}{}", "", Displayer::new(interval, self.verbose))?;
}
if self.value.scannable {
writeln!(f)?;
write!(f, "{:indent$}{:width$}: {}", "", "scannable", self.value.scannable)?;
}
if self.value.timeout != u64::default() {
writeln!(f)?;
write!(f, "{:indent$}{:width$}: {} ms", "", "timeout", self.value.timeout)?;
}
Ok(())
}
}
impl fmt::Display for Displayer<&model::chip::ble_beacon::AdvertiseData> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 25;
if self.value.include_device_name != bool::default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}: {}",
"", "include device name", self.value.include_device_name
)?;
}
if self.value.include_tx_power_level != bool::default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}: {}",
"", "include tx power level", self.value.include_tx_power_level
)?;
}
if self.value.manufacturer_data != Vec::<u8>::default() {
writeln!(f)?;
write!(
f,
"{:indent$}{:width$}: {}",
"",
"manufacturer data bytes",
self.value.manufacturer_data.len()
)?;
}
Ok(())
}
}
impl fmt::Display for Displayer<&advertise_settings::Interval> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 25;
match self.value {
advertise_settings::Interval::Milliseconds(interval) => {
write!(f, "{:indent$}{:width$}: {} ms", "", "interval", interval)
}
advertise_settings::Interval::AdvertiseMode(mode) => {
write!(f, "{:indent$}{:width$}: {:?}", "", "advertise mode", mode)
}
_ => Err(fmt::Error),
}
}
}
impl fmt::Display for Displayer<&advertise_settings::Tx_power> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 25;
match self.value {
advertise_settings::Tx_power::Dbm(dbm) => {
write!(f, "{:indent$}{:width$}: {} dBm", "", "tx power", dbm)
}
advertise_settings::Tx_power::TxPowerLevel(level) => {
write!(f, "{:indent$}{:width$}: {:?}", "", "tx power level", level)
}
_ => Err(fmt::Error),
}
}
}
impl fmt::Display for Displayer<&model::chip::Radio> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let count_width = 9;
write!(f, "{:indent$}{}", "", Displayer::new(&self.value.state, self.verbose),)?;
if self.verbose {
write!(
f,
"| rx_count: {:count_width$} | tx_count: {:count_width$}",
self.value.rx_count, self.value.tx_count
)?
}
Ok(())
}
}
impl fmt::Display for Displayer<&Option<bool>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let width = 9;
write!(
f,
"{:indent$}{:width$}",
"",
match self.value {
Some(true) => "up",
Some(false) => "down",
None => "unknown",
}
)
}
}
// Helper struct to display link's chip IDs, showing "ALL" for ID 0.
pub struct LinkChipIdDisplay(pub u32);
impl fmt::Display for LinkChipIdDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 == 0 {
write!(f, "ALL")
} else {
write!(f, "{}", self.0)
}
}
}
impl fmt::Display for Displayer<ListLinkResponse> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = self.indent;
let chip_width = 10;
let rssi_width = 6;
let phykind_width = 20;
if self.value.links.is_empty() {
write!(f, "{:indent$}No links with properties are currently set.", "")?;
} else {
// Print a header for the table
write!(
f,
"{:indent$}{:chip_width$} | {:chip_width$} | {:phykind_width$} | {:rssi_width$}",
"", "Sender", "Receiver", "Type", "RSSI"
)?;
writeln!(f)?;
write!(
f,
"{:indent$}{:-<chip_width$}-+-{:-<chip_width$}-+-{:-<phykind_width$}-+-{:-<rssi_width$}",
"", "", "", "", ""
)?;
// Iterate through the links and print each one as a row
for link in self.value.links.iter() {
writeln!(f)?;
write!(
f,
"{:indent$}{:<chip_width$} | {:<chip_width$} | {:<phykind_width$} | {:<rssi_width$}",
"",
format!("{}", LinkChipIdDisplay(link.sender_id)),
format!("{}", LinkChipIdDisplay(link.receiver_id)),
format!("{:?}", link.link_kind.enum_value_or_default()),
link.rssi,
)?;
}
}
Ok(())
}
}