| // 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 <fuchsia/wlan/minstrel/cpp/fidl.h> |
| #include <fuchsia/wlan/mlme/cpp/fidl.h> |
| #include <zircon/fidl.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <atomic> |
| #include <cinttypes> |
| #include <cstring> |
| #include <memory> |
| #include <sstream> |
| |
| #include <ddk/hw/wlan/wlaninfo.h> |
| #include <wlan/common/band.h> |
| #include <wlan/common/channel.h> |
| #include <wlan/common/mac_frame.h> |
| #include <wlan/common/stats.h> |
| #include <wlan/mlme/ap/ap_mlme.h> |
| #include <wlan/mlme/client/client_mlme.h> |
| #include <wlan/mlme/device_interface.h> |
| #include <wlan/mlme/dispatcher.h> |
| #include <wlan/mlme/mac_frame.h> |
| #include <wlan/mlme/packet.h> |
| #include <wlan/mlme/service.h> |
| #include <wlan/protocol/mac.h> |
| |
| namespace wlan { |
| |
| namespace wlan_common = ::fuchsia::wlan::common; |
| namespace wlan_mlme = ::fuchsia::wlan::mlme; |
| namespace wlan_minstrel = ::fuchsia::wlan::minstrel; |
| namespace wlan_stats = ::fuchsia::wlan::stats; |
| |
| Dispatcher::Dispatcher(DeviceInterface* device, std::unique_ptr<Mlme> mlme) |
| : device_(device), mlme_(std::move(mlme)) { |
| debugfn(); |
| ZX_ASSERT(mlme_ != nullptr); |
| } |
| |
| Dispatcher::~Dispatcher() {} |
| |
| zx_status_t Dispatcher::HandlePacket(std::unique_ptr<Packet> packet) { |
| debugfn(); |
| |
| ZX_DEBUG_ASSERT(packet != nullptr); |
| ZX_DEBUG_ASSERT(packet->peer() != Packet::Peer::kUnknown); |
| |
| WLAN_STATS_INC(any_packet.in); |
| |
| // If there is no active MLME, block all packets but service ones. |
| // MLME-JOIN.request and MLME-START.request implicitly select a mode and |
| // initialize the MLME. DEVICE_QUERY.request is used to obtain device |
| // capabilities. |
| |
| auto service_msg = (packet->peer() == Packet::Peer::kService); |
| if (mlme_ == nullptr && !service_msg) { |
| WLAN_STATS_INC(any_packet.drop); |
| return ZX_OK; |
| } |
| |
| WLAN_STATS_INC(any_packet.out); |
| |
| zx_status_t status = ZX_OK; |
| switch (packet->peer()) { |
| case Packet::Peer::kEthernet: |
| status = mlme_->HandleFramePacket(std::move(packet)); |
| break; |
| case Packet::Peer::kWlan: |
| if (auto fc = packet->field<FrameControl>(0)) { |
| switch (fc->type()) { |
| case FrameType::kManagement: |
| WLAN_STATS_INC(mgmt_frame.in); |
| break; |
| case FrameType::kControl: |
| WLAN_STATS_INC(ctrl_frame.in); |
| break; |
| case FrameType::kData: |
| WLAN_STATS_INC(data_frame.in); |
| break; |
| default: |
| break; |
| } |
| |
| status = mlme_->HandleFramePacket(std::move(packet)); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t Dispatcher::HandlePortPacket(uint64_t key) { |
| debugfn(); |
| ZX_DEBUG_ASSERT(ToPortKeyType(key) == PortKeyType::kMlme); |
| |
| ObjectId id(ToPortKeyId(key)); |
| switch (id.subtype()) { |
| case to_enum_type(ObjectSubtype::kTimer): { |
| auto status = mlme_->HandleTimeout(id); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| warnf("unknown MLME timer target: %u\n", id.target()); |
| } |
| break; |
| } |
| default: |
| warnf("unknown MLME event subtype: %u\n", id.subtype()); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Dispatcher::HandleAnyMlmeMessage(fbl::Span<uint8_t> span) { |
| debugfn(); |
| // Attempt to process encoded message in MLME. |
| auto hdr = FromBytes<fidl_message_header_t>(span); |
| if (hdr == nullptr) { |
| errorf("short mlme message, len=%zu\n", span.size()); |
| return ZX_OK; |
| } |
| uint64_t ordinal = hdr->ordinal; |
| debughdr("service packet txid=%u ordinal=%lu\n", hdr->txid, ordinal); |
| |
| // TODO(fxbug.dev/44480): Rust MLME message handler does not support transaction ID. |
| switch (ordinal) { |
| case fuchsia::wlan::mlme::internal::kMLME_QueryDeviceInfo_Ordinal: |
| return HandleQueryDeviceInfo(hdr->txid); |
| case fuchsia::wlan::mlme::internal::kMLME_ListMinstrelPeers_Ordinal: |
| return HandleMinstrelPeerList(ordinal, hdr->txid); |
| case fuchsia::wlan::mlme::internal::kMLME_GetMinstrelStats_Ordinal: |
| return HandleMinstrelTxStats(span, ordinal, hdr->txid); |
| // TODO(fxbug.dev/44485): Rust MLME does not support Mesh. |
| case fuchsia::wlan::mlme::internal::kMLME_SendMpOpenAction_Ordinal: |
| return HandleMlmeMessage<wlan_mlme::MeshPeeringOpenAction>(span, ordinal); |
| case fuchsia::wlan::mlme::internal::kMLME_SendMpConfirmAction_Ordinal: |
| return HandleMlmeMessage<wlan_mlme::MeshPeeringConfirmAction>(span, ordinal); |
| case fuchsia::wlan::mlme::internal::kMLME_MeshPeeringEstablished_Ordinal: |
| return HandleMlmeMessage<wlan_mlme::MeshPeeringParams>(span, ordinal); |
| case fuchsia::wlan::mlme::internal::kMLME_GetMeshPathTableReq_Ordinal: |
| return HandleMlmeMessage<wlan_mlme::GetMeshPathTableRequest>(span, ordinal); |
| default: |
| return mlme_->HandleEncodedMlmeMsg(span); |
| } |
| } |
| |
| template <typename Message> |
| zx_status_t Dispatcher::HandleMlmeMessage(fbl::Span<uint8_t> span, uint64_t ordinal) { |
| // If the encoded message was not handled, manually decode and dispatch message. |
| auto msg = MlmeMsg<Message>::Decode(span, ordinal); |
| if (!msg.has_value()) { |
| errorf("could not deserialize MLME primitive %lu: \n", ordinal); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return mlme_->HandleMlmeMsg(*msg); |
| } |
| |
| zx_status_t Dispatcher::HandleQueryDeviceInfo(zx_txid_t txid) { |
| debugfn(); |
| |
| wlan_mlme::DeviceInfo resp; |
| const wlan_info_t& info = device_->GetWlanInfo().ifc_info; |
| |
| memcpy(resp.mac_addr.data(), info.mac_addr, ETH_MAC_SIZE); |
| |
| // mac_role is a bitfield, but only a single value is supported for an |
| // interface |
| switch (info.mac_role) { |
| case WLAN_INFO_MAC_ROLE_CLIENT: |
| resp.role = wlan_mlme::MacRole::CLIENT; |
| break; |
| case WLAN_INFO_MAC_ROLE_AP: |
| resp.role = wlan_mlme::MacRole::AP; |
| break; |
| case WLAN_INFO_MAC_ROLE_MESH: |
| resp.role = wlan_mlme::MacRole::MESH; |
| break; |
| default: |
| // TODO(fxbug.dev/28723): return an error! |
| break; |
| } |
| |
| auto wlanmac_info = device_->GetWlanInfo().ifc_info; |
| |
| resp.bands.resize(0); |
| for (uint8_t band_idx = 0; band_idx < info.bands_count; band_idx++) { |
| const wlan_info_band_info_t& band_info = info.bands[band_idx]; |
| wlan_mlme::BandCapabilities band; |
| band.band_id = wlan::common::BandToFidl(band_info.band); |
| band.rates.resize(0); |
| for (size_t rate_idx = 0; rate_idx < sizeof(band_info.rates); rate_idx++) { |
| if (band_info.rates[rate_idx] != 0) { |
| band.rates.push_back(band_info.rates[rate_idx]); |
| } |
| } |
| const wlan_info_channel_list_t& chan_list = band_info.supported_channels; |
| band.base_frequency = chan_list.base_freq; |
| band.channels.resize(0); |
| for (size_t chan_idx = 0; chan_idx < sizeof(chan_list.channels); chan_idx++) { |
| if (chan_list.channels[chan_idx] != 0) { |
| band.channels.push_back(chan_list.channels[chan_idx]); |
| } |
| } |
| |
| band.cap = CapabilityInfo::FromDdk(wlanmac_info.caps).val(); |
| |
| if (band_info.ht_supported) { |
| auto ht_cap = HtCapabilities::FromDdk(band_info.ht_caps); |
| band.ht_cap = wlan_mlme::HtCapabilities::New(); |
| static_assert(sizeof(band.ht_cap->bytes) == sizeof(ht_cap)); |
| memcpy(band.ht_cap->bytes.data(), &ht_cap, sizeof(ht_cap)); |
| } |
| if (band_info.vht_supported) { |
| auto vht_cap = VhtCapabilities::FromDdk(band_info.vht_caps); |
| band.vht_cap = wlan_mlme::VhtCapabilities::New(); |
| static_assert(sizeof(band.vht_cap->bytes) == sizeof(vht_cap)); |
| memcpy(band.vht_cap->bytes.data(), &vht_cap, sizeof(vht_cap)); |
| } |
| |
| resp.bands.push_back(std::move(band)); |
| } |
| |
| resp.driver_features.resize(0); |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_SCAN_OFFLOAD) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::SCAN_OFFLOAD); |
| } |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_RATE_SELECTION) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::RATE_SELECTION); |
| } |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_SYNTH) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::SYNTH); |
| } |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_TX_STATUS_REPORT) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::TX_STATUS_REPORT); |
| } |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_DFS) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::DFS); |
| } |
| if (info.driver_features & WLAN_INFO_DRIVER_FEATURE_PROBE_RESP_OFFLOAD) { |
| resp.driver_features.push_back(wlan_common::DriverFeature::PROBE_RESP_OFFLOAD); |
| } |
| resp.driver_features.push_back(wlan_common::DriverFeature::SAE_SME_AUTH); |
| // TODO(fxbug.dev/41640): Remove this flag once FullMAC drivers no longer needs SME. |
| // This flag tells SME that SoftMAC drivers need SME to derive association capabilities. |
| resp.driver_features.push_back(wlan_common::DriverFeature::TEMP_SOFTMAC); |
| |
| return SendServiceMsg(device_, &resp, |
| fuchsia::wlan::mlme::internal::kMLME_QueryDeviceInfo_Ordinal, txid); |
| } |
| |
| zx_status_t Dispatcher::HandleMlmeStats(uint64_t ordinal) const { |
| debugfn(); |
| wlan_mlme::StatsQueryResponse resp = GetStatsToFidl(); |
| return SendServiceMsg(device_, &resp, |
| fuchsia::wlan::mlme::internal::kMLME_StatsQueryResp_Ordinal); |
| } |
| |
| zx_status_t Dispatcher::HandleMinstrelPeerList(uint64_t ordinal, zx_txid_t txid) const { |
| debugfn(); |
| wlan_mlme::MinstrelListResponse resp{}; |
| zx_status_t status = device_->GetMinstrelPeers(&resp.peers); |
| if (status != ZX_OK) { |
| errorf("cannot get minstrel peer list: %s\n", zx_status_get_string(status)); |
| resp.peers.peers.resize(0); |
| } |
| return SendServiceMsg(device_, &resp, |
| fuchsia::wlan::mlme::internal::kMLME_ListMinstrelPeers_Ordinal, txid); |
| } |
| |
| zx_status_t Dispatcher::HandleMinstrelTxStats(fbl::Span<uint8_t> span, uint64_t ordinal, |
| zx_txid_t txid) const { |
| debugfn(); |
| wlan_mlme::MinstrelStatsResponse resp{}; |
| auto req = MlmeMsg<wlan_mlme::MinstrelStatsRequest>::Decode( |
| span, fuchsia::wlan::mlme::internal::kMLME_GetMinstrelStats_Ordinal); |
| if (!req.has_value()) { |
| errorf("could not deserialize MLME primitive %lu\n", ordinal); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| common::MacAddr addr(req->body()->mac_addr); |
| |
| wlan_minstrel::Peer peer; |
| auto status = device_->GetMinstrelStats(addr, &peer); |
| if (status == ZX_OK) { |
| resp.peer = std::make_unique<wlan_minstrel::Peer>(std::move(peer)); |
| } else { |
| errorf("could not get peer stats: %s\n", zx_status_get_string(status)); |
| } |
| return SendServiceMsg(device_, &resp, |
| fuchsia::wlan::mlme::internal::kMLME_GetMinstrelStats_Ordinal, txid); |
| } |
| |
| void Dispatcher::HwIndication(uint32_t ind) { |
| debugfn(); |
| mlme_->HwIndication(ind); |
| } |
| |
| void Dispatcher::HwScanComplete(uint8_t result_code) { |
| debugfn(); |
| mlme_->HwScanComplete(result_code); |
| } |
| |
| void Dispatcher::ResetStats() { |
| stats_.Reset(); |
| if (mlme_) { |
| mlme_->ResetMlmeStats(); |
| } |
| } |
| |
| wlan_mlme::StatsQueryResponse Dispatcher::GetStatsToFidl() const { |
| wlan_mlme::StatsQueryResponse stats_response; |
| stats_response.stats.dispatcher_stats = stats_.ToFidl(); |
| auto mlme_stats = mlme_->GetMlmeStats(); |
| if (!mlme_stats.has_invalid_tag()) { |
| stats_response.stats.mlme_stats = |
| std::make_unique<wlan_stats::MlmeStats>(mlme_->GetMlmeStats()); |
| } |
| return stats_response; |
| } |
| |
| } // namespace wlan |