blob: 897d6c9f90faff046399c27027591f65ae689651 [file] [log] [blame]
// 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.
#include <wlan/common/channel.h>
#include <wlan/mlme/beacon.h>
#include <wlan/mlme/rates_elements.h>
namespace wlan {
static bool WriteSsid(ElementWriter* w, const BeaconConfig& config) {
if (config.ssid != nullptr) {
return w->write<SsidElement>(config.ssid, config.ssid_len);
} else {
return true;
}
}
static bool WriteDsssParamSet(ElementWriter* w, const BeaconConfig& config) {
return w->write<DsssParamSetElement>(config.channel.primary);
}
static bool WriteTim(ElementWriter* w, const PsCfg* ps_cfg, size_t* rel_tim_ele_offset) {
if (!ps_cfg) { return true; }
// To get the TIM offset in frame, we have to count the header, fixed parameters and tagged
// parameters before TIM is written.
*rel_tim_ele_offset = w->size();
size_t bitmap_len;
uint8_t bitmap_offset;
uint8_t pvb[TimElement::kMaxLenBmp];
auto status =
ps_cfg->GetTim()->WritePartialVirtualBitmap(pvb, sizeof(pvb), &bitmap_len, &bitmap_offset);
if (status != ZX_OK) { return false; }
BitmapControl bmp_ctrl;
bmp_ctrl.set_offset(bitmap_offset);
if (ps_cfg->IsDtim()) { bmp_ctrl.set_group_traffic_ind(ps_cfg->GetTim()->HasGroupTraffic()); }
return w->write<TimElement>(ps_cfg->dtim_count(), ps_cfg->dtim_period(), bmp_ctrl, pvb,
bitmap_len);
}
static bool WriteCountry(ElementWriter* w, const BeaconConfig& config) {
// TODO(NET-799): Read from dot11CountryString MIB
const uint8_t kCountry[3] = {'U', 'S', ' '};
std::vector<SubbandTriplet> subbands;
// TODO(porce): Read from the AP's regulatory domain
if (wlan::common::Is2Ghz(config.channel)) {
subbands.push_back({1, 11, 36});
} else {
subbands.push_back({36, 4, 36});
subbands.push_back({52, 4, 30});
subbands.push_back({100, 12, 30});
subbands.push_back({149, 5, 36});
}
return w->write<CountryElement>(kCountry, subbands);
}
static bool WriteHtCapabilities(ElementWriter* w, const BeaconConfig& config) {
if (config.ht.ready) {
auto h = BuildHtCapabilities(config.ht);
return w->write<HtCapabilities>(h.ht_cap_info, h.ampdu_params, h.mcs_set, h.ht_ext_cap,
h.txbf_cap, h.asel_cap);
} else {
return true;
}
}
static bool WriteHtOperation(ElementWriter* w, const BeaconConfig& config) {
if (config.ht.ready) {
HtOperation hto = BuildHtOperation(config.channel);
return w->write<HtOperation>(hto.primary_chan, hto.head, hto.tail, hto.basic_mcs_set);
} else {
return true;
}
}
static bool WriteRsne(ElementWriter* w, const BeaconConfig& config) {
if (config.rsne != nullptr) {
return w->write<RsnElement>(config.rsne, config.rsne_len);
} else {
return true;
}
}
static bool WriteMeshConfiguration(ElementWriter* w, const BeaconConfig& config) {
if (config.mesh_config != nullptr) {
return w->write<MeshConfigurationElement>(*config.mesh_config);
} else {
return true;
}
}
static bool WriteMeshId(ElementWriter* w, const BeaconConfig& config) {
if (config.mesh_id != nullptr) {
return w->write<MeshIdElement>(config.mesh_id, config.mesh_id_len);
} else {
return true;
}
}
static bool WriteElements(ElementWriter* w, const BeaconConfig& config, size_t* rel_tim_ele_offset) {
RatesWriter rates_writer { config.rates, config.rates_len };
// TODO(hahnr): Query from hardware which IEs must be filled out here.
return WriteSsid(w, config)
&& rates_writer.WriteSupportedRates(w)
&& WriteDsssParamSet(w, config)
&& WriteTim(w, config.ps_cfg, rel_tim_ele_offset)
&& WriteCountry(w, config)
&& rates_writer.WriteExtendedSupportedRates(w)
&& WriteRsne(w, config)
&& WriteHtCapabilities(w, config)
&& WriteHtOperation(w, config)
&& WriteMeshConfiguration(w, config)
&& WriteMeshId(w, config);
}
template<typename T>
static void SetBssType(T* bcn, BssType bss_type) {
// IEEE Std 802.11-2016, 9.4.1.4
switch (bss_type) {
case BssType::kInfrastructure:
bcn->cap.set_ess(1);
bcn->cap.set_ibss(0);
break;
case BssType::kIndependent:
bcn->cap.set_ess(0);
bcn->cap.set_ibss(1);
break;
case BssType::kMesh:
bcn->cap.set_ess(0);
bcn->cap.set_ibss(0);
break;
}
}
template <typename T>
static zx_status_t BuildBeaconOrProbeResponse(const BeaconConfig& config, common::MacAddr addr1,
MgmtFrame<T>* buffer, size_t* tim_ele_offset) {
constexpr size_t reserved_ie_len = 256;
auto status = CreateMgmtFrame(buffer, reserved_ie_len);
if (status != ZX_OK) { return status; }
auto hdr = buffer->hdr();
hdr->addr1 = addr1;
hdr->addr2 = config.bssid;
hdr->addr3 = config.bssid;
auto bcn = buffer->body();
bcn->beacon_interval = config.beacon_period;
bcn->timestamp = config.timestamp;
bcn->cap.set_privacy(config.rsne != nullptr);
SetBssType(bcn, config.bss_type);
bcn->cap.set_short_preamble(1);
// Write elements.
ElementWriter w(bcn->elements, reserved_ie_len);
size_t rel_tim_ele_offset = SIZE_MAX;
if (!WriteElements(&w, config, &rel_tim_ele_offset)) { return ZX_ERR_BUFFER_TOO_SMALL; }
ZX_DEBUG_ASSERT(bcn->Validate(w.size()));
// Update the length with final values
size_t body_len = buffer->body()->len() + w.size();
status = buffer->set_body_len(body_len);
if (status != ZX_OK) {
errorf("could not set body length to %zu: %d\n", body_len, status);
return status;
}
if (tim_ele_offset != nullptr) {
if (rel_tim_ele_offset == SIZE_MAX) {
// A tim element offset was requested but no element was written
return ZX_ERR_INVALID_ARGS;
}
*tim_ele_offset = buffer->View().body_offset() + bcn->len() + rel_tim_ele_offset;
}
return ZX_OK;
}
zx_status_t BuildBeacon(const BeaconConfig& config, MgmtFrame<Beacon>* buffer,
size_t* tim_ele_offset) {
return BuildBeaconOrProbeResponse(config, common::kBcastMac, buffer, tim_ele_offset);
}
zx_status_t BuildProbeResponse(const BeaconConfig& config, common::MacAddr addr1,
MgmtFrame<ProbeResponse>* buffer) {
return BuildBeaconOrProbeResponse(config, addr1, buffer, nullptr);
}
} // namespace wlan