blob: 69dd5d9a8c69269e4f7da55f0c8e83136ce3d933 [file] [log] [blame]
// 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.
/// A Chip is a generic emulated radio that connects to Chip Facade
/// library.
///
/// The chip facade is a library that implements the controller protocol.
///
use crate::bluetooth as bluetooth_facade;
use crate::devices::id_factory::IdFactory;
use crate::wifi as wifi_facade;
use lazy_static::lazy_static;
use log::info;
use log::warn;
use netsim_proto::common::ChipKind as ProtoChipKind;
use netsim_proto::model::Chip as ProtoChip;
use netsim_proto::stats::{netsim_radio_stats, NetsimRadioStats as ProtoRadioStats};
use protobuf::EnumOrUnknown;
use std::sync::RwLock;
use std::time::Instant;
pub type ChipIdentifier = u32;
pub type FacadeIdentifier = u32;
const INITIAL_CHIP_ID: ChipIdentifier = 1000;
// Allocator for chip identifiers.
lazy_static! {
static ref IDS: RwLock<IdFactory<ChipIdentifier>> =
RwLock::new(IdFactory::new(INITIAL_CHIP_ID, 1));
}
pub struct Chip {
pub id: ChipIdentifier,
pub facade_id: Option<FacadeIdentifier>,
pub kind: ProtoChipKind,
pub address: String,
pub name: String,
// TODO: may not be necessary
pub device_name: String,
// These are patchable
pub manufacturer: String,
pub product_name: String,
pub start: Instant,
}
impl Chip {
#[allow(clippy::too_many_arguments)]
fn new(
id: ChipIdentifier,
facade_id: Option<FacadeIdentifier>,
kind: ProtoChipKind,
address: &str,
name: &str,
device_name: &str,
manufacturer: &str,
product_name: &str,
) -> Self {
Self {
id,
facade_id,
kind,
address: address.to_string(),
name: name.to_string(),
device_name: device_name.to_string(),
manufacturer: manufacturer.to_string(),
product_name: product_name.to_string(),
start: Instant::now(),
}
}
// Get the stats protobuf for a chip controller instance.
//
// This currently wraps the chip "get" facade method because the
// counts are phy level. We need a vec since Bluetooth reports
// stats for BLE and CLASSIC.
pub fn get_stats(&self) -> Vec<ProtoRadioStats> {
let mut vec = Vec::<ProtoRadioStats>::new();
let mut stats = ProtoRadioStats::new();
stats.set_duration_secs(self.start.elapsed().as_secs());
if let Some(facade_id) = self.facade_id {
match self.kind {
ProtoChipKind::BLUETOOTH => {
let bt = bluetooth_facade::bluetooth_get(facade_id);
stats.set_kind(netsim_radio_stats::Kind::BT_LE);
stats.set_tx_count(bt.low_energy.tx_count);
stats.set_rx_count(bt.low_energy.rx_count);
vec.push(stats);
stats = ProtoRadioStats::new();
stats.set_duration_secs(self.start.elapsed().as_secs());
stats.set_kind(netsim_radio_stats::Kind::BT_CLASSIC);
stats.set_tx_count(bt.classic.tx_count);
stats.set_rx_count(bt.classic.rx_count);
}
ProtoChipKind::BLUETOOTH_BEACON => {
stats.set_kind(netsim_radio_stats::Kind::BT_LE_BEACON);
if let Ok(beacon) = bluetooth_facade::ble_beacon_get(self.id, facade_id) {
stats.set_tx_count(beacon.bt.low_energy.tx_count);
stats.set_rx_count(beacon.bt.low_energy.rx_count);
} else {
warn!("Unknown beacon");
}
}
ProtoChipKind::WIFI => {
stats.set_kind(netsim_radio_stats::Kind::WIFI);
let wifi = wifi_facade::wifi_get(facade_id);
stats.set_tx_count(wifi.tx_count);
stats.set_rx_count(wifi.rx_count);
}
_ => {
info!("Unhandled chip in get_stats {:?}", self.kind);
}
}
} else {
warn!("No facade id in get_stats");
}
vec.push(stats);
vec
}
/// Create the model protobuf
pub fn get(&self) -> Result<ProtoChip, String> {
let mut chip = ProtoChip::new();
chip.kind = EnumOrUnknown::new(self.kind);
chip.id = self.id;
chip.name = self.name.clone();
chip.manufacturer = self.manufacturer.clone();
chip.product_name = self.product_name.clone();
match (chip.kind.enum_value(), self.facade_id) {
(Ok(ProtoChipKind::BLUETOOTH), Some(facade_id)) => {
chip.set_bt(bluetooth_facade::bluetooth_get(facade_id));
}
(Ok(ProtoChipKind::BLUETOOTH_BEACON), Some(facade_id)) => {
chip.set_ble_beacon(bluetooth_facade::ble_beacon_get(self.id, facade_id)?);
}
(Ok(ProtoChipKind::WIFI), Some(facade_id)) => {
chip.set_wifi(wifi_facade::wifi_get(facade_id));
}
(_, None) => {
return Err(format!(
"Facade Id hasn't been added yet to frontend resource for chip_id: {}",
self.id
));
}
_ => {
return Err(format!("Unknown chip kind: {:?}", chip.kind));
}
}
Ok(chip)
}
/// Patch processing for the chip. Validate and move state from the patch
/// into the chip changing the ChipFacade as needed.
pub fn patch(&mut self, patch: &ProtoChip) -> Result<(), String> {
if !patch.manufacturer.is_empty() {
self.manufacturer = patch.manufacturer.clone();
}
if !patch.product_name.is_empty() {
self.product_name = patch.product_name.clone();
}
match self.facade_id {
Some(facade_id) => {
// Check both ChipKind and RadioKind fields, they should be consistent
if self.kind == ProtoChipKind::BLUETOOTH && patch.has_bt() {
bluetooth_facade::bluetooth_patch(facade_id, patch.bt());
Ok(())
} else if self.kind == ProtoChipKind::BLUETOOTH_BEACON
&& patch.has_ble_beacon()
&& patch.ble_beacon().bt.is_some()
{
bluetooth_facade::ble_beacon_patch(facade_id, self.id, patch.ble_beacon())?;
Ok(())
} else if self.kind == ProtoChipKind::WIFI && patch.has_wifi() {
wifi_facade::wifi_patch(facade_id, patch.wifi());
Ok(())
} else {
Err(format!("Unknown chip kind or missing radio: {:?}", self.kind))
}
}
None => Err(format!(
"Facade Id hasn't been added yet to frontend resource for chip_id: {}",
self.id
)),
}
}
pub fn reset(&mut self) -> Result<(), String> {
match (self.kind, self.facade_id) {
(ProtoChipKind::BLUETOOTH, Some(facade_id)) => {
bluetooth_facade::bluetooth_reset(facade_id);
Ok(())
}
(ProtoChipKind::WIFI, Some(facade_id)) => {
wifi_facade::wifi_reset(facade_id);
Ok(())
}
(_, None) => Err(format!(
"Facade Id hasn't been added yet to frontend resource for chip_id: {}",
self.id
)),
_ => Err(format!("Unknown chip kind: {:?}", self.kind)),
}
}
}
/// Allocates a new chip with a facade_id.
pub fn chip_new(
chip_kind: ProtoChipKind,
chip_address: &str,
chip_name: Option<&str>,
device_name: &str,
chip_manufacturer: &str,
chip_product_name: &str,
) -> Result<Chip, String> {
let id = IDS.write().unwrap().next_id();
Ok(Chip::new(
id,
None,
chip_kind,
chip_address,
chip_name.unwrap_or(&format!("chip-{id}")),
device_name,
chip_manufacturer,
chip_product_name,
))
}