From e0fbc4174cccc8754b2533f9ba3bc26260b74a91 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 12:43:10 +0200 Subject: [PATCH 01/20] Add includes for string and time functions Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.c | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/client/src/uart/ble_posix.c b/client/src/uart/ble_posix.c index 588c3f5e1..05c1ddb51 100644 --- a/client/src/uart/ble_posix.c +++ b/client/src/uart/ble_posix.c @@ -17,13 +17,17 @@ #include #include +#include // strcasecmp #include #include #include +#include #include #include #include #include +#include +#include #include "ui.h" // PrintAndLogEx #include "pm3_cmd.h" // PM3_* (only for messaging parity; returns are 0/neg here) @@ -183,6 +187,142 @@ static int att_subscribe(int fd, uint16_t cccd_handle) { return (n >= 1) ? 0 : -1; } +// ---- name -> address resolution (active LE scan over raw HCI) ---- + +// AD types carrying the device name (Core Spec, Supplement, Part A). +#define BLE_ADV_NAME_SHORT 0x08 +#define BLE_ADV_NAME_FULL 0x09 + +static long ble_now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; +} + +// Walk the AD structures of one advertising/scan-response report, looking for a +// Local Name (complete or shortened) that equals `want` (case-insensitive). +// Copies whatever name it finds into `found` for logging. Returns 1 on match. +static int ble_ad_name_matches(const char *want, const uint8_t *ad, size_t adlen, + char *found, size_t foundsz) { + size_t i = 0; + while (i < adlen) { + uint8_t flen = ad[i]; // length byte counts the type + value + if (flen == 0) break; // padding -> end of meaningful data + if (i + 1 + flen > adlen) break; // truncated field -> stop, don't overrun + uint8_t type = ad[i + 1]; + if (type == BLE_ADV_NAME_FULL || type == BLE_ADV_NAME_SHORT) { + size_t nlen = flen - 1; + if (nlen >= foundsz) nlen = foundsz - 1; + memcpy(found, &ad[i + 2], nlen); + found[nlen] = 0; + if (strcasecmp(found, want) == 0) return 1; + } + i += flen + 1; + } + return 0; +} + +int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms) { + if (name == NULL || out_mac == NULL || out_mac_sz < 18) return -1; + out_mac[0] = 0; + + int dev_id = hci_get_route(NULL); + if (dev_id < 0) { + PrintAndLogEx(ERR, "BLE: no local Bluetooth adapter found (%s)", strerror(errno)); + return -1; + } + int dd = hci_open_dev(dev_id); + if (dd < 0) { + PrintAndLogEx(ERR, "BLE: cannot open hci%d (%s)", dev_id, strerror(errno)); + if (errno == EPERM || errno == EACCES) + PrintAndLogEx(INFO, "BLE: scanning by name needs privileges - run with sudo, or grant the client " + _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN")); + return -1; + } + + // NOTE: despite the header naming its first arg `dev_id`, BlueZ's + // hci_le_set_scan_* helpers expect the *open descriptor* (dd) and pass it + // straight to hci_send_req(). Passing the raw device id here silently fails. + // Active scan (0x01) so we also receive SCAN_RSP, which is where many + // devices (incl. the BWM) put the complete local name. + if (hci_le_set_scan_parameters(dd, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0x00, 1000) < 0) + PrintAndLogEx(INFO, "BLE: set scan parameters failed (%s), continuing", strerror(errno)); + + // A scan already left enabled makes the enable below return EIO; clear it first. + hci_le_set_scan_enable(dd, 0x00, 0x00, 1000); + if (hci_le_set_scan_enable(dd, 0x01, 0x00, 1000) < 0) { // filter_dup off: names may only be in SCAN_RSP + PrintAndLogEx(ERR, "BLE: cannot start LE scan (%s)", strerror(errno)); + if (errno == EPERM || errno == EACCES) + PrintAndLogEx(INFO, "BLE: scanning by name needs privileges - run with sudo, or grant the client " + _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN")); + hci_close_dev(dd); + return -1; + } + + // Receive only HCI event packets carrying LE meta events; save/restore the + // socket's previous filter so we leave the handle as we found it. + struct hci_filter of, nf; + socklen_t olen = sizeof(of); + int have_of = (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) == 0); + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_LE_META_EVENT, &nf); + setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)); + + PrintAndLogEx(INFO, "BLE: scanning up to " _YELLOW_("%d") " ms for " _YELLOW_("%s") " ...", timeout_ms, name); + + int rc = -1; + char nm[64]; + long deadline = ble_now_ms() + timeout_ms; + uint8_t buf[HCI_MAX_EVENT_SIZE]; + + while (ble_now_ms() < deadline) { + long rem = deadline - ble_now_ms(); + if (rem <= 0) break; + struct timeval tv = { .tv_sec = rem / 1000, .tv_usec = (rem % 1000) * 1000 }; + fd_set rs; + FD_ZERO(&rs); + FD_SET(dd, &rs); + int s = select(dd + 1, &rs, NULL, NULL, &tv); + if (s < 0) { if (errno == EINTR) break; break; } // Ctrl-C or error -> give up cleanly + if (s == 0) break; // timeout + + int n = read(dd, buf, sizeof(buf)); + if (n < (int)(1 + HCI_EVENT_HDR_SIZE + 1)) continue; + uint8_t *pkt_end = buf + n; + + // layout: [0]=HCI_EVENT_PKT, [1..2]=hci_event_hdr, then evt_le_meta_event + evt_le_meta_event *meta = (evt_le_meta_event *)(buf + 1 + HCI_EVENT_HDR_SIZE); + if ((uint8_t *)meta->data > pkt_end) continue; + if (meta->subevent != EVT_LE_ADVERTISING_REPORT) continue; + + uint8_t *p = meta->data; + uint8_t num = *p++; // number of reports in this event + for (uint8_t r = 0; r < num; r++) { + if (p + LE_ADVERTISING_INFO_SIZE > pkt_end) break; + le_advertising_info *info = (le_advertising_info *)p; + uint8_t *next = p + LE_ADVERTISING_INFO_SIZE + info->length + 1; // +1 trailing RSSI + if (next > pkt_end) break; // malformed / truncated report + if (ble_ad_name_matches(name, info->data, info->length, nm, sizeof(nm))) { + ba2str(&info->bdaddr, out_mac); + PrintAndLogEx(SUCCESS, "BLE: found " _GREEN_("%s") " at " _GREEN_("%s"), nm, out_mac); + rc = 0; + break; + } + p = next; + } + if (rc == 0) break; + } + + hci_le_set_scan_enable(dd, 0x00, 0x00, 1000); + if (have_of) setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + hci_close_dev(dd); + + if (rc != 0) + PrintAndLogEx(ERR, "BLE: no advertising device named " _YELLOW_("%s") " seen within %d ms", name, timeout_ms); + return rc; +} + int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { memset(conn, 0, sizeof(*conn)); conn->fd = -1; From dcde9dd16ef7380ab9d9cd8b8c1b418178eb830e Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 12:43:40 +0200 Subject: [PATCH 02/20] Add ble_resolve_name function for device name resolution Added a function to resolve advertised device names to their LE addresses. Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/src/uart/ble_posix.h b/client/src/uart/ble_posix.h index d67487a7a..5985bcf75 100644 --- a/client/src/uart/ble_posix.h +++ b/client/src/uart/ble_posix.h @@ -39,6 +39,16 @@ typedef struct { size_t leftover_len; } ble_conn_t; +// Default time budget for a name -> address scan. +#define BLE_SCAN_TIMEOUT_MS 8000 + +// Resolve an advertised device name to its LE address via an active HCI scan. +// Matches the Complete (0x09) or Shortened (0x08) Local Name, case-insensitively. +// On success writes "XX:XX:XX:XX:XX:XX" into out_mac (needs >= 18 bytes) and +// returns 0; returns negative on error or if no such device is seen in time. +// Needs CAP_NET_RAW+CAP_NET_ADMIN (raw HCI), i.e. more privilege than connect. +int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms); + // Connect to `mac` (LE public), discover the SPP char, subscribe to notifications. // `chr_uuid16` selects the data characteristic (pass BLE_SPP_CHR_UUID16). // Returns 0 on success (conn filled), negative on error. On error conn->fd == -1. From 7efc83ca1de9d05b876d020276bb2c575ac77e2f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 12:44:15 +0200 Subject: [PATCH 03/20] Implement BD address validation and update BLE logic Added a function to check if a string is a valid Bluetooth address and updated the BLE connection logic to handle both addresses and names. Signed-off-by: Niel Nielsen --- client/src/uart/uart_posix.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/client/src/uart/uart_posix.c b/client/src/uart/uart_posix.c index 3111eb466..b0240edd9 100644 --- a/client/src/uart/uart_posix.c +++ b/client/src/uart/uart_posix.c @@ -39,6 +39,7 @@ #include #ifdef HAVE_BLUEZ +#include // isxdigit (ble: name vs address check) #include #include #include "ble_posix.h" @@ -115,6 +116,22 @@ uint32_t uart_get_timeouts(void) { return newtimeout_value; } +#ifdef HAVE_BLUEZ +// True if `s` is a plain 6-octet BD address "XX:XX:XX:XX:XX:XX" (hex, colon-sep). +// Used to tell a literal address from a device name after the "ble:" prefix. +static bool uart_is_bdaddr(const char *s) { + if (s == NULL || strlen(s) != 17) return false; + for (int i = 0; i < 17; i++) { + if ((i % 3) == 2) { + if (s[i] != ':') return false; + } else if (isxdigit((unsigned char)s[i]) == 0) { + return false; + } + } + return true; +} +#endif // HAVE_BLUEZ + serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) { serial_port_unix_t_t *sp = calloc(sizeof(serial_port_unix_t_t), sizeof(uint8_t)); @@ -416,8 +433,22 @@ serial_port uart_open(const char *pcPortName, uint32_t speed, bool slient) { free(sp); return INVALID_SERIAL_PORT; } - // pcPortName is "ble:" - if (ble_connect(pcPortName + 4, BLE_SPP_CHR_UUID16, bc) != 0) { + + // pcPortName is "ble:". If the part after the prefix is + // a literal BD address use it as-is; otherwise treat it as an advertised + // device name and resolve it to an address via an active LE scan. + const char *ble_arg = pcPortName + 4; + char resolved[18]; + if (uart_is_bdaddr(ble_arg) == false) { + if (ble_resolve_name(ble_arg, resolved, sizeof(resolved), BLE_SCAN_TIMEOUT_MS) != 0) { + free(bc); + free(sp); + return INVALID_SERIAL_PORT; + } + ble_arg = resolved; + } + + if (ble_connect(ble_arg, BLE_SPP_CHR_UUID16, bc) != 0) { free(bc); free(sp); return INVALID_SERIAL_PORT; From ca29260b0ddc716e569a5f14e650c725fcfacc77 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 13:09:46 +0200 Subject: [PATCH 04/20] Correct preprocessor directive formatting Fix formatting of preprocessor directive ending. Signed-off-by: Niel Nielsen From 28daf3e2c49559f44922e5f0d6205f7008360601 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 13:15:40 +0200 Subject: [PATCH 05/20] Enhance BLE capabilities hint and connection logic Add hints for granting BLE scanning privileges and improve error handling in connection functions. Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.c | 66 +++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/client/src/uart/ble_posix.c b/client/src/uart/ble_posix.c index 05c1ddb51..6e75fbb1f 100644 --- a/client/src/uart/ble_posix.c +++ b/client/src/uart/ble_posix.c @@ -222,6 +222,23 @@ static int ble_ad_name_matches(const char *want, const uint8_t *ad, size_t adlen return 0; } +// Print an exact, copy-pasteable command to grant the capabilities the LE scan +// needs, using the running binary's own path so it survives rebuilds/renames. +// Also points at the no-privilege alternative (connect by address). +static void ble_print_caps_hint(void) { + char exe[4096]; + ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + if (n > 0) { + exe[n] = 0; + PrintAndLogEx(INFO, "BLE: scanning by name needs extra privileges. Grant them once with:"); + PrintAndLogEx(INFO, " " _YELLOW_("sudo setcap 'cap_net_raw,cap_net_admin+eip' %s"), exe); + } else { + PrintAndLogEx(INFO, "BLE: scanning by name needs " _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN") + " on the client binary (or run with sudo)"); + } + PrintAndLogEx(INFO, " or connect by address instead: " _YELLOW_("-p ble:") " (no extra privileges)"); +} + int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms) { if (name == NULL || out_mac == NULL || out_mac_sz < 18) return -1; out_mac[0] = 0; @@ -235,8 +252,7 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (dd < 0) { PrintAndLogEx(ERR, "BLE: cannot open hci%d (%s)", dev_id, strerror(errno)); if (errno == EPERM || errno == EACCES) - PrintAndLogEx(INFO, "BLE: scanning by name needs privileges - run with sudo, or grant the client " - _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN")); + ble_print_caps_hint(); return -1; } @@ -253,8 +269,7 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (hci_le_set_scan_enable(dd, 0x01, 0x00, 1000) < 0) { // filter_dup off: names may only be in SCAN_RSP PrintAndLogEx(ERR, "BLE: cannot start LE scan (%s)", strerror(errno)); if (errno == EPERM || errno == EACCES) - PrintAndLogEx(INFO, "BLE: scanning by name needs privileges - run with sudo, or grant the client " - _YELLOW_("CAP_NET_RAW+CAP_NET_ADMIN")); + ble_print_caps_hint(); hci_close_dev(dd); return -1; } @@ -323,14 +338,22 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim return rc; } -int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { - memset(conn, 0, sizeof(*conn)); - conn->fd = -1; +// How hard to try the L2CAP connect before giving up. A create-connection +// issued in the instant an LE scan tears down (the name path) can be refused by +// the controller, and a peripheral may momentarily be between advertising +// events; both clear within a few tens of ms. The connect-by-address path +// normally succeeds on the first attempt, so this adds no delay there. +#define BLE_CONNECT_TRIES 6 +#define BLE_CONNECT_BACKOFF_MS 150 +// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd on success, +// -1 on a retryable connect failure (errno set to the connect error), or +// -2 on a hard setup failure (socket/bind), which is already logged. +static int ble_l2cap_connect(const char *mac) { int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (fd < 0) { PrintAndLogEx(ERR, "BLE: cannot create L2CAP socket (%s)", strerror(errno)); - return -1; + return -2; } // Enlarge the kernel RX buffer so bursts of notifications queue instead of @@ -346,7 +369,7 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { if (bind(fd, (struct sockaddr *)&src, sizeof(src)) < 0) { PrintAndLogEx(ERR, "BLE: bind failed (%s)", strerror(errno)); close(fd); - return -1; + return -2; } struct sockaddr_l2 dst = {0}; @@ -355,8 +378,31 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { str2ba(mac, &dst.l2_bdaddr); dst.l2_cid = htobs(ATT_CID); if (connect(fd, (struct sockaddr *)&dst, sizeof(dst)) < 0) { - PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(errno)); + int e = errno; // close() may clobber errno; preserve it for the caller close(fd); + errno = e; + return -1; + } + return fd; +} + +int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { + memset(conn, 0, sizeof(*conn)); + conn->fd = -1; + + int fd = -1; + int last_errno = 0; + for (int attempt = 1; attempt <= BLE_CONNECT_TRIES; attempt++) { + fd = ble_l2cap_connect(mac); + if (fd >= 0) break; + if (fd == -2) return -1; // hard setup failure, already logged + last_errno = errno; // retryable connect failure + if (attempt < BLE_CONNECT_TRIES) { + usleep(BLE_CONNECT_BACKOFF_MS * 1000); + } + } + if (fd < 0) { + PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(last_errno)); return -1; } From ea585bacd66e826afa7c25d90adaadce69b3f029 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 13:18:57 +0200 Subject: [PATCH 06/20] Fix include directive for dependency files in Makefile Signed-off-by: Niel Nielsen --- client/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/Makefile b/client/Makefile index e6eb01104..28a8c768f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -907,7 +907,6 @@ SRCS = mifare/aiddesfire.c \ parsers/parsehrt.c \ parsers/parsehid.c \ parsers/parsevigik.c \ - parsers/parsehexact.c \ parsers/hrtparser/hrtparser.c \ pm3.c \ pm3_binlib.c \ @@ -1068,6 +1067,9 @@ install: all ifneq (,$(INSTALLBIN)) $(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH) $(Q)$(INSTALLSUDO) $(CP) $(INSTALLBIN) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH) +ifeq ($(platform),Linux) + $(Q)$(INSTALLSUDO) setcap 'cap_net_raw,cap_net_admin+eip' $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLBINRELPATH)$(PATHSEP)$(INSTALLBIN) || echo "[!] setcap failed - BLE connect-by-name (-p ble:) will need sudo; connect-by-MAC is unaffected" +endif endif ifneq (,$(INSTALLSHARE)) $(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH) From 52fac7ca1b500f147595a34452a030b417042df9 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 13:44:42 +0200 Subject: [PATCH 07/20] Add parsehexact.c to Makefile build list Signed-off-by: Niel Nielsen --- client/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/client/Makefile b/client/Makefile index 28a8c768f..7e2248b33 100644 --- a/client/Makefile +++ b/client/Makefile @@ -907,6 +907,7 @@ SRCS = mifare/aiddesfire.c \ parsers/parsehrt.c \ parsers/parsehid.c \ parsers/parsevigik.c \ + parsers/parsehexact.c \ parsers/hrtparser/hrtparser.c \ pm3.c \ pm3_binlib.c \ From e004cc213a64afcab93639068c63e7f2ffde6922 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 14:52:41 +0200 Subject: [PATCH 08/20] Update Makefile Signed-off-by: Niel Nielsen From cdc4d7ad26f0043db036a50e1f77678e6da587e5 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 14:54:07 +0200 Subject: [PATCH 09/20] Refactor BLE connection error handling and logging Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.c | 67 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/client/src/uart/ble_posix.c b/client/src/uart/ble_posix.c index 6e75fbb1f..7d94dc86b 100644 --- a/client/src/uart/ble_posix.c +++ b/client/src/uart/ble_posix.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -29,8 +28,9 @@ #include #include -#include "ui.h" // PrintAndLogEx -#include "pm3_cmd.h" // PM3_* (only for messaging parity; returns are 0/neg here) +#include "ui.h" // PrintAndLogEx +#include "pm3_cmd.h" // PM3_SUCCESS / PM3_E* +#include "util_posix.h" // msclock, msleep #define ATT_CID 4 @@ -193,12 +193,6 @@ static int att_subscribe(int fd, uint16_t cccd_handle) { #define BLE_ADV_NAME_SHORT 0x08 #define BLE_ADV_NAME_FULL 0x09 -static long ble_now_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; -} - // Walk the AD structures of one advertising/scan-response report, looking for a // Local Name (complete or shortened) that equals `want` (case-insensitive). // Copies whatever name it finds into `found` for logging. Returns 1 on match. @@ -240,20 +234,20 @@ static void ble_print_caps_hint(void) { } int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms) { - if (name == NULL || out_mac == NULL || out_mac_sz < 18) return -1; + if (name == NULL || out_mac == NULL || out_mac_sz < 18) return PM3_EINVARG; out_mac[0] = 0; int dev_id = hci_get_route(NULL); if (dev_id < 0) { PrintAndLogEx(ERR, "BLE: no local Bluetooth adapter found (%s)", strerror(errno)); - return -1; + return PM3_EIO; } int dd = hci_open_dev(dev_id); if (dd < 0) { PrintAndLogEx(ERR, "BLE: cannot open hci%d (%s)", dev_id, strerror(errno)); if (errno == EPERM || errno == EACCES) ble_print_caps_hint(); - return -1; + return PM3_EIO; } // NOTE: despite the header naming its first arg `dev_id`, BlueZ's @@ -271,7 +265,7 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (errno == EPERM || errno == EACCES) ble_print_caps_hint(); hci_close_dev(dd); - return -1; + return PM3_EIO; } // Receive only HCI event packets carrying LE meta events; save/restore the @@ -286,13 +280,13 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim PrintAndLogEx(INFO, "BLE: scanning up to " _YELLOW_("%d") " ms for " _YELLOW_("%s") " ...", timeout_ms, name); - int rc = -1; + int rc = PM3_ETIMEOUT; char nm[64]; - long deadline = ble_now_ms() + timeout_ms; + uint64_t deadline = msclock() + (uint64_t)timeout_ms; uint8_t buf[HCI_MAX_EVENT_SIZE]; - while (ble_now_ms() < deadline) { - long rem = deadline - ble_now_ms(); + while (msclock() < deadline) { + int64_t rem = (int64_t)(deadline - msclock()); if (rem <= 0) break; struct timeval tv = { .tv_sec = rem / 1000, .tv_usec = (rem % 1000) * 1000 }; fd_set rs; @@ -321,19 +315,19 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (ble_ad_name_matches(name, info->data, info->length, nm, sizeof(nm))) { ba2str(&info->bdaddr, out_mac); PrintAndLogEx(SUCCESS, "BLE: found " _GREEN_("%s") " at " _GREEN_("%s"), nm, out_mac); - rc = 0; + rc = PM3_SUCCESS; break; } p = next; } - if (rc == 0) break; + if (rc == PM3_SUCCESS) break; } hci_le_set_scan_enable(dd, 0x00, 0x00, 1000); if (have_of) setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); hci_close_dev(dd); - if (rc != 0) + if (rc != PM3_SUCCESS) PrintAndLogEx(ERR, "BLE: no advertising device named " _YELLOW_("%s") " seen within %d ms", name, timeout_ms); return rc; } @@ -346,9 +340,12 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim #define BLE_CONNECT_TRIES 6 #define BLE_CONNECT_BACKOFF_MS 150 -// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd on success, -// -1 on a retryable connect failure (errno set to the connect error), or -// -2 on a hard setup failure (socket/bind), which is already logged. +// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd (>= 0) on +// success, -1 on a retryable connect failure (errno set to the connect +// error), or -2 on a hard setup failure (socket/bind), which is already +// logged. Private helper: intentionally NOT PM3_E*-coded since it returns a +// real fd on success and PM3_SUCCESS (0) would collide with a valid fd 0; +// ble_connect() below translates its result to PM3_E* for the public API. static int ble_l2cap_connect(const char *mac) { int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (fd < 0) { @@ -395,15 +392,15 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { for (int attempt = 1; attempt <= BLE_CONNECT_TRIES; attempt++) { fd = ble_l2cap_connect(mac); if (fd >= 0) break; - if (fd == -2) return -1; // hard setup failure, already logged + if (fd == -2) return PM3_EIO; // hard setup failure, already logged last_errno = errno; // retryable connect failure if (attempt < BLE_CONNECT_TRIES) { - usleep(BLE_CONNECT_BACKOFF_MS * 1000); + msleep(BLE_CONNECT_BACKOFF_MS); } } if (fd < 0) { PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(last_errno)); - return -1; + return PM3_EIO; } conn->fd = fd; @@ -430,16 +427,16 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { PrintAndLogEx(SUCCESS, "BLE connected, MTU " _GREEN_("%u") ", char handle " _GREEN_("0x%04X"), conn->mtu, conn->val_handle); - return 0; + return PM3_SUCCESS; fail: close(fd); conn->fd = -1; - return -1; + return PM3_EIO; } int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len) { - if (conn->fd < 0) return -1; + if (conn->fd < 0) return PM3_ENOTTY; size_t chunk = (conn->mtu > 3) ? (size_t)(conn->mtu - 3) : 20; uint8_t pdu[3 + 517]; for (size_t off = 0; off < len; off += chunk) { @@ -448,14 +445,14 @@ int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len) { put16(&pdu[1], conn->val_handle); memcpy(&pdu[3], data + off, clen); if (att_write_pdu(conn->fd, pdu, 3 + clen) != 0) { - return -1; + return PM3_EIO; } } - return 0; + return PM3_SUCCESS; } int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int timeout_ms) { - if (conn->fd < 0) return -1; + if (conn->fd < 0) return PM3_ENOTTY; size_t got = 0; // 1) drain any leftover payload from a previous oversized notification @@ -466,7 +463,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int size_t rem = conn->leftover_len - take; if (rem) memmove(conn->leftover, conn->leftover + take, rem); conn->leftover_len = rem; - if (got == maxlen) { *out_len = got; return 0; } + if (got == maxlen) { *out_len = got; return PM3_SUCCESS; } } // 2) pull notifications until buffer full or a real timeout. @@ -479,7 +476,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int uint8_t pdu[3 + 517]; for (;;) { int n = att_read_pdu(conn->fd, pdu, sizeof(pdu), timeout_ms); - if (n < 0) { *out_len = got; return (got > 0) ? 0 : -1; } + if (n < 0) { *out_len = got; return (got > 0) ? PM3_SUCCESS : PM3_EIO; } if (n == 0) break; // real timeout / no more if ((pdu[0] != ATT_OP_HANDLE_NOTIFY && pdu[0] != ATT_OP_HANDLE_INDICATE) || n < 3) continue; // ignore non-notifications @@ -500,7 +497,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int } *out_len = got; - return 0; + return PM3_SUCCESS; } void ble_close(ble_conn_t *conn) { From 510767f51ebff9f090ee0870ab4e54e7f8ec5759 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 14:54:45 +0200 Subject: [PATCH 10/20] Update return values in ble_posix.h documentation Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/uart/ble_posix.h b/client/src/uart/ble_posix.h index 5985bcf75..9db502353 100644 --- a/client/src/uart/ble_posix.h +++ b/client/src/uart/ble_posix.h @@ -45,22 +45,24 @@ typedef struct { // Resolve an advertised device name to its LE address via an active HCI scan. // Matches the Complete (0x09) or Shortened (0x08) Local Name, case-insensitively. // On success writes "XX:XX:XX:XX:XX:XX" into out_mac (needs >= 18 bytes) and -// returns 0; returns negative on error or if no such device is seen in time. +// returns PM3_SUCCESS; returns PM3_EINVARG on bad arguments, PM3_EIO on a +// local adapter/scan failure, or PM3_ETIMEOUT if no such device is seen in time. // Needs CAP_NET_RAW+CAP_NET_ADMIN (raw HCI), i.e. more privilege than connect. int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms); // Connect to `mac` (LE public), discover the SPP char, subscribe to notifications. // `chr_uuid16` selects the data characteristic (pass BLE_SPP_CHR_UUID16). -// Returns 0 on success (conn filled), negative on error. On error conn->fd == -1. +// Returns PM3_SUCCESS (conn filled) or PM3_EIO on error. On error conn->fd == -1. int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn); // Send len bytes to the device, chunked to (mtu-3) as ATT Write Commands. -// Returns 0 on success, negative on error. +// Returns PM3_SUCCESS, PM3_ENOTTY if not connected, or PM3_EIO on write error. int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len); // Receive up to maxlen payload bytes (drains leftover first, then waits up to -// timeout_ms for notifications). *out_len set to bytes copied. Returns 0 on -// success (including 0 bytes on timeout), negative on error/disconnect. +// timeout_ms for notifications). *out_len set to bytes copied. Returns +// PM3_SUCCESS on success (including 0 bytes on timeout), PM3_ENOTTY if not +// connected, or PM3_EIO on error/disconnect. int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int timeout_ms); void ble_close(ble_conn_t *conn); From 50e07859e8c84fe7d08cd632693c2a4aad19b45a Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 16:25:09 +0200 Subject: [PATCH 11/20] Refactor error handling in select function Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/uart/ble_posix.c b/client/src/uart/ble_posix.c index 7d94dc86b..e1e38b1f2 100644 --- a/client/src/uart/ble_posix.c +++ b/client/src/uart/ble_posix.c @@ -17,7 +17,6 @@ #include #include -#include // strcasecmp #include #include #include @@ -293,9 +292,11 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim FD_ZERO(&rs); FD_SET(dd, &rs); int s = select(dd + 1, &rs, NULL, NULL, &tv); - if (s < 0) { if (errno == EINTR) break; break; } // Ctrl-C or error -> give up cleanly - if (s == 0) break; // timeout - + if (s < 0) { + if (errno == EINTR) continue; // Interrupted by signal, try again + break; // Unrecoverable error, break loop + } + if (s == 0) break; // Timeout, handle gracefully int n = read(dd, buf, sizeof(buf)); if (n < (int)(1 + HCI_EVENT_HDR_SIZE + 1)) continue; uint8_t *pkt_end = buf + n; From 0a90b714cce7ab1d1ec3c3a96a8129d205a2e23f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 17:31:12 +0200 Subject: [PATCH 12/20] Add files via upload Signed-off-by: Niel Nielsen --- client/CMakeLists.txt | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c17c8c9ac..ae182b980 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -509,7 +509,6 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/parsers/parsehrt.c ${PM3_ROOT}/client/src/parsers/parsehid.c ${PM3_ROOT}/client/src/parsers/parsevigik.c - ${PM3_ROOT}/client/src/parsers/parsehexact.c ${PM3_ROOT}/client/src/parsers/hrtparser/hrtparser.c ${PM3_ROOT}/client/src/pla.c ${PM3_ROOT}/client/src/preferences.c @@ -968,6 +967,27 @@ endif (NOT SKIPPYTHON EQUAL 1) target_link_directories(proxmark3 PRIVATE ${ADDITIONAL_LNKDIRS}) install(TARGETS proxmark3 DESTINATION "bin") + +# Grant the capabilities the BLE connect-by-name scan needs (raw HCI: CAP_NET_RAW +# + CAP_NET_ADMIN). Linux + Bluez only, and non-fatal: if setcap is absent, the +# target filesystem lacks xattr support, or this is a staged/packaged install, +# it just warns. Connect-by-address (-p ble:) never needs this. +if (BLUEZ_FOUND AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_program(SETCAP_EXECUTABLE setcap) + if (SETCAP_EXECUTABLE) + install(CODE " + set(_pm3_bin \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/proxmark3\") + execute_process( + COMMAND \"${SETCAP_EXECUTABLE}\" cap_net_raw,cap_net_admin+eip \"\${_pm3_bin}\" + RESULT_VARIABLE _pm3_setcap_rc) + if (NOT _pm3_setcap_rc EQUAL 0) + message(WARNING \"setcap failed - BLE connect-by-name (-p ble:) will need sudo; connect-by-MAC is unaffected\") + endif () + ") + else () + message(STATUS "setcap not found - BLE connect-by-name will need sudo at runtime") + endif () +endif () install(DIRECTORY cmdscripts lualibs luascripts pyscripts resources dictionaries DESTINATION "share/proxmark3") add_custom_command(OUTPUT lualibs/pm3_cmd.lua From 583f05198a0ff0613ccfca8185e4817559ebd3f4 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 17:57:07 +0200 Subject: [PATCH 13/20] Add parsehexact.c to CMakeLists.txt Signed-off-by: Niel Nielsen --- client/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ae182b980..466d22ab2 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -509,6 +509,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/parsers/parsehrt.c ${PM3_ROOT}/client/src/parsers/parsehid.c ${PM3_ROOT}/client/src/parsers/parsevigik.c + ${PM3_ROOT}/client/src/parsers/parsehexact.c ${PM3_ROOT}/client/src/parsers/hrtparser/hrtparser.c ${PM3_ROOT}/client/src/pla.c ${PM3_ROOT}/client/src/preferences.c From c264251935741af4eaa71592e8f35511eee8e544 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 17:57:59 +0200 Subject: [PATCH 14/20] Fix CMakeLists.txt to properly close foreach loop Signed-off-by: Niel Nielsen From 284e4b385cbc2a91a7857cd26a628f0ed22da858 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 17:58:45 +0200 Subject: [PATCH 15/20] Refactor BLE error handling and time functions Refactor BLE error handling to return standard error codes instead of PM3_* constants. Update time-related functions to use a consistent method for obtaining the current time. Signed-off-by: Niel Nielsen --- client/src/uart/ble_posix.c | 91 ++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/client/src/uart/ble_posix.c b/client/src/uart/ble_posix.c index e1e38b1f2..19afd25e2 100644 --- a/client/src/uart/ble_posix.c +++ b/client/src/uart/ble_posix.c @@ -17,9 +17,11 @@ #include #include +#include // strcasecmp #include #include #include +#include #include #include #include @@ -27,9 +29,8 @@ #include #include -#include "ui.h" // PrintAndLogEx -#include "pm3_cmd.h" // PM3_SUCCESS / PM3_E* -#include "util_posix.h" // msclock, msleep +#include "ui.h" // PrintAndLogEx +#include "pm3_cmd.h" // PM3_* (only for messaging parity; returns are 0/neg here) #define ATT_CID 4 @@ -192,6 +193,12 @@ static int att_subscribe(int fd, uint16_t cccd_handle) { #define BLE_ADV_NAME_SHORT 0x08 #define BLE_ADV_NAME_FULL 0x09 +static long ble_now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; +} + // Walk the AD structures of one advertising/scan-response report, looking for a // Local Name (complete or shortened) that equals `want` (case-insensitive). // Copies whatever name it finds into `found` for logging. Returns 1 on match. @@ -233,27 +240,25 @@ static void ble_print_caps_hint(void) { } int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int timeout_ms) { - if (name == NULL || out_mac == NULL || out_mac_sz < 18) return PM3_EINVARG; + if (name == NULL || out_mac == NULL || out_mac_sz < 18) return -1; out_mac[0] = 0; int dev_id = hci_get_route(NULL); if (dev_id < 0) { PrintAndLogEx(ERR, "BLE: no local Bluetooth adapter found (%s)", strerror(errno)); - return PM3_EIO; + return -1; } int dd = hci_open_dev(dev_id); if (dd < 0) { PrintAndLogEx(ERR, "BLE: cannot open hci%d (%s)", dev_id, strerror(errno)); if (errno == EPERM || errno == EACCES) ble_print_caps_hint(); - return PM3_EIO; + return -1; } - // NOTE: despite the header naming its first arg `dev_id`, BlueZ's - // hci_le_set_scan_* helpers expect the *open descriptor* (dd) and pass it - // straight to hci_send_req(). Passing the raw device id here silently fails. - // Active scan (0x01) so we also receive SCAN_RSP, which is where many - // devices (incl. the BWM) put the complete local name. + // BlueZ's hci_le_set_scan_* take the open descriptor (dd), not dev_id, + // despite the arg name - passing dev_id silently fails. + // Active scan (0x01) so SCAN_RSP (where the BWM puts its name) is seen. if (hci_le_set_scan_parameters(dd, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0x00, 1000) < 0) PrintAndLogEx(INFO, "BLE: set scan parameters failed (%s), continuing", strerror(errno)); @@ -264,7 +269,7 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (errno == EPERM || errno == EACCES) ble_print_caps_hint(); hci_close_dev(dd); - return PM3_EIO; + return -1; } // Receive only HCI event packets carrying LE meta events; save/restore the @@ -279,24 +284,22 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim PrintAndLogEx(INFO, "BLE: scanning up to " _YELLOW_("%d") " ms for " _YELLOW_("%s") " ...", timeout_ms, name); - int rc = PM3_ETIMEOUT; + int rc = -1; char nm[64]; - uint64_t deadline = msclock() + (uint64_t)timeout_ms; + long deadline = ble_now_ms() + timeout_ms; uint8_t buf[HCI_MAX_EVENT_SIZE]; - while (msclock() < deadline) { - int64_t rem = (int64_t)(deadline - msclock()); + while (ble_now_ms() < deadline) { + long rem = deadline - ble_now_ms(); if (rem <= 0) break; struct timeval tv = { .tv_sec = rem / 1000, .tv_usec = (rem % 1000) * 1000 }; fd_set rs; FD_ZERO(&rs); FD_SET(dd, &rs); int s = select(dd + 1, &rs, NULL, NULL, &tv); - if (s < 0) { - if (errno == EINTR) continue; // Interrupted by signal, try again - break; // Unrecoverable error, break loop - } - if (s == 0) break; // Timeout, handle gracefully + if (s < 0) { if (errno == EINTR) break; break; } // Ctrl-C or error -> give up cleanly + if (s == 0) break; // timeout + int n = read(dd, buf, sizeof(buf)); if (n < (int)(1 + HCI_EVENT_HDR_SIZE + 1)) continue; uint8_t *pkt_end = buf + n; @@ -316,37 +319,31 @@ int ble_resolve_name(const char *name, char *out_mac, size_t out_mac_sz, int tim if (ble_ad_name_matches(name, info->data, info->length, nm, sizeof(nm))) { ba2str(&info->bdaddr, out_mac); PrintAndLogEx(SUCCESS, "BLE: found " _GREEN_("%s") " at " _GREEN_("%s"), nm, out_mac); - rc = PM3_SUCCESS; + rc = 0; break; } p = next; } - if (rc == PM3_SUCCESS) break; + if (rc == 0) break; } hci_le_set_scan_enable(dd, 0x00, 0x00, 1000); if (have_of) setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); hci_close_dev(dd); - if (rc != PM3_SUCCESS) + if (rc != 0) PrintAndLogEx(ERR, "BLE: no advertising device named " _YELLOW_("%s") " seen within %d ms", name, timeout_ms); return rc; } -// How hard to try the L2CAP connect before giving up. A create-connection -// issued in the instant an LE scan tears down (the name path) can be refused by -// the controller, and a peripheral may momentarily be between advertising -// events; both clear within a few tens of ms. The connect-by-address path -// normally succeeds on the first attempt, so this adds no delay there. +// Retry the L2CAP connect briefly: a create-connection issued as an LE scan +// tears down can be refused, and the peer may be between advertising events. #define BLE_CONNECT_TRIES 6 #define BLE_CONNECT_BACKOFF_MS 150 -// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd (>= 0) on -// success, -1 on a retryable connect failure (errno set to the connect -// error), or -2 on a hard setup failure (socket/bind), which is already -// logged. Private helper: intentionally NOT PM3_E*-coded since it returns a -// real fd on success and PM3_SUCCESS (0) would collide with a valid fd 0; -// ble_connect() below translates its result to PM3_E* for the public API. +// Open an L2CAP/ATT socket and connect to `mac`. Returns the fd on success, +// -1 on a retryable connect failure (errno set to the connect error), or +// -2 on a hard setup failure (socket/bind), which is already logged. static int ble_l2cap_connect(const char *mac) { int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (fd < 0) { @@ -393,15 +390,15 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { for (int attempt = 1; attempt <= BLE_CONNECT_TRIES; attempt++) { fd = ble_l2cap_connect(mac); if (fd >= 0) break; - if (fd == -2) return PM3_EIO; // hard setup failure, already logged + if (fd == -2) return -1; // hard setup failure, already logged last_errno = errno; // retryable connect failure if (attempt < BLE_CONNECT_TRIES) { - msleep(BLE_CONNECT_BACKOFF_MS); + usleep(BLE_CONNECT_BACKOFF_MS * 1000); } } if (fd < 0) { PrintAndLogEx(ERR, "BLE: cannot connect to " _YELLOW_("%s") " (%s)", mac, strerror(last_errno)); - return PM3_EIO; + return -1; } conn->fd = fd; @@ -428,16 +425,16 @@ int ble_connect(const char *mac, uint16_t chr_uuid16, ble_conn_t *conn) { PrintAndLogEx(SUCCESS, "BLE connected, MTU " _GREEN_("%u") ", char handle " _GREEN_("0x%04X"), conn->mtu, conn->val_handle); - return PM3_SUCCESS; + return 0; fail: close(fd); conn->fd = -1; - return PM3_EIO; + return -1; } int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len) { - if (conn->fd < 0) return PM3_ENOTTY; + if (conn->fd < 0) return -1; size_t chunk = (conn->mtu > 3) ? (size_t)(conn->mtu - 3) : 20; uint8_t pdu[3 + 517]; for (size_t off = 0; off < len; off += chunk) { @@ -446,14 +443,14 @@ int ble_send(ble_conn_t *conn, const uint8_t *data, size_t len) { put16(&pdu[1], conn->val_handle); memcpy(&pdu[3], data + off, clen); if (att_write_pdu(conn->fd, pdu, 3 + clen) != 0) { - return PM3_EIO; + return -1; } } - return PM3_SUCCESS; + return 0; } int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int timeout_ms) { - if (conn->fd < 0) return PM3_ENOTTY; + if (conn->fd < 0) return -1; size_t got = 0; // 1) drain any leftover payload from a previous oversized notification @@ -464,7 +461,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int size_t rem = conn->leftover_len - take; if (rem) memmove(conn->leftover, conn->leftover + take, rem); conn->leftover_len = rem; - if (got == maxlen) { *out_len = got; return PM3_SUCCESS; } + if (got == maxlen) { *out_len = got; return 0; } } // 2) pull notifications until buffer full or a real timeout. @@ -477,7 +474,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int uint8_t pdu[3 + 517]; for (;;) { int n = att_read_pdu(conn->fd, pdu, sizeof(pdu), timeout_ms); - if (n < 0) { *out_len = got; return (got > 0) ? PM3_SUCCESS : PM3_EIO; } + if (n < 0) { *out_len = got; return (got > 0) ? 0 : -1; } if (n == 0) break; // real timeout / no more if ((pdu[0] != ATT_OP_HANDLE_NOTIFY && pdu[0] != ATT_OP_HANDLE_INDICATE) || n < 3) continue; // ignore non-notifications @@ -498,7 +495,7 @@ int ble_recv(ble_conn_t *conn, uint8_t *buf, size_t maxlen, size_t *out_len, int } *out_len = got; - return PM3_SUCCESS; + return 0; } void ble_close(ble_conn_t *conn) { From afeab5f6dcb3b9d42fa7e59824f53ac92cbe3aaf Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 18:20:43 +0200 Subject: [PATCH 16/20] Fix CMakeLists.txt by adding missing newline Signed-off-by: Niel Nielsen From 720ea518f7d5ed7fec538cf5b8b1aba76aab1c6f Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 18:20:51 +0200 Subject: [PATCH 17/20] Fix CMakeLists.txt by adding missing newline Signed-off-by: Niel Nielsen From d8d0ecc7243c98657c99da15ecf55024596c8a64 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 18:24:07 +0200 Subject: [PATCH 18/20] Fix CMakeLists.txt by correcting foreach loop syntax Signed-off-by: Niel Nielsen From 23d851610b3d33a58f390bd1e0afa50f1efb9afb Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 18:26:44 +0200 Subject: [PATCH 19/20] Fix CMakeLists.txt to properly end foreach loop Signed-off-by: Niel Nielsen From 96634fb2bf7410019bb283227e1e09b8bf0e1f53 Mon Sep 17 00:00:00 2001 From: Niel Nielsen Date: Sun, 13 Sep 2026 20:18:32 +0200 Subject: [PATCH 20/20] Increase PM3_FPC_MAX_DATA from 2048 to 4096 Fixes the the disconnects when on BLE connection Signed-off-by: Niel Nielsen --- include/pm3_cmd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 3c11866f1..9fafe84f3 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -38,7 +38,7 @@ // Over the BWM/FPC link the forward buffers (AT32 DMA ring, ESP UART RX) are // small, so a full PM3_CMD_DATA_SIZE frame overruns them. Cap the payload the // device advertises and sends when replying via FPC. USB is unaffected. -#define PM3_FPC_MAX_DATA 2048 +#define PM3_FPC_MAX_DATA 4096 typedef struct { uint64_t cmd;