blob: 3d88c7d1ff404f2df5e970855278b4d0323d0e55 [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/common/channel.h>
#include <wlan/common/logging.h>
#include <zircon/assert.h>
bool operator==(const wlan_channel_t& lhs, const wlan_channel_t& rhs) {
// TODO(porce): Support 802.11ac Wave2 by lhs.secondary80 == rhs.secondary80
return (lhs.primary == rhs.primary && lhs.cbw == rhs.cbw);
}
bool operator!=(const wlan_channel_t& lhs, const wlan_channel_t& rhs) {
return !(lhs == rhs);
}
// TODO(porce): Look up constants from the operating class table.
// No need to use constexpr in this prototype.
namespace wlan {
namespace common {
namespace wlan_mlme = ::fuchsia::wlan::mlme;
const char* kCbwStr[] = {"CBW20", "CBW40", "CBW40B", "CBW80", "CBW160", "CBW80P80", "CBW_INV"};
const char* kCbwSuffix[] = {
// Fuchsia's short CBW notation. Not IEEE standard.
"", // Vanilla plain 20 MHz bandwidth
"+", // SCA, often denoted by "+1"
"-", // SCB, often denoted by "-1"
"V", // VHT 80 MHz
"W", // VHT Wave2 160 MHz
"P", // VHT Wave2 80Plus80 (not often obvious, but P is the first alphabet)
"!", // Invalid
};
bool Is5Ghz(uint8_t channel_number) {
// TODO(porce): Improve this humble function
return (channel_number > 14);
}
bool Is2Ghz(uint8_t channel_number) {
return !Is5Ghz(channel_number);
}
bool Is5Ghz(const wlan_channel_t& chan) {
return Is5Ghz(chan.primary);
}
bool Is2Ghz(const wlan_channel_t& chan) {
return !Is5Ghz(chan.primary);
}
bool IsValidChan2Ghz(const wlan_channel_t& chan) {
uint8_t p = chan.primary;
if (p < 1 || p > 14) { return false; }
switch (chan.cbw) {
case CBW20:
return true;
case CBW40ABOVE:
return (p <= 7);
case CBW40BELOW:
return (p >= 5);
default:
return false;
}
}
bool IsValidChan5Ghz(const wlan_channel_t& chan) {
uint8_t p = chan.primary;
uint8_t s = chan.secondary80;
// See IEEE Std 802.11-2016, Table 9-252, 9-253
// TODO(porce): Augment wlan_channel_t to carry
// "channel width" subfield of VHT Operation Info of VHT Operation IE.
// Test the validity of CCFS1, and the relation to the CCFS0.
if (p < 36 || p > 173) { return false; }
if (p > 64 && p < 100) { return false; }
if (p > 144 && p < 149) { return false; }
if (p <= 144 && (p % 4 != 0)) { return false; }
if (p >= 149 && (p % 4 != 1)) { return false; }
switch (chan.cbw) {
case CBW20:
break;
case CBW40ABOVE:
if (p <= 144 && (p % 8 != 4)) { return false; }
if (p >= 149 && (p % 8 != 5)) { return false; }
break;
case CBW40BELOW:
if (p <= 144 && (p % 8 != 0)) { return false; }
if (p >= 149 && (p % 8 != 1)) { return false; }
break;
case CBW80:
if (p == 165) { return false; }
break;
case CBW80P80: {
if (!(s == 42 || s == 58 || s == 106 || s == 122 || s == 138 || s == 155)) { return false; }
uint8_t ccfs0 = GetCenterChanIdx(chan);
uint8_t ccfs1 = s;
uint8_t gap = (ccfs0 >= ccfs1) ? (ccfs0 - ccfs1) : (ccfs1 - ccfs0);
if (gap <= 16) { return false; }
break;
}
case CBW160: {
if (p >= 132) { return false; }
break;
}
default:
return false;
}
return true;
}
bool IsValidChan(const wlan_channel_t& chan) {
auto result = Is2Ghz(chan) ? IsValidChan2Ghz(chan) : IsValidChan5Ghz(chan);
// TODO(porce): Revisit if wlan library may have active logging
// Prefer logging in the caller only
if (!result) { errorf("invalid channel value: %s\n", ChanStr(chan).c_str()); }
return result;
}
Mhz GetCenterFreq(const wlan_channel_t& chan) {
ZX_DEBUG_ASSERT(IsValidChan(chan));
Mhz spacing = 5;
Mhz channel_starting_frequency;
if (Is2Ghz(chan)) {
channel_starting_frequency = kBaseFreq2Ghz;
} else {
// 5 GHz
channel_starting_frequency = kBaseFreq5Ghz;
}
// IEEE Std 802.11-2016, 21.3.14
return channel_starting_frequency + spacing * GetCenterChanIdx(chan);
}
// Returns the channel index corresponding to the first frequency segment's center frequency
uint8_t GetCenterChanIdx(const wlan_channel_t& chan) {
uint8_t p = chan.primary;
switch (chan.cbw) {
case CBW20:
return p;
case CBW40ABOVE:
return p + 2;
case CBW40BELOW:
return p - 2;
case CBW80:
case CBW80P80:
if (p <= 48) {
return 42;
} else if (p <= 64) {
return 58;
} else if (p <= 112) {
return 106;
} else if (p <= 128) {
return 122;
} else if (p <= 144) {
return 138;
} else if (p <= 161) {
return 155;
} else {
// Not reachable
return p;
}
case CBW160:
// See IEEE Std 802.11-2016 Table 9-252 and 9-253.
// Note CBW160 has only one frequency segment, regardless of
// encodings on CCFS0 and CCFS1 in VHT Operation Information IE.
if (p <= 64) {
return 50;
} else if (p <= 128) {
return 114;
} else {
// Not reachable
return p;
}
default:
return chan.primary;
}
}
std::string ChanStr(const wlan_channel_t& chan) {
char buf[7 + 1];
uint8_t cbw = chan.cbw;
if (cbw >= CBW_COUNT) {
cbw = CBW_COUNT; // To be used to indicate invalid value.
}
int offset = std::snprintf(buf, sizeof(buf), "%u%s", chan.primary, kCbwSuffix[cbw]);
if (cbw == CBW80P80) {
std::snprintf(buf + offset, sizeof(buf) - offset, "%u", chan.secondary80);
}
return std::string(buf);
}
std::string ChanStrLong(const wlan_channel_t& chan) {
char buf[16 + 1];
uint8_t cbw = chan.cbw;
if (cbw >= CBW_COUNT) {
cbw = CBW_COUNT; // To be used to indicate invalid value;
}
int offset = std::snprintf(buf, sizeof(buf), "%u %s", chan.primary, kCbwStr[cbw]);
if (cbw == CBW80P80) {
std::snprintf(buf + offset, sizeof(buf) - offset, " %u", chan.secondary80);
}
return std::string(buf);
}
wlan_channel_t FromFidl(const wlan_mlme::WlanChan& fidl_chan) {
// Translate wlan::WlanChan class defined in wlan-mlme.fidl
// to wlan_channel_t struct defined in wlan.h
return wlan_channel_t{
.primary = fidl_chan.primary,
.cbw = static_cast<uint8_t>(fidl_chan.cbw),
.secondary80 = fidl_chan.secondary80,
};
}
wlan_mlme::WlanChan ToFidl(const wlan_channel_t& chan) {
return wlan_mlme::WlanChan{
.primary = chan.primary,
.cbw = static_cast<wlan_mlme::CBW>(chan.cbw),
.secondary80 = chan.secondary80,
};
}
// Sanitizes the user-providing channel value, and always returns a valid one,
// with respect to the primary channel.
// TODO(NET-449): Move this logic to policy engine
uint8_t GetValidCbw(const wlan_channel_t& chan) {
if (IsValidChan(chan)) { return chan.cbw; }
wlan_channel_t attempt = {
.primary = chan.primary,
.cbw = CBW20, // To be tested
//.secondary80 = 0,
};
// Search a valid combination in decreasing order of bandwidth.
// No precedence among CBW40*
attempt.cbw = CBW40ABOVE;
if (IsValidChan(attempt)) { return attempt.cbw; }
attempt.cbw = CBW40BELOW;
if (IsValidChan(attempt)) { return attempt.cbw; }
attempt.cbw = CBW20;
if (IsValidChan(attempt)) { return attempt.cbw; }
return CBW20; // Fallback to the minimum bandwidth
}
std::string GetPhyStr(PHY phy) {
switch (phy) {
case WLAN_PHY_DSSS:
return "802.11 DSSS";
case WLAN_PHY_CCK:
return "802.11b CCK/DSSS";
case WLAN_PHY_OFDM: // and WLAN_PHY_ERP
return "802.11a/g OFDM";
case WLAN_PHY_HT:
return "802.11n HT";
case WLAN_PHY_VHT:
return "802.11ac VHT";
default:
return "UNKNOWN_PHY";
}
}
PHY FromFidl(::fuchsia::wlan::mlme::PHY phy) {
// TODO(NET-1845): Streamline the enum values
switch (phy) {
case wlan_mlme::PHY::HR:
return WLAN_PHY_CCK;
case wlan_mlme::PHY::ERP:
return WLAN_PHY_OFDM;
case wlan_mlme::PHY::HT:
return WLAN_PHY_HT;
case wlan_mlme::PHY::VHT:
return WLAN_PHY_VHT;
case wlan_mlme::PHY::HEW:
return WLAN_PHY_HEW;
default:
errorf("Unknown phy value: %d\n", phy);
ZX_DEBUG_ASSERT(false);
return WLAN_PHY_HEW;
}
}
::fuchsia::wlan::mlme::PHY ToFidl(PHY phy) {
// TODO(NET-1845): Streamline the enum values
switch (phy) {
case WLAN_PHY_CCK:
return wlan_mlme::PHY::HR;
case WLAN_PHY_OFDM:
return wlan_mlme::PHY::ERP;
case WLAN_PHY_HT:
return wlan_mlme::PHY::HT;
case WLAN_PHY_VHT:
return wlan_mlme::PHY::VHT;
case WLAN_PHY_HEW:
return wlan_mlme::PHY::HEW;
default:
errorf("Unknown phy value: %d\n", phy);
ZX_DEBUG_ASSERT(false);
return wlan_mlme::PHY::HEW;
}
}
} // namespace common
} // namespace wlan