blob: c1237ed8b7912a8448af0b9e81a155b2169d399e [file] [log] [blame]
// Copyright 2017 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.
#include <wlan/mlme/client/scanner.h>
#include <wlan/common/logging.h>
#include <wlan/mlme/device_interface.h>
#include <wlan/mlme/mac_frame.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/sequence.h>
#include <wlan/mlme/service.h>
#include <wlan/mlme/timer.h>
#include <wlan/mlme/wlan.h>
#include <fuchsia/wlan/mlme/c/fidl.h>
#include "lib/fidl/cpp/vector.h"
#include <fbl/unique_ptr.h>
#include <lib/fxl/arraysize.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <cinttypes>
#include <utility>
namespace wlan {
namespace wlan_mlme = ::fuchsia::wlan::mlme;
static constexpr size_t kMaxBss = 1000;
static void SendScanEnd(DeviceInterface* device, uint64_t txn_id, wlan_mlme::ScanResultCodes code) {
wlan_mlme::ScanEnd msg;
msg.txn_id = txn_id;
msg.code = code;
zx_status_t s = SendServiceMsg(device, &msg, fuchsia_wlan_mlme_MLMEOnScanEndOrdinal);
if (s != ZX_OK) { errorf("failed to send OnScanEnd event: %d\n", s); }
}
static zx_status_t SendResults(DeviceInterface* device, uint64_t txn_id,
const std::unordered_map<uint64_t, Bss>& bss_map) {
for (auto& p : bss_map) {
wlan_mlme::ScanResult r;
r.txn_id = txn_id;
if (p.second.bss_desc().Clone(&r.bss) != ZX_OK) { continue; }
zx_status_t status = SendServiceMsg(device, &r, fuchsia_wlan_mlme_MLMEOnScanResultOrdinal);
if (status != ZX_OK) { return status; }
}
return ZX_OK;
}
// TODO(NET-500): The way we handle Beacons and ProbeResponses in here is kinda gross. Refactor.
Scanner::Scanner(DeviceInterface* device, ChannelScheduler* chan_sched)
: off_channel_handler_(this), device_(device), chan_sched_(chan_sched) {}
zx_status_t Scanner::HandleMlmeScanReq(const MlmeMsg<wlan_mlme::ScanRequest>& req) {
return Start(req);
}
zx_status_t Scanner::Start(const MlmeMsg<wlan_mlme::ScanRequest>& req) {
debugfn();
if (IsRunning()) {
SendScanEnd(device_, req.body()->txn_id, wlan_mlme::ScanResultCodes::NOT_SUPPORTED);
return ZX_ERR_UNAVAILABLE;
}
if (req.body()->channel_list->size() == 0 ||
req.body()->max_channel_time < req.body()->min_channel_time) {
SendScanEnd(device_, req.body()->txn_id, wlan_mlme::ScanResultCodes::INVALID_ARGS);
return ZX_ERR_INVALID_ARGS;
}
// TODO(NET-629): re-enable checking the enum value after fidl2 lands
// if (!BSSTypes_IsValidValue(req.body()->bss_type) ||
// !ScanTypes_IsValidValue(req.body()->scan_type)) {
// return SendScanConfirm();
//}
req_ = wlan_mlme::ScanRequest::New();
zx_status_t status = req.body()->Clone(req_.get());
if (status != ZX_OK) {
errorf("could not clone Scanrequest: %d\n", status);
SendScanEnd(device_, req.body()->txn_id, wlan_mlme::ScanResultCodes::INTERNAL_ERROR);
Reset();
return status;
}
if (device_->GetWlanInfo().ifc_info.driver_features & WLAN_DRIVER_FEATURE_SCAN_OFFLOAD) {
debugscan("starting a hardware scan\n");
return StartHwScan();
} else {
debugscan("starting a software scan\n");
ZX_DEBUG_ASSERT(channel_index_ == 0);
chan_sched_->RequestOffChannelTime(CreateOffChannelRequest());
return ZX_OK;
}
}
zx_status_t Scanner::StartHwScan() {
wlan_hw_scan_config_t config = {};
const auto& chans = req_->channel_list;
if (chans->size() > arraysize(config.channels)) {
errorf("too many channels to scan: %zu\n", chans->size());
SendScanEnd(device_, req_->txn_id, wlan_mlme::ScanResultCodes::INVALID_ARGS);
Reset();
return ZX_ERR_INVALID_ARGS;
}
config.num_channels = chans->size();
std::copy(chans->begin(), chans->end(), config.channels);
zx_status_t status = device_->StartHwScan(&config);
if (status != ZX_OK) {
errorf("StartHwScan returned an error: %s\n", zx_status_get_string(status));
SendScanEnd(device_, req_->txn_id, wlan_mlme::ScanResultCodes::INTERNAL_ERROR);
Reset();
return status;
}
return ZX_OK;
}
void Scanner::OffChannelHandlerImpl::BeginOffChannelTime() {
// Don't do anything for now. For active scans, we should send a probe request,
// or set a timer to send a probe request.
// TODO(NET-1294)
}
void Scanner::OffChannelHandlerImpl::HandleOffChannelFrame(fbl::unique_ptr<Packet> pkt) {
if (auto mgmt_frame = MgmtFrameView<>::CheckType(pkt.get()).CheckLength()) {
if (auto bcn_frame = mgmt_frame.CheckBodyType<Beacon>().CheckLength()) {
scanner_->HandleBeacon(bcn_frame);
}
}
}
bool Scanner::OffChannelHandlerImpl::EndOffChannelTime(bool interrupted,
OffChannelRequest* next_req) {
// If we were interrupted before the timeout ended, scan the channel again
if (interrupted) {
*next_req = scanner_->CreateOffChannelRequest();
return true;
}
scanner_->channel_index_ += 1;
if (scanner_->channel_index_ >= scanner_->req_->channel_list->size()) {
scanner_->SendResultsAndReset();
return false;
}
*next_req = scanner_->CreateOffChannelRequest();
return true;
}
void Scanner::Reset() {
debugfn();
req_.reset();
channel_index_ = 0;
current_bss_.clear();
}
bool Scanner::IsRunning() const {
return req_ != nullptr;
}
wlan_channel_t Scanner::ScanChannel() const {
debugfn();
ZX_DEBUG_ASSERT(IsRunning());
ZX_DEBUG_ASSERT(channel_index_ < req_->channel_list->size());
return wlan_channel_t{
.primary = req_->channel_list->at(channel_index_),
};
}
bool Scanner::ShouldDropMgmtFrame(const MgmtFrameHeader& hdr) {
// Ignore all management frames when scanner is not running.
if (!IsRunning()) { return true; }
common::MacAddr bssid(hdr.addr3);
common::MacAddr src_addr(hdr.addr2);
if (bssid != src_addr) {
// Undefined situation. Investigate if roaming needs this or this is a plain dark art.
// Do not process frame.
debugbcn("Rxed a beacon/probe_resp from the non-BSSID station: BSSID %s SrcAddr %s\n",
MACSTR(bssid), MACSTR(src_addr));
return true;
}
return false;
}
void Scanner::HandleBeacon(const MgmtFrameView<Beacon>& frame) {
debugfn();
if (!ShouldDropMgmtFrame(*frame.hdr())) { ProcessBeacon(frame); }
}
void Scanner::ProcessBeacon(const MgmtFrameView<Beacon>& mgmt_bcn_frame) {
debugfn();
auto bssid = mgmt_bcn_frame.hdr()->addr3;
auto it = current_bss_.find(bssid.ToU64());
if (it == current_bss_.end()) {
if (current_bss_.size() >= kMaxBss) {
errorf("maximum number of BSS reached: %lu\n", current_bss_.size());
return;
}
it = current_bss_
.emplace(std::piecewise_construct, std::forward_as_tuple(bssid.ToU64()),
std::forward_as_tuple(bssid))
.first;
}
auto rx_info = mgmt_bcn_frame.rx_info();
auto bcn_frame = mgmt_bcn_frame.NextFrame();
Span<const uint8_t> ie_chain = bcn_frame.body_data();
zx_status_t status = it->second.ProcessBeacon(*bcn_frame.hdr(), ie_chain, rx_info);
if (status != ZX_OK) {
debugbcn("Failed to handle beacon (err %3d): BSSID %s timestamp: %15" PRIu64 "\n", status,
MACSTR(bssid), bcn_frame.hdr()->timestamp);
}
}
OffChannelRequest Scanner::CreateOffChannelRequest() {
return OffChannelRequest{.chan = ScanChannel(),
.duration = WLAN_TU(req_->max_channel_time),
.handler = &off_channel_handler_};
}
void Scanner::HandleHwScanAborted() {
if (!IsRunning()) {
errorf("got a HwScanAborted event while the scanner is not running\n");
return;
}
errorf("scanner: hardware scan was aborted. Throwing out %zu BSS descriptions\n",
current_bss_.size());
SendScanEnd(device_, req_->txn_id, wlan_mlme::ScanResultCodes::INTERNAL_ERROR);
Reset();
}
void Scanner::HandleHwScanComplete() {
if (!IsRunning()) {
errorf("got a HwScanComplete event while the scanner is not running\n");
return;
}
SendResultsAndReset();
}
void Scanner::SendResultsAndReset() {
zx_status_t status = SendResults(device_, req_->txn_id, current_bss_);
if (status == ZX_OK) {
SendScanEnd(device_, req_->txn_id, wlan_mlme::ScanResultCodes::SUCCESS);
} else {
errorf("scanner: failed to send results: %s\n", zx_status_get_string(status));
SendScanEnd(device_, req_->txn_id, wlan_mlme::ScanResultCodes::INTERNAL_ERROR);
}
Reset();
}
} // namespace wlan