Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ tests/chipid_probe
# Local lab notes / Claude Code state (never commit)
INVENTORY.md
.claude/
build-upstream/
44 changes: 44 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,50 @@ target_include_directories(devourer PUBLIC hal/phydm/rtl8822b)
target_include_directories(devourer PUBLIC hal/phydm/rtl8821c)
target_include_directories(devourer PUBLIC src)

# ---------------------------------------------------------------------------
# APFPV additions -- station-mode WPA2/CCMP + the APFPV IP/FPV/session layer,
# ported onto upstream's rebuilt tree. Only WPA2Supplicant/Dot11Frames/
# Wpa2Crypto/crypto/*/ApfpvDhcp/LqFeedback/Wpa2Authenticator so far (zero
# driver-layer dependency, confirmed by direct #include inspection); RxDeframe
# and ApfpvStation come later once their driver-layer API updates land.
# ---------------------------------------------------------------------------
add_library(apfpv
src/apfpv/Wpa2Supplicant.cpp src/apfpv/Wpa2Supplicant.h
src/apfpv/Dot11Frames.cpp src/apfpv/Dot11Frames.h
src/apfpv/Wpa2Crypto.cpp src/apfpv/Wpa2Crypto.h
src/apfpv/crypto/AesCcm.cpp src/apfpv/crypto/AesCcm.h
src/apfpv/crypto/AesCmac.cpp src/apfpv/crypto/AesCmac.h
src/apfpv/crypto/Sha1Hmac.cpp src/apfpv/crypto/Sha1Hmac.h
src/apfpv/ScanProbe.cpp src/apfpv/ScanProbe.h
src/ApfpvDhcp.cpp src/ApfpvDhcp.h
src/LqFeedback.cpp src/LqFeedback.h
src/Wpa2Authenticator.cpp src/Wpa2Authenticator.h)
target_compile_features(apfpv PUBLIC cxx_std_20)
target_include_directories(apfpv PUBLIC src src/apfpv src/apfpv/crypto)
if(WIN32)
target_link_libraries(apfpv PUBLIC ws2_32 bcrypt)
endif()

# ApfpvStation/RxDeframe DO need the driver layer (RtlAdapter/RtlJaguarDevice/
# RadioManagementModule) -- separate target so `apfpv` itself stays
# driver-independent (it's also used as-is on hosts with no libusb at all).
add_library(apfpv_station
src/ApfpvStation.cpp src/ApfpvStation.h
src/RxDeframe.cpp src/RxDeframe.h
src/apfpv/StationMode.cpp src/apfpv/StationMode.h
src/apfpv/StationTxDesc.cpp src/apfpv/StationTxDesc.h)
target_link_libraries(apfpv_station PUBLIC apfpv devourer)
target_compile_features(apfpv_station PUBLIC cxx_std_20)
if(NOT ANDROID)
target_include_directories(apfpv_station PUBLIC src/apfpv/compat)
endif()
# Host (non-Android) builds: Wpa2Supplicant.cpp etc. call __android_log_print
# unconditionally (ported as-is from WiFiDriver) -- same compat shim already
# proven there, reused verbatim rather than reinvented.
if(NOT ANDROID)
target_include_directories(apfpv PUBLIC src/apfpv/compat)
endif()

