fix: complete wireless pairing frames

Match the upstream 802.11 management layouts for association responses,
pair responses, and reserved client commands by including the sequence
control and association capability fields. Reuse existing WCIDs for repeated
association requests and end pairing mode after a successful pair response.
Also restore the upstream radio calibration sequence needed for reliable
WLAN associations.

Co-Authored-By: openai/gpt-5.6-luna: fixed pairing frame layouts and lifecycle
This commit is contained in:
portersky
2026-08-29 12:47:14 +02:00
parent baebcb73c2
commit 325923751c
3 changed files with 68 additions and 35 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$")
endif()
cmake_minimum_required(VERSION 3.21)
project(xone_macos VERSION 0.1.12 LANGUAGES CXX Swift)
project(xone_macos VERSION 0.1.13 LANGUAGES CXX Swift)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+23 -6
View File
@@ -203,10 +203,23 @@ auto xone_dongle::set_pairing(bool enable) -> void
auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
{
// Find a free WCID slot (1..16).
std::array<std::uint8_t, 6> mac{};
std::memcpy(mac.data(), addr.data(), mac.size());
// Find a free WCID slot (1..16), or reuse the WCID for a retransmitted
// request from a controller already tracked by the session.
std::uint8_t wcid = 0;
bool existing = false;
{
std::lock_guard<std::mutex> guard(lock);
for (auto const& c : controllers) {
if (c.mac == mac) {
wcid = c.wcid;
existing = true;
break;
}
}
if (!existing) {
for (std::uint8_t i = 1; i <= 16; ++i) {
bool used = false;
for (auto const& c : controllers)
@@ -214,12 +227,10 @@ auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
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,
@@ -227,7 +238,7 @@ auto xone_dongle::handle_association(std::span<std::uint8_t const> addr) -> void
return;
}
{
if (!existing) {
std::lock_guard<std::mutex> guard(lock);
controllers.push_back(controller{wcid, mac});
}
@@ -274,7 +285,13 @@ auto xone_dongle::handle_client_command(std::span<std::uint8_t const> payload,
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);
if (auto err = chip->pair_client(mac); err != 0) {
xone::log_msg(xone::log_level::warn,
"api: pair response failed (%d)", err);
break;
}
// Upstream ends the 30-second pairing window after PAIR_RESP.
set_pairing(false);
xone::log_msg(xone::log_level::info,
"api: controller paired (wcid=%d)", wcid);
break;
+39 -23
View File
@@ -496,12 +496,25 @@ auto chip::set_idle_time() -> int
auto chip::calibrate_radio() -> int
{
// Enable AGC for all antennas.
write_register(mt_bbp_agc(0), 0x0000001f);
write_register(mt_bbp_agc(1), 0x0000001f);
write_register(mt_bbp_agc(2), 0x0000001f);
// Configure automatic gain control (port of upstream AGC setup).
write_register(mt_bbp_agc(8), 0x18365efa);
write_register(mt_bbp_agc(9), 0x18365efa);
return calibrate(calibration::cal_rc, 0);
// Required reset for reliable WLAN associations.
write_register(mt_mac_sys_ctrl, 0);
write_register(mt_rf_bypass_0, 0);
write_register(mt_rf_setting_0, 0);
if (auto err = calibrate(calibration::cal_temp_sensor, 0); err != 0)
return err;
if (auto err = calibrate(calibration::cal_rxdcoc, 1); err != 0)
return err;
if (auto err = calibrate(calibration::cal_rc, 0); err != 0)
return err;
write_register(mt_mac_sys_ctrl,
mt_mac_sys_ctrl_enable_rx | mt_mac_sys_ctrl_enable_tx);
return 0;
}
auto chip::get_channel_power(channel *chan) -> int
@@ -800,16 +813,18 @@ auto chip::associate_client(std::uint8_t wcid,
{
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] = {};
// struct ieee80211_mgmt (assoc_resp), 30 bytes:
// [header][capability_info][status_code][aid].
std::uint8_t mgmt[30] = {};
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)
// Capability info stays zero, matching the upstream zero-initialized
// struct. The original status code and association ID follow it.
xone::store_le16(mgmt + 26, 0x0110); // status_code
xone::store_le16(mgmt + 28, 0x0f00); // aid
// Payload: mgmt frame plus 8 bytes of zero padding.
std::vector<std::uint8_t> payload(sizeof(mgmt) + 8, 0);
@@ -832,20 +847,20 @@ 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] = {};
// struct ieee80211_hdr_3addr (24 bytes) plus the reserved command and a
// 9-byte payload: [header][0x70][PAIR_RESP][data].
std::uint8_t frame[35] = {};
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;
// seq_ctrl stays zero, matching the upstream zero-initialized header.
frame[24] = ieee80211_stype_wlan_reserved; // reserved (0x70)
frame[25] = 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));
std::memcpy(frame + 26, data, sizeof(data));
return send_wlan({ frame, frame + sizeof(frame) });
}
@@ -857,9 +872,9 @@ auto chip::send_client_command(std::uint8_t wcid,
{
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());
// Payload: [info(8)][txwi(20)][hdr(24)][0x70][cmd][data].
auto txwi = build_txwi(wcid - 1, 24 + 2 + data.size());
std::vector<std::uint8_t> payload(8 + txwi.size() + 24 + 2 + data.size());
// info: {0, 0, 0, wcid-1, 0, 0, 0, 0}.
payload[3] = wcid - 1;
@@ -871,10 +886,11 @@ auto chip::send_client_command(std::uint8_t wcid,
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);
// seq_ctrl stays zero, matching the upstream zero-initialized header.
hdr[24] = ieee80211_stype_wlan_reserved; // reserved (0x70)
hdr[25] = static_cast<std::uint8_t>(cmd);
if (!data.empty())
std::memcpy(hdr + 24, data.data(), data.size());
std::memcpy(hdr + 26, data.data(), data.size());
return send_command(0, payload.data(), payload.size());
}