/* * brcmf_harness.cpp * Host harness for the Broadcom FullMAC driver's protocol and connection * layers (BrcmfProto.cpp, BrcmfConnect.cpp, with the real supplicant). * * The SDIO bus and the backplane are replaced by a simulated CYW4345x * firmware: it decodes every SDPCM frame the driver writes to function 2 * (CDC ioctls, BDC data), answers ioctls the way the firmware does, and * feeds back events and data frames through the same framing. The access * point side of the WPA2 handshake is written here independently of * Wpa.cpp, on the crypto primitives crypto_vectors checks against the * published vectors. * * What stays untested is everything that needs the chip: the SDIO host, * the backplane, firmware download (BrcmfSdio.cpp, BrcmfChip.cpp), and * the firmware's own opinion of the structures it is sent. * * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include #include #include #include #include "Drivers/Net/Wifi/Brcmf.hpp" #include "Drivers/Net/Wifi/BrcmfSdio.hpp" #include "Drivers/Net/Wifi/Ieee80211.hpp" #include "Drivers/Net/Wifi/Wpa.hpp" #include "Drivers/Platform/RpiFirmware.hpp" #include "Libraries/Crypto.hpp" using namespace Drivers::Net::Wifi; using Bytes = std::vector; namespace Timekeeping { uint64_t g_ms = 1000; uint64_t GetMilliseconds() { return g_ms; } void BusyWaitMs(uint64_t ms) { g_ms += ms; } void BusyWaitUs(uint64_t us) { g_ms += (us + 999) / 1000; } } static int g_fails = 0; static void check(bool cond, const char* what) { printf(" %s %s\n", cond ? "PASS" : "FAIL", what); if (!cond) g_fails++; } static uint16_t be16(const uint8_t* p) { return (uint16_t)(p[0] << 8 | p[1]); } static uint16_t le16(const uint8_t* p) { return (uint16_t)(p[0] | p[1] << 8); } static uint32_t le32(const uint8_t* p) { uint32_t v; memcpy(&v, p, 4); return v; } static void putle16(Bytes& b, uint16_t v) { b.push_back((uint8_t)v); b.push_back((uint8_t)(v >> 8)); } static void putle32(Bytes& b, uint32_t v) { for (int i = 0; i < 4; i++) b.push_back((uint8_t)(v >> (8 * i))); } static void putbe16(Bytes& b, uint16_t v) { b.push_back((uint8_t)(v >> 8)); b.push_back((uint8_t)v); } static void putbe32(Bytes& b, uint32_t v) { for (int i = 3; i >= 0; i--) b.push_back((uint8_t)(v >> (8 * i))); } static void append(Bytes& b, const uint8_t* p, size_t n) { b.insert(b.end(), p, p + n); } static const uint8_t PLACEHOLDER_MAC[6] = { 0xb8, 0x27, 0xeb, 0x74, 0xf2, 0x6c }; static const uint8_t BOARD_MAC[6] = { 0xdc, 0xa6, 0x32, 0x01, 0x02, 0xff }; static const uint8_t WLAN_MAC[6] = { 0xdc, 0xa6, 0x32, 0x01, 0x03, 0x00 }; // board + 1, with carry static const uint8_t AP_OPEN[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x01 }; static const uint8_t AP_WPA[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x02 }; // ============================================================================= // Simulated firmware // ============================================================================= struct Ioctl { uint32_t Cmd; bool Set; std::string Name; // iovar name, for GET_VAR/SET_VAR Bytes Data; // what follows the name (or the whole payload) uint64_t Order; }; struct Fw { std::deque ToHost; // complete SDPCM frames Bytes Cur; size_t CurOff = 0; uint8_t HostSeq = 0; // next sequence number expected uint8_t Window = 8; bool SeqError = false; bool Mute = false; // stop answering ioctls std::vector Ioctls; std::vector> DataTx; // Ethernet frames, order uint64_t Order = 0; bool AutoJoin = true; void (*OnEapol)(const Bytes& body) = nullptr; }; static Fw fw; static void QueueFrame(uint8_t channel, const Bytes& payload) { Bytes f(12, 0); uint16_t len = (uint16_t)(12 + payload.size()); f[0] = (uint8_t)len; f[1] = (uint8_t)(len >> 8); f[2] = (uint8_t)~len; f[3] = (uint8_t)(~len >> 8); f[4] = 0; f[5] = channel; f[7] = 12; f[9] = (uint8_t)(fw.HostSeq + fw.Window); // credit append(f, payload.data(), payload.size()); fw.ToHost.push_back(f); } static Bytes Bdc(const Bytes& eth) { Bytes b = { 0x20, 0, 0, 0 }; append(b, eth.data(), eth.size()); return b; } static void QueueEvent(uint32_t type, uint32_t status, uint32_t reason, uint16_t flags, const uint8_t* addr, const Bytes& data = {}) { Bytes e; append(e, WLAN_MAC, 6); append(e, AP_WPA, 6); putbe16(e, 0x886C); putbe16(e, 0x8001); putbe16(e, (uint16_t)(48 + data.size())); e.push_back(1); e.push_back(0x00); e.push_back(0x10); e.push_back(0x18); putbe16(e, 1); putbe16(e, 2); // version putbe16(e, flags); putbe32(e, type); putbe32(e, status); putbe32(e, reason); putbe32(e, 0); // auth type putbe32(e, (uint32_t)data.size()); Bytes a(6, 0); if (addr) memcpy(a.data(), addr, 6); append(e, a.data(), 6); Bytes ifname(16, 0); memcpy(ifname.data(), "wlan0", 5); append(e, ifname.data(), 16); e.push_back(0); // ifidx e.push_back(0); // bsscfgidx append(e, data.data(), data.size()); QueueFrame(1, Bdc(e)); } static void QueueEthernet(const uint8_t* src, uint16_t type, const Bytes& body) { Bytes e; append(e, WLAN_MAC, 6); append(e, src, 6); putbe16(e, type); append(e, body.data(), body.size()); QueueFrame(2, Bdc(e)); } // One brcmf_bss_info_le, laid out as Linux's struct. static Bytes BssInfo(const uint8_t* bssid, const char* ssid, uint16_t chanspec, uint8_t ctl, int16_t rssi, uint16_t caps, const Bytes& ies) { Bytes b(128, 0); memcpy(&b[0], "\x6d\x00\x00\x00", 4); // version 109 uint32_t len = (uint32_t)(128 + ies.size()); memcpy(&b[4], &len, 4); memcpy(&b[8], bssid, 6); uint16_t bcn = 100; memcpy(&b[14], &bcn, 2); memcpy(&b[16], &caps, 2); b[18] = (uint8_t)strlen(ssid); memcpy(&b[19], ssid, strlen(ssid)); memcpy(&b[72], &chanspec, 2); b[76] = 3; memcpy(&b[78], &rssi, 2); b[88] = ctl; uint16_t ieOff = 128; memcpy(&b[116], &ieOff, 2); uint32_t ieLen = (uint32_t)ies.size(); memcpy(&b[120], &ieLen, 4); append(b, ies.data(), ies.size()); return b; } static const Bytes RSN_CCMP = { 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0x00, 0x00, }; static const Bytes RSN_GCMP = { 0x01, 0x00, 0x00, 0x0f, 0xac, 0x08, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x08, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0x00, 0x00, }; static Bytes Ies(const char* ssid, uint8_t channel, const Bytes* rsn) { Bytes ie; ie.push_back(0); ie.push_back((uint8_t)strlen(ssid)); append(ie, (const uint8_t*)ssid, strlen(ssid)); ie.push_back(1); ie.push_back(4); ie.push_back(0x82); ie.push_back(0x84); ie.push_back(0x0b); ie.push_back(0x16); ie.push_back(3); ie.push_back(1); ie.push_back(channel); if (rsn) { ie.push_back(48); ie.push_back((uint8_t)rsn->size()); append(ie, rsn->data(), rsn->size()); } return ie; } static void QueueEscan(uint16_t syncId) { Bytes bss1 = BssInfo(AP_OPEN, "OpenNet", 0x1006, 6, -48, 0x0401, Ies("OpenNet", 6, nullptr)); Bytes bss2 = BssInfo(AP_WPA, "SecureNet", 0xd024, 36, -61, 0x0411, Ies("SecureNet", 36, &RSN_CCMP)); for (const Bytes* bss : { &bss1, &bss2 }) { Bytes r; putle32(r, (uint32_t)(12 + bss->size())); putle32(r, 1); putle16(r, syncId); putle16(r, 1); append(r, bss->data(), bss->size()); QueueEvent(69, 8, 0, 0, nullptr, r); // PARTIAL } QueueEvent(69, 0, 0, 0, nullptr); // done } static void FwControl(const uint8_t* p, size_t len) { uint32_t cmd = le32(p), dlen = le32(p + 4), flags = le32(p + 8); bool set = flags & 2; const uint8_t* data = p + 16; size_t avail = len - 16; if (dlen > avail) dlen = (uint32_t)avail; Ioctl io{cmd, set, "", {}, fw.Order++}; if (cmd == 262 || cmd == 263) { size_t n = strnlen((const char*)data, dlen); io.Name.assign((const char*)data, n); if (n + 1 < dlen) io.Data.assign(data + n + 1, data + dlen); } else { io.Data.assign(data, data + dlen); } fw.Ioctls.push_back(io); if (fw.Mute) return; int32_t status = 0; Bytes resp(dlen, 0); if (cmd == 262) { if (io.Name == "cur_etheraddr") memcpy(resp.data(), PLACEHOLDER_MAC, 6); else if (io.Name == "ver") { const char* v = "wl0: Aug 29 2023 01:51:02 version 7.45.265 (28bca26 CY) FWID 01-b677b91b\n"; memcpy(resp.data(), v, strlen(v) < dlen ? strlen(v) : dlen); } else if (io.Name == "clmload_status") { } else status = -23; // BCME_UNSUPPORTED } else if (cmd == 263) { if (io.Name == "escan") { QueueEscan(le16(io.Data.data() + 6)); } else if (io.Name == "join" && fw.AutoJoin) { QueueEvent(0, 0, 0, 0, io.Data.data() + 56); QueueEvent(16, 0, 0, 1, io.Data.data() + 56); } else if (io.Name == "bus:rxglom" || io.Name == "mfp" || io.Name == "sup_wpa") { status = -23; } } else if (cmd == 217) { // valid channels uint32_t list[3] = { 2, 1, 36 }; memcpy(resp.data(), list, sizeof(list) < dlen ? sizeof(list) : dlen); } Bytes r; putle32(r, cmd); putle32(r, dlen); putle32(r, (flags & 0xFFFF0002u) | (status < 0 ? 1 : 0)); putle32(r, (uint32_t)status); append(r, resp.data(), resp.size()); QueueFrame(0, r); } static void FwReceive(const uint8_t* f, size_t busLen) { uint16_t len = le16(f), check = le16(f + 2); if ((uint16_t)(len ^ check) != 0xFFFF || len > busLen) { fw.SeqError = true; return; } if (f[4] != fw.HostSeq) fw.SeqError = true; fw.HostSeq = (uint8_t)(f[4] + 1); uint8_t ch = f[5] & 0x0F, off = f[7]; const uint8_t* p = f + off; size_t plen = len - off; if (ch == 0) { FwControl(p, plen); } else if (ch == 2) { if (plen < 4 || (p[0] >> 4) != 2 || p[3] != 0) { fw.SeqError = true; return; } Bytes eth(p + 4, p + plen); fw.DataTx.push_back({eth, fw.Order++}); if (eth.size() >= 14 && be16(ð[12]) == 0x888E && fw.OnEapol) fw.OnEapol(Bytes(eth.begin() + 14, eth.end())); } } // ============================================================================= // What the driver links against instead of the hardware // ============================================================================= namespace Drivers::Net::Wifi { BrcmfDevice g_brcmf; bool BrcmfBpRead32(uint32_t addr, uint32_t& value) { value = 0; if (addr == g_brcmf.SdioCoreBase + 0x20 && (!fw.ToHost.empty() || !fw.Cur.empty())) value = 0x40; // I_HMB_FRAME_IND return true; } bool BrcmfBpWrite32(uint32_t, uint32_t) { return true; } bool BrcmfBpReadMem(uint32_t, uint8_t*, uint32_t) { return true; } bool BrcmfBpWriteMem(uint32_t, const uint8_t*, uint32_t) { return true; } bool BrcmfBpSelectWindow(uint32_t) { return true; } bool BrcmfF1Read8(uint32_t, uint8_t& v) { v = 0; return true; } bool BrcmfF1Write8(uint32_t, uint8_t) { return true; } bool BrcmfChipBringUp() { g_brcmf.FwName = "43456"; g_brcmf.SdioCoreBase = 0x18002000; g_brcmf.HostIntMask = 0x200000F0; return true; } void BrcmfChipPowerOff() {} void BrcmfDumpConsole() {} uint8_t* BrcmfLoadFile(const char* ext, uint32_t& size, bool) { if (strcmp(ext, "clm_blob") != 0) return nullptr; size = 3000; uint8_t* p = (uint8_t*)malloc(size); for (uint32_t i = 0; i < size; i++) p[i] = (uint8_t)i; return p; } struct Seen { Bytes Bssid; Bytes Ies; uint16_t Beacon, Caps; uint8_t Channel; int8_t Rssi; }; static std::vector g_seen; static std::vector g_delivered; void WifiRxBssInfo(const uint8_t* bssid, uint16_t beaconInterval, uint16_t capability, const uint8_t* ies, uint32_t iesLen, uint8_t channel, int8_t rssiDbm) { g_seen.push_back({Bytes(bssid, bssid + 6), Bytes(ies, ies + iesLen), beaconInterval, capability, channel, rssiDbm}); } void WifiRxEthernet(const uint8_t* frame, uint32_t len) { g_delivered.push_back(Bytes(frame, frame + len)); } // Routed by Wifi.cpp in the kernel. bool WpaTxEapol(const uint8_t* body, uint32_t len) { return BrcmfWpaTxEapol(body, len); } bool WpaInstallPtk(const uint8_t* tk, uint32_t tkLen, uint8_t cipher) { return BrcmfWpaInstallPtk(tk, tkLen, cipher); } bool WpaInstallGtk(const uint8_t* gtk, uint32_t gtkLen, uint8_t keyIdx, uint8_t cipher, const uint8_t* rsc) { return BrcmfWpaInstallGtk(gtk, gtkLen, keyIdx, cipher, rsc); } } namespace Drivers::Net::Wifi::Sdio { bool HostInit(uint64_t, uint32_t) { return true; } bool CardInit() { return true; } bool Read8(uint32_t, uint32_t, uint8_t& v) { v = 0; return true; } bool Write8(uint32_t, uint32_t, uint8_t) { return true; } bool EnableFunction(uint32_t, bool, uint64_t) { return true; } bool EnableCardInterrupts(uint8_t) { return true; } void Abort(uint32_t) {} bool CardInterruptPending() { return !fw.ToHost.empty(); } bool IsInitialized() { return true; } bool ReadBlock(uint32_t fn, uint32_t, bool, uint8_t* buffer, uint32_t len) { if (fn != FN2 || (len & 3)) return false; Timekeeping::g_ms++; if (fw.Cur.empty()) { if (fw.ToHost.empty()) { memset(buffer, 0, len); return true; } fw.Cur = fw.ToHost.front(); fw.ToHost.pop_front(); fw.CurOff = 0; } for (uint32_t i = 0; i < len; i++, fw.CurOff++) buffer[i] = fw.CurOff < fw.Cur.size() ? fw.Cur[fw.CurOff] : 0; if (fw.CurOff >= ((fw.Cur.size() + 3) & ~(size_t)3)) fw.Cur.clear(); return true; } bool WriteBlock(uint32_t fn, uint32_t, bool, const uint8_t* buffer, uint32_t len) { if (fn != FN2 || (len & 3)) return false; if (len > FN2_BLOCK && len % FN2_BLOCK) fw.SeqError = true; // padded to whole blocks Timekeeping::g_ms++; FwReceive(buffer, len); return true; } } namespace Drivers::Platform::RpiFirmware { bool GetBoardMacAddress(uint8_t mac[6]) { memcpy(mac, BOARD_MAC, 6); return true; } bool SetGpioOutput(uint32_t, bool) { return true; } } // ============================================================================= // An independent WPA2-PSK authenticator // ============================================================================= namespace Ap { using namespace Kt::Crypto; static const char* SSID = "SecureNet"; static const char* PASS = "supersecret123"; static uint8_t pmk[32], ptk[48], anonce[32], snonce[32], gtk[16]; static uint64_t replay = 0; static Bytes lastMsg2, lastMsg4, staRsn; static bool msg2MicOk = false, msg4MicOk = false; static void Prf(const uint8_t* key, const char* label, const Bytes& data, uint8_t* out, size_t n) { uint8_t counter = 0; size_t done = 0; while (done < n) { Bytes m((const uint8_t*)label, (const uint8_t*)label + strlen(label) + 1); append(m, data.data(), data.size()); m.push_back(counter++); uint8_t h[20]; HmacSha1(key, 32, m.data(), m.size(), h); size_t take = n - done < 20 ? n - done : 20; memcpy(out + done, h, take); done += take; } } static Bytes KeyFrame(uint16_t info, const Bytes& keyData, bool mic) { Bytes k(99, 0); k[0] = 2; k[1] = 3; uint16_t bodyLen = (uint16_t)(95 + keyData.size()); k[2] = (uint8_t)(bodyLen >> 8); k[3] = (uint8_t)bodyLen; k[4] = 2; k[5] = (uint8_t)(info >> 8); k[6] = (uint8_t)info; k[8] = 16; for (int i = 0; i < 8; i++) k[9 + i] = (uint8_t)(replay >> (56 - 8 * i)); memcpy(&k[17], anonce, 32); k[97] = (uint8_t)(keyData.size() >> 8); k[98] = (uint8_t)keyData.size(); append(k, keyData.data(), keyData.size()); if (mic) { uint8_t h[20]; HmacSha1(ptk, 16, k.data(), k.size(), h); memcpy(&k[81], h, 16); } return k; } static bool MicOk(Bytes frame) { if (frame.size() < 99) return false; uint8_t mic[16]; memcpy(mic, &frame[81], 16); memset(&frame[81], 0, 16); uint8_t h[20]; HmacSha1(ptk, 16, frame.data(), frame.size(), h); return memcmp(h, mic, 16) == 0; } static void Start(const char* pass) { Pbkdf2Sha1(pass, strlen(pass), (const uint8_t*)SSID, strlen(SSID), 4096, pmk, 32); for (int i = 0; i < 32; i++) anonce[i] = (uint8_t)(0xA0 + i); for (int i = 0; i < 16; i++) gtk[i] = (uint8_t)(0x30 + i); replay = 1; lastMsg2.clear(); lastMsg4.clear(); staRsn.clear(); msg2MicOk = msg4MicOk = false; QueueEthernet(AP_WPA, 0x888E, KeyFrame(0x008A, {}, false)); // msg 1 } static void OnEapol(const Bytes& f) { if (f.size() < 99 || f[1] != 3) return; uint16_t info = be16(&f[5]); if (!(info & 0x0100)) return; if (lastMsg2.empty()) { lastMsg2 = f; memcpy(snonce, &f[17], 32); Bytes data; const uint8_t* a = memcmp(AP_WPA, WLAN_MAC, 6) < 0 ? AP_WPA : WLAN_MAC; const uint8_t* b = a == AP_WPA ? WLAN_MAC : AP_WPA; append(data, a, 6); append(data, b, 6); const uint8_t* n1 = memcmp(anonce, snonce, 32) < 0 ? anonce : snonce; const uint8_t* n2 = n1 == anonce ? snonce : anonce; append(data, n1, 32); append(data, n2, 32); Prf(pmk, "Pairwise key expansion", data, ptk, 48); msg2MicOk = MicOk(f); uint16_t kdl = be16(&f[97]); staRsn.assign(f.begin() + 99, f.begin() + 99 + kdl); if (!msg2MicOk) { // Wrong passphrase: the AP gives up and deauthenticates. QueueEvent(6, 0, 15, 0, AP_WPA); return; } // Message 3: our RSN IE and the GTK KDE, key-wrapped under the KEK. Bytes plain = { 0x30, (uint8_t)RSN_CCMP.size() }; append(plain, RSN_CCMP.data(), RSN_CCMP.size()); Bytes kde = { 0xdd, 22, 0x00, 0x0f, 0xac, 0x01, 0x01, 0x00 }; append(kde, gtk, 16); append(plain, kde.data(), kde.size()); plain.push_back(0xdd); while (plain.size() % 8) plain.push_back(0); Bytes wrapped(plain.size() + 8); AesKeyWrap(ptk + 16, 16, plain.data(), plain.size(), wrapped.data()); replay = 2; QueueEthernet(AP_WPA, 0x888E, KeyFrame(0x13CA, wrapped, true)); } else if (lastMsg4.empty()) { lastMsg4 = f; msg4MicOk = MicOk(f); } } } // ============================================================================= // Cases // ============================================================================= static void Pump(int rounds = 20) { for (int i = 0; i < rounds; i++) { BrcmfService(); Timekeeping::g_ms += 5; } } static const Ioctl* LastSet(const char* name) { for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it) if (it->Set && it->Name == name) return &*it; return nullptr; } static uint32_t SetInt(const char* name) { const Ioctl* io = LastSet(name); return io && io->Data.size() >= 4 ? le32(io->Data.data()) : 0xDEADBEEF; } static const Ioctl* LastCmd(uint32_t cmd) { for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it) if (it->Cmd == cmd) return &*it; return nullptr; } static constexpr int CONN_IDLE = 0, CONN_ASSOCIATING = 4, CONN_HANDSHAKING = 6, CONN_CONNECTED = 7, CONN_FAILED = 8; static void CaseInit() { printf("== bring-up and preinit ==\n"); check(BrcmfProbe(0xFE300000), "the Arasan controller is claimed"); check(BrcmfCompleteInit(), "preinit completes against the firmware"); check(!fw.SeqError, "every frame carries the next sequence number and a valid header"); check(BrcmfGetState() == BrcmfState::Running, "the radio is running"); check(memcmp(g_brcmf.Mac, WLAN_MAC, 6) == 0, "the NVRAM placeholder MAC becomes the board address + 1"); const Ioctl* mac = LastSet("cur_etheraddr"); check(mac && mac->Data.size() == 6 && memcmp(mac->Data.data(), WLAN_MAC, 6) == 0, "and is programmed into the firmware"); check(strcmp(g_brcmf.FwVersion, "7.45.265") == 0, "the firmware version is read from \"ver\""); std::vector clm; for (auto& io : fw.Ioctls) if (io.Set && io.Name == "clmload") clm.push_back(&io); check(clm.size() == 3, "a 3000-byte CLM blob goes down in three chunks"); if (clm.size() == 3) { check(le16(clm[0]->Data.data()) == 0x1002 && le16(clm[1]->Data.data()) == 0x1000 && le16(clm[2]->Data.data()) == 0x1004, "flagged begin / middle / end, handler version 1"); check(le16(clm[0]->Data.data() + 2) == 2, "of type CLM"); check(le32(clm[0]->Data.data() + 4) == 1400 && le32(clm[2]->Data.data() + 4) == 200, "with 1400-byte chunks and the remainder last"); check(clm[1]->Data[12] == (uint8_t)1400 && clm[2]->Data[12 + 199] == (uint8_t)2999, "carrying the blob's bytes in order"); } const Ioctl* ev = LastSet("event_msgs"); bool evOk = ev && ev->Data.size() == 16; if (evOk) { for (uint32_t e : { 0u, 5u, 6u, 12u, 16u, 69u }) evOk = evOk && (ev->Data[e / 8] & (1u << (e % 8))); } check(evOk, "events for join, link, deauth/disassoc and escan are enabled"); check(SetInt("roam_off") == 1, "firmware roaming is off"); const Ioctl* up = LastCmd(2); check(up && up->Set, "WLC_UP is sent"); const Ioctl* pm = LastCmd(86); check(pm && le32(pm->Data.data()) == 0, "power save is off"); check(g_brcmf.ChannelCount == 2 && g_brcmf.Band24 && g_brcmf.Band5, "the valid channel list gives the channel count and bands"); uint32_t v = 0; check(!BrcmfIovarGetInt("no_such_iovar", v, true), "an iovar the firmware rejects reports failure"); check(BrcmfGetState() == BrcmfState::Running, "without counting against the firmware's health"); } static void CaseScan() { printf("== escan ==\n"); check(BrcmfStartScan(), "a scan starts"); const Ioctl* es = LastSet("escan"); check(es && es->Data.size() == 72, "escan carries the 72-byte v1 parameter block"); if (es && es->Data.size() == 72) { const uint8_t* p = es->Data.data(); check(le32(p) == 1 && le16(p + 4) == 1, "version 1, action start"); bool bcast = true; for (int i = 0; i < 6; i++) bcast = bcast && p[44 + i] == 0xFF; check(bcast && p[50] == 2 && p[51] == 0, "broadcast BSSID, any BSS type, active"); check(le32(p + 52) == 0xFFFFFFFF && le32(p + 68) == 0, "default dwells, every channel"); } Pump(); check(!BrcmfScanActive(), "the completion event ends the scan"); check(g_seen.size() == 2, "both BSSs reach the scan table"); if (g_seen.size() == 2) { check(memcmp(g_seen[0].Bssid.data(), AP_OPEN, 6) == 0 && g_seen[0].Channel == 6 && g_seen[0].Rssi == -48 && g_seen[0].Beacon == 100, "BSSID, channel, RSSI, beacon interval"); check(g_seen[1].Channel == 36 && (g_seen[1].Caps & 0x10), "the 5 GHz network, privacy set"); check(g_seen[1].Ies.size() == Ies("SecureNet", 36, &RSN_CCMP).size() && g_seen[1].Ies[0] == 0, "with its information elements intact"); } } static void CaseOpenJoin() { printf("== open network ==\n"); check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "the join is accepted"); check(SetInt("wpa_auth") == 0 && SetInt("wsec") == 0 && SetInt("auth") == 0, "no WPA, no encryption, open system authentication"); const Ioctl* j = LastSet("join"); check(j && j->Data.size() == 72, "join carries brcmf_ext_join_params_le"); if (j && j->Data.size() == 72) { const uint8_t* p = j->Data.data(); check(le32(p) == 7 && memcmp(p + 4, "OpenNet", 7) == 0, "the SSID"); check(memcmp(p + 56, AP_OPEN, 6) == 0 && le32(p + 64) == 1 && le16(p + 68) == 0x1006, "the BSSID and the chanspec escan reported"); } check(BrcmfConnectState() == CONN_ASSOCIATING, "associating until the firmware says otherwise"); Pump(); check(BrcmfConnectState() == CONN_CONNECTED && BrcmfLinkUp(), "E_SET_SSID + E_LINK bring the link up"); Bytes eth; append(eth, AP_OPEN, 6); append(eth, WLAN_MAC, 6); putbe16(eth, 0x0800); for (int i = 0; i < 100; i++) eth.push_back((uint8_t)i); size_t before = fw.DataTx.size(); check(BrcmfSendEthernet(eth.data(), (uint32_t)eth.size()), "an Ethernet frame is accepted"); Pump(2); check(fw.DataTx.size() == before + 1 && fw.DataTx.back().first == eth, "and reaches the firmware unchanged behind a BDC header"); Bytes big = eth; big.resize(1514, 0x5a); check(BrcmfSendEthernet(big.data(), (uint32_t)big.size()), "a full-size frame is accepted"); Pump(2); check(fw.DataTx.back().first == big && !fw.SeqError, "and goes out padded to whole blocks"); g_delivered.clear(); QueueEthernet(AP_OPEN, 0x0800, Bytes(60, 0x11)); Pump(2); check(g_delivered.size() == 1 && g_delivered[0].size() == 74 && be16(&g_delivered[0][12]) == 0x0800, "a received data frame is delivered to the stack"); QueueEvent(6, 0, 3, 0, AP_OPEN); // DEAUTH_IND Pump(); check(BrcmfConnectState() == CONN_IDLE && !BrcmfLinkUp(), "a deauthentication takes the link down"); check(!BrcmfSendEthernet(eth.data(), (uint32_t)eth.size()), "and nothing is sent while it is down"); } static const Ioctl* FindKey(uint32_t index) { for (auto it = fw.Ioctls.rbegin(); it != fw.Ioctls.rend(); ++it) if (it->Set && it->Name == "wsec_key" && it->Data.size() == 164 && le32(it->Data.data()) == index) return &*it; return nullptr; } static void CaseWpaJoin() { printf("== WPA2-PSK ==\n"); fw.OnEapol = Ap::OnEapol; size_t ioctlsBefore = fw.Ioctls.size(); check(BrcmfConnectStart(AP_WPA, 36, "SecureNet", Ap::PASS, RSN_CCMP.data(), (uint8_t)RSN_CCMP.size()), "the join is accepted"); check(SetInt("wpa_auth") == 0x80 && SetInt("wsec") == 4 && SetInt("auth") == 0, "WPA2-PSK, AES, open system authentication"); const Ioctl* ie = LastSet("wpaie"); Bytes want = { 0x30, (uint8_t)RSN_CCMP.size() }; append(want, RSN_CCMP.data(), RSN_CCMP.size()); check(ie && ie->Data == want, "the supplicant's RSN element goes into the association request"); const Ioctl* j = LastSet("join"); check(j && le16(j->Data.data() + 68) == 0xd024, "the 5 GHz chanspec from the scan"); Pump(); check(BrcmfConnectState() == CONN_HANDSHAKING && !BrcmfLinkUp(), "association waits for the handshake before the link is usable"); Ap::Start(Ap::PASS); Pump(40); check(!Ap::lastMsg2.empty() && Ap::msg2MicOk, "message 2 carries a MIC the AP verifies"); check(Ap::staRsn == want, "and the same RSN element"); check(!Ap::lastMsg4.empty() && Ap::msg4MicOk, "message 4 follows message 3, MIC verified"); check(BrcmfConnectState() == CONN_CONNECTED && BrcmfLinkUp(), "the link is up once keyed"); const Ioctl* ptk = FindKey(0); const Ioctl* gtk = FindKey(1); check(ptk && memcmp(ptk->Data.data() + 8, Ap::ptk + 32, 16) == 0 && le32(ptk->Data.data() + 4) == 16, "the pairwise key is the TK the AP derived"); if (ptk) { const uint8_t* k = ptk->Data.data(); check(le32(k + 112) == 4 && le32(k + 116) == 0 && memcmp(k + 156, AP_WPA, 6) == 0, "AES-CCM, bound to the AP's address"); } check(gtk && memcmp(gtk->Data.data() + 8, Ap::gtk, 16) == 0, "the group key is the AP's GTK"); if (gtk) { const uint8_t* k = gtk->Data.data(); bool noEa = true; for (int i = 0; i < 6; i++) noEa = noEa && k[156 + i] == 0; check(le32(k + 116) == 2 && noEa && le32(k + 132) == 1, "primary, broadcast, receive IV set"); } uint64_t msg4Order = 0; for (auto& d : fw.DataTx) if (d.first.size() > 14 && be16(&d.first[12]) == 0x888E) msg4Order = d.second; check(ptk && msg4Order != 0 && msg4Order < ptk->Order, "message 4 reaches the radio before the pairwise key is installed"); (void)ioctlsBefore; QueueEvent(16, 0, 0, 0, AP_WPA); // link down Pump(); check(BrcmfConnectState() == CONN_IDLE, "losing the link returns to idle"); } static void CaseWrongPassphrase() { printf("== wrong passphrase ==\n"); check(BrcmfConnectStart(AP_WPA, 36, "SecureNet", "not-the-password", RSN_CCMP.data(), (uint8_t)RSN_CCMP.size()), "the join is accepted"); Pump(); Ap::Start(Ap::PASS); Pump(40); check(!Ap::lastMsg2.empty() && !Ap::msg2MicOk, "the AP rejects message 2's MIC"); check(BrcmfConnectState() == CONN_FAILED, "the deauthentication fails the join"); check(BrcmfJoinFailedOnAuth(), "and it is reported as an authentication failure"); BrcmfConnectAbort(); const Ioctl* d = LastCmd(52); check(d && d->Set && memcmp(d->Data.data() + 4, AP_WPA, 6) == 0, "abort disassociates from the AP"); check(BrcmfConnectState() == CONN_IDLE, "and leaves the connection idle"); } static void CaseRefusals() { printf("== refusals ==\n"); check(!BrcmfConnectStart(AP_WPA, 36, "SecureNet", Ap::PASS, RSN_GCMP.data(), (uint8_t)RSN_GCMP.size()), "a GCMP network is refused before anything is sent"); check(BrcmfConnectRefusedForSecurity(), "as a security refusal"); check(!BrcmfConnectStart(AP_WPA, 36, "SecureNet", nullptr, RSN_CCMP.data(), (uint8_t)RSN_CCMP.size()), "an encrypted network without a passphrase is refused"); } static void CaseJoinTimeout() { printf("== join timeout ==\n"); fw.AutoJoin = false; check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "the join is accepted"); Timekeeping::g_ms += 13000; Pump(2); check(BrcmfConnectState() == CONN_FAILED, "no answer from the firmware fails the join in bounded time"); BrcmfConnectAbort(); check(BrcmfConnectStart(AP_OPEN, 6, "OpenNet", nullptr, nullptr, 0), "another join is accepted"); QueueEvent(0, 3, 0, 0, AP_OPEN); // SET_SSID: no networks Pump(); check(BrcmfConnectState() == CONN_FAILED, "an E_SET_SSID failure fails it at once"); BrcmfConnectAbort(); fw.AutoJoin = true; } static void CaseDeadFirmware() { printf("== firmware stops answering ==\n"); fw.Mute = true; uint32_t v = 0; for (int i = 0; i < 3; i++) BrcmfIovarGetInt("ver", v, true); check(BrcmfGetState() == BrcmfState::Error, "three unanswered commands mark the radio failed"); check(!BrcmfStartScan(), "and nothing more is attempted"); fw.Mute = false; } int main() { CaseInit(); CaseScan(); CaseOpenJoin(); CaseWpaJoin(); CaseWrongPassphrase(); CaseRefusals(); CaseJoinTimeout(); CaseDeadFirmware(); if (g_fails) { printf("\n%d Broadcom case(s) FAILED\n", g_fails); return 1; } printf("\nAll Broadcom protocol and connection cases passed.\n"); return 0; }