[PATCH v4 13/20] wpa_supplicant: Add FTM peer measurement result handling

Kavita Kavita kavita.kavita at oss.qualcomm.com
Fri May 22 18:23:45 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 6b40628a8..ba56ab519 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 7ba3b4dbe..327b6af06 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -601,6 +601,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 cb509fc1c..e136a61bf 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -509,6 +509,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 e8088d7c5..55d71a7e4 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -6650,6 +6650,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,
 };
 
 
@@ -7701,6 +7708,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 b51ba9449..8023ec6e2 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
 
@@ -4833,6 +5085,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 baae94b73..64808c19f 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -7672,6 +7672,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 234f76f5e..876b2c151 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -1576,4 +1576,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 a9fdfdecd..f2014f3cb 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -218,6 +218,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 6b110ff18..5085df977 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -461,6 +461,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;
 }
 
@@ -764,6 +765,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)
@@ -1393,6 +1439,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 e81166ea0..991a8dd5e 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 */
 
@@ -119,6 +121,12 @@ 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