#[cfg(any(feature = "async-print", feature = "auto"))]
use embassy_time::with_timeout;
use embassy_futures::join::{join, join3};
use embassy_futures::select::{Either, select};
use embassy_time::{Duration, Timer};
use enumset::EnumSet;
use esp_radio::esp_now::WifiPhyRate;
#[cfg(feature = "esp32c5")]
use esp_radio::wifi::BandMode;
use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{Interfaces, Protocol, Protocols, SecondaryChannel, WifiController};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use portable_atomic::Ordering;
use crate::central::ap::{ap_init, run_ap};
use crate::central::esp_now::run_esp_now_central;
use crate::central::esp_now_fast::run_esp_now_fast_collector;
use crate::central::sta::{run_sta_connect, sta_init};
use crate::config::CsiConfig as CsiConfiguration;
use crate::peripheral::esp_now::run_esp_now_peripheral;
use crate::peripheral::esp_now_fast::run_esp_now_fast_source;
use crate::profile::{RadioProfile, StandardProfile};
use crate::csi::delivery::{
CSINodeClient, IS_COLLECTOR, build_csi_config, run_process_csi_packet, set_csi,
};
use crate::espnow_phy::bring_up_espnow_sta;
use crate::espnow_phy::{
apply_espnow_ht40_mode, install_static_espnow_recv, takeover_esp_now_recv,
with_espnow_recv_suspended,
};
#[cfg(feature = "esp32c5")]
use crate::espnow_phy::apply_espnow_band_for_channel;
use crate::log_ln;
use crate::stats::set_seq_drop_detection;
pub(crate) static STOP_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[cfg(feature = "esp32c5")]
const C5_RADIO_SETTLE_MS: u64 = 60;
async fn c5_radio_settle() {
#[cfg(feature = "esp32c5")]
Timer::after(Duration::from_millis(C5_RADIO_SETTLE_MS)).await;
}
async fn csi_data_collection(client: &mut CSINodeClient, duration: u64) {
#[cfg(any(feature = "async-print", feature = "auto"))]
if crate::logging::logging::is_async_logging_active() {
with_timeout(Duration::from_secs(duration), async {
loop {
client.print_csi_w_metadata().await;
}
})
.await
.unwrap_err();
client.send_stop().await;
return;
}
#[cfg(not(any(feature = "async-print", feature = "auto")))]
{
let _ = client;
}
Timer::after(Duration::from_secs(duration)).await;
client.send_stop().await;
}
async fn wait_for_stop() {
STOP_SIGNAL.wait().await;
STOP_SIGNAL.signal(());
}
async fn stop_after_duration(duration: u64) {
match select(
STOP_SIGNAL.wait(),
Timer::after(Duration::from_secs(duration)),
)
.await
{
Either::First(_) | Either::Second(_) => STOP_SIGNAL.signal(()),
}
}
pub struct EspNowConfig {
phy_rate: WifiPhyRate,
pub(crate) channel: u8,
peer_mac: Option<[u8; 6]>,
secondary_channel: Option<SecondaryChannel>,
force_phy: bool,
}
impl Default for EspNowConfig {
fn default() -> Self {
Self {
phy_rate: WifiPhyRate::RateMcs0Lgi,
channel: 1,
peer_mac: None,
secondary_channel: None,
force_phy: false,
}
}
}
impl EspNowConfig {
pub fn fast_default() -> Self {
Self::default().with_phy_rate(WifiPhyRate::RateMcs7Lgi)
}
pub fn with_channel(mut self, channel: u8) -> Self {
self.channel = channel;
self
}
pub fn with_phy_rate(mut self, phy_rate: WifiPhyRate) -> Self {
self.phy_rate = phy_rate;
self.force_phy = true;
self
}
pub fn with_peer_mac(mut self, peer_mac: [u8; 6]) -> Self {
self.peer_mac = Some(peer_mac);
self
}
pub fn channel(&self) -> u8 {
self.channel
}
pub fn phy_rate(&self) -> &WifiPhyRate {
&self.phy_rate
}
pub fn peer_mac(&self) -> Option<[u8; 6]> {
self.peer_mac
}
pub fn with_ht40(mut self, secondary: SecondaryChannel) -> Self {
self.secondary_channel = Some(secondary);
self.force_phy = true;
self
}
pub fn secondary_channel(&self) -> Option<SecondaryChannel> {
self.secondary_channel
}
pub fn force_phy(&self) -> bool {
self.force_phy
}
}
#[derive(Debug, Clone)]
pub struct WifiSnifferConfig {
#[allow(dead_code)]
mac_filter: Option<[u8; 6]>,
channel: u8,
}
impl Default for WifiSnifferConfig {
fn default() -> Self {
Self {
mac_filter: None,
channel: 1,
}
}
}
impl WifiSnifferConfig {
pub fn with_channel(mut self, channel: u8) -> Self {
self.channel = channel;
self
}
pub fn channel(&self) -> u8 {
self.channel
}
}
#[derive(Debug, Clone)]
pub struct WifiStationConfig {
pub client_config: StationConfig,
pub channel_hint: Option<u8>,
}
impl WifiStationConfig {
pub fn new(client_config: StationConfig) -> Self {
Self {
client_config,
channel_hint: None,
}
}
pub fn with_channel_hint(mut self, channel: u8) -> Self {
self.channel_hint = Some(channel);
self
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for WifiStationConfig {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(fmt, "WifiStationConfig {{ client_config: <opaque> }}");
}
}
pub struct WifiApConfig {
pub ap_config: esp_radio::wifi::ap::AccessPointConfig,
pub channel: u8,
pub secondary_channel: Option<SecondaryChannel>,
pub ap_ipv4: core::net::Ipv4Addr,
pub lease_ipv4: core::net::Ipv4Addr,
pub lease_count: u8,
pub serve_dhcp: bool,
pub sync_burst: bool,
}
impl WifiApConfig {
pub fn new(
ap_config: esp_radio::wifi::ap::AccessPointConfig,
channel: u8,
secondary: Option<SecondaryChannel>,
) -> Self {
Self {
ap_config,
channel,
secondary_channel: secondary,
ap_ipv4: core::net::Ipv4Addr::new(192, 168, 13, 1),
lease_ipv4: core::net::Ipv4Addr::new(192, 168, 13, 2),
lease_count: 1,
serve_dhcp: true,
sync_burst: false,
}
}
pub fn with_ipv4(mut self, ap: core::net::Ipv4Addr, lease: core::net::Ipv4Addr) -> Self {
self.ap_ipv4 = ap;
self.lease_ipv4 = lease;
self
}
pub fn with_lease_pool(mut self, count: u8) -> Self {
self.lease_count = count.max(1);
self
}
pub fn lease_ip_at(&self, index: u8) -> core::net::Ipv4Addr {
let idx = index.min(self.lease_count.saturating_sub(1));
let mut oct = self.lease_ipv4.octets();
oct[3] = oct[3].saturating_add(idx);
core::net::Ipv4Addr::from(oct)
}
pub fn lease_pool(&self) -> heapless::Vec<core::net::Ipv4Addr, 8> {
let mut v = heapless::Vec::new();
for i in 0..self.lease_count.min(8) {
let _ = v.push(self.lease_ip_at(i));
}
v
}
pub fn with_dhcp_server(mut self, enabled: bool) -> Self {
self.serve_dhcp = enabled;
self
}
pub fn with_sync_burst(mut self, enabled: bool) -> Self {
self.sync_burst = enabled;
self
}
pub fn channel(&self) -> u8 {
self.channel
}
pub fn secondary_channel(&self) -> Option<SecondaryChannel> {
self.secondary_channel
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for WifiApConfig {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(fmt, "WifiApConfig {{ ap_config: <opaque> }}");
}
}
pub enum CentralOpMode {
EspNow(EspNowConfig),
WifiStation(WifiStationConfig),
WifiAccessPoint(WifiApConfig),
EspNowFastCollector(EspNowConfig),
}
pub enum PeripheralOpMode {
EspNow(EspNowConfig),
WifiSniffer(WifiSnifferConfig),
EspNowFastSource(EspNowConfig),
}
pub enum Node {
Peripheral(PeripheralOpMode),
Central(CentralOpMode),
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum CollectionMode {
Collector,
Listener,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IOTaskConfig {
pub tx_enabled: bool,
pub rx_enabled: bool,
}
impl IOTaskConfig {
pub const fn new(tx_enabled: bool, rx_enabled: bool) -> Self {
Self {
tx_enabled,
rx_enabled,
}
}
}
impl Default for IOTaskConfig {
fn default() -> Self {
Self::new(true, true)
}
}
pub struct CSINodeHardware<'a> {
interfaces: &'a mut Interfaces<'static>,
controller: &'a mut WifiController<'static>,
}
impl<'a> CSINodeHardware<'a> {
pub fn new(
interfaces: &'a mut Interfaces<'static>,
controller: &'a mut WifiController<'static>,
) -> Self {
Self {
interfaces,
controller,
}
}
}
pub(crate) fn reset_globals() {
crate::csi::delivery::reset();
crate::stats::reset();
}
pub struct CSINode<'a> {
kind: Node,
collection_mode: CollectionMode,
io_tasks: IOTaskConfig,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
protocol: Option<Protocol>,
flood_unsolicited_reply: bool,
profile: &'static dyn RadioProfile,
}
impl<'a> CSINode<'a> {
pub fn new(
kind: Node,
collection_mode: CollectionMode,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
) -> Self {
Self {
kind,
collection_mode,
io_tasks: IOTaskConfig::default(),
csi_config,
traffic_freq_hz,
hardware,
protocol: None,
flood_unsolicited_reply: false,
profile: &StandardProfile,
}
}
pub fn new_central_node(
op_mode: CentralOpMode,
collection_mode: CollectionMode,
csi_config: Option<CsiConfiguration>,
traffic_freq_hz: Option<u16>,
hardware: CSINodeHardware<'a>,
) -> Self {
Self {
kind: Node::Central(op_mode),
collection_mode,
io_tasks: IOTaskConfig::default(),
csi_config,
traffic_freq_hz,
hardware,
protocol: None,
flood_unsolicited_reply: false,
profile: &StandardProfile,
}
}
pub fn get_node_type(&self) -> &Node {
&self.kind
}
pub fn get_collection_mode(&self) -> CollectionMode {
self.collection_mode
}
pub fn get_central_op_mode(&self) -> Option<&CentralOpMode> {
match &self.kind {
Node::Central(mode) => Some(mode),
Node::Peripheral(_) => None,
}
}
pub fn get_peripheral_op_mode(&self) -> Option<&PeripheralOpMode> {
match &self.kind {
Node::Peripheral(mode) => Some(mode),
Node::Central(_) => None,
}
}
pub fn set_csi_config(&mut self, config: CsiConfiguration) {
self.csi_config = Some(config);
}
pub fn set_station_config(&mut self, config: WifiStationConfig) {
if let Node::Central(CentralOpMode::WifiStation(_)) = &mut self.kind {
self.kind = Node::Central(CentralOpMode::WifiStation(config));
}
}
pub fn set_traffic_frequency(&mut self, freq_hz: u16) {
self.traffic_freq_hz = Some(freq_hz);
}
pub fn set_collection_mode(&mut self, mode: CollectionMode) {
self.collection_mode = mode;
}
pub fn set_io_tasks(&mut self, io_tasks: IOTaskConfig) {
self.io_tasks = io_tasks;
}
pub fn set_tx_enabled(&mut self, enabled: bool) {
self.io_tasks.tx_enabled = enabled;
}
pub fn set_rx_enabled(&mut self, enabled: bool) {
self.io_tasks.rx_enabled = enabled;
}
pub fn get_io_tasks(&self) -> IOTaskConfig {
self.io_tasks
}
pub fn set_op_mode(&mut self, mode: Node) {
self.kind = mode;
}
pub fn set_protocol(&mut self, protocol: Protocol) {
self.protocol = Some(protocol);
}
pub fn set_radio_profile(&mut self, profile: &'static dyn RadioProfile) {
self.profile = profile;
}
pub fn set_flood_unsolicited_reply(&mut self, enabled: bool) {
self.flood_unsolicited_reply = enabled;
}
pub fn set_rate(&mut self, rate: WifiPhyRate) {
match &mut self.kind {
Node::Central(CentralOpMode::EspNow(cfg))
| Node::Central(CentralOpMode::EspNowFastCollector(cfg))
| Node::Peripheral(PeripheralOpMode::EspNow(cfg))
| Node::Peripheral(PeripheralOpMode::EspNowFastSource(cfg)) => {
cfg.phy_rate = rate;
cfg.force_phy = true;
}
_ => {}
}
}
pub async fn run_duration(&mut self, duration: u64, client: &mut CSINodeClient) {
self.run_inner(Some(duration), Some(client)).await;
}
async fn run_inner(&mut self, duration: Option<u64>, client: Option<&mut CSINodeClient>) {
let interfaces = &mut self.hardware.interfaces;
let controller = &mut self.hardware.controller;
crate::central::sta::set_icmp_flood_unsolicited(self.flood_unsolicited_reply);
takeover_esp_now_recv(matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::WifiSniffer(_))
));
c5_radio_settle().await;
let espnow_ht40 = matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNow(c))
| Node::Peripheral(PeripheralOpMode::EspNowFastSource(c))
| Node::Central(CentralOpMode::EspNow(c))
| Node::Central(CentralOpMode::EspNowFastCollector(c))
if c.secondary_channel().is_some()
);
let is_ap = matches!(&self.kind, Node::Central(CentralOpMode::WifiAccessPoint(_)));
let profile = self.profile;
let bringup = profile.wants_bringup(&self.kind, self.protocol);
if let Some(protocol) = self.protocol.take() {
let old_protocol = reconstruct_protocol(&protocol);
let base = Protocols::default().with_2_4(EnumSet::only(protocol));
let protocols = profile.tune_protocols(&self.kind, protocol, base);
with_espnow_recv_suspended(|| {
controller.set_protocols(protocols).unwrap();
});
self.protocol = Some(old_protocol);
c5_radio_settle().await;
}
if bringup {
with_espnow_recv_suspended(|| {
profile.apply_bandwidth(controller);
});
c5_radio_settle().await;
}
let needs_sta_bringup = matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNow(c)) | Node::Central(CentralOpMode::EspNow(c))
if self.io_tasks.rx_enabled
|| {
#[cfg(not(feature = "esp32c5"))]
{
c.force_phy()
}
#[cfg(feature = "esp32c5")]
{
c.peer_mac().is_some()
}
}
) || matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNowFastSource(_))
| Node::Central(CentralOpMode::EspNowFastCollector(_))
);
if needs_sta_bringup {
with_espnow_recv_suspended(|| {
bring_up_espnow_sta(controller, false);
});
c5_radio_settle().await;
}
let sta_interface = if let Node::Central(CentralOpMode::WifiStation(config)) = &self.kind {
#[cfg(feature = "esp32c5")]
if let Some(channel) = config.channel_hint {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, channel);
});
c5_radio_settle().await;
}
Some(sta_init(
&mut interfaces.station,
config,
controller,
profile,
bringup,
))
} else {
None
};
if bringup && sta_interface.is_some() {
with_espnow_recv_suspended(|| {
profile.apply_protocols_post(controller);
});
c5_radio_settle().await;
}
let ap_interface = if let Node::Central(CentralOpMode::WifiAccessPoint(config)) = &self.kind {
#[cfg(feature = "esp32c5")]
if config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, config.channel());
});
}
if let Some(secondary) = config.secondary_channel() {
with_espnow_recv_suspended(|| {
apply_espnow_ht40_mode(controller, config.channel(), secondary);
});
c5_radio_settle().await;
}
let ifaces = ap_init(
&mut interfaces.access_point,
config,
controller,
profile,
bringup,
);
if bringup {
with_espnow_recv_suspended(|| {
profile.apply_protocols_post(controller);
});
}
c5_radio_settle().await;
Some(ifaces)
} else {
None
};
let mut config = match self.csi_config {
Some(ref config) => {
log_ln!("CSI Configuration Set: {:?}", config);
build_csi_config(config)
}
None => {
let default_config = CsiConfiguration::default();
log_ln!(
"No CSI Configuration Provided. Going with defaults: {:?}",
default_config
);
build_csi_config(&default_config)
}
};
profile.tune_csi_acquisition(&mut config);
log_ln!("Wi-Fi Controller Started");
let is_collector = self.collection_mode == CollectionMode::Collector;
IS_COLLECTOR.store(is_collector, Ordering::Relaxed);
set_seq_drop_detection(matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::EspNow(_))
| Node::Peripheral(PeripheralOpMode::EspNowFastSource(_))
| Node::Central(CentralOpMode::EspNow(_))
| Node::Central(CentralOpMode::EspNowFastCollector(_))
));
let csi_config_for_recovery = config.clone();
let is_sniffer = matches!(
&self.kind,
Node::Peripheral(PeripheralOpMode::WifiSniffer(_))
);
if self.io_tasks.rx_enabled && !is_sniffer && !espnow_ht40 && !is_ap {
with_espnow_recv_suspended(|| {
set_csi(controller, config.clone());
});
c5_radio_settle().await;
}
let rx_enabled = self.io_tasks.rx_enabled;
let sniffer = &interfaces.sniffer;
match &self.kind {
Node::Peripheral(op_mode) => match op_mode {
PeripheralOpMode::EspNow(esp_now_config) => {
#[cfg(feature = "esp32c5")]
if esp_now_config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, esp_now_config.channel());
});
}
if let Some(secondary) = esp_now_config.secondary_channel() {
with_espnow_recv_suspended(|| {
apply_espnow_ht40_mode(controller, esp_now_config.channel(), secondary);
});
install_static_espnow_recv();
c5_radio_settle().await;
if rx_enabled {
with_espnow_recv_suspended(|| {
set_csi(controller, config.clone());
});
c5_radio_settle().await;
}
}
let main_task = run_esp_now_peripheral(
&mut interfaces.esp_now,
esp_now_config,
self.traffic_freq_hz,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
PeripheralOpMode::EspNowFastSource(esp_now_config) => {
#[cfg(feature = "esp32c5")]
if esp_now_config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, esp_now_config.channel());
});
}
if let Some(secondary) = esp_now_config.secondary_channel() {
with_espnow_recv_suspended(|| {
apply_espnow_ht40_mode(controller, esp_now_config.channel(), secondary);
});
install_static_espnow_recv();
c5_radio_settle().await;
if rx_enabled {
with_espnow_recv_suspended(|| {
set_csi(controller, config.clone());
});
c5_radio_settle().await;
}
}
let main_task = run_esp_now_fast_source(
&mut interfaces.esp_now,
esp_now_config,
self.traffic_freq_hz,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
PeripheralOpMode::WifiSniffer(sniffer_config) => {
#[cfg(feature = "esp32c5")]
{
let band = if sniffer_config.channel() >= 36 {
BandMode::_5G
} else {
BandMode::_2_4G
};
controller.set_band_mode(band).unwrap();
}
sniffer.set_promiscuous_mode(true).unwrap();
controller
.set_channel(sniffer_config.channel(), SecondaryChannel::None)
.unwrap();
if bringup {
with_espnow_recv_suspended(|| {
profile.apply_sniffer_radio(controller);
});
c5_radio_settle().await;
}
if rx_enabled {
set_csi(controller, config.clone());
}
match (duration, rx_enabled) {
(Some(d), true) => {
join(
run_process_csi_packet(),
csi_data_collection(client.unwrap(), d),
)
.await;
run_process_csi_packet().await;
}
(Some(d), false) => stop_after_duration(d).await,
(None, true) => run_process_csi_packet().await,
(None, false) => wait_for_stop().await,
}
sniffer.set_promiscuous_mode(false).unwrap();
}
},
Node::Central(op_mode) => match op_mode {
CentralOpMode::EspNow(esp_now_config) => {
#[cfg(feature = "esp32c5")]
if esp_now_config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, esp_now_config.channel());
});
}
if let Some(secondary) = esp_now_config.secondary_channel() {
with_espnow_recv_suspended(|| {
apply_espnow_ht40_mode(controller, esp_now_config.channel(), secondary);
});
install_static_espnow_recv();
c5_radio_settle().await;
if rx_enabled {
with_espnow_recv_suspended(|| {
set_csi(controller, config.clone());
});
c5_radio_settle().await;
}
}
let main_task = run_esp_now_central(
&mut interfaces.esp_now,
interfaces.station.mac_address(),
esp_now_config,
self.traffic_freq_hz,
is_collector,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
CentralOpMode::EspNowFastCollector(esp_now_config) => {
#[cfg(feature = "esp32c5")]
if esp_now_config.secondary_channel().is_none() {
with_espnow_recv_suspended(|| {
apply_espnow_band_for_channel(controller, esp_now_config.channel());
});
}
if let Some(secondary) = esp_now_config.secondary_channel() {
with_espnow_recv_suspended(|| {
apply_espnow_ht40_mode(controller, esp_now_config.channel(), secondary);
});
install_static_espnow_recv();
c5_radio_settle().await;
if rx_enabled {
with_espnow_recv_suspended(|| {
set_csi(controller, config.clone());
});
c5_radio_settle().await;
}
}
let main_task = run_esp_now_fast_collector(
&mut interfaces.esp_now,
esp_now_config,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
}
CentralOpMode::WifiAccessPoint(ap_config) => {
let (ap_stack, ap_runner) = ap_interface.unwrap();
let main_task = run_ap(
controller,
ap_stack,
ap_runner,
ap_config,
csi_config_for_recovery,
self.io_tasks,
self.traffic_freq_hz,
);
drive_main(main_task, rx_enabled, duration, client).await;
sniffer.set_promiscuous_mode(false).unwrap();
}
CentralOpMode::WifiStation(_sta_config) => {
let (sta_stack, sta_runner) = sta_interface.unwrap();
let main_task = run_sta_connect(
controller,
self.traffic_freq_hz,
sta_stack,
sta_runner,
csi_config_for_recovery,
self.io_tasks,
);
drive_main(main_task, rx_enabled, duration, client).await;
sniffer.set_promiscuous_mode(false).unwrap();
}
},
}
STOP_SIGNAL.reset();
reset_globals();
}
pub async fn run(&mut self) {
self.run_inner(None, None).await;
}
}
async fn drive_main(
main_task: impl core::future::Future,
rx_enabled: bool,
duration: Option<u64>,
client: Option<&mut CSINodeClient>,
) {
match (duration, rx_enabled) {
(Some(d), true) => {
join3(
main_task,
run_process_csi_packet(),
csi_data_collection(client.unwrap(), d),
)
.await;
}
(Some(d), false) => {
join3(main_task, wait_for_stop(), stop_after_duration(d)).await;
}
(None, true) => {
join(main_task, run_process_csi_packet()).await;
}
(None, false) => {
join(main_task, wait_for_stop()).await;
}
}
}
fn reconstruct_protocol(protocol: &Protocol) -> Protocol {
*protocol
}