From 885cbc15d32dcd01a21019c4396597133ad9ef61 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 12:39:04 +0200 Subject: [PATCH 01/17] Refactor flow control and update CRC handling Signed-off-by: Niel Nielsen --- armsrc/bwm_forward.c | 82 +++++++++++++------------------------------- 1 file changed, 23 insertions(+), 59 deletions(-) diff --git a/armsrc/bwm_forward.c b/armsrc/bwm_forward.c index 09b077593..f49bda28a 100644 --- a/armsrc/bwm_forward.c +++ b/armsrc/bwm_forward.c @@ -14,15 +14,13 @@ #include "bwm_forward.h" #include "bwm_uart_at32.h" -#include "pm3_cmd.h" // PM3_CMD_DATA_SIZE, PM3_* return codes +#include "pm3_cmd.h" #include "string.h" #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif -// CRC-16/CCITT-FALSE, byte-identical to the BWM firmware's crc16_ccitt() -// (poly 0x1021, init 0xFFFF, MSB-first, no reflection, no xorout). static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { for (size_t i = 0; i < len; i++) { crc ^= (uint16_t)data[i] << 8; @@ -37,47 +35,29 @@ static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { return crc; } -// --------------------------------------------------------------------------- -// TX: wrap one reply frame into a SEND_FORWARD_DATA app_com frame. -// A full NG/OLD frame is <= PM3_CMD_DATA_SIZE + a small header/postamble, well -// under the BWM 4096-byte payload cap, so a single frame always suffices. -// --------------------------------------------------------------------------- #define BWM_TX_OVERHEAD (2 + 2 + 2 + 2) // hdr + cmd + len + crc #define BWM_TX_MAX_PAYLOAD (PM3_CMD_DATA_SIZE + 64) // NG/OLD frame ceiling #define BWM_TX_BUFSZ (BWM_TX_OVERHEAD + BWM_TX_MAX_PAYLOAD) -static void bwm_pump(void); // fwd decl: TX gate pumps RX to collect credit grants +static void bwm_pump(void); -// --- Flow control (credit window) ------------------------------------------ -// s_fc_granted: cumulative # of frames the ESP has authorized (updated from -// BWM_CMD_FLOW_CREDIT frames). s_fc_sent: cumulative # the ARM has sent. -// Sending is allowed while (int16_t)(granted - sent) > 0; the signed diff is -// wrap-safe. Grants are absolute/cumulative, so a lost credit frame self-corrects. -static volatile uint16_t s_fc_granted = BWM_FC_INITIAL_CREDIT; -static uint16_t s_fc_sent = 0; - -static inline bool bwm_fc_may_send(void) { - return (int16_t)(s_fc_granted - s_fc_sent) > 0; -} +static volatile int16_t s_fwd_inflight = 0; int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { - static uint8_t frame[BWM_TX_BUFSZ]; // single-threaded bare-metal: static OK + static uint8_t frame[BWM_TX_BUFSZ]; if (len > BWM_TX_MAX_PAYLOAD) { - len = BWM_TX_MAX_PAYLOAD; // defensive; should never trigger + len = BWM_TX_MAX_PAYLOAD; } size_t idx = 0; - // Flow control: block until the ESP has granted credit for another frame. - // bwm_pump() drains the IRQ-filled RX ring, so grants are collected even - // while we sit inside a tight download loop (the reply_old firehose). The - // spin cap is a safety valve so a dead/disconnected ESP can't hard-hang us. { uint32_t spins = 0; - while (bwm_fc_may_send() == false) { + while (s_fwd_inflight >= BWM_FC_WINDOW) { bwm_pump(); - if (++spins > BWM_FC_STALL_SPINS) { - break; // best-effort: proceed even without a fresh grant + if (++spins > BWM_FC_ACK_TIMEOUT_SPINS) { + s_fwd_inflight = 0; + break; } } } @@ -97,16 +77,10 @@ int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { frame[idx++] = (uint8_t)((crc >> 8) & 0xFF); int wr = bwm_uart_write(frame, idx); - s_fc_sent++; // one forward frame consumed a credit + s_fwd_inflight++; return wr; } -// --------------------------------------------------------------------------- -// RX: persistent app_com de-framer. Feeds raw FPC bytes through a state machine -// and pushes the payloads of valid DATA_FORWARD (0xD2 0xD3 / cmd 8089) frames -// into a byte FIFO that bwm_read_ng() drains. Non-DATA_FORWARD frames (slave -// responses, forwarded logs, cmd-error reports) are validated and discarded. -// --------------------------------------------------------------------------- #define BWM_DEFIFO_SZ 2048 // >= one full NG frame's payload #define BWM_RXFRAME_MAX (PM3_CMD_DATA_SIZE + 64) @@ -117,28 +91,27 @@ typedef enum { typedef struct { bwm_state_t state; uint8_t hdr1; - bool is_bcast; // header pair is 0xD2 0xD3 + bool is_bcast; uint16_t cmd; uint16_t len; - uint16_t got; // payload bytes received - uint16_t crc_calc; // running CRC over hdr..payload + uint16_t got; + uint16_t crc_calc; uint16_t crc_recv; uint8_t payload[BWM_RXFRAME_MAX]; } bwm_parser_t; static bwm_parser_t s_p = { .state = S_IDLE }; -// De-framed payload ring static uint8_t s_fifo[BWM_DEFIFO_SZ]; -static volatile uint16_t s_fifo_head = 0; // write -static volatile uint16_t s_fifo_tail = 0; // read +static volatile uint16_t s_fifo_head = 0; +static volatile uint16_t s_fifo_tail = 0; static uint16_t fifo_count(void) { return (uint16_t)((s_fifo_head - s_fifo_tail) & (BWM_DEFIFO_SZ - 1)); } static void fifo_push(uint8_t b) { uint16_t next = (uint16_t)((s_fifo_head + 1) & (BWM_DEFIFO_SZ - 1)); - if (next != s_fifo_tail) { // drop on overflow rather than corrupt + if (next != s_fifo_tail) { s_fifo[s_fifo_head] = b; s_fifo_head = next; } @@ -153,8 +126,6 @@ static void bwm_reset_frame(bwm_parser_t *p) { p->state = S_IDLE; } -// Update running CRC one byte at a time (mirrors the streaming update in the -// BWM firmware parser). static void crc_step(bwm_parser_t *p, uint8_t byte) { p->crc_calc = bwm_crc16(&byte, 1, p->crc_calc); } @@ -167,14 +138,12 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } else if (byte == BWM_HDR_SLAVE_RESP_1) { p->hdr1 = byte; p->is_bcast = false; p->state = S_HDR2; } - // any other byte: stay idle (resync) break; case S_HDR2: { bool ok = (p->is_bcast && byte == BWM_HDR_SLAVE_BCAST_2) || (!p->is_bcast && byte == BWM_HDR_SLAVE_RESP_2); if (!ok) { - // header mismatch: reset and re-examine this byte as a potential SOF p->state = S_IDLE; bwm_feed_byte(p, byte); return; @@ -192,7 +161,7 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { p->len |= (uint16_t)byte << 8; crc_step(p, byte); p->got = 0; - if (p->len > BWM_RXFRAME_MAX) { // oversized -> drop frame + if (p->len > BWM_RXFRAME_MAX) { bwm_reset_frame(p); } else { p->state = (p->len == 0) ? S_CRC_LO : S_PAYLOAD; @@ -210,17 +179,17 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { case S_CRC_LO: p->crc_recv = byte; p->state = S_CRC_HI; break; case S_CRC_HI: p->crc_recv |= (uint16_t)byte << 8; - if (p->crc_recv == p->crc_calc && p->is_bcast) { - if (p->cmd == BWM_CMD_DATA_FORWARD) { + if (p->crc_recv == p->crc_calc) { + if (p->is_bcast && p->cmd == BWM_CMD_DATA_FORWARD) { for (uint16_t i = 0; i < p->len; i++) { fifo_push(p->payload[i]); } - } else if (p->cmd == BWM_CMD_FLOW_CREDIT && p->len >= 2) { - // absolute cumulative grant from the ESP - s_fc_granted = (uint16_t)(p->payload[0] | ((uint16_t)p->payload[1] << 8)); + } else if ((p->is_bcast == false) && p->cmd == BWM_CMD_SEND_FORWARD_DATA) { + if (s_fwd_inflight > 0) { + s_fwd_inflight--; + } } } - // valid non-DATA_FORWARD frames and CRC failures alike: just resync bwm_reset_frame(p); break; @@ -230,7 +199,6 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } } -// Pull whatever raw framed bytes are waiting and run them through the parser. static void bwm_pump(void) { uint8_t scratch[64]; uint16_t avail = bwm_uart_rx_available(); @@ -250,8 +218,6 @@ uint16_t bwm_fwd_rxdata_available(void) { if (fifo_count() > 0) { return fifo_count(); } - // No de-framed payload yet, but raw frame bytes may be waiting; pump once so - // receive_ng()'s gate reflects real forward data. bwm_pump(); return fifo_count(); } @@ -261,8 +227,6 @@ uint32_t bwm_read_ng(uint8_t *data, size_t len) { return 0; } - // Same bounded-retry budget shape as bwm_uart_read(); USART_SLOW_LINK (set - // for the BWM/BLE link) widens it so a slow round-trip doesn't time out. uint32_t tryconstant = 0; #ifdef USART_SLOW_LINK tryconstant = 50000; From 8b8c5623e202557822c9373525da3d2d93afc932 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 12:41:06 +0200 Subject: [PATCH 02/17] Refactor BWM forward header definitions Signed-off-by: Niel Nielsen --- armsrc/bwm_forward.h | 55 ++++++++------------------------------------ 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/armsrc/bwm_forward.h b/armsrc/bwm_forward.h index 290410731..1ad04ccdb 100644 --- a/armsrc/bwm_forward.h +++ b/armsrc/bwm_forward.h @@ -9,25 +9,6 @@ // See LICENSE.txt for the text of the license. //----------------------------------------------------------------------------- // Proxmark5 Battery Wireless Module (BWM) transport shim. -// -// The BWM (ESP32-C2, RfidResearchGroup/Proxmark5_BWM_esp32) bridges the AT32 -// host <-> BLE/WiFi. Its ESP<->AT32 UART link does NOT carry raw PacketCommandNG; -// it uses a framed "app_com" protocol. Transparent host<->wireless traffic rides -// inside that framing: -// -// AT32 -> ESP (our reply, toward wireless host): -// [0x7C 0xC7] cmd=APP_CMD_SEND_FORWARD_DATA(5000) len(LE) payload CRC16(LE) -// ESP -> AT32 (command from wireless host): -// [0xD2 0xD3] cmd=APP_BROADCAST_DATA_FORWARD(8089) len(LE) payload CRC16(LE) -// -// Frame = HDR1 HDR2 | CMD(LE16) | LEN(LE16) | PAYLOAD[LEN] | CRC(LE16) -// CRC = CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, MSB-first, no xorout) -// over HDR..PAYLOAD. (NOT compute_crc(CRC_14443_A) - different CRC.) -// -// This shim wraps outgoing NG/OLD reply bytes into a SEND_FORWARD_DATA frame and -// de-frames incoming DATA_FORWARD frames back into a raw NG byte stream, so the -// stock reply_ng()/receive_ng() paths work unchanged over the BWM link. -// // Enabled by -DWITH_BWM_FORWARD (implies WITH_FPC_USART_HOST). //----------------------------------------------------------------------------- @@ -36,43 +17,27 @@ #include "common.h" -// app_com framing constants (verified against BWM firmware app_cmd_uart.[ch] / -// app_com_defs.h). -#define BWM_HDR_HOST_CMD_1 0x7C // AT32 -> ESP (host command) +#define BWM_HDR_HOST_CMD_1 0x7C #define BWM_HDR_HOST_CMD_2 0xC7 -#define BWM_HDR_SLAVE_BCAST_1 0xD2 // ESP -> AT32 (slave broadcast) +#define BWM_HDR_SLAVE_BCAST_1 0xD2 #define BWM_HDR_SLAVE_BCAST_2 0xD3 -#define BWM_HDR_SLAVE_RESP_1 0x2D // ESP -> AT32 (slave response, skipped here) +#define BWM_HDR_SLAVE_RESP_1 0x2D #define BWM_HDR_SLAVE_RESP_2 0x3D -#define BWM_CMD_SEND_FORWARD_DATA 5000 // host cmd: payload -> BLE/WiFi endpoint -#define BWM_CMD_DATA_FORWARD 8089 // slave bcast: payload came from endpoint -#define BWM_CMD_FLOW_CREDIT 8092 // slave bcast: payload = uint16 cumulative granted-frame count (LE) - -// Flow control (credit window). The ESP grants the ARM permission to send a -// bounded number of forward frames; it advances the cumulative grant as it -// drains frames to BLE. Sizing: BWM_FC_INITIAL_CREDIT frames must fit inside the -// ESP's UART RX FIFO + BLE mbuf pool so an in-flight window never overflows. -#define BWM_FC_INITIAL_CREDIT 8 // frames the ARM may send before the first grant -#ifndef BWM_FC_STALL_SPINS -#define BWM_FC_STALL_SPINS 200000 -#endif // safety valve: give up waiting for credit (avoid hard hang) +#define BWM_CMD_SEND_FORWARD_DATA 5000 +#define BWM_CMD_DATA_FORWARD 8089 +#define BWM_FC_WINDOW 4 +#ifndef BWM_FC_ACK_TIMEOUT_SPINS +#define BWM_FC_ACK_TIMEOUT_SPINS 200000 +#endif #define BWM_CRC16_POLY 0x1021 #define BWM_CRC16_INIT 0xFFFF -// Wrap `len` raw reply bytes (a whole PacketResponseNG/OLD frame) into one -// SEND_FORWARD_DATA app_com frame and write it synchronously to the FPC USART. -// Returns PM3_SUCCESS or the underlying usart error. Drop-in for the FPC -// usart_writebuffer_sync() call in reply_ng_internal()/reply_old(). int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len); -// De-framed read: returns up to `len` raw NG bytes recovered from inbound -// DATA_FORWARD frames, blocking-with-timeout exactly like usart_read_ng(). -// Drop-in for usart_read_ng() as the receive_ng() read callback. uint32_t bwm_read_ng(uint8_t *data, size_t len); -// >0 when raw bytes are waiting on the FPC USART (gate for receive_ng()). uint16_t bwm_fwd_rxdata_available(void); -#endif // __BWM_FORWARD_H +#endif From 74d1c72d479868448867b2fd6dfb52e373c260e6 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:01:04 +0200 Subject: [PATCH 03/17] Add files via upload Signed-off-by: Niel Nielsen --- armsrc/bwm_wifi.c | 182 ++++++++++++++++++++++++++++++++++++++++++++++ armsrc/bwm_wifi.h | 40 ++++++++++ 2 files changed, 222 insertions(+) create mode 100644 armsrc/bwm_wifi.c create mode 100644 armsrc/bwm_wifi.h diff --git a/armsrc/bwm_wifi.c b/armsrc/bwm_wifi.c new file mode 100644 index 000000000..f090cddf9 --- /dev/null +++ b/armsrc/bwm_wifi.c @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// BWM WiFi bring-up (PM5 / AT32 side). See bwm_wifi.h. +//----------------------------------------------------------------------------- +#include "bwm_wifi.h" +#include "bwm_uart_at32.h" // bwm_uart_write / bwm_uart_rx_available / bwm_uart_read +#include "bwm_forward.h" // app_com header bytes + BWM_CMD_* forward codes +#include "ticks.h" // GetTickCount / GetTickCountDelta +#include "dbprint.h" +#include "string.h" + +// app_com CRC-16/CCITT-FALSE (same as bwm_forward) +static uint16_t wifi_crc16(const uint8_t *d, size_t n, uint16_t crc) { + for (size_t i = 0; i < n; i++) { + crc ^= (uint16_t)d[i] << 8; + for (int b = 0; b < 8; b++) + crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1); + } + return crc; +} + +// Minimal one-shot app_com response parser: scans the UART RX stream for a +// SLAVE_RESP (0x2D 0x3D) echoing want_cmd, or a SLAVE_BCAST CMD_ERROR (8091). +// Runs only during setup, so it fully owns the RX stream here. +typedef enum { W_H1, W_H2, W_CL, W_CH, W_LL, W_LH, W_PL, W_KL, W_KH } wstate_t; + +int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, + uint8_t *resp, uint16_t *resp_len, uint32_t timeout_ms) { + + // ---- build + send HOST_CMD frame ---- + uint8_t frame[8 + 256]; + if (req_len > sizeof(frame) - 8) { + return PM3_EOVFLOW; + } + size_t idx = 0; + frame[idx++] = BWM_HDR_HOST_CMD_1; + frame[idx++] = BWM_HDR_HOST_CMD_2; + frame[idx++] = (uint8_t)(cmd & 0xFF); + frame[idx++] = (uint8_t)(cmd >> 8); + frame[idx++] = (uint8_t)(req_len & 0xFF); + frame[idx++] = (uint8_t)(req_len >> 8); + if (req_len) { + memcpy(&frame[idx], req, req_len); + idx += req_len; + } + uint16_t crc = wifi_crc16(frame, idx, 0xFFFF); + frame[idx++] = (uint8_t)(crc & 0xFF); + frame[idx++] = (uint8_t)(crc >> 8); + bwm_uart_write(frame, idx); + + // ---- wait for the matching SLAVE_RESP ---- + wstate_t st = W_H1; + bool is_resp = false; + uint16_t rcmd = 0, rlen = 0, rgot = 0, rcrc_recv = 0, rcrc_calc = 0; + uint8_t pbuf[256]; + uint8_t h1 = 0; + + uint32_t t0 = GetTickCount(); + for (;;) { + if (GetTickCountDelta(t0) > timeout_ms) { + return PM3_ETIMEOUT; + } + uint8_t buf[64]; + uint16_t avail = bwm_uart_rx_available(); + if (avail == 0) { + continue; + } + uint32_t n = bwm_uart_read(buf, (uint32_t)MIN(avail, (uint16_t)sizeof(buf))); + for (uint32_t i = 0; i < n; i++) { + uint8_t byte = buf[i]; + switch (st) { + case W_H1: + if (byte == BWM_HDR_SLAVE_RESP_1 || byte == BWM_HDR_SLAVE_BCAST_1) { + h1 = byte; + is_resp = (byte == BWM_HDR_SLAVE_RESP_1); + st = W_H2; + } + break; + case W_H2: { + bool ok = (is_resp && byte == BWM_HDR_SLAVE_RESP_2) || + (!is_resp && byte == BWM_HDR_SLAVE_BCAST_2); + if (ok) { + uint8_t hdr[2] = { h1, byte }; + rcrc_calc = wifi_crc16(hdr, 2, 0xFFFF); + rgot = 0; + st = W_CL; + } else { + st = W_H1; + } + break; + } + case W_CL: rcmd = byte; rcrc_calc = wifi_crc16(&byte,1,rcrc_calc); st = W_CH; break; + case W_CH: rcmd |= (uint16_t)byte << 8; rcrc_calc = wifi_crc16(&byte,1,rcrc_calc); st = W_LL; break; + case W_LL: rlen = byte; rcrc_calc = wifi_crc16(&byte,1,rcrc_calc); st = W_LH; break; + case W_LH: + rlen |= (uint16_t)byte << 8; rcrc_calc = wifi_crc16(&byte,1,rcrc_calc); + st = (rlen ? W_PL : W_KL); + break; + case W_PL: + if (rgot < sizeof(pbuf)) pbuf[rgot] = byte; + rcrc_calc = wifi_crc16(&byte,1,rcrc_calc); + if (++rgot >= rlen) st = W_KL; + break; + case W_KL: rcrc_recv = byte; st = W_KH; break; + case W_KH: { + rcrc_recv |= (uint16_t)byte << 8; + if (rcrc_recv == rcrc_calc) { + if (is_resp && rcmd == cmd) { + // matching ack for our command + if (resp && resp_len) { + uint16_t cpy = MIN(rlen, *resp_len); + memcpy(resp, pbuf, cpy); + *resp_len = cpy; + } + return PM3_SUCCESS; + } + // a CMD_ERROR broadcast referencing our cmd -> failure. + // (payload carries the failing cmd; treat any error report + // that arrives while we wait as a failure of this step.) + if (!is_resp && rcmd == BWM_CMD_CMD_ERROR) { + return PM3_EFAILED; + } + // otherwise: some other frame (e.g. a stray broadcast) - ignore + } + st = W_H1; + break; + } + } + } + } +} + +// --------------------------------------------------------------------------- + +static int step(uint16_t cmd, const uint8_t *req, uint16_t rl, + uint8_t *resp, uint16_t *resp_len, uint32_t to, const char *what) { + int r = bwm_cmd(cmd, req, rl, resp, resp_len, to); + if (r != PM3_SUCCESS) { + Dbprintf("[bwm-wifi] %s failed (%d)", what, r); + } + return r; +} + +int bwm_wifi_forward_up(const char *ssid, const char *password, + uint16_t tcp_port, uint32_t *ip_out) { + int r; + uint8_t b; + const uint32_t TO = 2000; // per-config-step timeout (ms) + + // 1) forwarding target = TCP server + b = BWM_WIFI_FORWARD_TCP_SERVER; + if ((r = step(BWM_CMD_SET_TO_WIFI_FORWARD_MODE, &b, 1, NULL, NULL, TO, "set forward mode")) != PM3_SUCCESS) return r; + + // 2) STA credentials + if ((r = step(BWM_CMD_SET_WIFI_CONNECT_CFG_SSID, (const uint8_t *)ssid, (uint16_t)strlen(ssid), NULL, NULL, TO, "set ssid")) != PM3_SUCCESS) return r; + if ((r = step(BWM_CMD_SET_WIFI_CONNECT_CFG_PWD, (const uint8_t *)password, (uint16_t)strlen(password), NULL, NULL, TO, "set password")) != PM3_SUCCESS) return r; + + // 3) TCP listen port (LE) + uint8_t p2[2] = { (uint8_t)(tcp_port & 0xFF), (uint8_t)(tcp_port >> 8) }; + if ((r = step(BWM_CMD_SET_TCP_SERVER_PORT, p2, 2, NULL, NULL, TO, "set tcp port")) != PM3_SUCCESS) return r; + + // 4) join the AP and wait for it to finish (up to ~15s) + if ((r = step(BWM_CMD_START_WIFI_CONNECT_TASK, NULL, 0, NULL, NULL, TO, "start connect")) != PM3_SUCCESS) return r; + uint8_t secs = 15; + uint8_t wr[2]; uint16_t wl = sizeof(wr); + if ((r = step(BWM_CMD_WAIT_FOR_WIFI_CONNECT_TASK, &secs, 1, wr, &wl, 20000, "wait connect")) != PM3_SUCCESS) return r; + + // 5) read the DHCP-assigned IP; a zero IP means the join did not succeed + uint8_t ipb[12]; uint16_t il = sizeof(ipb); + if ((r = step(BWM_CMD_GET_WIFI_CFG_IP_ADDR, NULL, 0, ipb, &il, TO, "get ip")) != PM3_SUCCESS) return r; + if (il < 4) return PM3_EFAILED; + uint32_t ip = (uint32_t)ipb[0] | ((uint32_t)ipb[1] << 8) | ((uint32_t)ipb[2] << 16) | ((uint32_t)ipb[3] << 24); + if (ip == 0) { + Dbprintf("[bwm-wifi] joined but no IP (connect_result=%u err=%u)", wr[0], wr[1]); + return PM3_EFAILED; + } + + // 6) start the TCP server (binds the STA interface now that it has an IP) + if ((r = step(BWM_CMD_START_TCP_SERVER, NULL, 0, NULL, NULL, TO, "start tcp server")) != PM3_SUCCESS) return r; + + *ip_out = ip; + return PM3_SUCCESS; +} diff --git a/armsrc/bwm_wifi.h b/armsrc/bwm_wifi.h new file mode 100644 index 000000000..d4b70afce --- /dev/null +++ b/armsrc/bwm_wifi.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// BWM WiFi bring-up (PM5 / AT32 side). +// +// Drives the BWM ESP32 into STA + TCP-server forward mode over the existing +// app_com UART4 link, so the pm3 client can connect with `tcp::`. +// This is the request/response counterpart to bwm_forward's transparent path: +// it is used at setup time, when no PM3 traffic is being forwarded. +//----------------------------------------------------------------------------- +#ifndef BWM_WIFI_H +#define BWM_WIFI_H + +#include "common.h" + +// app_com command codes (authoritative, from BWM main/app_com_defs.h) +#define BWM_CMD_SET_TO_WIFI_FORWARD_MODE 2001 // payload: 1 byte forward type (0 = TCP server) +#define BWM_CMD_GET_WIFI_CFG_IP_ADDR 2020 // resp: 3x uint32 LE {ip, netmask, gw} +#define BWM_CMD_SET_WIFI_CONNECT_CFG_SSID 2023 // payload: SSID bytes +#define BWM_CMD_SET_WIFI_CONNECT_CFG_PWD 2025 // payload: password bytes +#define BWM_CMD_START_WIFI_CONNECT_TASK 2048 // no payload +#define BWM_CMD_GET_WIFI_CONNECT_STATUS 2050 // resp: 1 byte status +#define BWM_CMD_WAIT_FOR_WIFI_CONNECT_TASK 2051 // payload: 1 byte timeout(s); resp: {result, err_reason} +#define BWM_CMD_START_TCP_SERVER 2201 // no payload +#define BWM_CMD_SET_TCP_SERVER_PORT 2205 // payload: uint16 LE port + +#define BWM_WIFI_FORWARD_TCP_SERVER 0 // wifi_forward_type_t::WIFI_FORWARD_TCP_SERVER +#define BWM_CMD_CMD_ERROR 8091 // slave bcast: command error report + +// Low-level: send one app_com HOST_CMD and wait for its SLAVE_RESP. +// resp/resp_len may be NULL if no response payload is expected. +// Returns PM3_SUCCESS on the matching ack, PM3_EFAILED on a CMD_ERROR report, +// PM3_ETIMEOUT if no response within timeout_ms. +int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, + uint8_t *resp, uint16_t *resp_len, uint32_t timeout_ms); + +// High-level: full STA-join + TCP-server bring-up. On success writes the +// BWM's IPv4 (host byte order, a in low byte) to *ip_out. +int bwm_wifi_forward_up(const char *ssid, const char *password, + uint16_t tcp_port, uint32_t *ip_out); + +#endif From b30627c761785779b4c8f6a887448448ec45a99a Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:03:24 +0200 Subject: [PATCH 04/17] Add CMD_PM5_BWM_WIFI command definition Signed-off-by: Niel Nielsen --- include/pm3_cmd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 9dd79232c..c4f23c75f 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -656,6 +656,7 @@ typedef struct { #define CMD_PM5_BWM_CHARGE_EN 0x017A // PM5, toggle automatic power-off on USB unplug. Used by `hw bwmautooff`. #define CMD_PM5_BWM_AUTOOFF 0x017B +#define CMD_PM5_BWM_WIFI 0x017C // For low-frequency tags #define CMD_LF_TI_READ 0x0202 #define CMD_LF_TI_WRITE 0x0203 From 917e23f698519c5a0d2f5f39f405fff828b272d9 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:10:42 +0200 Subject: [PATCH 05/17] Add WiFi forwarding command handling Signed-off-by: Niel Nielsen --- armsrc/appmain.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 2ac40c255..bc8e17c8c 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -4187,6 +4187,19 @@ static void PacketReceived(PacketCommandNG *packet) { reply_ng(CMD_PM5_BWM_CHARGE_EN, ok ? PM3_SUCCESS : PM3_EFAILED, NULL, 0); break; } + case CMD_PM5_BWM_WIFI: { + #if defined(WITH_BWM_FORWARD) + uint16_t port = packet->data.asBytes[0] | (packet->data.asBytes[1] << 8); + char *ssid = (char *)&packet->data.asBytes[2]; + char *pwd = ssid + strlen(ssid) + 1; + uint32_t ip = 0; + int res = bwm_wifi_forward_up(ssid, pwd, port, &ip); + reply_ng (CMD_PM5_BWM_WIFI, res, (uint8_t *)&ip, sizeof(ip)); + #else + reply_ng(CMD_PM5_BWM_WIFI, PM3_ENOTIMPL, NULL, 0); + #endif + break; + } case CMD_PM5_BWM_AUTOOFF: { // Toggle automatic power-off on USB unplug (runtime, default on). // Payload: 1 byte, non-zero = enable (default), zero = disable. From fd479f8f9ca6b11c85876546ab95b5edb9e2b7ef Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:13:29 +0200 Subject: [PATCH 06/17] Add bwm_wifi.c to SRC_BWM when WITH_BWM_FORWARD is set Signed-off-by: Niel Nielsen --- armsrc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/Makefile b/armsrc/Makefile index 498be215f..93f5e653d 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -108,7 +108,7 @@ else endif ifneq (,$(findstring WITH_BWM_FORWARD,$(APP_CFLAGS))) - SRC_BWM = bwm_uart_at32.c bwm_forward.c + SRC_BWM = bwm_uart_at32.c bwm_forward.c bwm_wifi.c else SRC_BWM = endif From fffd5cdf335450d814140fd0e4c6bb6d4cd1b5e4 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:15:10 +0200 Subject: [PATCH 07/17] Add bwm_wifi.h include for WiFi functionality Signed-off-by: Niel Nielsen --- armsrc/appmain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index bc8e17c8c..f32f8712e 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -24,6 +24,7 @@ #include "usb_cdc_apis.h" #ifdef WITH_BWM_FORWARD #include "bwm_uart_at32.h" +#include "bwm_wifi.h" #endif #include "proxmark3_arm.h" #include "dbprint.h" From 3b92e411e952a167c91488a10d3c2de6383ac128 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:17:31 +0200 Subject: [PATCH 08/17] Add CmdBWMWifi function for WiFi BWM logging Signed-off-by: Niel Nielsen --- client/src/cmdhw.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index f6d26d65b..389c59157 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -1550,6 +1550,14 @@ static int CmdBwmAutoOff(const char *Cmd) { return PM3_SUCCESS; } +static int CmdBWMWifi(const char *Cmd) { + PrintAndLogEx(SUCCESS, "BWM on WiFi at %u.%u.%u.%u", + ip & 0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF); + PrintAndLogEx(HINT, "Connect with: " _YELLOW_("pm3 -p tcp:%u.%u.%u.%u:%u"), + ip & 0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF, port); + return PM3_SUCCESS; +} + static int CmdBwmCharge(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hw bwmcharge", From 9769372618b75a48beb32f08e18fc3e61a1a059f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:25:49 +0200 Subject: [PATCH 09/17] Implement CmdBWMWifi for WiFi connection setup Signed-off-by: Niel Nielsen --- client/src/cmdhw.c | 67 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index 389c59157..b452ded85 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -1551,10 +1551,70 @@ static int CmdBwmAutoOff(const char *Cmd) { } static int CmdBWMWifi(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hw bwmwifi", + "Bring up the BWM in STA + TCP-server mode: join a WiFi network and\n" + "start a TCP server so the client can connect over WiFi. PM5 only.", + "hw bwmwifi --ssid Home --pwd secret --> port 7777\n" + "hw bwmwifi --ssid Home --pwd secret --port 9000"); + + void *argtable[] = { + arg_param_begin, + arg_str1(NULL, "ssid", "", "WiFi SSID to join"), + arg_str0(NULL, "pwd", "", "WiFi password (omit for open network)"), + arg_int0(NULL, "port", "", "TCP server listen port (default 7777)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + uint8_t ssid[64] = {0}; + int ssid_len = 0; + CLIParamStrToBuf(arg_get_str(ctx, 1), ssid, sizeof(ssid) - 1, &ssid_len); + + uint8_t pwd[64] = {0}; + int pwd_len = 0; + CLIParamStrToBuf(arg_get_str(ctx, 2), pwd, sizeof(pwd) - 1, &pwd_len); + + int port = arg_get_int_def(ctx, 3, 7777); + CLIParserFree(ctx); + + if (ssid_len == 0) { + PrintAndLogEx(FAILED, "an SSID is required"); + return PM3_EINVARG; + } + if (port < 1 || port > 65535) { + PrintAndLogEx(FAILED, "port must be 1..65535"); + return PM3_EINVARG; + } + + // payload: [port:u16 LE][ssid\0][pwd\0] + uint8_t data[140] = {0}; + int n = 0; + data[n++] = (uint8_t)(port & 0xFF); + data[n++] = (uint8_t)((port >> 8) & 0xFF); + memcpy(&data[n], ssid, ssid_len); n += ssid_len; data[n++] = 0; + memcpy(&data[n], pwd, pwd_len); n += pwd_len; data[n++] = 0; + + PrintAndLogEx(INFO, "Bringing up BWM WiFi (SSID \"%s\", port %d)... this can take ~15s", ssid, port); + + clearCommandBuffer(); + SendCommandNG(CMD_PM5_BWM_WIFI, data, n); + PacketResponseNG resp; + // ARM blocks during the join (WAIT is up to ~15s), so allow a long client timeout + if (WaitForResponseTimeout(CMD_PM5_BWM_WIFI, &resp, 25000) == false) { + PrintAndLogEx(WARNING, "command timeout (is this a PM5 with a BWM fitted?)"); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "BWM WiFi bring-up failed (check SSID/password and signal)"); + return resp.status; + } + + uint32_t ip = resp.data.asDwords[0]; PrintAndLogEx(SUCCESS, "BWM on WiFi at %u.%u.%u.%u", - ip & 0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF); - PrintAndLogEx(HINT, "Connect with: " _YELLOW_("pm3 -p tcp:%u.%u.%u.%u:%u"), - ip & 0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF, port); + ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); + PrintAndLogEx(HINT, "Connect with: " _YELLOW_("pm3 -p tcp:%u.%u.%u.%u:%d"), + ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF, port); return PM3_SUCCESS; } @@ -2121,6 +2181,7 @@ static command_t CommandTable[] = { {"bwmsetcap", CmdBwmSetCap, IfPm5, "Set BWM fuel-gauge design capacity (PM5, run once after battery change)"}, {"bwmcharge", CmdBwmCharge, IfPm5, "Enable/disable BWM battery charging (PM5, one-shot)"}, {"bwmautooff", CmdBwmAutoOff, IfPm5, "Toggle auto power-off on USB unplug (PM5, BWM)"}, + {"bwmwifi", CmdBWMWifi, IfPm5, "Bring up BWM WiFi (STA + TCP server) for a tcp: connection (PM5)"}, {"tune", CmdTune, IfPm3Lf, "Measure tuning of device antenna"}, {"decay", CmdDecay, IfPm3Present, "Measure HF antenna decay after field-off"}, {NULL, NULL, NULL, NULL} From 0fd8f8ea36ba11ef7ca9d3089d77f589b2f81381 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:26:50 +0200 Subject: [PATCH 10/17] Replace ticks.h with ticks_apis.h in bwm_wifi.c Signed-off-by: Niel Nielsen --- armsrc/bwm_wifi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/bwm_wifi.c b/armsrc/bwm_wifi.c index f090cddf9..73936fed7 100644 --- a/armsrc/bwm_wifi.c +++ b/armsrc/bwm_wifi.c @@ -4,7 +4,7 @@ #include "bwm_wifi.h" #include "bwm_uart_at32.h" // bwm_uart_write / bwm_uart_rx_available / bwm_uart_read #include "bwm_forward.h" // app_com header bytes + BWM_CMD_* forward codes -#include "ticks.h" // GetTickCount / GetTickCountDelta +#include "ticks_apis.h" // GetTickCount / GetTickCountDelta #include "dbprint.h" #include "string.h" From b60f5e2ac8e5037dcb4c411beb08326d3a973c8d Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 13:30:55 +0200 Subject: [PATCH 11/17] Add PM3 command includes and fix function closing Signed-off-by: Niel Nielsen --- armsrc/bwm_wifi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/bwm_wifi.c b/armsrc/bwm_wifi.c index 73936fed7..2347f751e 100644 --- a/armsrc/bwm_wifi.c +++ b/armsrc/bwm_wifi.c @@ -4,6 +4,7 @@ #include "bwm_wifi.h" #include "bwm_uart_at32.h" // bwm_uart_write / bwm_uart_rx_available / bwm_uart_read #include "bwm_forward.h" // app_com header bytes + BWM_CMD_* forward codes +#include "pm3_cmd.h" // PM3_SUCCESS / PM3_ETIMEOUT / PM3_EFAILED / PM3_EOVFLOW #include "ticks_apis.h" // GetTickCount / GetTickCountDelta #include "dbprint.h" #include "string.h" From 4aa7e2ecb6c993afdcf8a9a35032cb3ed4fb0331 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 16:44:35 +0200 Subject: [PATCH 12/17] Add hostname parameter to WiFi command Signed-off-by: Niel Nielsen --- client/src/cmdhw.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index b452ded85..62c010465 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -1563,6 +1563,7 @@ static int CmdBWMWifi(const char *Cmd) { arg_str1(NULL, "ssid", "", "WiFi SSID to join"), arg_str0(NULL, "pwd", "", "WiFi password (omit for open network)"), arg_int0(NULL, "port", "", "TCP server listen port (default 7777)"), + arg_str0(NULL, "hostname", "", "DHCP hostname (default Proxmark5)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -1576,6 +1577,14 @@ static int CmdBWMWifi(const char *Cmd) { CLIParamStrToBuf(arg_get_str(ctx, 2), pwd, sizeof(pwd) - 1, &pwd_len); int port = arg_get_int_def(ctx, 3, 7777); + + uint8_t host[33] = {0}; + int host_len = 0; + CLIParamStrToBuf(arg_get_str(ctx, 4), host, sizeof(host) - 1, &host_len); + if (host_len == 0) { + strcpy((char *)host, "Proxmark5"); + host_len = 9; + } CLIParserFree(ctx); if (ssid_len == 0) { @@ -1587,13 +1596,14 @@ static int CmdBWMWifi(const char *Cmd) { return PM3_EINVARG; } - // payload: [port:u16 LE][ssid\0][pwd\0] - uint8_t data[140] = {0}; + // payload: [port:u16 LE][ssid\0][pwd\0][hostname\0] + uint8_t data[200] = {0}; int n = 0; data[n++] = (uint8_t)(port & 0xFF); data[n++] = (uint8_t)((port >> 8) & 0xFF); memcpy(&data[n], ssid, ssid_len); n += ssid_len; data[n++] = 0; memcpy(&data[n], pwd, pwd_len); n += pwd_len; data[n++] = 0; + memcpy(&data[n], host, host_len); n += host_len; data[n++] = 0; PrintAndLogEx(INFO, "Bringing up BWM WiFi (SSID \"%s\", port %d)... this can take ~15s", ssid, port); @@ -1615,6 +1625,7 @@ static int CmdBWMWifi(const char *Cmd) { ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); PrintAndLogEx(HINT, "Connect with: " _YELLOW_("pm3 -p tcp:%u.%u.%u.%u:%d"), ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF, port); + PrintAndLogEx(HINT, "Or by name (router-dependent): " _YELLOW_("pm3 -p tcp:%s:%d"), host, port); return PM3_SUCCESS; } From cc8a2c04ca07599a2876f54a80cf0e25c74bfc6e Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 16:45:50 +0200 Subject: [PATCH 13/17] Add host parameter to bwm_wifi_forward_up function Signed-off-by: Niel Nielsen --- armsrc/appmain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index f32f8712e..0e10529e0 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -4193,8 +4193,9 @@ static void PacketReceived(PacketCommandNG *packet) { uint16_t port = packet->data.asBytes[0] | (packet->data.asBytes[1] << 8); char *ssid = (char *)&packet->data.asBytes[2]; char *pwd = ssid + strlen(ssid) + 1; + char *host = pwd + strlen(pwd) + 1; uint32_t ip = 0; - int res = bwm_wifi_forward_up(ssid, pwd, port, &ip); + int res = bwm_wifi_forward_up(ssid, pwd, host, port, &ip); reply_ng (CMD_PM5_BWM_WIFI, res, (uint8_t *)&ip, sizeof(ip)); #else reply_ng(CMD_PM5_BWM_WIFI, PM3_ENOTIMPL, NULL, 0); From 6cbd863abe59b0c6d85fac6b34b9c17123bef712 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 16:46:25 +0200 Subject: [PATCH 14/17] Add hostname parameter to bwm_wifi_forward_up function Signed-off-by: Niel Nielsen --- armsrc/bwm_wifi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/armsrc/bwm_wifi.c b/armsrc/bwm_wifi.c index 2347f751e..528616963 100644 --- a/armsrc/bwm_wifi.c +++ b/armsrc/bwm_wifi.c @@ -142,7 +142,7 @@ static int step(uint16_t cmd, const uint8_t *req, uint16_t rl, } int bwm_wifi_forward_up(const char *ssid, const char *password, - uint16_t tcp_port, uint32_t *ip_out) { + const char *hostname, uint16_t tcp_port, uint32_t *ip_out) { int r; uint8_t b; const uint32_t TO = 2000; // per-config-step timeout (ms) @@ -159,6 +159,9 @@ int bwm_wifi_forward_up(const char *ssid, const char *password, uint8_t p2[2] = { (uint8_t)(tcp_port & 0xFF), (uint8_t)(tcp_port >> 8) }; if ((r = step(BWM_CMD_SET_TCP_SERVER_PORT, p2, 2, NULL, NULL, TO, "set tcp port")) != PM3_SUCCESS) return r; + // 3b) DHCP hostname - must be set before the join so it rides the DHCP request + if ((r = step(BWM_CMD_SET_WIFI_CFG_HOST_NAME, (const uint8_t *)hostname, (uint16_t)strlen(hostname), NULL, NULL, TO, "set hostname")) != PM3_SUCCESS) return r; + // 4) join the AP and wait for it to finish (up to ~15s) if ((r = step(BWM_CMD_START_WIFI_CONNECT_TASK, NULL, 0, NULL, NULL, TO, "start connect")) != PM3_SUCCESS) return r; uint8_t secs = 15; From 426c9f11fd45397c42bf0489aef9698cb65b9536 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 16:46:49 +0200 Subject: [PATCH 15/17] Add hostname configuration command to bwm_wifi.h Signed-off-by: Niel Nielsen --- armsrc/bwm_wifi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/armsrc/bwm_wifi.h b/armsrc/bwm_wifi.h index d4b70afce..f0b55b47b 100644 --- a/armsrc/bwm_wifi.h +++ b/armsrc/bwm_wifi.h @@ -16,6 +16,7 @@ #define BWM_CMD_GET_WIFI_CFG_IP_ADDR 2020 // resp: 3x uint32 LE {ip, netmask, gw} #define BWM_CMD_SET_WIFI_CONNECT_CFG_SSID 2023 // payload: SSID bytes #define BWM_CMD_SET_WIFI_CONNECT_CFG_PWD 2025 // payload: password bytes +#define BWM_CMD_SET_WIFI_CFG_HOST_NAME 2021 // payload: hostname bytes (no NUL) #define BWM_CMD_START_WIFI_CONNECT_TASK 2048 // no payload #define BWM_CMD_GET_WIFI_CONNECT_STATUS 2050 // resp: 1 byte status #define BWM_CMD_WAIT_FOR_WIFI_CONNECT_TASK 2051 // payload: 1 byte timeout(s); resp: {result, err_reason} @@ -35,6 +36,6 @@ int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, // High-level: full STA-join + TCP-server bring-up. On success writes the // BWM's IPv4 (host byte order, a in low byte) to *ip_out. int bwm_wifi_forward_up(const char *ssid, const char *password, - uint16_t tcp_port, uint32_t *ip_out); + const char *hostname, uint16_t tcp_port, uint32_t *ip_out); #endif From 5fa3ac2446db2b69f4e3eb011a6f91233779a94f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 19:40:20 +0200 Subject: [PATCH 16/17] Fix formatting and comments in bwm_forward.c Signed-off-by: Niel Nielsen --- armsrc/bwm_forward.c | 61 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/armsrc/bwm_forward.c b/armsrc/bwm_forward.c index f49bda28a..12765654f 100644 --- a/armsrc/bwm_forward.c +++ b/armsrc/bwm_forward.c @@ -14,13 +14,15 @@ #include "bwm_forward.h" #include "bwm_uart_at32.h" -#include "pm3_cmd.h" +#include "pm3_cmd.h" // PM3_CMD_DATA_SIZE, PM3_* return codes #include "string.h" #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif +// CRC-16/CCITT-FALSE, byte-identical to the BWM firmware's crc16_ccitt() +// (poly 0x1021, init 0xFFFF, MSB-first, no reflection, no xorout). static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { for (size_t i = 0; i < len; i++) { crc ^= (uint16_t)data[i] << 8; @@ -35,28 +37,43 @@ static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { return crc; } +// --------------------------------------------------------------------------- +// TX: wrap one reply frame into a SEND_FORWARD_DATA app_com frame. +// A full NG/OLD frame is <= PM3_CMD_DATA_SIZE + a small header/postamble, well +// under the BWM 4096-byte payload cap, so a single frame always suffices. +// --------------------------------------------------------------------------- #define BWM_TX_OVERHEAD (2 + 2 + 2 + 2) // hdr + cmd + len + crc #define BWM_TX_MAX_PAYLOAD (PM3_CMD_DATA_SIZE + 64) // NG/OLD frame ceiling #define BWM_TX_BUFSZ (BWM_TX_OVERHEAD + BWM_TX_MAX_PAYLOAD) -static void bwm_pump(void); +static void bwm_pump(void); // fwd decl: TX gate pumps RX to collect forward-frame acks +// --- Flow control (ack window) --------------------------------------------- +// s_fwd_inflight: forward frames sent but not yet acked by the ESP. Bumped on +// send, decremented when a SLAVE_RESP echoing cmd=SEND_FORWARD_DATA arrives. +// We may send while it is below BWM_FC_WINDOW; at the cap we wait for an ack. static volatile int16_t s_fwd_inflight = 0; int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { - static uint8_t frame[BWM_TX_BUFSZ]; + static uint8_t frame[BWM_TX_BUFSZ]; // single-threaded bare-metal: static OK if (len > BWM_TX_MAX_PAYLOAD) { - len = BWM_TX_MAX_PAYLOAD; + len = BWM_TX_MAX_PAYLOAD; // defensive; should never trigger } size_t idx = 0; + // Flow control: block while the in-flight window is full, waiting for the + // ESP to ack an earlier forward frame. bwm_pump() drains the IRQ-filled RX + // ring, so acks are collected even while we sit inside a tight download loop + // (the reply_old firehose). The spin cap is a safety valve so a dead or + // disconnected ESP can't hard-hang us. A window >= 1 means single command + // replies never block - only sustained bursts hit the cap. { uint32_t spins = 0; while (s_fwd_inflight >= BWM_FC_WINDOW) { bwm_pump(); if (++spins > BWM_FC_ACK_TIMEOUT_SPINS) { - s_fwd_inflight = 0; + s_fwd_inflight = 0; // best-effort: assume the pipe cleared break; } } @@ -77,10 +94,16 @@ int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { frame[idx++] = (uint8_t)((crc >> 8) & 0xFF); int wr = bwm_uart_write(frame, idx); - s_fwd_inflight++; + s_fwd_inflight++; // one more forward frame awaiting its ack return wr; } +// --------------------------------------------------------------------------- +// RX: persistent app_com de-framer. Feeds raw FPC bytes through a state machine +// and pushes the payloads of valid DATA_FORWARD (0xD2 0xD3 / cmd 8089) frames +// into a byte FIFO that bwm_read_ng() drains. Non-DATA_FORWARD frames (slave +// responses, forwarded logs, cmd-error reports) are validated and discarded. +// --------------------------------------------------------------------------- #define BWM_DEFIFO_SZ 2048 // >= one full NG frame's payload #define BWM_RXFRAME_MAX (PM3_CMD_DATA_SIZE + 64) @@ -91,27 +114,28 @@ typedef enum { typedef struct { bwm_state_t state; uint8_t hdr1; - bool is_bcast; + bool is_bcast; // header pair is 0xD2 0xD3 uint16_t cmd; uint16_t len; - uint16_t got; - uint16_t crc_calc; + uint16_t got; // payload bytes received + uint16_t crc_calc; // running CRC over hdr..payload uint16_t crc_recv; uint8_t payload[BWM_RXFRAME_MAX]; } bwm_parser_t; static bwm_parser_t s_p = { .state = S_IDLE }; +// De-framed payload ring static uint8_t s_fifo[BWM_DEFIFO_SZ]; -static volatile uint16_t s_fifo_head = 0; -static volatile uint16_t s_fifo_tail = 0; +static volatile uint16_t s_fifo_head = 0; // write +static volatile uint16_t s_fifo_tail = 0; // read static uint16_t fifo_count(void) { return (uint16_t)((s_fifo_head - s_fifo_tail) & (BWM_DEFIFO_SZ - 1)); } static void fifo_push(uint8_t b) { uint16_t next = (uint16_t)((s_fifo_head + 1) & (BWM_DEFIFO_SZ - 1)); - if (next != s_fifo_tail) { + if (next != s_fifo_tail) { // drop on overflow rather than corrupt s_fifo[s_fifo_head] = b; s_fifo_head = next; } @@ -126,6 +150,8 @@ static void bwm_reset_frame(bwm_parser_t *p) { p->state = S_IDLE; } +// Update running CRC one byte at a time (mirrors the streaming update in the +// BWM firmware parser). static void crc_step(bwm_parser_t *p, uint8_t byte) { p->crc_calc = bwm_crc16(&byte, 1, p->crc_calc); } @@ -138,12 +164,14 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } else if (byte == BWM_HDR_SLAVE_RESP_1) { p->hdr1 = byte; p->is_bcast = false; p->state = S_HDR2; } + // any other byte: stay idle (resync) break; case S_HDR2: { bool ok = (p->is_bcast && byte == BWM_HDR_SLAVE_BCAST_2) || (!p->is_bcast && byte == BWM_HDR_SLAVE_RESP_2); if (!ok) { + // header mismatch: reset and re-examine this byte as a potential SOF p->state = S_IDLE; bwm_feed_byte(p, byte); return; @@ -161,7 +189,7 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { p->len |= (uint16_t)byte << 8; crc_step(p, byte); p->got = 0; - if (p->len > BWM_RXFRAME_MAX) { + if (p->len > BWM_RXFRAME_MAX) { // oversized -> drop frame bwm_reset_frame(p); } else { p->state = (p->len == 0) ? S_CRC_LO : S_PAYLOAD; @@ -185,11 +213,13 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { fifo_push(p->payload[i]); } } else if ((p->is_bcast == false) && p->cmd == BWM_CMD_SEND_FORWARD_DATA) { + // SLAVE_RESP ack for a forward frame -> one slot freed if (s_fwd_inflight > 0) { s_fwd_inflight--; } } } + // valid non-DATA_FORWARD frames and CRC failures alike: just resync bwm_reset_frame(p); break; @@ -199,6 +229,7 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } } +// Pull whatever raw framed bytes are waiting and run them through the parser. static void bwm_pump(void) { uint8_t scratch[64]; uint16_t avail = bwm_uart_rx_available(); @@ -218,6 +249,8 @@ uint16_t bwm_fwd_rxdata_available(void) { if (fifo_count() > 0) { return fifo_count(); } + // No de-framed payload yet, but raw frame bytes may be waiting; pump once so + // receive_ng()'s gate reflects real forward data. bwm_pump(); return fifo_count(); } @@ -227,6 +260,8 @@ uint32_t bwm_read_ng(uint8_t *data, size_t len) { return 0; } + // Same bounded-retry budget shape as bwm_uart_read(); USART_SLOW_LINK (set + // for the BWM/BLE link) widens it so a slow round-trip doesn't time out. uint32_t tryconstant = 0; #ifdef USART_SLOW_LINK tryconstant = 50000; From bfa279cd57c1081d5b54694674b8130cb477d837 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Thu, 27 Aug 2026 19:40:57 +0200 Subject: [PATCH 17/17] Update BWM framing constants and comments Signed-off-by: Niel Nielsen --- armsrc/bwm_forward.h | 55 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/armsrc/bwm_forward.h b/armsrc/bwm_forward.h index 1ad04ccdb..b6b570a74 100644 --- a/armsrc/bwm_forward.h +++ b/armsrc/bwm_forward.h @@ -9,6 +9,25 @@ // See LICENSE.txt for the text of the license. //----------------------------------------------------------------------------- // Proxmark5 Battery Wireless Module (BWM) transport shim. +// +// The BWM (ESP32-C2, RfidResearchGroup/Proxmark5_BWM_esp32) bridges the AT32 +// host <-> BLE/WiFi. Its ESP<->AT32 UART link does NOT carry raw PacketCommandNG; +// it uses a framed "app_com" protocol. Transparent host<->wireless traffic rides +// inside that framing: +// +// AT32 -> ESP (our reply, toward wireless host): +// [0x7C 0xC7] cmd=APP_CMD_SEND_FORWARD_DATA(5000) len(LE) payload CRC16(LE) +// ESP -> AT32 (command from wireless host): +// [0xD2 0xD3] cmd=APP_BROADCAST_DATA_FORWARD(8089) len(LE) payload CRC16(LE) +// +// Frame = HDR1 HDR2 | CMD(LE16) | LEN(LE16) | PAYLOAD[LEN] | CRC(LE16) +// CRC = CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, MSB-first, no xorout) +// over HDR..PAYLOAD. (NOT compute_crc(CRC_14443_A) - different CRC.) +// +// This shim wraps outgoing NG/OLD reply bytes into a SEND_FORWARD_DATA frame and +// de-frames incoming DATA_FORWARD frames back into a raw NG byte stream, so the +// stock reply_ng()/receive_ng() paths work unchanged over the BWM link. +// // Enabled by -DWITH_BWM_FORWARD (implies WITH_FPC_USART_HOST). //----------------------------------------------------------------------------- @@ -17,27 +36,45 @@ #include "common.h" -#define BWM_HDR_HOST_CMD_1 0x7C +// app_com framing constants (verified against BWM firmware app_cmd_uart.[ch] / +// app_com_defs.h). +#define BWM_HDR_HOST_CMD_1 0x7C // AT32 -> ESP (host command) #define BWM_HDR_HOST_CMD_2 0xC7 -#define BWM_HDR_SLAVE_BCAST_1 0xD2 +#define BWM_HDR_SLAVE_BCAST_1 0xD2 // ESP -> AT32 (slave broadcast) #define BWM_HDR_SLAVE_BCAST_2 0xD3 -#define BWM_HDR_SLAVE_RESP_1 0x2D +#define BWM_HDR_SLAVE_RESP_1 0x2D // ESP -> AT32 (slave response; forward-frame ack) #define BWM_HDR_SLAVE_RESP_2 0x3D -#define BWM_CMD_SEND_FORWARD_DATA 5000 -#define BWM_CMD_DATA_FORWARD 8089 -#define BWM_FC_WINDOW 4 +#define BWM_CMD_SEND_FORWARD_DATA 5000 // host cmd: payload -> BLE/WiFi endpoint +#define BWM_CMD_DATA_FORWARD 8089 // slave bcast: payload came from endpoint +// Flow control (ack window) - ARM-side only, no BWM firmware change required. +// The ESP already replies to every forward frame with a SLAVE_RESP echoing +// cmd=SEND_FORWARD_DATA, and it sends that ack only *after* app_ble_send() has +// drained the frame to BLE. So the un-acked count is a live measure of how far +// ahead of the wireless link we are. We allow up to BWM_FC_WINDOW frames in +// flight, then block for an ack before sending more - which paces us to the real +// BLE/WiFi rate and prevents the ESP UART-RX overrun that dropped bulk downloads. +// WINDOW frames must fit the ESP UART RX FIFO + wireless send buffer. +#define BWM_FC_WINDOW 4 // max un-acked forward frames in flight #ifndef BWM_FC_ACK_TIMEOUT_SPINS -#define BWM_FC_ACK_TIMEOUT_SPINS 200000 -#endif +#define BWM_FC_ACK_TIMEOUT_SPINS 200000 // safety valve: proceed if an ack is lost (avoid hard hang) +#endif // safety valve: give up waiting for credit (avoid hard hang) #define BWM_CRC16_POLY 0x1021 #define BWM_CRC16_INIT 0xFFFF +// Wrap `len` raw reply bytes (a whole PacketResponseNG/OLD frame) into one +// SEND_FORWARD_DATA app_com frame and write it synchronously to the FPC USART. +// Returns PM3_SUCCESS or the underlying usart error. Drop-in for the FPC +// usart_writebuffer_sync() call in reply_ng_internal()/reply_old(). int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len); +// De-framed read: returns up to `len` raw NG bytes recovered from inbound +// DATA_FORWARD frames, blocking-with-timeout exactly like usart_read_ng(). +// Drop-in for usart_read_ng() as the receive_ng() read callback. uint32_t bwm_read_ng(uint8_t *data, size_t len); +// >0 when raw bytes are waiting on the FPC USART (gate for receive_ng()). uint16_t bwm_fwd_rxdata_available(void); -#endif +#endif // __BWM_FORWARD_H