# --- per-chip source groups (see options above) ---
if(DEVOURER_JAGUAR1)
target_sources(devourer PRIVATE
Expand Down
94 changes: 94 additions & 0 deletions src/ApfpvDhcp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// ============================================================================
// ApfpvDhcp.cpp — minimal DHCP client, route/DNS-suppressed (impl)
// Mirrors the real VRX udhcpc.apfpv.script ("not add routes and dns for apfpv
// interfaces"): take the IP, ignore router(opt 3)/DNS(opt 6). Declarations in
// ApfpvDhcp.h. APFPV reliably hands 192.168.0.10.
// ============================================================================
#include "ApfpvDhcp.h"
#include <cstring>

namespace apfpv {

static void put32(std::vector<uint8_t>& v, uint32_t x) {
v.push_back(x>>24); v.push_back(x>>16); v.push_back(x>>8); v.push_back(x);
}

ApfpvDhcp::ApfpvDhcp(const Mac& self, SendUdpFn send) : _self(self), _send(std::move(send)) {}

// Build a BOOTP/DHCP message (op=1 BOOTREQUEST). Minimal but valid.
static std::vector<uint8_t> bootp(const Mac& self, uint32_t xid, uint8_t msgType,
uint32_t reqIp, uint32_t serverId) {
std::vector<uint8_t> p;
p.push_back(1); p.push_back(1); p.push_back(6); p.push_back(0); // op,htype,hlen,hops
put32(p, xid);
p.push_back(0); p.push_back(0); // secs
p.push_back(0x80); p.push_back(0x00); // flags = broadcast
put32(p, 0); put32(p, 0); put32(p, 0); put32(p, 0); // ci,yi,si,gi addr
p.insert(p.end(), self.begin(), self.end()); // chaddr
p.resize(p.size() + 10, 0); // chaddr pad
p.resize(236, 0); // sname+file
put32(p, 0x63825363); // magic cookie
// option 53: DHCP message type
p.push_back(53); p.push_back(1); p.push_back(msgType);
if (reqIp) { p.push_back(50); p.push_back(4); put32(p, reqIp); } // requested IP
if (serverId) { p.push_back(54); p.push_back(4); put32(p, serverId); } // server id
// option 55: param request list — deliberately request ONLY subnet mask,
// NOT router(3)/DNS(6), matching udhcpc.apfpv.script intent.
p.push_back(55); p.push_back(1); p.push_back(1);
p.push_back(255); // end
return p;
}

std::vector<uint8_t> ApfpvDhcp::buildDiscover() { return bootp(_self, _xid, 1, 0, 0); }
std::vector<uint8_t> ApfpvDhcp::buildRequest(uint32_t ip, uint32_t sid) { return bootp(_self, _xid, 3, ip, sid); }

bool ApfpvDhcp::start() {
_xid = 0x41504650; // "APFP"
_state = St::Discover;
return _send(buildDiscover());
}

void ApfpvDhcp::onBootpReply(const uint8_t* p, size_t len) {
if (len < 240) return;
if ((p[236]<<24|p[237]<<16|p[238]<<8|p[239]) != 0x63825363) return; // cookie
uint32_t yi = (p[16]<<24)|(p[17]<<16)|(p[18]<<8)|p[19];
// parse options for msg type (53), netmask (1), server id (54). IGNORE 3/6.
uint8_t msgType = 0; uint32_t mask = 0, sid = 0;
size_t i = 240;
while (i + 2 <= len) {
uint8_t opt = p[i], olen = p[i+1];
if (opt == 255) break;
if (opt == 0) { i++; continue; }
if (i + 2 + olen > len) break;
const uint8_t* d = p + i + 2;
if (opt == 53 && olen == 1) msgType = d[0];
else if (opt == 1 && olen == 4) mask = (d[0]<<24)|(d[1]<<16)|(d[2]<<8)|d[3];
else if (opt == 54 && olen == 4) sid = (d[0]<<24)|(d[1]<<16)|(d[2]<<8)|d[3];
// opt 3 (router) and opt 6 (DNS) intentionally skipped.
i += 2 + olen;
}
if (_state == St::Discover && msgType == 2) { // OFFER -> REQUEST
_state = St::Request;
_offerIp = yi; _offerSid = sid; // remember for REQUEST retransmits
_send(buildRequest(yi, sid));
} else if (_state == St::Request && msgType == 5) { // ACK -> bound
_lease.ip = yi; _lease.netmask = mask; _lease.server = sid; _lease.valid = true;
_state = St::Bound;
}
}

void ApfpvDhcp::retransmit() {
// Resend whichever message we're waiting on a reply for, WITHOUT resetting the state
// machine — so a missed ACK retries the REQUEST (not a fresh DISCOVER that restarts DORA).
if (_state == St::Discover || _state == St::Init) _send(buildDiscover());
else if (_state == St::Request) _send(buildRequest(_offerIp, _offerSid));
}

void ApfpvDhcp::claimStatic(uint32_t ip, uint32_t netmask, uint32_t gateway) {
if (netmask == 0) netmask = 0xFFFFFF00; // default /24
if (gateway == 0) gateway = (ip & netmask) | 1; // default = subnet's .1
_lease.ip = ip; _lease.netmask = netmask; _lease.server = gateway;
_lease.valid = true; _state = St::Bound;
}

} // namespace apfpv
27 changes: 27 additions & 0 deletions src/ApfpvDhcp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <functional>
namespace apfpv {
using Mac = std::array<uint8_t,6>;
class ApfpvDhcp {
public:
using SendUdpFn = std::function<bool(const std::vector<uint8_t>&)>;
struct Lease { uint32_t ip=0, netmask=0, server=0; bool valid=false; };
ApfpvDhcp(const Mac& self, SendUdpFn send);
bool start();
void retransmit(); // resend the CURRENT pending message (DISCOVER or REQUEST), no reset
void onBootpReply(const uint8_t* bootp, size_t len);
const Lease& lease() const { return _lease; }
// Skip DHCP and bind a fixed lease (static-IP option). netmask 0 -> /24, gateway 0 -> subnet .1.
void claimStatic(uint32_t ip, uint32_t netmask = 0, uint32_t gateway = 0);
void claimStatic_192_168_0_10() { claimStatic(0xC0A8000A, 0xFFFFFF00, 0xC0A80001); }
private:
enum class St { Init, Discover, Request, Bound };
std::vector<uint8_t> buildDiscover();
std::vector<uint8_t> buildRequest(uint32_t offeredIp, uint32_t serverId);
Mac _self; SendUdpFn _send; St _state = St::Init; Lease _lease; uint32_t _xid = 0;
uint32_t _offerIp = 0, _offerSid = 0; // remembered from the OFFER for REQUEST retransmits
};
}
Loading