| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| #![deny(warnings)] |
| #![recursion_limit = "128"] |
| |
| use { |
| byteorder::{LittleEndian, ReadBytesExt}, |
| fidl_fuchsia_wlan_common as wlan_common, fidl_fuchsia_wlan_device as wlan_device, |
| fidl_fuchsia_wlan_tap as wlantap, fuchsia_async as fasync, |
| fuchsia_zircon::prelude::*, |
| futures::prelude::*, |
| std::io, |
| std::sync::{Arc, Mutex}, |
| wlantap_client::Wlantap, |
| }; |
| |
| mod ap; |
| mod config; |
| mod mac_frames; |
| |
| #[cfg(test)] |
| mod eth_frames; |
| #[cfg(test)] |
| mod test_utils; |
| |
| const HW_MAC_ADDR: [u8; 6] = [0x67, 0x62, 0x6f, 0x6e, 0x69, 0x6b]; |
| const BSSID: [u8; 6] = [0x62, 0x73, 0x73, 0x62, 0x73, 0x73]; |
| |
| // Remedy for FLK-24 (DNO-389) |
| // Refer to |KMinstrelUpdateIntervalForHwSim| in //garnet/drivers/wlan/wlan/device.cpp |
| #[cfg(test)] |
| const MINSTREL_DATA_FRAME_INTERVAL_NANOS: i64 = 4_000_000; |
| |
| fn create_wlantap_config() -> wlantap::WlantapPhyConfig { |
| config::create_wlantap_config(HW_MAC_ADDR, wlan_device::MacRole::Client) |
| } |
| |
| struct State { |
| current_channel: wlan_common::WlanChan, |
| frame_buf: Vec<u8>, |
| is_associated: bool, |
| } |
| |
| impl State { |
| fn new() -> Self { |
| Self { |
| current_channel: wlan_common::WlanChan { |
| primary: 0, |
| cbw: wlan_common::Cbw::Cbw20, |
| secondary80: 0, |
| }, |
| frame_buf: vec![], |
| is_associated: false, |
| } |
| } |
| } |
| |
| fn send_beacon( |
| frame_buf: &mut Vec<u8>, |
| channel: &wlan_common::WlanChan, |
| bss_id: &[u8; 6], |
| ssid: &[u8], |
| proxy: &wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| frame_buf.clear(); |
| mac_frames::MacFrameWriter::<&mut Vec<u8>>::new(frame_buf) |
| .beacon( |
| &mac_frames::MgmtHeader { |
| frame_control: mac_frames::FrameControl(0), // will be filled automatically |
| duration: 0, |
| addr1: mac_frames::BROADCAST_ADDR.clone(), |
| addr2: bss_id.clone(), |
| addr3: bss_id.clone(), |
| seq_control: mac_frames::SeqControl { frag_num: 0, seq_num: 123 }, |
| ht_control: None, |
| }, |
| &mac_frames::BeaconFields { |
| timestamp: 0, |
| beacon_interval: 100, |
| capability_info: mac_frames::CapabilityInfo(0), |
| }, |
| )? |
| .ssid(ssid)? |
| .supported_rates(&[0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24])? |
| .dsss_parameter_set(channel.primary)?; |
| |
| let rx_info = &mut create_rx_info(channel); |
| proxy.rx(0, &mut frame_buf.iter().cloned(), rx_info)?; |
| Ok(()) |
| } |
| |
| fn send_authentication( |
| frame_buf: &mut Vec<u8>, |
| channel: &wlan_common::WlanChan, |
| bss_id: &[u8; 6], |
| proxy: &wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| frame_buf.clear(); |
| mac_frames::MacFrameWriter::<&mut Vec<u8>>::new(frame_buf).authentication( |
| &mac_frames::MgmtHeader { |
| frame_control: mac_frames::FrameControl(0), // will be filled automatically |
| duration: 0, |
| addr1: HW_MAC_ADDR, |
| addr2: bss_id.clone(), |
| addr3: bss_id.clone(), |
| seq_control: mac_frames::SeqControl { frag_num: 0, seq_num: 123 }, |
| ht_control: None, |
| }, |
| &mac_frames::AuthenticationFields { |
| auth_algorithm_number: mac_frames::AuthAlgorithm::OpenSystem as u16, |
| auth_txn_seq_number: 2, // Always 2 for successful authentication |
| status_code: mac_frames::StatusCode::Success as u16, |
| }, |
| )?; |
| |
| let rx_info = &mut create_rx_info(channel); |
| proxy.rx(0, &mut frame_buf.iter().cloned(), rx_info)?; |
| Ok(()) |
| } |
| |
| fn send_association_response( |
| frame_buf: &mut Vec<u8>, |
| channel: &wlan_common::WlanChan, |
| bss_id: &[u8; 6], |
| proxy: &wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| frame_buf.clear(); |
| let mut cap_info = mac_frames::CapabilityInfo(0); |
| cap_info.set_ess(true); |
| cap_info.set_short_preamble(true); |
| mac_frames::MacFrameWriter::<&mut Vec<u8>>::new(frame_buf) |
| .association_response( |
| &mac_frames::MgmtHeader { |
| frame_control: mac_frames::FrameControl(0), // will be filled automatically |
| duration: 0, |
| addr1: HW_MAC_ADDR, |
| addr2: bss_id.clone(), |
| addr3: bss_id.clone(), |
| seq_control: mac_frames::SeqControl { frag_num: 0, seq_num: 123 }, |
| ht_control: None, |
| }, |
| &mac_frames::AssociationResponseFields { |
| capability_info: cap_info, |
| status_code: 0, // Success |
| association_id: 2, // Can be any |
| }, |
| )? |
| // These elements will be captured in assoc_ctx to initialize Minstrel |
| // tx_vec_idx: 129 130 131 132 |
| .supported_rates(&[0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24])? |
| // tx_vec_idx: 133 134 (basic)135 136 |
| .extended_supported_rates(&[48, 72, 128 + 96, 108])?; |
| |
| let rx_info = &mut create_rx_info(channel); |
| proxy.rx(0, &mut frame_buf.iter().cloned(), rx_info)?; |
| Ok(()) |
| } |
| |
| fn create_rx_info(channel: &wlan_common::WlanChan) -> wlantap::WlanRxInfo { |
| wlantap::WlanRxInfo { |
| rx_flags: 0, |
| valid_fields: 0, |
| phy: 0, |
| data_rate: 0, |
| chan: wlan_common::WlanChan { |
| // TODO(FIDL-54): use clone() |
| primary: channel.primary, |
| cbw: channel.cbw, |
| secondary80: channel.secondary80, |
| }, |
| mcs: 0, |
| rssi_dbm: 0, |
| rcpi_dbmh: 0, |
| snr_dbh: 0, |
| } |
| } |
| |
| fn get_frame_ctrl(packet_data: &Vec<u8>) -> mac_frames::FrameControl { |
| let mut reader = io::Cursor::new(packet_data); |
| let frame_ctrl = reader.read_u16::<LittleEndian>().unwrap(); |
| mac_frames::FrameControl(frame_ctrl) |
| } |
| |
| fn handle_tx(args: wlantap::TxArgs, state: &mut State, proxy: &wlantap::WlantapPhyProxy) { |
| let frame_ctrl = get_frame_ctrl(&args.packet.data); |
| if frame_ctrl.typ() == mac_frames::FrameControlType::Mgmt as u16 { |
| handle_mgmt_tx(frame_ctrl.subtype(), state, proxy); |
| } |
| } |
| |
| fn handle_mgmt_tx(subtype: u16, state: &mut State, proxy: &wlantap::WlantapPhyProxy) { |
| if subtype == mac_frames::MgmtSubtype::Authentication as u16 { |
| println!("Authentication received."); |
| send_authentication(&mut state.frame_buf, &state.current_channel, &BSSID, proxy) |
| .expect("Error sending fake authentication frame."); |
| println!("Authentication sent."); |
| } else if subtype == mac_frames::MgmtSubtype::AssociationRequest as u16 { |
| println!("Association Request received."); |
| send_association_response(&mut state.frame_buf, &state.current_channel, &BSSID, proxy) |
| .expect("Error sending fake association response frame."); |
| println!("Association Response sent."); |
| state.is_associated = true; |
| } |
| } |
| |
| fn main() -> Result<(), failure::Error> { |
| let mut exec = fasync::Executor::new().expect("error creating executor"); |
| let wlantap = Wlantap::open().expect("error with Wlantap::open()"); |
| let state = Arc::new(Mutex::new(State::new())); |
| let proxy = wlantap.create_phy(create_wlantap_config()).expect("error creating wlantap config"); |
| |
| let event_listener = event_listener(state.clone(), proxy.clone()); |
| let beacon_timer = beacon_sender(state.clone(), proxy.clone()); |
| println!("Hardware simlulator started. Try to scan or connect to \"fakenet\""); |
| |
| exec.run_singlethreaded(event_listener.join(beacon_timer)); |
| Ok(()) |
| } |
| |
| async fn event_listener(state: Arc<Mutex<State>>, proxy: wlantap::WlantapPhyProxy) { |
| let mut events = proxy.take_event_stream(); |
| while let Some(event) = await!(events.try_next()).unwrap() { |
| match event { |
| wlantap::WlantapPhyEvent::SetChannel { args } => { |
| let mut state = state.lock().unwrap(); |
| state.current_channel = args.chan; |
| println!("setting channel to {:?}", state.current_channel); |
| } |
| wlantap::WlantapPhyEvent::Tx { args } => { |
| let mut state = state.lock().unwrap(); |
| handle_tx(args, &mut state, &proxy); |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| async fn beacon_sender(state: Arc<Mutex<State>>, proxy: wlantap::WlantapPhyProxy) { |
| let mut beacon_timer_stream = fasync::Interval::new(102_400_000.nanos()); |
| while let Some(_) = await!(beacon_timer_stream.next()) { |
| let state = &mut *state.lock().unwrap(); |
| if state.current_channel.primary == 6 { |
| if !state.is_associated { |
| eprintln!("sending beacon!"); |
| } |
| send_beacon( |
| &mut state.frame_buf, |
| &state.current_channel, |
| &BSSID, |
| "fakenet".as_bytes(), |
| &proxy, |
| ) |
| .unwrap(); |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod simulation_tests { |
| use super::*; |
| use crate::ap; |
| use { |
| failure::{ensure, format_err}, |
| fidl_fuchsia_wlan_service as fidl_wlan_service, fuchsia_app as app, fuchsia_zircon as zx, |
| futures::{channel::mpsc, poll, select}, |
| pin_utils::pin_mut, |
| std::{ |
| collections::HashMap, |
| fs::{self, File}, |
| panic, |
| }, |
| }; |
| |
| const BSS_FOO: [u8; 6] = [0x62, 0x73, 0x73, 0x66, 0x6f, 0x6f]; |
| const SSID_FOO: &[u8] = b"foo"; |
| const BSS_BAR: [u8; 6] = [0x62, 0x73, 0x73, 0x62, 0x61, 0x72]; |
| const SSID_BAR: &[u8] = b"bar"; |
| const BSS_BAZ: [u8; 6] = [0x62, 0x73, 0x73, 0x62, 0x61, 0x7a]; |
| const SSID_BAZ: &[u8] = b"baz"; |
| |
| const CHANNEL: wlan_common::WlanChan = |
| wlan_common::WlanChan { primary: 6, secondary80: 0, cbw: wlan_common::Cbw::Cbw20 }; |
| |
| // Temporary workaround to run tests synchronously. This is because wlan service only works with |
| // one PHY, so having tests with multiple PHYs running in parallel make them flaky. |
| #[test] |
| fn test_client_and_ap() { |
| let mut ok = true; |
| // client tests |
| ok = run_test("verify_ethernet", test_verify_ethernet) && ok; |
| ok = run_test("simulate_scan", test_simulate_scan) && ok; |
| ok = run_test("connecting_to_ap", test_connecting_to_ap) && ok; |
| ok = run_test("ethernet_tx_rx", test_ethernet_tx_rx) && ok; |
| ok = run_test("verify_rate_selection", test_verify_rate_selection) && ok; |
| |
| // ap tests |
| ok = run_test("open_ap_connect", ap::tests::test_open_ap_connect) && ok; |
| assert!(ok); |
| } |
| |
| fn test_verify_ethernet() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| // Make sure there is no existing ethernet device. |
| let client = exec |
| .run_singlethreaded(create_eth_client(&HW_MAC_ADDR)) |
| .expect(&format!("creating ethernet client: {:?}", &HW_MAC_ADDR)); |
| assert!(client.is_none()); |
| // Create wlan_tap device which will in turn create ethernet device. |
| let mut helper = test_utils::TestHelper::begin_test(&mut exec, create_wlantap_config()); |
| let mut retry = test_utils::RetryWithBackoff::new(5.seconds()); |
| loop { |
| let client = exec |
| .run_singlethreaded(create_eth_client(&HW_MAC_ADDR)) |
| .expect(&format!("creating ethernet client: {:?}", &HW_MAC_ADDR)); |
| if client.is_some() { |
| break; |
| } |
| let slept = retry.sleep_unless_timed_out(); |
| assert!(slept, "No ethernet client with mac_addr {:?} found in time", &HW_MAC_ADDR); |
| } |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("connecting to wlan service"); |
| loop_until_iface_is_found(&mut exec, &wlan_service, &mut helper); |
| } |
| |
| fn clear_ssid_and_ensure_iface_gone() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("Failed to connect to wlan service"); |
| exec.run_singlethreaded(wlan_service.clear_saved_networks()).expect("Clearing SSID"); |
| |
| let mut retry = test_utils::RetryWithBackoff::new(5.seconds()); |
| loop { |
| let status = exec |
| .run_singlethreaded(wlan_service.status()) |
| .expect("error getting status() from wlan_service"); |
| if status.error.code == fidl_wlan_service::ErrCode::NotFound { |
| return; |
| } |
| let slept = retry.sleep_unless_timed_out(); |
| assert!(slept, "The interface was not removed in time"); |
| } |
| } |
| |
| fn test_simulate_scan() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| let mut helper = test_utils::TestHelper::begin_test(&mut exec, create_wlantap_config()); |
| |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("Failed to connect to wlan service"); |
| |
| let proxy = helper.proxy(); |
| let scan_result = scan(&mut exec, &wlan_service, &proxy, &mut helper); |
| |
| assert_eq!( |
| fidl_wlan_service::ErrCode::Ok, |
| scan_result.error.code, |
| "The error message was: {}", |
| scan_result.error.description |
| ); |
| let mut aps: Vec<_> = scan_result |
| .aps |
| .expect("Got empty scan results") |
| .into_iter() |
| .map(|ap| (ap.ssid, ap.bssid)) |
| .collect(); |
| aps.sort(); |
| let mut expected_aps = [ |
| (String::from_utf8_lossy(SSID_FOO).to_string(), BSS_FOO.to_vec()), |
| (String::from_utf8_lossy(SSID_BAR).to_string(), BSS_BAR.to_vec()), |
| (String::from_utf8_lossy(SSID_BAZ).to_string(), BSS_BAZ.to_vec()), |
| ]; |
| expected_aps.sort(); |
| assert_eq!(&expected_aps, &aps[..]); |
| } |
| |
| fn test_connecting_to_ap() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| let mut helper = test_utils::TestHelper::begin_test(&mut exec, create_wlantap_config()); |
| |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("Failed to connect to wlan service"); |
| let proxy = helper.proxy(); |
| loop_until_iface_is_found(&mut exec, &wlan_service, &mut helper); |
| |
| connect(&mut exec, &wlan_service, &proxy, &mut helper, SSID_FOO, &BSS_FOO); |
| |
| let status = status(&mut exec, &wlan_service, &mut helper); |
| assert_eq!(status.error.code, fidl_wlan_service::ErrCode::Ok); |
| assert_eq!(status.state, fidl_wlan_service::State::Associated); |
| let ap = status.current_ap.expect("expect to be associated to an AP"); |
| assert_eq!(ap.bssid, BSS_FOO.to_vec()); |
| assert_eq!(ap.ssid, String::from_utf8_lossy(SSID_FOO).to_string()); |
| assert_eq!(ap.chan, CHANNEL); |
| assert!(ap.is_compatible); |
| assert!(!ap.is_secure); |
| } |
| |
| fn loop_until_iface_is_found( |
| exec: &mut fasync::Executor, |
| wlan_service: &fidl_wlan_service::WlanProxy, |
| helper: &mut test_utils::TestHelper, |
| ) { |
| let mut retry = test_utils::RetryWithBackoff::new(5.seconds()); |
| loop { |
| let status = status(exec, wlan_service, helper); |
| if status.error.code != fidl_wlan_service::ErrCode::Ok { |
| let slept = retry.sleep_unless_timed_out(); |
| assert!(slept, "Wlanstack did not recognize the interface in time"); |
| } else { |
| return; |
| } |
| } |
| } |
| |
| fn status( |
| exec: &mut fasync::Executor, |
| wlan_service: &fidl_wlan_service::WlanProxy, |
| helper: &mut test_utils::TestHelper, |
| ) -> fidl_wlan_service::WlanStatus { |
| helper |
| .run(exec, 1.seconds(), "status request", |_| {}, wlan_service.status()) |
| .expect("expect wlan status") |
| } |
| |
| fn scan( |
| exec: &mut fasync::Executor, |
| wlan_service: &fidl_wlan_service::WlanProxy, |
| phy: &wlantap::WlantapPhyProxy, |
| helper: &mut test_utils::TestHelper, |
| ) -> fidl_wlan_service::ScanResult { |
| let mut wlanstack_retry = test_utils::RetryWithBackoff::new(5.seconds()); |
| loop { |
| let scan_result = helper |
| .run( |
| exec, |
| 10.seconds(), |
| "receive a scan response", |
| |event| match event { |
| wlantap::WlantapPhyEvent::SetChannel { args } => { |
| println!("set channel to {:?}", args.chan); |
| if args.chan.primary == 1 { |
| send_beacon(&mut vec![], &args.chan, &BSS_FOO, SSID_FOO, &phy) |
| .unwrap(); |
| } else if args.chan.primary == 6 { |
| send_beacon(&mut vec![], &args.chan, &BSS_BAR, SSID_BAR, &phy) |
| .unwrap(); |
| } else if args.chan.primary == 11 { |
| send_beacon(&mut vec![], &args.chan, &BSS_BAZ, SSID_BAZ, &phy) |
| .unwrap(); |
| } |
| } |
| _ => {} |
| }, |
| wlan_service.scan(&mut fidl_wlan_service::ScanRequest { timeout: 5 }), |
| ) |
| .unwrap(); |
| if scan_result.error.code == fidl_wlan_service::ErrCode::NotFound { |
| let slept = wlanstack_retry.sleep_unless_timed_out(); |
| assert!(slept, "Wlanstack did not recognize the interface in time"); |
| } else { |
| return scan_result; |
| } |
| } |
| } |
| |
| fn create_connect_config(ssid: &[u8], bssid: &[u8; 6]) -> fidl_wlan_service::ConnectConfig { |
| fidl_wlan_service::ConnectConfig { |
| ssid: String::from_utf8_lossy(ssid).to_string(), |
| pass_phrase: "".to_string(), |
| scan_interval: 5, |
| bssid: String::from_utf8_lossy(bssid).to_string(), |
| } |
| } |
| |
| fn connect( |
| exec: &mut fasync::Executor, |
| wlan_service: &fidl_wlan_service::WlanProxy, |
| phy: &wlantap::WlantapPhyProxy, |
| helper: &mut test_utils::TestHelper, |
| ssid: &[u8], |
| bssid: &[u8; 6], |
| ) { |
| let mut connect_config = create_connect_config(ssid, bssid); |
| let connect_fut = wlan_service.connect(&mut connect_config); |
| let error = helper |
| .run( |
| exec, |
| 10.seconds(), |
| &format!("connect to {}({:2x?})", String::from_utf8_lossy(ssid), bssid), |
| |event| { |
| handle_connect_events(event, &phy, ssid, bssid); |
| }, |
| connect_fut, |
| ) |
| .unwrap(); |
| assert_eq!(error.code, fidl_wlan_service::ErrCode::Ok, "connect failed: {:?}", error); |
| } |
| |
| fn handle_connect_events( |
| event: wlantap::WlantapPhyEvent, |
| phy: &wlantap::WlantapPhyProxy, |
| ssid: &[u8], |
| bssid: &[u8; 6], |
| ) { |
| match event { |
| wlantap::WlantapPhyEvent::SetChannel { args } => { |
| println!("channel: {:?}", args.chan); |
| if args.chan.primary == CHANNEL.primary { |
| send_beacon(&mut vec![], &args.chan, bssid, ssid, &phy).unwrap(); |
| } |
| } |
| wlantap::WlantapPhyEvent::Tx { args } => { |
| let frame_ctrl = get_frame_ctrl(&args.packet.data); |
| if frame_ctrl.typ() == mac_frames::FrameControlType::Mgmt as u16 { |
| let subtyp = frame_ctrl.subtype(); |
| if subtyp == mac_frames::MgmtSubtype::Authentication as u16 { |
| send_authentication(&mut vec![], &CHANNEL, bssid, &phy) |
| .expect("Error sending fake authentication frame."); |
| } else if subtyp == mac_frames::MgmtSubtype::AssociationRequest as u16 { |
| send_association_response(&mut vec![], &CHANNEL, bssid, &phy) |
| .expect("Error sending fake association response frame."); |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| const BSS_MINSTL: [u8; 6] = [0x6d, 0x69, 0x6e, 0x73, 0x74, 0x0a]; |
| const SSID_MINSTREL: &[u8] = b"minstrel"; |
| |
| fn test_verify_rate_selection() { |
| let mut exec = fasync::Executor::new().expect("error creating executor"); |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("Error connecting to wlan service"); |
| let mut helper = test_utils::TestHelper::begin_test( |
| &mut exec, |
| wlantap::WlantapPhyConfig { quiet: true, ..create_wlantap_config() }, |
| ); |
| |
| loop_until_iface_is_found(&mut exec, &wlan_service, &mut helper); |
| |
| let phy = helper.proxy(); |
| connect(&mut exec, &wlan_service, &phy, &mut helper, SSID_MINSTREL, &BSS_MINSTL); |
| |
| let beacon_sender_fut = beacon_sender(&BSS_MINSTL, SSID_MINSTREL, &phy).fuse(); |
| pin_mut!(beacon_sender_fut); |
| |
| let (sender, mut receiver) = mpsc::channel(1); |
| let eth_sender_fut = eth_sender(&mut receiver).fuse(); |
| pin_mut!(eth_sender_fut); |
| |
| let test_fut = async { |
| select! { |
| _ = eth_sender_fut => Ok(()), |
| _ = beacon_sender_fut => Err(format_err!("beacon sender should not have exited")), |
| } |
| }; |
| pin_mut!(test_fut); |
| |
| // Simulated hardware supports 8 ERP tx vectors with idx 129 to 136, both inclusive. |
| // (see `fn send_association_response(...)`) |
| const MUST_USE_IDX: &[u16] = &[129, 130, 131, 132, 133, 134, 136]; // Missing 135 is OK. |
| const ALL_SUPPORTED_IDX: &[u16] = &[129, 130, 131, 132, 133, 134, 135, 136]; |
| const ERP_STARTING_IDX: u16 = 129; |
| const MAX_SUCCESSFUL_IDX: u16 = 130; |
| // Only the lowest ones can succeed in the simulated environment. |
| let will_succeed = |idx| ERP_STARTING_IDX <= idx && idx <= MAX_SUCCESSFUL_IDX; |
| let is_done = |hm: &HashMap<u16, u64>| { |
| // Due to its randomness, Minstrel may skip 135. But the other 7 must be present. |
| if hm.keys().len() < MUST_USE_IDX.len() { |
| return false; |
| } |
| // safe to unwrap below because there are at least 7 entries |
| let max_key = hm.keys().max_by_key(|k| hm[&k]).unwrap(); |
| let second_largest = |
| hm.iter().map(|(k, v)| if k == max_key { 0 } else { *v }).max().unwrap(); |
| let max_val = hm[&max_key]; |
| if max_val % 100 == 0 { |
| println!("{:?}", hm); |
| } |
| // One tx vector has become dominant as the number of data frames transmitted with it |
| // is at least 15 times as large as anyone else. 15 is the number of non-probing data |
| // frames between 2 consecutive probing data frames. |
| const NORMAL_FRAMES_PER_PROBE: u64 = 15; |
| max_val >= NORMAL_FRAMES_PER_PROBE * second_largest |
| }; |
| let mut hm = HashMap::new(); |
| helper |
| .run( |
| &mut exec, |
| 30.seconds(), |
| "verify rate selection is working", |
| |event| { |
| handle_rate_selection_event( |
| event, |
| &phy, |
| &BSS_MINSTL, |
| &mut hm, |
| will_succeed, |
| is_done, |
| sender.clone(), |
| ); |
| }, |
| test_fut, |
| ) |
| .expect("running main future"); |
| |
| let total = hm.values().sum::<u64>(); |
| let others = total - hm[&MAX_SUCCESSFUL_IDX]; |
| println!("{:#?}\ntotal: {}, others: {}", hm, total, others); |
| let mut tx_vec_idx_seen: Vec<_> = hm.keys().cloned().collect(); |
| tx_vec_idx_seen.sort(); |
| if tx_vec_idx_seen.len() == MUST_USE_IDX.len() { |
| // 135 may not be attempted due to randomness and it is OK. |
| assert_eq!(&tx_vec_idx_seen[..], MUST_USE_IDX); |
| } else { |
| assert_eq!(&tx_vec_idx_seen[..], ALL_SUPPORTED_IDX); |
| } |
| let most_frequently_used_idx = hm.keys().max_by_key(|k| hm[&k]).unwrap(); |
| println!( |
| "If the test fails due to QEMU slowness outside of the scope of WLAN(FLK-24, \ |
| DNO-389). Try increasing |MINSTREL_DATA_FRAME_INTERVAL_NANOS| above." |
| ); |
| assert_eq!(most_frequently_used_idx, &MAX_SUCCESSFUL_IDX); |
| } |
| |
| fn handle_rate_selection_event<F, G>( |
| event: wlantap::WlantapPhyEvent, |
| phy: &wlantap::WlantapPhyProxy, |
| bssid: &[u8; 6], |
| hm: &mut HashMap<u16, u64>, |
| should_succeed: F, |
| is_done: G, |
| mut sender: mpsc::Sender<()>, |
| ) where |
| F: Fn(u16) -> bool, |
| G: Fn(&HashMap<u16, u64>) -> bool, |
| { |
| match event { |
| wlantap::WlantapPhyEvent::Tx { args } => { |
| let frame_ctrl = get_frame_ctrl(&args.packet.data); |
| if frame_ctrl.typ() == mac_frames::FrameControlType::Data as u16 { |
| let tx_vec_idx = args.packet.info.tx_vector_idx; |
| send_tx_status_report(*bssid, tx_vec_idx, should_succeed(tx_vec_idx), phy) |
| .expect("Error sending tx_status report"); |
| let count = hm.entry(tx_vec_idx).or_insert(0); |
| *count += 1; |
| if *count == 1 { |
| println!("new tx_vec_idx: {} at #{}", tx_vec_idx, hm.values().sum::<u64>()); |
| } |
| if is_done(hm) { |
| sender.try_send(()).expect("Indicating test successful"); |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| |
| async fn eth_sender(receiver: &mut mpsc::Receiver<()>) -> Result<(), failure::Error> { |
| let mut client = await!(create_eth_client(&HW_MAC_ADDR)) |
| .expect("cannot create ethernet client") |
| .expect(&format!("ethernet client not found {:?}", &HW_MAC_ADDR)); |
| |
| let mut buf: Vec<u8> = vec![]; |
| eth_frames::write_eth_header( |
| &mut buf, |
| ð_frames::EthHeader { |
| dst: BSSID, |
| src: HW_MAC_ADDR, |
| eth_type: eth_frames::EtherType::Ipv4 as u16, |
| }, |
| ) |
| .expect("Error creating fake ethernet frame"); |
| |
| let mut timer_stream = fasync::Interval::new(MINSTREL_DATA_FRAME_INTERVAL_NANOS.nanos()); |
| let mut client_stream = client.get_stream().fuse(); |
| 'eth_sender: loop { |
| select! { |
| event = client_stream.next() => { |
| if let Some(Ok(ethernet::Event::StatusChanged)) = event { |
| println!("status changed to: {:?}", await!(client.get_status()).unwrap()); |
| } |
| }, |
| () = timer_stream.select_next_some() => { client.send(&buf); }, |
| } |
| if let Ok(Some(())) = receiver.try_next() { |
| break 'eth_sender; |
| } |
| } |
| // Send any packets that are still in the buffer |
| poll!(client_stream.next()); |
| Ok(()) |
| } |
| |
| fn create_wlan_tx_status_entry(tx_vec_idx: u16) -> wlantap::WlanTxStatusEntry { |
| fidl_fuchsia_wlan_tap::WlanTxStatusEntry { tx_vec_idx: tx_vec_idx, attempts: 1 } |
| } |
| |
| fn send_tx_status_report( |
| bssid: [u8; 6], |
| tx_vec_idx: u16, |
| is_successful: bool, |
| proxy: &wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| use fidl_fuchsia_wlan_tap::WlanTxStatus; |
| |
| let mut ts = WlanTxStatus { |
| peer_addr: bssid, |
| success: is_successful, |
| tx_status_entries: [ |
| create_wlan_tx_status_entry(tx_vec_idx), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| create_wlan_tx_status_entry(0), |
| ], |
| }; |
| proxy.report_tx_status(0, &mut ts)?; |
| Ok(()) |
| } |
| |
| async fn create_eth_client(mac: &[u8; 6]) -> Result<Option<ethernet::Client>, failure::Error> { |
| const ETH_PATH: &str = "/dev/class/ethernet"; |
| let files = fs::read_dir(ETH_PATH)?; |
| for file in files { |
| let vmo = zx::Vmo::create_with_opts( |
| zx::VmoOptions::NON_RESIZABLE, |
| 256 * ethernet::DEFAULT_BUFFER_SIZE as u64, |
| )?; |
| |
| let path = file?.path(); |
| let dev = File::open(path)?; |
| if let Ok(client) = await!(ethernet::Client::from_file( |
| dev, |
| vmo, |
| ethernet::DEFAULT_BUFFER_SIZE, |
| "wlan-hw-sim" |
| )) { |
| if let Ok(info) = await!(client.info()) { |
| if &info.mac.octets == mac { |
| println!("ethernet client created: {:?}", client); |
| await!(client.start()).expect("error starting ethernet device"); |
| // must call get_status() after start() to clear |
| // zx::Signals::USER_0 otherwise there will be a stream |
| // of infinite StatusChanged events that blocks |
| // fasync::Interval |
| println!( |
| "info: {:?} status: {:?}", |
| await!(client.info()).expect("calling client.info()"), |
| await!(client.get_status()).expect("getting client status()") |
| ); |
| return Ok(Some(client)); |
| } |
| } |
| } |
| } |
| Ok(None) |
| } |
| |
| async fn beacon_sender<'a>( |
| bssid: &'a [u8; 6], |
| ssid: &'a [u8], |
| phy: &'a wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| let mut beacon_timer_stream = fasync::Interval::new(102_400_000.nanos()); |
| while let Some(_) = await!(beacon_timer_stream.next()) { |
| send_beacon(&mut vec![], &CHANNEL, bssid, ssid, &phy).unwrap(); |
| } |
| Ok(()) |
| } |
| |
| const BSS_ETHNET: [u8; 6] = [0x65, 0x74, 0x68, 0x6e, 0x65, 0x74]; |
| const SSID_ETHERNET: &[u8] = b"ethernet"; |
| const PAYLOAD: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; |
| |
| fn test_ethernet_tx_rx() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| let mut helper = test_utils::TestHelper::begin_test(&mut exec, create_wlantap_config()); |
| |
| let wlan_service = app::client::connect_to_service::<fidl_wlan_service::WlanMarker>() |
| .expect("Failed to connect to wlan service"); |
| loop_until_iface_is_found(&mut exec, &wlan_service, &mut helper); |
| |
| let proxy = helper.proxy(); |
| connect(&mut exec, &wlan_service, &proxy, &mut helper, SSID_ETHERNET, &BSS_ETHNET); |
| |
| let mut client = exec |
| .run_singlethreaded(create_eth_client(&HW_MAC_ADDR)) |
| .expect("cannot create ethernet client") |
| .expect(&format!("ethernet client not found {:?}", &HW_MAC_ADDR)); |
| |
| verify_tx_and_rx(&mut client, &mut exec, &mut helper); |
| } |
| |
| fn verify_tx_and_rx( |
| client: &mut ethernet::Client, |
| exec: &mut fasync::Executor, |
| helper: &mut test_utils::TestHelper, |
| ) { |
| let mut buf: Vec<u8> = Vec::new(); |
| eth_frames::write_eth_header( |
| &mut buf, |
| ð_frames::EthHeader { |
| dst: BSSID, |
| src: HW_MAC_ADDR, |
| eth_type: eth_frames::EtherType::Ipv4 as u16, |
| }, |
| ) |
| .expect("Error creating fake ethernet frame"); |
| buf.extend_from_slice(PAYLOAD); |
| |
| let eth_tx_rx_fut = send_and_receive(client, &buf); |
| pin_mut!(eth_tx_rx_fut); |
| |
| let phy = helper.proxy(); |
| let mut actual = Vec::new(); |
| let (header, payload) = helper |
| .run( |
| exec, |
| 5.seconds(), |
| "verify ethernet_tx_rx", |
| |event| { |
| handle_eth_tx(event, &mut actual, &phy); |
| }, |
| eth_tx_rx_fut, |
| ) |
| .expect("send and receive eth"); |
| assert_eq!(&actual[..], PAYLOAD); |
| assert_eq!(header.dst, HW_MAC_ADDR); |
| assert_eq!(header.src, BSSID); |
| assert_eq!(&payload[..], PAYLOAD); |
| } |
| |
| async fn send_and_receive<'a>( |
| client: &'a mut ethernet::Client, |
| buf: &'a Vec<u8>, |
| ) -> Result<(eth_frames::EthHeader, Vec<u8>), failure::Error> { |
| use fidl_fuchsia_hardware_ethernet_ext::EthernetQueueFlags; |
| let mut client_stream = client.get_stream(); |
| client.send(&buf); |
| loop { |
| let event = await!(client_stream.next()).expect("receiving ethernet event")?; |
| match event { |
| ethernet::Event::StatusChanged => { |
| await!(client.get_status()).expect("getting status"); |
| } |
| ethernet::Event::Receive(buffer, flags) => { |
| ensure!(flags.intersects(EthernetQueueFlags::RX_OK), "RX_OK not set"); |
| let mut eth_frame = vec![0u8; buffer.len()]; |
| buffer.read(&mut eth_frame); |
| let mut cursor = io::Cursor::new(ð_frame); |
| let header = eth_frames::EthHeader::from_reader(&mut cursor)?; |
| let payload = eth_frame.split_off(cursor.position() as usize); |
| return Ok((header, payload)); |
| } |
| } |
| } |
| } |
| |
| fn handle_eth_tx( |
| event: wlantap::WlantapPhyEvent, |
| actual: &mut Vec<u8>, |
| phy: &wlantap::WlantapPhyProxy, |
| ) { |
| if let wlantap::WlantapPhyEvent::Tx { args } = event { |
| let frame_ctrl = get_frame_ctrl(&args.packet.data); |
| if frame_ctrl.typ() == mac_frames::FrameControlType::Data as u16 { |
| let mut cursor = io::Cursor::new(args.packet.data); |
| let data_header = mac_frames::DataHeader::from_reader(&mut cursor) |
| .expect("Getting data frame header"); |
| // Ignore DHCP packets sent by netstack. |
| if data_header.addr1 == BSS_ETHNET |
| && data_header.addr2 == HW_MAC_ADDR |
| && data_header.addr3 == BSSID |
| { |
| mac_frames::LlcHeader::from_reader(&mut cursor).expect("skipping llc header"); |
| io::Read::read_to_end(&mut cursor, actual).expect("reading payload"); |
| rx_wlan_data_frame(&HW_MAC_ADDR, &BSS_ETHNET, &BSSID, &PAYLOAD, phy) |
| .expect("sending wlan data frame"); |
| } |
| } |
| } |
| } |
| |
| fn rx_wlan_data_frame( |
| addr1: &[u8; 6], |
| addr2: &[u8; 6], |
| addr3: &[u8; 6], |
| payload: &[u8], |
| phy: &wlantap::WlantapPhyProxy, |
| ) -> Result<(), failure::Error> { |
| let mut buf: Vec<u8> = vec![]; |
| mac_frames::MacFrameWriter::<&mut Vec<u8>>::new(&mut buf).data( |
| &mac_frames::DataHeader { |
| frame_control: mac_frames::FrameControl(0), // will be filled automatically |
| duration: 0, |
| addr1: addr1.clone(), |
| addr2: addr2.clone(), |
| addr3: addr3.clone(), |
| seq_control: mac_frames::SeqControl { frag_num: 0, seq_num: 3 }, |
| addr4: None, |
| qos_control: None, |
| ht_control: None, |
| }, |
| &mac_frames::LlcHeader { |
| dsap: 170, |
| ssap: 170, |
| control: 3, |
| oui: [0; 3], |
| protocol_id: eth_frames::EtherType::Ipv4 as u16, |
| }, |
| payload, |
| )?; |
| |
| let rx_info = &mut create_rx_info(&CHANNEL); |
| phy.rx(0, &mut buf.iter().cloned(), rx_info)?; |
| Ok(()) |
| } |
| |
| fn run_test<F>(name: &str, f: F) -> bool |
| where |
| F: FnOnce() + panic::UnwindSafe, |
| { |
| println!("\nTest `{}` started\n", name); |
| let result = panic::catch_unwind(f); |
| clear_ssid_and_ensure_iface_gone(); |
| match result { |
| Ok(_) => { |
| println!("\nTest `{}` passed\n", name); |
| true |
| } |
| Err(_) => { |
| println!("\nTest `{}` failed\n", name); |
| false |
| } |
| } |
| } |
| } |