├── LICENSE ├── README.md └── bin └── wlan └── wlantool └── src └── main.rs /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Fuchsia Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Variants of `await` syntax for Rust 2 | 3 | As the drive for async/await stabilization in Rust enters its final stretch, 4 | the latest [discussion of await syntax](https://internals.rust-lang.org/t/await-syntax-discussion-summary/9914) 5 | has shown that the issue is as contentious as ever, with several firmly 6 | established camps. The lang team's excellent [write up](https://paper.dropbox.com/doc/Await-Syntax-Write-Up-t9NlOSeI4RQ8AINsaSSyJ) 7 | has narrowed down the choices to "prefix" and "postfix" categories, each of 8 | which contains several possible solutions, but so far the team hasn't reached 9 | consensus. 10 | 11 | While there have been commendable attempts to [illustrate the syntax](https://github.com/rust-lang/rust/issues/57640#issuecomment-455846086) 12 | with more substantial code excerpts, in general I feel that the discussion 13 | has been rather light on concrete, in-context examples. Even if a certain 14 | amount of abstractness is inevitable, in this case there is an existing 15 | corpus of working async code which can be adapted to various proposed 16 | syntaxes without much difficulty: components of Google's Fuchsia OS written 17 | in Rust. 18 | 19 | I believe that there is value in seeing a language construct in the context 20 | of the rest of the source code. This repo starts with the source of an 21 | async-heavy utility and translates it to four variant syntaxes proposed by 22 | the lang team. More source files and await syntaxes can be added if there's 23 | interest: see the __Contributing__ section. 24 | 25 | ## Organization of the repo 26 | 27 | The original source is copied from the tree at the commit `66e1924` of the 28 | `master` branch of the Fuchsia [Garnet](https://fuchsia.googlesource.com/garnet/) 29 | repo. This source uses the unstable `await!` macro. Translations to other 30 | syntaxes are in separate branches, named after the proposals in the lang team 31 | writeup: 32 | 33 | * `prefix-mandatory`: Prefix await with mandatory delimiters. 34 | 35 | * `prefix-sugar`: Prefix syntactic sugar for await+try. 36 | 37 | * `postfix-field`: Postfix field access syntax. 38 | 39 | * `postfix-method`: Postfix method syntax. 40 | 41 | (The order of syntaxes in the above list follows the order in the writeup.) 42 | 43 | ## Contributing 44 | 45 | If you notice an error in a translation, create a PR against the branch of the 46 | translation. 47 | 48 | If you want to add a source file with at least two translations, first create 49 | a PR with the original against `master`, then the PRs for each translation 50 | against the corresponding branch. The original should be from the tree 51 | referenced in the __Organization__ section. 52 | 53 | If you want to contribute a translation to another syntax, open an issue and 54 | propose the creation of the appropriate branch. To keep the repo focused, 55 | please don't propose syntaxes which aren't in the lang team's writeup. Then, 56 | create a PR against the new branch. 57 | 58 | ## License 59 | 60 | The contents of this repo is covered by Google's Fuchsia license, which can 61 | be found in the LICENSE file. 62 | -------------------------------------------------------------------------------- /bin/wlan/wlantool/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Fuchsia Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #![feature(async_await, await_macro, futures_api)] 6 | #![deny(warnings)] 7 | 8 | use failure::{bail, format_err, Error, ResultExt}; 9 | use fidl::endpoints; 10 | use fidl_fuchsia_wlan_device_service::{ 11 | self as wlan_service, DeviceServiceMarker, DeviceServiceProxy, 12 | }; 13 | use fidl_fuchsia_wlan_minstrel::Peer; 14 | use fidl_fuchsia_wlan_sme::{ 15 | self as fidl_sme, ConnectResultCode, ConnectTransactionEvent, ScanTransactionEvent, 16 | }; 17 | use fuchsia_app::client::connect_to_service; 18 | use fuchsia_async as fasync; 19 | use fuchsia_zircon as zx; 20 | use futures::prelude::*; 21 | use std::fmt; 22 | use std::str::FromStr; 23 | use structopt::StructOpt; 24 | 25 | mod opts; 26 | use crate::opts::*; 27 | 28 | const SCAN_REQUEST_TIMEOUT_SEC: u8 = 10; 29 | 30 | type WlanSvc = DeviceServiceProxy; 31 | 32 | fn main() -> Result<(), Error> { 33 | let opt = Opt::from_args(); 34 | println!("{:?}", opt); 35 | 36 | let mut exec = fasync::Executor::new().context("error creating event loop")?; 37 | let wlan_svc = connect_to_service::() 38 | .context("failed to connect to device service")?; 39 | 40 | let fut = async { 41 | match opt { 42 | Opt::Phy(cmd) => await!(do_phy(cmd, wlan_svc)), 43 | Opt::Iface(cmd) => await!(do_iface(cmd, wlan_svc)), 44 | Opt::Client(cmd) => await!(do_client(cmd, wlan_svc)), 45 | Opt::Ap(cmd) => await!(do_ap(cmd, wlan_svc)), 46 | Opt::Mesh(cmd) => await!(do_mesh(cmd, wlan_svc)), 47 | } 48 | }; 49 | exec.run_singlethreaded(fut) 50 | } 51 | 52 | async fn do_phy(cmd: opts::PhyCmd, wlan_svc: WlanSvc) -> Result<(), Error> { 53 | match cmd { 54 | opts::PhyCmd::List => { 55 | // TODO(tkilbourn): add timeouts to prevent hanging commands 56 | let response = await!(wlan_svc.list_phys()).context("error getting response")?; 57 | println!("response: {:?}", response); 58 | } 59 | opts::PhyCmd::Query { phy_id } => { 60 | let mut req = wlan_service::QueryPhyRequest { phy_id }; 61 | let response = await!(wlan_svc.query_phy(&mut req)).context("error querying phy")?; 62 | println!("response: {:?}", response); 63 | } 64 | } 65 | Ok(()) 66 | } 67 | 68 | async fn do_iface(cmd: opts::IfaceCmd, wlan_svc: WlanSvc) -> Result<(), Error> { 69 | match cmd { 70 | opts::IfaceCmd::New { phy_id, role } => { 71 | let mut req = wlan_service::CreateIfaceRequest { phy_id: phy_id, role: role.into() }; 72 | 73 | let response = 74 | await!(wlan_svc.create_iface(&mut req)).context("error getting response")?; 75 | println!("response: {:?}", response); 76 | } 77 | opts::IfaceCmd::Delete { phy_id, iface_id } => { 78 | let mut req = wlan_service::DestroyIfaceRequest { phy_id: phy_id, iface_id: iface_id }; 79 | 80 | let response = 81 | await!(wlan_svc.destroy_iface(&mut req)).context("error destroying iface")?; 82 | match zx::Status::ok(response) { 83 | Ok(()) => println!("destroyed iface {:?}", iface_id), 84 | Err(s) => println!("error destroying iface: {:?}", s), 85 | } 86 | } 87 | opts::IfaceCmd::List => { 88 | let response = await!(wlan_svc.list_ifaces()).context("error getting response")?; 89 | println!("response: {:?}", response); 90 | } 91 | opts::IfaceCmd::Query { iface_id } => { 92 | let response = 93 | await!(wlan_svc.query_iface(iface_id)).context("error querying iface")?; 94 | println!("response: {:?}", response); 95 | } 96 | opts::IfaceCmd::Stats { iface_id } => { 97 | let ids = await!(get_iface_ids(wlan_svc.clone(), iface_id))?; 98 | 99 | for iface_id in ids { 100 | let (status, resp) = await!(wlan_svc.get_iface_stats(iface_id)) 101 | .context("error getting stats for iface")?; 102 | match status { 103 | zx::sys::ZX_OK => { 104 | match resp { 105 | // TODO(eyw): Implement fmt::Display 106 | Some(r) => println!("Iface {}: {:#?}", iface_id, r), 107 | None => println!("Iface {} returns empty stats resonse", iface_id), 108 | } 109 | } 110 | status => println!("error getting stats for Iface {}: {}", iface_id, status), 111 | } 112 | } 113 | } 114 | opts::IfaceCmd::Minstrel(cmd) => match cmd { 115 | opts::MinstrelCmd::List { iface_id } => { 116 | let ids = await!(get_iface_ids(wlan_svc.clone(), iface_id))?; 117 | for id in ids { 118 | if let Ok(peers) = await!(list_minstrel_peers(wlan_svc.clone(), id)) { 119 | if peers.is_empty() { 120 | continue; 121 | } 122 | println!("iface {} has {} peers:", id, peers.len()); 123 | for peer in peers { 124 | println!("{}", peer); 125 | } 126 | } 127 | } 128 | } 129 | opts::MinstrelCmd::Show { iface_id, peer_addr } => { 130 | let peer_addr = match peer_addr { 131 | Some(s) => Some(s.parse()?), 132 | None => None, 133 | }; 134 | let ids = await!(get_iface_ids(wlan_svc.clone(), iface_id))?; 135 | for id in ids { 136 | if let Err(e) = 137 | await!(show_minstrel_peer_for_iface(wlan_svc.clone(), id, peer_addr)) 138 | { 139 | println!( 140 | "querying peer(s) {} on iface {} returned an error: {}", 141 | peer_addr.unwrap_or(MacAddr([0; 6])), 142 | id, 143 | e 144 | ); 145 | } 146 | } 147 | } 148 | }, 149 | } 150 | Ok(()) 151 | } 152 | 153 | async fn do_client(cmd: opts::ClientCmd, wlan_svc: WlanSvc) -> Result<(), Error> { 154 | match cmd { 155 | opts::ClientCmd::Scan { iface_id, scan_type } => { 156 | let sme = await!(get_client_sme(wlan_svc, iface_id))?; 157 | let (local, remote) = endpoints::create_proxy()?; 158 | let mut req = fidl_sme::ScanRequest { 159 | timeout: SCAN_REQUEST_TIMEOUT_SEC, 160 | scan_type: scan_type.into(), 161 | }; 162 | sme.scan(&mut req, remote).context("error sending scan request")?; 163 | await!(handle_scan_transaction(local)) 164 | } 165 | opts::ClientCmd::Connect { iface_id, ssid, password, phy, cbw, scan_type } => { 166 | let sme = await!(get_client_sme(wlan_svc, iface_id))?; 167 | let (local, remote) = endpoints::create_proxy()?; 168 | let mut req = fidl_sme::ConnectRequest { 169 | ssid: ssid.as_bytes().to_vec(), 170 | password: password.unwrap_or(String::new()).as_bytes().to_vec(), 171 | radio_cfg: fidl_sme::RadioConfig { 172 | override_phy: phy.is_some(), 173 | phy: phy.unwrap_or(PhyArg::Vht).into(), 174 | override_cbw: cbw.is_some(), 175 | cbw: cbw.unwrap_or(CbwArg::Cbw80).into(), 176 | override_primary_chan: false, 177 | primary_chan: 0, 178 | }, 179 | scan_type: scan_type.into(), 180 | }; 181 | sme.connect(&mut req, Some(remote)).context("error sending connect request")?; 182 | await!(handle_connect_transaction(local)) 183 | } 184 | opts::ClientCmd::Disconnect { iface_id } => { 185 | let sme = await!(get_client_sme(wlan_svc, iface_id))?; 186 | await!(sme.disconnect()) 187 | .map_err(|e| format_err!("error sending disconnect request: {}", e)) 188 | } 189 | opts::ClientCmd::Status { iface_id } => { 190 | let sme = await!(get_client_sme(wlan_svc, iface_id))?; 191 | let st = await!(sme.status())?; 192 | match st.connected_to { 193 | Some(bss) => { 194 | println!( 195 | "Connected to '{}' (bssid {})", 196 | String::from_utf8_lossy(&bss.ssid), 197 | MacAddr(bss.bssid) 198 | ); 199 | } 200 | None => println!("Not connected to a network"), 201 | } 202 | if !st.connecting_to_ssid.is_empty() { 203 | println!("Connecting to '{}'", String::from_utf8_lossy(&st.connecting_to_ssid)); 204 | } 205 | Ok(()) 206 | } 207 | } 208 | } 209 | 210 | async fn do_ap(cmd: opts::ApCmd, wlan_svc: WlanSvc) -> Result<(), Error> { 211 | match cmd { 212 | opts::ApCmd::Start { iface_id, ssid, password, channel } => { 213 | let sme = await!(get_ap_sme(wlan_svc, iface_id))?; 214 | let mut config = fidl_sme::ApConfig { 215 | ssid: ssid.as_bytes().to_vec(), 216 | password: password.map_or(vec![], |p| p.as_bytes().to_vec()), 217 | channel, 218 | }; 219 | let r = await!(sme.start(&mut config))?; 220 | match r { 221 | fidl_sme::StartApResultCode::InvalidArguments => { 222 | println!("{:?}: Channel {:?} is invalid", r, config.channel); 223 | } 224 | fidl_sme::StartApResultCode::DfsUnsupported => { 225 | println!( 226 | "{:?}: The specified role does not support DFS channel {:?}", 227 | r, config.channel 228 | ); 229 | } 230 | _ => { 231 | println!("{:?}", r); 232 | } 233 | } 234 | } 235 | opts::ApCmd::Stop { iface_id } => { 236 | let sme = await!(get_ap_sme(wlan_svc, iface_id))?; 237 | let r = await!(sme.stop()); 238 | println!("{:?}", r); 239 | } 240 | } 241 | Ok(()) 242 | } 243 | 244 | async fn do_mesh(cmd: opts::MeshCmd, wlan_svc: WlanSvc) -> Result<(), Error> { 245 | match cmd { 246 | opts::MeshCmd::Join { iface_id, mesh_id, channel } => { 247 | let sme = await!(get_mesh_sme(wlan_svc, iface_id))?; 248 | let mut config = fidl_sme::MeshConfig { mesh_id: mesh_id.as_bytes().to_vec(), channel }; 249 | let r = await!(sme.join(&mut config))?; 250 | match r { 251 | fidl_sme::JoinMeshResultCode::InvalidArguments => { 252 | println!("{:?}: Channel {:?} is invalid", r, config.channel); 253 | } 254 | fidl_sme::JoinMeshResultCode::DfsUnsupported => { 255 | println!( 256 | "{:?}: The specified role does not support DFS channel {:?}", 257 | r, config.channel 258 | ); 259 | } 260 | _ => { 261 | println!("{:?}", r); 262 | } 263 | } 264 | } 265 | opts::MeshCmd::Leave { iface_id } => { 266 | let sme = await!(get_mesh_sme(wlan_svc, iface_id))?; 267 | let r = await!(sme.leave()); 268 | println!("{:?}", r); 269 | } 270 | } 271 | Ok(()) 272 | } 273 | 274 | #[derive(Debug, Clone, Copy, PartialEq)] 275 | struct MacAddr([u8; 6]); 276 | 277 | impl fmt::Display for MacAddr { 278 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 279 | write!( 280 | f, 281 | "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", 282 | self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] 283 | ) 284 | } 285 | } 286 | 287 | impl FromStr for MacAddr { 288 | type Err = Error; 289 | 290 | fn from_str(s: &str) -> Result { 291 | let mut bytes = [0; 6]; 292 | let mut index = 0; 293 | 294 | for octet in s.split(|c| c == ':' || c == '-') { 295 | if index == 6 { 296 | bail!("Too many octets"); 297 | } 298 | bytes[index] = u8::from_str_radix(octet, 16)?; 299 | index += 1; 300 | } 301 | 302 | if index != 6 { 303 | bail!("Too few octets"); 304 | } 305 | Ok(MacAddr(bytes)) 306 | } 307 | } 308 | 309 | async fn handle_scan_transaction(scan_txn: fidl_sme::ScanTransactionProxy) -> Result<(), Error> { 310 | let mut printed_header = false; 311 | let mut events = scan_txn.take_event_stream(); 312 | while let Some(evt) = await!(events.try_next()) 313 | .context("failed to fetch all results before the channel was closed")? 314 | { 315 | match evt { 316 | ScanTransactionEvent::OnResult { aps } => { 317 | if !printed_header { 318 | print_scan_header(); 319 | printed_header = true; 320 | } 321 | for ap in aps { 322 | print_scan_result(ap); 323 | } 324 | } 325 | ScanTransactionEvent::OnFinished {} => break, 326 | ScanTransactionEvent::OnError { error } => { 327 | eprintln!("Error: {}", error.message); 328 | break; 329 | } 330 | } 331 | } 332 | Ok(()) 333 | } 334 | 335 | fn print_scan_header() { 336 | println!("BSSID dBm Chan Protected SSID"); 337 | } 338 | 339 | fn is_ascii(v: &Vec) -> bool { 340 | for val in v { 341 | if val > &0x7e { 342 | return false; 343 | } 344 | } 345 | return true; 346 | } 347 | 348 | fn is_printable_ascii(v: &Vec) -> bool { 349 | for val in v { 350 | if val < &0x20 || val > &0x7e { 351 | return false; 352 | } 353 | } 354 | return true; 355 | } 356 | 357 | fn print_scan_result(ess: fidl_sme::EssInfo) { 358 | let is_ascii = is_ascii(&ess.best_bss.ssid); 359 | let is_ascii_print = is_printable_ascii(&ess.best_bss.ssid); 360 | let is_utf8 = String::from_utf8(ess.best_bss.ssid.clone()).is_ok(); 361 | let is_hex = !is_utf8 || (is_ascii && !is_ascii_print); 362 | 363 | let ssid_str; 364 | if is_hex { 365 | ssid_str = format!("({:X?})", &*ess.best_bss.ssid); 366 | } else { 367 | ssid_str = format!("\"{}\"", String::from_utf8_lossy(&ess.best_bss.ssid)); 368 | } 369 | 370 | println!( 371 | "{} {:4} {:8} {:9} {}", 372 | MacAddr(ess.best_bss.bssid), 373 | ess.best_bss.rx_dbm, 374 | ess.best_bss.channel, 375 | if ess.best_bss.protected { "Y" } else { "N" }, 376 | ssid_str 377 | ); 378 | } 379 | 380 | async fn handle_connect_transaction( 381 | connect_txn: fidl_sme::ConnectTransactionProxy, 382 | ) -> Result<(), Error> { 383 | let mut events = connect_txn.take_event_stream(); 384 | while let Some(evt) = await!(events.try_next()) 385 | .context("failed to receive connect result before the channel was closed")? 386 | { 387 | match evt { 388 | ConnectTransactionEvent::OnFinished { code } => { 389 | match code { 390 | ConnectResultCode::Success => println!("Connected successfully"), 391 | ConnectResultCode::Canceled => { 392 | eprintln!("Connecting was canceled or superseded by another command") 393 | } 394 | ConnectResultCode::Failed => eprintln!("Failed to connect to network"), 395 | ConnectResultCode::BadCredentials => { 396 | eprintln!("Failed to connect to network; bad credentials") 397 | } 398 | } 399 | break; 400 | } 401 | } 402 | } 403 | Ok(()) 404 | } 405 | 406 | async fn get_client_sme( 407 | wlan_svc: WlanSvc, 408 | iface_id: u16, 409 | ) -> Result { 410 | let (proxy, remote) = endpoints::create_proxy()?; 411 | let status = await!(wlan_svc.get_client_sme(iface_id, remote)) 412 | .context("error sending GetClientSme request")?; 413 | if status == zx::sys::ZX_OK { 414 | Ok(proxy) 415 | } else { 416 | Err(format_err!("Invalid interface id {}", iface_id)) 417 | } 418 | } 419 | 420 | async fn get_ap_sme(wlan_svc: WlanSvc, iface_id: u16) -> Result { 421 | let (proxy, remote) = endpoints::create_proxy()?; 422 | let status = 423 | await!(wlan_svc.get_ap_sme(iface_id, remote)).context("error sending GetApSme request")?; 424 | if status == zx::sys::ZX_OK { 425 | Ok(proxy) 426 | } else { 427 | Err(format_err!("Invalid interface id {}", iface_id)) 428 | } 429 | } 430 | 431 | async fn get_mesh_sme(wlan_svc: WlanSvc, iface_id: u16) -> Result { 432 | let (proxy, remote) = endpoints::create_proxy()?; 433 | let status = await!(wlan_svc.get_mesh_sme(iface_id, remote)) 434 | .context("error sending GetMeshSme request")?; 435 | if status == zx::sys::ZX_OK { 436 | Ok(proxy) 437 | } else { 438 | Err(format_err!("Invalid interface id {}", iface_id)) 439 | } 440 | } 441 | 442 | async fn get_iface_ids(wlan_svc: WlanSvc, iface_id: Option) -> Result, Error> { 443 | match iface_id { 444 | Some(id) => Ok(vec![id]), 445 | None => { 446 | let response = await!(wlan_svc.list_ifaces()).context("error listing ifaces")?; 447 | Ok(response.ifaces.into_iter().map(|iface| iface.iface_id).collect()) 448 | } 449 | } 450 | } 451 | 452 | async fn list_minstrel_peers(wlan_svc: WlanSvc, iface_id: u16) -> Result, Error> { 453 | let (status, resp) = await!(wlan_svc.get_minstrel_list(iface_id)) 454 | .context(format!("Error getting minstrel peer list iface {}", iface_id))?; 455 | if status == zx::sys::ZX_OK { 456 | Ok(resp 457 | .peers 458 | .into_iter() 459 | .map(|v| { 460 | let mut arr = [0u8; 6]; 461 | arr.copy_from_slice(v.as_slice()); 462 | MacAddr(arr) 463 | }) 464 | .collect()) 465 | } else { 466 | println!("Error getting minstrel peer list from iface {}: {}", iface_id, status); 467 | Ok(vec![]) 468 | } 469 | } 470 | 471 | async fn show_minstrel_peer_for_iface( 472 | wlan_svc: WlanSvc, 473 | id: u16, 474 | peer_addr: Option, 475 | ) -> Result<(), Error> { 476 | let peer_addrs = await!(get_peer_addrs(wlan_svc.clone(), id, peer_addr))?; 477 | let mut first_peer = true; 478 | for mut peer_addr in peer_addrs { 479 | let (status, resp) = await!(wlan_svc.get_minstrel_stats(id, &mut peer_addr.0)) 480 | .context(format!("Error getting minstrel stats from peer {}", peer_addr))?; 481 | if status != zx::sys::ZX_OK { 482 | println!( 483 | "error getting minstrel stats for {} from iface {}: {}", 484 | peer_addr, id, status 485 | ); 486 | } else if let Some(peer) = resp { 487 | if first_peer { 488 | println!("iface {}", id); 489 | first_peer = false; 490 | } 491 | print_minstrel_stats(peer); 492 | } 493 | } 494 | Ok(()) 495 | } 496 | 497 | async fn get_peer_addrs( 498 | wlan_svc: WlanSvc, 499 | iface_id: u16, 500 | peer_addr: Option, 501 | ) -> Result, Error> { 502 | match peer_addr { 503 | Some(addr) => Ok(vec![addr]), 504 | None => await!(list_minstrel_peers(wlan_svc, iface_id)), 505 | } 506 | } 507 | 508 | fn print_minstrel_stats(mut peer: Box) { 509 | let total_attempts: f64 = peer.entries.iter().map(|e| e.attempts_total as f64).sum(); 510 | let total_success: f64 = peer.entries.iter().map(|e| e.success_total as f64).sum(); 511 | println!( 512 | "{}, max_tp: {}, max_probability: {}, attempts/success: {:.6}, probes: {}", 513 | MacAddr(peer.mac_addr), 514 | peer.max_tp, 515 | peer.max_probability, 516 | total_attempts / total_success, 517 | peer.probes 518 | ); 519 | println!( 520 | " TxVector succ_c att_c succ_t att_t \ 521 | probability throughput probes probe_cycles_skipped" 522 | ); 523 | peer.entries.sort_by(|l, r| l.tx_vector_idx.cmp(&r.tx_vector_idx)); 524 | for e in peer.entries { 525 | println!( 526 | "{}{} {:<36} {:7} {:7} {:7} {:7} {:11.4} {:10.3} {:6} {:20}", 527 | if e.tx_vector_idx == peer.max_tp { "T" } else { " " }, 528 | if e.tx_vector_idx == peer.max_probability { "P" } else { " " }, 529 | e.tx_vec_desc, 530 | e.success_cur, 531 | e.attempts_cur, 532 | e.success_total, 533 | e.attempts_total, 534 | e.probability * 100.0, 535 | e.cur_tp, 536 | e.probes_total, 537 | e.probe_cycles_skipped, 538 | ); 539 | } 540 | } 541 | 542 | #[cfg(test)] 543 | mod tests { 544 | use super::*; 545 | 546 | #[test] 547 | fn format_bssid() { 548 | assert_eq!( 549 | "01:02:03:ab:cd:ef", 550 | format!("{}", MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef])) 551 | ); 552 | } 553 | 554 | #[test] 555 | fn mac_addr_from_str() { 556 | assert_eq!( 557 | MacAddr::from_str("01:02:03:ab:cd:ef").unwrap(), 558 | MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef]) 559 | ); 560 | assert_eq!( 561 | MacAddr::from_str("01:02-03:ab-cd:ef").unwrap(), 562 | MacAddr([0x01, 0x02, 0x03, 0xab, 0xcd, 0xef]) 563 | ); 564 | assert!(MacAddr::from_str("01:02:03:ab:cd").is_err()); 565 | assert!(MacAddr::from_str("01:02:03:04:05:06:07").is_err()); 566 | assert!(MacAddr::from_str("01:02:gg:gg:gg:gg").is_err()); 567 | } 568 | } 569 | --------------------------------------------------------------------------------