feat: pair controllers and show them in the app
Port the MT76 client functions (send_wlan, associate_client, pair_client, set_client_key, remove_client) and wire the RX dispatch into the session: EP IN frames are parsed, ASSOC_REQ associates a controller (WCID plus chip programming), PAIR_REQ replies with PAIR_RESP, and DISASSOC or client-lost removes it. The C API exposes connected controllers and the app lists them by MAC. Bumps xone_cli/xone_app to C++23: they were falling back to the default standard once mt76.hpp started using std::span. Adds a unit test pinning the WCID regions, the 32-byte rxwi, and the new frame control values. Co-Authored-By: qwen3.8-27b@q2_k_xl: client functions, RX dispatch, GUI list
This commit is contained in:
+218
-2
@@ -2,12 +2,15 @@
|
||||
|
||||
#include "app/xone_api.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/log.hpp"
|
||||
#include "mt76/mt76.hpp"
|
||||
@@ -17,11 +20,19 @@
|
||||
#define XONE_VERSION "0.1.0"
|
||||
#endif
|
||||
|
||||
// One connected controller (tracked for the GUI). Guarded by xone_dongle's
|
||||
// lock; written by the transport reader thread, read by the UI thread.
|
||||
struct controller {
|
||||
std::uint8_t wcid;
|
||||
std::array<std::uint8_t, 6> mac;
|
||||
};
|
||||
|
||||
// Definition of the opaque struct xone_dongle from app/xone_api.h.
|
||||
struct xone_dongle {
|
||||
std::unique_ptr<xone::usb::transport> transport;
|
||||
std::unique_ptr<xone::mt76::chip> chip;
|
||||
// Guards state, firmware_build, and error (written by the start worker).
|
||||
// Guards state, firmware_build, error, and controllers. Written by the
|
||||
// start worker and the transport reader thread; read by the UI thread.
|
||||
mutable std::mutex lock;
|
||||
std::uint16_t pid = 0;
|
||||
std::uint16_t chip_id = 0;
|
||||
@@ -32,13 +43,29 @@ struct xone_dongle {
|
||||
// Firmware load + radio init runs here so the UI thread never blocks.
|
||||
std::thread worker;
|
||||
|
||||
// Connected controllers (wcid 1..16). Guarded by lock.
|
||||
std::vector<controller> controllers;
|
||||
|
||||
~xone_dongle()
|
||||
{
|
||||
// Stop the reader thread before the chip is torn down so a late IN
|
||||
// completion cannot touch freed state.
|
||||
transport.reset();
|
||||
// The session is destroyed at process exit even without an explicit
|
||||
// xone_close(); joining here avoids terminating on a joinable thread.
|
||||
if (worker.joinable())
|
||||
worker.join();
|
||||
}
|
||||
|
||||
// RX dispatch, driven from the transport reader thread. Port of the
|
||||
// xone_dongle_process_* functions in transport/dongle.c.
|
||||
auto process_message(void const *data, std::size_t len) -> void;
|
||||
auto process_wlan(std::span<std::uint8_t const> buf) -> void;
|
||||
auto handle_association(std::span<std::uint8_t const> addr) -> void;
|
||||
auto handle_disassociation(std::uint8_t wcid) -> void;
|
||||
auto handle_client_command(std::span<std::uint8_t const> payload,
|
||||
std::uint8_t wcid,
|
||||
std::span<std::uint8_t const> addr) -> void;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -47,6 +74,9 @@ namespace {
|
||||
std::unique_ptr<xone_dongle> current_session;
|
||||
std::mutex session_mutex; // guards current_session
|
||||
|
||||
// 802.11 management frame header length (three addresses, no extensions).
|
||||
constexpr std::size_t k_mgmt_hdr_len = 24;
|
||||
|
||||
// Select the firmware image for a product ID: firmware/xone_dongle_<pid>.bin.
|
||||
auto firmware_path_for(std::uint16_t pid) -> std::string
|
||||
{
|
||||
@@ -57,6 +87,161 @@ auto firmware_path_for(std::uint16_t pid) -> std::string
|
||||
|
||||
} // namespace
|
||||
|
||||
// --- RX dispatch (port of xone_dongle_process_*) ---------------------------
|
||||
|
||||
auto xone_dongle::process_message(void const *data, std::size_t len) -> void
|
||||
{
|
||||
using namespace xone::mt76;
|
||||
|
||||
if (!chip || len < 2 * cmd_hdr_len)
|
||||
return;
|
||||
|
||||
auto buf = std::span<std::uint8_t const>(
|
||||
static_cast<std::uint8_t const *>(data), len);
|
||||
|
||||
auto info = xone::load_le32(buf.data());
|
||||
auto port = field_get(mt_mcu_msg_port, info); // bits 29:27
|
||||
|
||||
// Ignore command responses (CMD_SEQ == 1).
|
||||
if (field_get(mt_mcu_msg_cmd_seq, info) == 0x01)
|
||||
return;
|
||||
|
||||
// Strip the header + trailer.
|
||||
buf = buf.subspan(cmd_hdr_len, len - 2 * cmd_hdr_len);
|
||||
|
||||
if (port == dma_msg_port::wlan_port) {
|
||||
process_wlan(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (port != dma_msg_port::cpu_rx_port)
|
||||
return;
|
||||
|
||||
switch (field_get(mt_rx_fce_info_evt_type, info)) {
|
||||
case cpu_evt::evt_button:
|
||||
// Pairing mode is entered via the CLI for now.
|
||||
break;
|
||||
case cpu_evt::evt_packet_rx:
|
||||
process_wlan(buf);
|
||||
break;
|
||||
case cpu_evt::evt_client_lost:
|
||||
if (!buf.empty())
|
||||
handle_disassociation(buf[0]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto xone_dongle::process_wlan(std::span<std::uint8_t const> buf) -> void
|
||||
{
|
||||
using namespace xone::mt76;
|
||||
|
||||
if (buf.size() < sizeof(mt76_rxwi))
|
||||
return;
|
||||
|
||||
auto rxinfo = xone::load_le32(buf.data());
|
||||
auto ctl = xone::load_le32(buf.data() + 4);
|
||||
auto wcid = static_cast<std::uint8_t>(field_get(mt_rxwi_ctl_wcid, ctl));
|
||||
|
||||
// The frame starts after the 32-byte rxwi.
|
||||
std::span<std::uint8_t const> frame = buf.subspan(sizeof(mt76_rxwi));
|
||||
if (frame.size() < k_mgmt_hdr_len)
|
||||
return;
|
||||
|
||||
auto fc = xone::load_le16(frame.data());
|
||||
auto match = fc & (ieee80211_fctl_ftype | ieee80211_fctl_stype);
|
||||
|
||||
// L2PAD: 2 bytes of padding after the 802.11 header.
|
||||
std::size_t pad = (rxinfo & mt_rxinfo_l2pad) ? 2 : 0;
|
||||
|
||||
if (match == (ieee80211_ftype_mgmt | ieee80211_stype_assoc_req)) {
|
||||
handle_association(frame.subspan(10, 6)); // addr2
|
||||
} else if (match == (ieee80211_ftype_mgmt | ieee80211_stype_disassoc)) {
|
||||
handle_disassociation(wcid);
|
||||
} else if (match == (ieee80211_ftype_mgmt | ieee80211_stype_wlan_reserved)) {
|
||||
handle_client_command(frame.subspan(k_mgmt_hdr_len + pad), wcid,
|
||||
frame.subspan(10, 6)); // addr2
|
||||
}
|
||||
// DATA|QOS_DATA (GIP) is handled in a later increment.
|
||||
}
|
||||
|
||||
auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
|
||||
{
|
||||
// Find a free WCID slot (1..16).
|
||||
std::uint8_t wcid = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
for (std::uint8_t i = 1; i <= 16; ++i) {
|
||||
bool used = false;
|
||||
for (auto const& c : controllers)
|
||||
if (c.wcid == i) { used = true; break; }
|
||||
if (!used) { wcid = i; break; }
|
||||
}
|
||||
}
|
||||
if (wcid == 0)
|
||||
return; // no free slot
|
||||
|
||||
std::array<std::uint8_t, 6> mac{};
|
||||
std::memcpy(mac.data(), addr.data(), mac.size());
|
||||
|
||||
// Program the chip outside the lock (synchronous USB I/O).
|
||||
if (chip->associate_client(wcid, mac) != 0) {
|
||||
xone::log_msg(xone::log_level::warn,
|
||||
"api: associate wcid=%d failed", wcid);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
controllers.push_back(controller{wcid, mac});
|
||||
xone::log_msg(xone::log_level::info,
|
||||
"api: controller associated (wcid=%d)", wcid);
|
||||
}
|
||||
|
||||
auto xone_dongle::handle_disassociation(std::uint8_t wcid) -> void
|
||||
{
|
||||
if (wcid == 0 || wcid > 16)
|
||||
return;
|
||||
|
||||
// Remove from the chip outside the lock.
|
||||
chip->remove_client(wcid);
|
||||
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
for (auto it = controllers.begin(); it != controllers.end(); ++it) {
|
||||
if (it->wcid == wcid) {
|
||||
controllers.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
xone::log_msg(xone::log_level::info,
|
||||
"api: controller removed (wcid=%d)", wcid);
|
||||
}
|
||||
|
||||
auto xone_dongle::handle_client_command(std::span<std::uint8_t const> payload,
|
||||
std::uint8_t wcid,
|
||||
std::span<std::uint8_t const> addr) -> void
|
||||
{
|
||||
using namespace xone::mt76;
|
||||
if (payload.size() < 2 || payload[0] != ieee80211_stype_wlan_reserved)
|
||||
return;
|
||||
|
||||
switch (payload[1]) {
|
||||
case client_cmd::client_pair_req: {
|
||||
std::array<std::uint8_t, 6> mac{};
|
||||
std::memcpy(mac.data(), addr.data(), mac.size());
|
||||
chip->pair_client(mac);
|
||||
xone::log_msg(xone::log_level::info,
|
||||
"api: controller paired (wcid=%d)", wcid);
|
||||
break;
|
||||
}
|
||||
case client_cmd::client_enable_encryption:
|
||||
// Encryption is enabled in a later increment (needs the GIP key).
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" const char *xone_version(void)
|
||||
{
|
||||
return XONE_VERSION;
|
||||
@@ -74,7 +259,17 @@ extern "C" xone_dongle *xone_open(void)
|
||||
return nullptr;
|
||||
|
||||
auto s = std::make_unique<xone_dongle>();
|
||||
s->transport = xone::usb::transport::probe(nullptr, nullptr);
|
||||
// Wire the RX dispatch into the transport's read pump. The callback
|
||||
// fires on the reader thread; process_message is null-safe until the
|
||||
// chip is created below.
|
||||
auto *session = s.get();
|
||||
xone::usb::frame_callback frames = [session](std::uint8_t ep,
|
||||
void const *data,
|
||||
std::size_t len) {
|
||||
(void)ep;
|
||||
session->process_message(data, len);
|
||||
};
|
||||
s->transport = xone::usb::transport::probe(frames, nullptr);
|
||||
if (!s->transport)
|
||||
return nullptr;
|
||||
|
||||
@@ -184,3 +379,24 @@ extern "C" const char *xone_firmware_build(const xone_dongle *d)
|
||||
std::lock_guard<std::mutex> guard(d->lock);
|
||||
return d->firmware_build;
|
||||
}
|
||||
|
||||
// Number of connected controllers.
|
||||
extern "C" int xone_controller_count(xone_dongle const *d)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(d->lock);
|
||||
return static_cast<int>(d->controllers.size());
|
||||
}
|
||||
|
||||
// Copy the controller's MAC into buf as "xx:xx:xx:xx:xx:xx". Returns 0 on
|
||||
// success, -1 if index is out of range.
|
||||
extern "C" int xone_controller_mac(xone_dongle const *d, int index, char *buf,
|
||||
int len)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(d->lock);
|
||||
if (index < 0 || index >= static_cast<int>(d->controllers.size()))
|
||||
return -1;
|
||||
auto const& c = d->controllers[index];
|
||||
std::snprintf(buf, len, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
c.mac[0], c.mac[1], c.mac[2], c.mac[3], c.mac[4], c.mac[5]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+29
-2
@@ -30,6 +30,8 @@ struct ContentView: View {
|
||||
// Radio state polled from the background worker each tick; a change here
|
||||
// is what re-renders the debug section.
|
||||
@State private var radioState = XONE_STATE_IDLE
|
||||
// Connected controllers (MAC strings), refreshed each tick.
|
||||
@State private var controllers: [String] = []
|
||||
|
||||
private let timer = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
@@ -84,6 +86,7 @@ struct ContentView: View {
|
||||
session = nil
|
||||
} else if let s = session {
|
||||
radioState = Int(xone_state(s))
|
||||
refreshControllers(s)
|
||||
}
|
||||
donglePresent = present
|
||||
}
|
||||
@@ -130,6 +133,19 @@ struct ContentView: View {
|
||||
return build.isEmpty ? "not loaded" : build
|
||||
}
|
||||
|
||||
// Query the C API for connected controllers and store their MACs.
|
||||
private func refreshControllers(_ session: OpaquePointer) {
|
||||
let count = Int(xone_controller_count(session))
|
||||
var list: [String] = []
|
||||
for i in 0..<count {
|
||||
var buf = [CChar](repeating: 0, count: 18)
|
||||
if xone_controller_mac(session, Int32(i), &buf, 18) == 0 {
|
||||
list.append(String(cString: buf))
|
||||
}
|
||||
}
|
||||
controllers = list
|
||||
}
|
||||
|
||||
private var statusView: some View {
|
||||
HStack(spacing: 8) {
|
||||
Circle()
|
||||
@@ -144,8 +160,19 @@ struct ContentView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Controllers")
|
||||
.font(.headline)
|
||||
Text("No controllers connected.")
|
||||
.foregroundStyle(.secondary)
|
||||
if controllers.isEmpty {
|
||||
Text("No controllers connected.")
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
ForEach(controllers, id: \.self) { mac in
|
||||
HStack {
|
||||
Image(systemName: "gamecontroller")
|
||||
.foregroundStyle(.secondary)
|
||||
Text(mac)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+169
-1
@@ -337,7 +337,7 @@ auto chip::load_cr(cr_mode mode) -> int
|
||||
return send_command(mcu_cmd::cmd_load_cr, payload, sizeof(payload));
|
||||
}
|
||||
|
||||
auto chip::write_burst(std::uint32_t idx, void *data, std::size_t len) -> int
|
||||
auto chip::write_burst(std::uint32_t idx, void const *data, std::size_t len) -> int
|
||||
{
|
||||
std::vector<std::uint8_t> buf(4 + len);
|
||||
xone::store_le32(buf.data(), idx + mt_mcu_memmap_wlan); // Register offset in memory.
|
||||
@@ -761,4 +761,172 @@ auto chip::resume_radio() -> int
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build a TXWI (struct mt76_txwi, 20 bytes, packed) for a WLAN frame.
|
||||
auto build_txwi(std::uint8_t wcid, std::size_t len_ctl)
|
||||
-> std::array<std::uint8_t, 20>
|
||||
{
|
||||
std::array<std::uint8_t, 20> txwi{};
|
||||
xone::store_le16(txwi.data() + 0, field_prep(mt_txwi_flags_mpdu_density,
|
||||
ieee80211_ht_mpdu_density_4));
|
||||
xone::store_le16(txwi.data() + 2,
|
||||
field_prep(mt_rxwi_rate_phy, phy_type::phy_ofdm));
|
||||
txwi[4] = mt_txwi_ack_ctl_req;
|
||||
txwi[5] = wcid; // 0xff for broadcast (assoc/pair), wcid-1 for a client.
|
||||
xone::store_le16(txwi.data() + 6, static_cast<std::uint16_t>(len_ctl));
|
||||
return txwi;
|
||||
}
|
||||
|
||||
auto chip::send_wlan(std::span<std::uint8_t const> frame) -> int
|
||||
{
|
||||
auto txwi = build_txwi(0xff, frame.size());
|
||||
|
||||
std::vector<std::uint8_t> payload(txwi.size() + frame.size());
|
||||
std::memcpy(payload.data(), txwi.data(), txwi.size());
|
||||
std::memcpy(payload.data() + txwi.size(), frame.data(), frame.size());
|
||||
|
||||
// Enhanced distributed channel access (EDCA), wireless info valid (WIV).
|
||||
auto info = field_prep(mt_txd_info_dport, dma_msg_port::wlan_port)
|
||||
| field_prep(mt_txd_info_qsel, qsel::qsel_edca)
|
||||
| mt_txd_info_wiv
|
||||
| mt_txd_info_80211;
|
||||
|
||||
auto buf = build_message(info, payload.data(), payload.size());
|
||||
auto ret = transport_.bulk_write(buf.data(), buf.size());
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
auto chip::associate_client(std::uint8_t wcid,
|
||||
std::span<std::uint8_t const> addr) -> int
|
||||
{
|
||||
auto address = mac_address();
|
||||
|
||||
// struct ieee80211_mgmt (assoc_resp), 26 bytes:
|
||||
// [frame_control][duration][da][sa][bssid][status_code][aid].
|
||||
std::uint8_t mgmt[26] = {};
|
||||
xone::store_le16(mgmt + 0, ieee80211_ftype_mgmt
|
||||
| ieee80211_stype_assoc_resp);
|
||||
std::memcpy(mgmt + 4, addr.data(), 6); // da
|
||||
std::memcpy(mgmt + 10, address.data(), 6); // sa
|
||||
std::memcpy(mgmt + 16, address.data(), 6); // bssid
|
||||
xone::store_le16(mgmt + 22, 0x0110); // status_code (original)
|
||||
xone::store_le16(mgmt + 24, 0x0f00); // aid (original)
|
||||
|
||||
// Payload: mgmt frame plus 8 bytes of zero padding.
|
||||
std::vector<std::uint8_t> payload(sizeof(mgmt) + 8, 0);
|
||||
std::memcpy(payload.data(), mgmt, sizeof(mgmt));
|
||||
|
||||
if (auto err = write_burst(mt_wcid_addr(wcid), addr.data(), 6); err != 0)
|
||||
return err;
|
||||
|
||||
// ADD_CLIENT ms_command: {wcid-1, 0, 0, 0, 0x40, 0x1f, 0, 0}.
|
||||
auto idx = static_cast<std::uint8_t>(wcid - 1);
|
||||
std::uint8_t data[8] = { idx, 0x00, 0x00, 0x00, 0x40, 0x1f, 0x00, 0x00 };
|
||||
if (auto err = send_ms_command(ms_command::ms_add_client,
|
||||
data, sizeof(data)); err != 0)
|
||||
return err;
|
||||
|
||||
return send_wlan(payload);
|
||||
}
|
||||
|
||||
auto chip::pair_client(std::span<std::uint8_t const> addr) -> int
|
||||
{
|
||||
auto address = mac_address();
|
||||
|
||||
// struct ieee80211_hdr_3addr (22 bytes) plus the reserved command and a
|
||||
// 9-byte payload: [fc][duration][addr1][addr2][addr3][0x70][PAIR_RESP]
|
||||
// [data].
|
||||
std::uint8_t frame[33] = {};
|
||||
xone::store_le16(frame + 0, ieee80211_ftype_mgmt
|
||||
| ieee80211_stype_wlan_reserved);
|
||||
std::memcpy(frame + 4, addr.data(), 6); // addr1
|
||||
std::memcpy(frame + 10, address.data(), 6); // addr2
|
||||
std::memcpy(frame + 16, address.data(), 6); // addr3
|
||||
frame[22] = ieee80211_stype_wlan_reserved; // reserved (0x70)
|
||||
frame[23] = client_cmd::client_pair_resp;
|
||||
std::uint8_t data[9] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87,
|
||||
0x1f };
|
||||
std::memcpy(frame + 24, data, sizeof(data));
|
||||
|
||||
return send_wlan({ frame, frame + sizeof(frame) });
|
||||
}
|
||||
|
||||
auto chip::send_client_command(std::uint8_t wcid,
|
||||
std::span<std::uint8_t const> addr,
|
||||
client_cmd cmd,
|
||||
std::span<std::uint8_t const> data) -> int
|
||||
{
|
||||
auto address = mac_address();
|
||||
|
||||
// Payload: [info(8)][txwi(20)][hdr(22)][0x70][cmd][data].
|
||||
auto txwi = build_txwi(wcid - 1, 22 + 2 + data.size());
|
||||
std::vector<std::uint8_t> payload(8 + txwi.size() + 22 + 2 + data.size());
|
||||
|
||||
// info: {0, 0, 0, wcid-1, 0, 0, 0, 0}.
|
||||
payload[3] = wcid - 1;
|
||||
std::memcpy(payload.data() + 8, txwi.data(), txwi.size());
|
||||
|
||||
auto hdr = payload.data() + 28;
|
||||
xone::store_le16(hdr + 0, ieee80211_ftype_mgmt
|
||||
| ieee80211_stype_wlan_reserved);
|
||||
std::memcpy(hdr + 4, addr.data(), 6); // addr1
|
||||
std::memcpy(hdr + 10, address.data(), 6); // addr2
|
||||
std::memcpy(hdr + 16, address.data(), 6); // addr3
|
||||
hdr[22] = ieee80211_stype_wlan_reserved; // reserved (0x70)
|
||||
hdr[23] = static_cast<std::uint8_t>(cmd);
|
||||
if (!data.empty())
|
||||
std::memcpy(hdr + 24, data.data(), data.size());
|
||||
|
||||
return send_command(0, payload.data(), payload.size());
|
||||
}
|
||||
|
||||
auto chip::set_client_key(std::uint8_t wcid,
|
||||
std::span<std::uint8_t const> key) -> int
|
||||
{
|
||||
if (key.size() != xone_mt_wcid_key_len)
|
||||
return -22; // -EINVAL
|
||||
|
||||
// IV: {0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00}.
|
||||
std::uint8_t iv[8] = { 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00 };
|
||||
auto attr = field_prep(mt_wcid_attr_pkey_mode, cipher_type::cipher_aes_ccmp)
|
||||
| mt_wcid_attr_pairwise;
|
||||
|
||||
if (auto err = write_burst(mt_wcid_key(wcid), key.data(), key.size());
|
||||
err != 0)
|
||||
return err;
|
||||
|
||||
if (auto err = write_burst(mt_wcid_iv(wcid), iv, sizeof(iv)); err != 0)
|
||||
return err;
|
||||
|
||||
return write_burst(mt_wcid_attr(wcid), &attr, sizeof(attr));
|
||||
}
|
||||
|
||||
auto chip::remove_client(std::uint8_t wcid) -> int
|
||||
{
|
||||
// REMOVE_CLIENT ms_command: {wcid-1, 0, 0, 0}.
|
||||
std::uint8_t data[4] = { static_cast<std::uint8_t>(wcid - 1), 0x00, 0x00,
|
||||
0x00 };
|
||||
if (auto err = send_ms_command(ms_command::ms_remove_client,
|
||||
data, sizeof(data)); err != 0)
|
||||
return err;
|
||||
|
||||
// Zero the WCID regions.
|
||||
std::uint8_t addr[6] = {};
|
||||
std::uint8_t iv[8] = {};
|
||||
std::uint32_t attr = 0;
|
||||
std::uint8_t key[xone_mt_wcid_key_len] = {};
|
||||
|
||||
if (auto err = write_burst(mt_wcid_addr(wcid), addr, sizeof(addr));
|
||||
err != 0)
|
||||
return err;
|
||||
|
||||
if (auto err = write_burst(mt_wcid_iv(wcid), iv, sizeof(iv)); err != 0)
|
||||
return err;
|
||||
|
||||
if (auto err = write_burst(mt_wcid_attr(wcid), &attr, sizeof(attr));
|
||||
err != 0)
|
||||
return err;
|
||||
|
||||
return write_burst(mt_wcid_key(wcid), key, sizeof(key));
|
||||
}
|
||||
|
||||
} // namespace xone::mt76
|
||||
|
||||
Reference in New Issue
Block a user