// 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; 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 { value: T, verbose: bool, indent: usize, } impl Displayer { /// 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 { 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::::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> { 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", } ) } }