[PATCH v3 35/46] wpa_supplicant: Add FTM peer measurement result handling
Kavita Kavita
kavita.kavita at oss.qualcomm.com
Wed May 13 02:59:59 PDT 2026
From: Peddolla Harshavardhan Reddy <peddolla.reddy at oss.qualcomm.com>
Add support for receiving and processing FTM ranging measurement
results from the kernel via NL80211_CMD_PEER_MEASUREMENT_RESULT.
Add EVENT_PEER_MEASUREMENT_RESULT event and peer_measurement_result
structure containing RTT, distance, RSSI, burst parameters, LCI,
civic location, and NTB-specific fields.
nl80211_peer_measurement_result_event() parses the netlink message
and fires the event. wpas_pr_measurement_result() validates the
cookie against the pending session and forwards all results including
failures to the upper layer via PR-PEER-MEASUREMENT ctrl event.
On final result, ranging_final_received is set to block any further
results for the session. Session cleanup is handled separately on
the COMPLETE event.
Signed-off-by: Peddolla Harshavardhan Reddy <peddolla.reddy at oss.qualcomm.com>
---
src/common/proximity_ranging.c | 1 +
src/common/proximity_ranging.h | 3 +
src/common/wpa_ctrl.h | 3 +
src/drivers/driver.h | 63 +++++++
src/drivers/driver_common.c | 1 +
src/drivers/driver_nl80211_event.c | 257 +++++++++++++++++++++++++++++
wpa_supplicant/events.c | 7 +
wpa_supplicant/notify.c | 28 ++++
wpa_supplicant/notify.h | 2 +
wpa_supplicant/pr_supplicant.c | 47 ++++++
wpa_supplicant/pr_supplicant.h | 8 +
11 files changed, 420 insertions(+)
diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 06ee58b3d..5b5f3bf8a 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -151,6 +151,7 @@ void pr_deinit(struct pr_data *pr)
#ifdef CONFIG_PASN
os_free(pr->pr_pasn_params);
pr->pr_pasn_params = NULL;
+ pr->ranging_final_received = false;
pasn_initiator_pmksa_cache_deinit(pr->initiator_pmksa);
pasn_responder_pmksa_cache_deinit(pr->responder_pmksa);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index b8e49ab77..52a9620ec 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -596,6 +596,9 @@ struct pr_data {
/* PR PASN request tracking - similar to pasn_params in wpa_supplicant */
struct pr_pasn_ranging_params *pr_pasn_params;
+
+ /* Set when final measurement result received; blocks further results */
+ bool ranging_final_received;
};
/* PR Device Identity Resolution Attribute parameters */
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index 922d5f82a..70fe22576 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -507,6 +507,9 @@ extern "C" {
/* Proximity Ranging parameters to use in ranging */
#define PR_RANGING_PARAMS "PR-RANGING-PARAMS "
+/* Proximity Ranging measurement result */
+#define PR_EVENT_PEER_MEASUREMENT "PR-PEER-MEASUREMENT "
+
/* BSS command information masks */
#define WPA_BSS_MASK_ALL 0xFFFDFFFF
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 4689bc5b0..22a4ad02e 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -6668,6 +6668,13 @@ enum wpa_event_type {
* activities).
*/
EVENT_NAN_CHAN_EVACUATION,
+
+ /**
+ * EVENT_PEER_MEASUREMENT_RESULT - Ranging measurement result
+ *
+ * This event is used to indicate a ranging measurement result.
+ */
+ EVENT_PEER_MEASUREMENT_RESULT,
};
@@ -7719,6 +7726,62 @@ union wpa_event_data {
struct nan_chan_evacuation_info {
int freq;
} nan_chan_evacuation_info;
+
+ /**
+ * struct peer_measurement_result - Ranging measurement result
+ */
+ struct peer_measurement_result {
+ u8 addr[ETH_ALEN];
+ u32 status;
+ u64 host_time;
+ u64 ap_tsf;
+ u8 final;
+ u64 cookie;
+
+ /**
+ * struct peer_measurement_ftm_result -
+ * FTM-specific measurement results
+ */
+ struct peer_measurement_ftm_result {
+ u8 fail;
+ u32 fail_reason;
+ u32 burst_index;
+ u32 num_ftmr_attempts;
+ u32 num_ftmr_successes;
+ u32 busy_retry_time;
+ u32 num_bursts_exp;
+ u32 burst_duration;
+ u32 ftms_per_burst;
+ s32 rssi_avg;
+ s32 rssi_spread;
+ s64 rtt_avg;
+ u64 rtt_variance;
+ u64 rtt_spread;
+ s64 dist_avg;
+ u64 dist_variance;
+ u64 dist_spread;
+ const u8 *lci;
+ size_t lci_len;
+ const u8 *civicloc;
+ size_t civicloc_len;
+ /* Additional FTM parameters */
+ u32 burst_period;
+ u32 tx_ltf_repetition_count;
+ u32 rx_ltf_repetition_count;
+ u32 max_time_between_measurements;
+ u32 min_time_between_measurements;
+ u32 num_tx_spatial_streams;
+ u32 num_rx_spatial_streams;
+ u32 nominal_time;
+ u8 availability_window;
+ u32 band_width;
+ u32 preamble;
+ u8 is_delayed_lmr;
+ /* Flag indicating if FTM data is present */
+ u8 has_data;
+ } ftm;
+
+ } peer_measurement_result;
};
/**
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index f04f737c0..170821423 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -108,6 +108,7 @@ const char * event_to_string(enum wpa_event_type event)
E2S(NAN_SCHED_UPDATE_DONE);
E2S(NAN_ULW_UPDATE);
E2S(NAN_CHAN_EVACUATION);
+ E2S(PEER_MEASUREMENT_RESULT);
}
return "UNKNOWN";
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 2f5d352e2..c16508404 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -4258,6 +4258,258 @@ static void nl80211_assoc_comeback(struct wpa_driver_nl80211_data *drv,
MAC2STR((u8 *) nla_data(mac)), nla_get_u32(timeout));
}
+#ifdef CONFIG_PR
+
+static void
+nl80211_parse_peer_ftm_result(struct peer_measurement_ftm_result *ftm,
+ struct nlattr *ftm_data)
+{
+ struct nlattr *ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX + 1];
+
+ if (nla_parse_nested(ftm_tb, NL80211_PMSR_FTM_RESP_ATTR_MAX,
+ ftm_data, NULL))
+ return;
+
+ ftm->has_data = 1;
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]) {
+ ftm->fail = 1;
+ ftm->fail_reason =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]);
+ wpa_printf(MSG_DEBUG,
+ "nl80211: Ranging failed with reason %u",
+ ftm->fail_reason);
+ return;
+ }
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX])
+ ftm->burst_index =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS])
+ ftm->num_ftmr_attempts =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES])
+ ftm->num_ftmr_successes =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME])
+ ftm->busy_retry_time =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP])
+ ftm->num_bursts_exp =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION])
+ ftm->burst_duration =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST])
+ ftm->ftms_per_burst =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG])
+ ftm->rssi_avg =
+ nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD])
+ ftm->rssi_spread =
+ nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG])
+ ftm->rtt_avg =
+ nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE])
+ ftm->rtt_variance =
+ nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD])
+ ftm->rtt_spread =
+ nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG])
+ ftm->dist_avg =
+ nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE])
+ ftm->dist_variance =
+ nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD])
+ ftm->dist_spread =
+ nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]) {
+ ftm->lci = os_memdup(
+ nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]),
+ nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]));
+ ftm->lci_len = nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]);
+ }
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]) {
+ ftm->civicloc = os_memdup(
+ nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]),
+ nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]));
+ ftm->civicloc_len =
+ nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]);
+ }
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD])
+ ftm->burst_period =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT])
+ ftm->tx_ltf_repetition_count =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT])
+ ftm->rx_ltf_repetition_count =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS])
+ ftm->max_time_between_measurements =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS])
+ ftm->min_time_between_measurements =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS])
+ ftm->num_tx_spatial_streams =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS])
+ ftm->num_rx_spatial_streams =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME])
+ ftm->nominal_time =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW])
+ ftm->availability_window =
+ nla_get_u8(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH])
+ ftm->band_width =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE])
+ ftm->preamble =
+ nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE]);
+
+ if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR])
+ ftm->is_delayed_lmr =
+ !!ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR];
+}
+
+
+static void nl80211_peer_measurement_result_event(struct i802_bss *bss,
+ struct nlattr **tb)
+{
+ union wpa_event_data data;
+ struct nlattr *pmsr[NL80211_PMSR_ATTR_MAX + 1];
+ struct nlattr *peer;
+ u64 cookie = 0;
+ int rem;
+ struct nla_policy pmsr_policy[NL80211_PMSR_ATTR_MAX + 1] = {
+ [NL80211_PMSR_ATTR_PEERS] = { .type = NLA_NESTED },
+ };
+ struct nla_policy peer_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = {
+ [NL80211_PMSR_PEER_ATTR_ADDR] = {
+ .minlen = ETH_ALEN,
+ .maxlen = ETH_ALEN,
+ },
+ [NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_NESTED },
+ };
+ struct nla_policy resp_policy[NL80211_PMSR_RESP_ATTR_MAX + 1] = {
+ [NL80211_PMSR_RESP_ATTR_DATA] = { .type = NLA_NESTED },
+ [NL80211_PMSR_RESP_ATTR_STATUS] = { .type = NLA_U32 },
+ [NL80211_PMSR_RESP_ATTR_HOST_TIME] = { .type = NLA_U64 },
+ [NL80211_PMSR_RESP_ATTR_AP_TSF] = { .type = NLA_U64 },
+ [NL80211_PMSR_RESP_ATTR_FINAL] = { .type = NLA_FLAG },
+ };
+
+ os_memset(&data, 0, sizeof(data));
+ if (tb[NL80211_ATTR_COOKIE]) {
+ cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]);
+ wpa_printf(MSG_DEBUG,
+ "nl80211: PR: Peer measurement cookie: %llu",
+ (unsigned long long) cookie);
+ }
+
+ if (!tb[NL80211_ATTR_PEER_MEASUREMENTS] ||
+ nla_parse_nested(pmsr, NL80211_PMSR_ATTR_MAX,
+ tb[NL80211_ATTR_PEER_MEASUREMENTS], pmsr_policy))
+ return;
+
+ if (!pmsr[NL80211_PMSR_ATTR_PEERS])
+ return;
+
+ nla_for_each_nested(peer, pmsr[NL80211_PMSR_ATTR_PEERS], rem) {
+ struct nlattr *peer_tb[NL80211_PMSR_PEER_ATTR_MAX + 1];
+ struct nlattr *resp_tb[NL80211_PMSR_RESP_ATTR_MAX + 1];
+
+ if (nla_parse_nested(peer_tb, NL80211_PMSR_PEER_ATTR_MAX,
+ peer, peer_policy))
+ continue;
+
+ if (!peer_tb[NL80211_PMSR_PEER_ATTR_ADDR] ||
+ !peer_tb[NL80211_PMSR_PEER_ATTR_RESP])
+ continue;
+
+ os_memset(&data.peer_measurement_result, 0,
+ sizeof(data.peer_measurement_result));
+ data.peer_measurement_result.cookie = cookie;
+
+ os_memcpy(data.peer_measurement_result.addr,
+ nla_data(peer_tb[NL80211_PMSR_PEER_ATTR_ADDR]),
+ ETH_ALEN);
+
+ if (nla_parse_nested(resp_tb, NL80211_PMSR_RESP_ATTR_MAX,
+ peer_tb[NL80211_PMSR_PEER_ATTR_RESP],
+ resp_policy))
+ continue;
+
+ if (resp_tb[NL80211_PMSR_RESP_ATTR_STATUS])
+ data.peer_measurement_result.status =
+ nla_get_u32(resp_tb[NL80211_PMSR_RESP_ATTR_STATUS]);
+
+ if (resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME])
+ data.peer_measurement_result.host_time =
+ nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME]);
+
+ if (resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF])
+ data.peer_measurement_result.ap_tsf =
+ nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF]);
+
+ if (resp_tb[NL80211_PMSR_RESP_ATTR_FINAL])
+ data.peer_measurement_result.final = 1;
+
+ if (resp_tb[NL80211_PMSR_RESP_ATTR_DATA]) {
+ struct nlattr *data_type_tb[NL80211_PMSR_TYPE_MAX + 1];
+
+ if (nla_parse_nested(data_type_tb, NL80211_PMSR_TYPE_MAX,
+ resp_tb[NL80211_PMSR_RESP_ATTR_DATA],
+ NULL))
+ continue;
+
+ if (data_type_tb[NL80211_PMSR_TYPE_FTM])
+ nl80211_parse_peer_ftm_result(&data.peer_measurement_result.ftm,
+ data_type_tb[NL80211_PMSR_TYPE_FTM]);
+ }
+
+ wpa_supplicant_event(bss->ctx, EVENT_PEER_MEASUREMENT_RESULT, &data);
+ /* Free deep-copied LCI/civic location data */
+ os_free((void *) data.peer_measurement_result.ftm.lci);
+ os_free((void *) data.peer_measurement_result.ftm.civicloc);
+ }
+}
+
+#endif /* CONFIG_PR */
#ifdef CONFIG_IEEE80211AX
@@ -4808,6 +5060,11 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd,
case NL80211_CMD_INCUMBENT_SIGNAL_DETECT:
nl80211_incumbt_sig_intf_event(bss, tb);
break;
+#ifdef CONFIG_PR
+ case NL80211_CMD_PEER_MEASUREMENT_RESULT:
+ nl80211_peer_measurement_result_event(bss, tb);
+ break;
+#endif /* CONFIG_PR */
default:
wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event "
"(cmd=%d)", cmd);
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 22d6ce196..1fcc57ee1 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -7648,6 +7648,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
if (data)
wpas_setup_link_reconfig(wpa_s, &data->reconfig_info);
break;
+ case EVENT_PEER_MEASUREMENT_RESULT:
+#ifdef CONFIG_PR
+ if (data)
+ wpas_pr_measurement_result(wpa_s,
+ &data->peer_measurement_result);
+#endif /* CONFIG_PR */
+ break;
#ifdef CONFIG_NAN
case EVENT_NAN_CLUSTER_JOIN:
wpas_nan_cluster_join(wpa_s, data->nan_cluster_join_info.bssid,
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index 59fce8e9b..58661c7ec 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -1561,4 +1561,32 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s,
protocol_type, freq, channel, bw, format_bw);
}
+
+void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s,
+ const struct peer_measurement_result *result)
+{
+ char rtt[32] = "";
+ char dist[32] = "";
+
+ if (result->ftm.fail) {
+ wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT
+ "addr=" MACSTR " status=%u burst_index=%u fail=1 fail_reason=%u",
+ MAC2STR(result->addr), result->status,
+ result->ftm.burst_index, result->ftm.fail_reason);
+ return;
+ }
+
+ if (result->ftm.rtt_avg || result->ftm.has_data)
+ os_snprintf(rtt, sizeof(rtt), " rtt_avg=%lld",
+ (long long) result->ftm.rtt_avg);
+ if (result->ftm.dist_avg || result->ftm.has_data)
+ os_snprintf(dist, sizeof(dist), " dist_avg=%lld",
+ (long long) result->ftm.dist_avg);
+
+ wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT
+ "addr=" MACSTR " status=%u burst_index=%u%s%s",
+ MAC2STR(result->addr), result->status,
+ result->ftm.burst_index, rtt, dist);
+}
+
#endif /* CONFIG_PR */
diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
index eb4ce89a7..1ed9978b9 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -216,6 +216,8 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s,
const u8 *dev_addr, const u8 *peer_addr,
u8 role, u8 protocol, int freq, int channel,
int bw, int format_bw);
+void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s,
+ const struct peer_measurement_result *result);
void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s,
const u8 *peer_addr, u16 pbm,
int handle, u8 requestor_instance_id);
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 7a0557ea7..e28b68c13 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -468,6 +468,7 @@ static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s,
fail:
os_free(pr->pr_pasn_params);
pr->pr_pasn_params = NULL;
+ pr->ranging_final_received = false;
return -1;
}
@@ -756,6 +757,51 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik,
}
+void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s,
+ struct peer_measurement_result *result)
+{
+ struct pr_data *pr = wpa_s->global->pr;
+
+ if (!result) {
+ wpa_printf(MSG_ERROR, "PR: Invalid measurement result");
+ return;
+ }
+
+ /* Drop results after final has been received */
+ if (pr && pr->ranging_final_received) {
+ wpa_printf(MSG_DEBUG,
+ "PR: Ignoring result after final for " MACSTR,
+ MAC2STR(result->addr));
+ return;
+ }
+
+ /* Validate cookie if we have a pending ranging request */
+ if (pr && pr->pr_pasn_params && result->cookie != 0) {
+ if (pr->pr_pasn_params->cookie != result->cookie) {
+ wpa_printf(MSG_WARNING,
+ "PR: Cookie mismatch - expected %llu, got %llu. Ignoring result.",
+ (unsigned long long) pr->pr_pasn_params->cookie,
+ (unsigned long long) result->cookie);
+ return;
+ }
+ }
+
+ /* Forward result to upper layer — includes failures and final */
+ if (result->ftm.has_data || result->ftm.fail)
+ wpas_notify_pr_measurement_result(wpa_s, result);
+
+ /* After final result, mark session done — no more results accepted */
+ if (result->final) {
+ wpa_printf(MSG_DEBUG,
+ "PR: Final result received for " MACSTR
+ " — no further results will be processed",
+ MAC2STR(result->addr));
+ if (pr)
+ pr->ranging_final_received = true;
+ }
+}
+
+
#ifdef CONFIG_PASN
static int wpas_pr_start_pd(struct wpa_supplicant *wpa_s, const u8 *src_addr)
@@ -1388,6 +1434,7 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s,
pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE;
os_free(pr->pr_pasn_params);
pr->pr_pasn_params = NULL;
+ pr->ranging_final_received = false;
return;
}
} else {
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index f8b114985..679c755e2 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -41,6 +41,8 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
unsigned int freq);
void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s,
struct pr_pasn_ranging_params *pr_pasn_params);
+void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s,
+ struct peer_measurement_result *result);
#else /* CONFIG_PR */
@@ -118,6 +120,12 @@ static inline void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s,
{
}
+static inline void
+wpas_pr_measurement_result(struct wpa_supplicant *wpa_s,
+ struct peer_measurement_result *result)
+{
+}
+
#endif /* CONFIG_PR */
#endif /* PR_SUPPLICANT_H */
--
2.34.1
More information about the Hostap
mailing list