218 lines
8.5 KiB
C++
218 lines
8.5 KiB
C++
/*
|
|
* mlme_harness.cpp
|
|
* Host harness for the 802.11 MLME and data path.
|
|
*
|
|
* Compiles the real IwxConnect.cpp (and the supplicant behind it) against a
|
|
* stubbed transport, so everything the driver puts on the air and everything
|
|
* it makes of what comes back can be checked without the adapter. The
|
|
* firmware/radio interaction is what remains untestable here; the frame
|
|
* construction, parsing, encapsulation and state machine are not.
|
|
*
|
|
* Speaks hex over stdio; ap_mlme.py is the peer.
|
|
*
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <iostream>
|
|
|
|
#include "Drivers/Net/Wifi/Iwx.hpp"
|
|
#include "Drivers/Net/Wifi/Ieee80211.hpp"
|
|
#include "Drivers/Net/Wifi/Wpa.hpp"
|
|
|
|
namespace Timekeeping {
|
|
uint64_t g_ms = 1000;
|
|
uint64_t GetMilliseconds() { return g_ms; }
|
|
}
|
|
|
|
// A host command is a round trip to the firmware: it takes real time, and the
|
|
// clock moves on while the service loop is inside one. Modelling that is what
|
|
// catches elapsed-time arithmetic that samples the clock once and then compares
|
|
// it against timestamps taken later in the same pass (an unsigned underflow
|
|
// that reads as an instant timeout). A frozen clock hides that class of bug
|
|
// entirely, which is why this is not simply left at a constant.
|
|
static constexpr uint64_t CMD_ROUND_TRIP_MS = 1;
|
|
|
|
using namespace Drivers::Net::Wifi;
|
|
|
|
static void puthex(const char* tag, const uint8_t* p, uint32_t n) {
|
|
printf("%s ", tag);
|
|
for (uint32_t i = 0; i < n; i++) printf("%02x", p[i]);
|
|
printf("\n");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Stubbed transport
|
|
// =============================================================================
|
|
|
|
namespace Drivers::Net::Wifi {
|
|
|
|
IwxState g_iwx;
|
|
|
|
// Host commands: record the opcode and the exact bytes, always succeed.
|
|
// The payload matters -- a struct that does not match the version the
|
|
// firmware advertises asserts it on real hardware.
|
|
static void DumpCmd(uint32_t id, const void* data, uint32_t len) {
|
|
printf("CMD %u ", id);
|
|
const uint8_t* p = (const uint8_t*)data;
|
|
for (uint32_t i = 0; i < len; i++) printf("%02x", p[i]);
|
|
printf("\n");
|
|
Timekeeping::g_ms += CMD_ROUND_TRIP_MS;
|
|
}
|
|
bool IwxSendCmdPdu(uint32_t id, const void* data, uint32_t len) {
|
|
DumpCmd(id, data, len);
|
|
return true;
|
|
}
|
|
bool IwxSendCmdStatus(uint32_t id, const void* data, uint32_t len,
|
|
uint32_t* statusOut) {
|
|
DumpCmd(id, data, len);
|
|
// ADD_STA reports success in the low byte; everything else uses 0.
|
|
if (statusOut) *statusOut = (id == IWX_ADD_STA || id == IWX_ADD_STA_KEY)
|
|
? IWX_ADD_STA_SUCCESS : 0;
|
|
return true;
|
|
}
|
|
bool IwxSendCmd(IwxHostCmd& cmd) { (void)cmd; return true; }
|
|
|
|
int IwxLookupCmdVer(uint8_t group, uint8_t cmd) {
|
|
// Match what AX211 firmware 89 advertises for the versions the MLME
|
|
// branches on.
|
|
if (group == IWX_DATA_PATH_GROUP && cmd == IWX_RLC_CONFIG_CMD) return 2;
|
|
if (group == IWX_DATA_PATH_GROUP && cmd == IWX_SCD_QUEUE_CONFIG_CMD) return 3;
|
|
return -1;
|
|
}
|
|
int IwxLookupNotifVer(uint8_t, uint8_t) { return 7; }
|
|
|
|
bool IwxAbortScan() { return true; }
|
|
|
|
bool IwxEnableTxq(IwxTxRing& ring, int staId, int qid, int tid) {
|
|
(void)staId; (void)tid;
|
|
ring.Qid = qid;
|
|
ring.Active = true;
|
|
ring.StageSlots = IWX_TX_STAGE_SLOTS;
|
|
printf("TXQ-UP %d\n", qid);
|
|
return true;
|
|
}
|
|
void IwxDisableTxq(IwxTxRing& ring, int staId, int tid) {
|
|
(void)staId; (void)tid;
|
|
ring.Active = false;
|
|
printf("TXQ-DOWN\n");
|
|
}
|
|
|
|
// Capture what the driver wants to put on the air.
|
|
bool IwxTxFrame(IwxTxRing& ring, const uint8_t* hdr, uint32_t hdrLen,
|
|
const uint8_t* payload, uint32_t payloadLen,
|
|
bool encrypt, bool fixedRate) {
|
|
if (!ring.Active) { printf("TX-DROP queue-down\n"); return false; }
|
|
printf("TX enc=%d rate=%d ", encrypt ? 1 : 0, fixedRate ? 1 : 0);
|
|
for (uint32_t i = 0; i < hdrLen; i++) printf("%02x", hdr[i]);
|
|
printf(" ");
|
|
for (uint32_t i = 0; i < payloadLen; i++) printf("%02x", payload[i]);
|
|
printf("\n");
|
|
return true;
|
|
}
|
|
|
|
bool IwxSetKey(const uint8_t* key, uint32_t keyLen, uint8_t keyIdx,
|
|
bool pairwise, uint8_t cipher, const uint8_t* rsc) {
|
|
printf("KEY pairwise=%d idx=%u cipher=%u ", pairwise ? 1 : 0, keyIdx, cipher);
|
|
for (uint32_t i = 0; i < keyLen; i++) printf("%02x", key[i]);
|
|
printf(" ");
|
|
if (rsc) for (int i = 0; i < 6; i++) printf("%02x", rsc[i]);
|
|
printf("\n");
|
|
return true;
|
|
}
|
|
|
|
bool IwxRemoveKey(uint8_t keyIdx, bool pairwise, uint8_t cipher,
|
|
uint32_t keyLen) {
|
|
printf("KEY-REMOVE pairwise=%d idx=%u cipher=%u len=%u\n",
|
|
pairwise ? 1 : 0, keyIdx, cipher, keyLen);
|
|
return true;
|
|
}
|
|
|
|
// Sink normally provided by Wifi.cpp.
|
|
void WifiRxEthernet(const uint8_t* frame, uint32_t len) {
|
|
puthex("ETH", frame, len);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Driver
|
|
// =============================================================================
|
|
|
|
static int unhex(const std::string& s, uint8_t* out) {
|
|
int n = 0;
|
|
for (size_t i = 0; i + 1 < s.size(); i += 2) {
|
|
unsigned v; sscanf(s.c_str() + i, "%2x", &v); out[n++] = (uint8_t)v;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int main() {
|
|
// A firmware state good enough for the MLME: alive, one antenna, a MAC.
|
|
g_iwx.State = IwxFwState::Running;
|
|
g_iwx.Fw.PhyConfig = (1u << IWX_FW_PHY_CFG_TX_CHAIN_POS)
|
|
| (1u << IWX_FW_PHY_CFG_RX_CHAIN_POS);
|
|
g_iwx.Nvm.ValidTxAnt = 1;
|
|
g_iwx.Nvm.ValidRxAnt = 1;
|
|
|
|
std::string line;
|
|
uint8_t buf[4096];
|
|
|
|
while (std::getline(std::cin, line)) {
|
|
if (line.rfind("MAC ", 0) == 0) {
|
|
unhex(line.substr(4), g_iwx.Nvm.HwAddr);
|
|
printf("DONE\n");
|
|
} else if (line.rfind("CAPA ", 0) == 0) {
|
|
unsigned bit, on;
|
|
sscanf(line.c_str(), "CAPA %u %u", &bit, &on);
|
|
if (on) g_iwx.Fw.Capa[bit / 8] |= (uint8_t)(1 << (bit % 8));
|
|
else g_iwx.Fw.Capa[bit / 8] &= (uint8_t)~(1 << (bit % 8));
|
|
printf("DONE\n");
|
|
} else if (line.rfind("CONNECT ", 0) == 0) {
|
|
// CONNECT <bssid> <channel> <is5> <ssid> <pass|-> <rsnie|->
|
|
char bssid[64], ssid[64], pass[128], rsn[256];
|
|
unsigned chan, is5;
|
|
sscanf(line.c_str(), "CONNECT %63s %u %u %63s %127s %255s",
|
|
bssid, &chan, &is5, ssid, pass, rsn);
|
|
uint8_t bs[6]; unhex(bssid, bs);
|
|
uint8_t ie[128]; int ieLen = 0;
|
|
if (strcmp(rsn, "-") != 0) ieLen = unhex(rsn, ie);
|
|
bool ok = IwxConnectStart(bs, (uint8_t)chan, is5 != 0, ssid,
|
|
strcmp(pass, "-") == 0 ? nullptr : pass,
|
|
ieLen ? ie : nullptr, (uint32_t)ieLen,
|
|
100, 2);
|
|
printf("CONNECT-OK %d STATE %d\n", ok ? 1 : 0, IwxConnectState());
|
|
} else if (line.rfind("RXMGMT ", 0) == 0) {
|
|
int n = unhex(line.substr(7), buf);
|
|
IwxConnectRxMgmt(buf, (uint32_t)n);
|
|
printf("DONE STATE %d\n", IwxConnectState());
|
|
} else if (line.rfind("RXDATA ", 0) == 0) {
|
|
int n = unhex(line.substr(7), buf);
|
|
IwxConnectRxData(buf, (uint32_t)n);
|
|
printf("DONE STATE %d\n", IwxConnectState());
|
|
} else if (line.rfind("TXETH ", 0) == 0) {
|
|
int n = unhex(line.substr(6), buf);
|
|
bool ok = IwxConnectSendEthernet(buf, (uint32_t)n);
|
|
printf("TXETH-OK %d\n", ok ? 1 : 0);
|
|
} else if (line.rfind("SERVICE", 0) == 0) {
|
|
IwxConnectService();
|
|
printf("DONE STATE %d LINK %d\n", IwxConnectState(), IwxLinkUp() ? 1 : 0);
|
|
} else if (line.rfind("TICK ", 0) == 0) {
|
|
unsigned ms; sscanf(line.c_str(), "TICK %u", &ms);
|
|
Timekeeping::g_ms += ms;
|
|
printf("DONE\n");
|
|
} else if (line.rfind("ABORT", 0) == 0) {
|
|
IwxConnectAbort();
|
|
printf("DONE STATE %d\n", IwxConnectState());
|
|
} else if (line.rfind("STATE", 0) == 0) {
|
|
printf("STATE %d LINK %d\n", IwxConnectState(), IwxLinkUp() ? 1 : 0);
|
|
} else if (line.rfind("QUIT", 0) == 0) {
|
|
break;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
return 0;
|
|
}
|