MBO-STA: Set non-preferred channel list

Set non-preferred channel list using sta_preset_testparameters and
sta_set_rfeature commands. The channel list is buffered and new list is
appended to it. The list is cleared when UCC sends clear indication
("ch_pref" with value "clear") or sends sta_reset_default command.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
diff --git a/sigma_dut.c b/sigma_dut.c
index 98ca085..5401f4e 100644
--- a/sigma_dut.c
+++ b/sigma_dut.c
@@ -1049,6 +1049,8 @@
 #ifdef MIRACAST
 	miracast_deinit(&sigma_dut);
 #endif /* MIRACAST */
+	free(sigma_dut.non_pref_ch_list);
+	sigma_dut.non_pref_ch_list = NULL;
 	sigma_dut_unreg_cmds(&sigma_dut);
 
 	return 0;
diff --git a/sigma_dut.h b/sigma_dut.h
index 2001fe9..36d59b5 100644
--- a/sigma_dut.h
+++ b/sigma_dut.h
@@ -625,6 +625,8 @@
 	enum value_not_set_enabled_disabled wnm_bss_max_feature;
 	int wnm_bss_max_idle_time;
 	enum value_not_set_enabled_disabled wnm_bss_max_protection;
+
+	char *non_pref_ch_list; /* MBO: non-preferred channel report */
 };
 
 
diff --git a/sta.c b/sta.c
index 35f03ff..e4ea598 100644
--- a/sta.c
+++ b/sta.c
@@ -47,6 +47,8 @@
 #define IEEE80211_SNAP_LEN_DMG 8
 #endif
 
+#define NON_PREF_CH_LIST_SIZE 100
+
 extern char *sigma_wpas_ctrl;
 extern char *sigma_cert_path;
 extern enum driver_type wifi_chip_type;
@@ -2877,6 +2879,92 @@
 }
 
 
+static int mbo_set_non_pref_ch_list(struct sigma_dut *dut,
+				    struct sigma_conn *conn,
+				    const char *intf,
+				    struct sigma_cmd *cmd)
+{
+	const char *ch, *pref, *op_class, *reason;
+	char buf[120];
+	int len, ret;
+
+	pref = get_param(cmd, "Ch_Pref");
+	if (!pref)
+		return 1;
+
+	if (strcasecmp(pref, "clear") == 0) {
+		free(dut->non_pref_ch_list);
+		dut->non_pref_ch_list = NULL;
+	} else {
+		op_class = get_param(cmd, "Ch_Op_Class");
+		if (!op_class) {
+			send_resp(dut, conn, SIGMA_INVALID,
+				  "ErrorCode,Ch_Op_Class not provided");
+			return 0;
+		}
+
+		ch = get_param(cmd, "Ch_Pref_Num");
+		if (!ch) {
+			send_resp(dut, conn, SIGMA_INVALID,
+				  "ErrorCode,Ch_Pref_Num not provided");
+			return 0;
+		}
+
+		reason = get_param(cmd, "Ch_Reason_Code");
+		if (!reason) {
+			send_resp(dut, conn, SIGMA_INVALID,
+				  "ErrorCode,Ch_Reason_Code not provided");
+			return 0;
+		}
+
+		if (!dut->non_pref_ch_list) {
+			dut->non_pref_ch_list =
+				calloc(1, NON_PREF_CH_LIST_SIZE);
+			if (!dut->non_pref_ch_list) {
+				send_resp(dut, conn, SIGMA_ERROR,
+					  "ErrorCode,Failed to allocate memory for non_pref_ch_list");
+				return 0;
+			}
+		}
+		len = strlen(dut->non_pref_ch_list);
+		ret = snprintf(dut->non_pref_ch_list + len,
+			       NON_PREF_CH_LIST_SIZE - len,
+			       " %s:%s:%s:%s", op_class, ch, pref, reason);
+		if (ret > 0 && ret < NON_PREF_CH_LIST_SIZE - len) {
+			sigma_dut_print(dut, DUT_MSG_DEBUG, "non_pref_list: %s",
+					dut->non_pref_ch_list);
+		} else {
+			sigma_dut_print(dut, DUT_MSG_ERROR,
+					"snprintf failed for non_pref_list, ret = %d",
+					ret);
+			send_resp(dut, conn, SIGMA_ERROR,
+				  "ErrorCode,snprintf failed");
+			free(dut->non_pref_ch_list);
+			dut->non_pref_ch_list = NULL;
+			return 0;
+		}
+	}
+
+	ret = snprintf(buf, sizeof(buf), "SET non_pref_chan%s",
+		       dut->non_pref_ch_list ? dut->non_pref_ch_list : " ");
+	if (ret < 0 || ret >= (int) sizeof(buf)) {
+		sigma_dut_print(dut, DUT_MSG_DEBUG,
+				"snprintf failed for set non_pref_chan, ret: %d",
+				ret);
+		send_resp(dut, conn, SIGMA_ERROR, "ErrorCode,snprint failed");
+		return 0;
+	}
+
+	if (wpa_command(intf, buf) < 0) {
+		send_resp(dut, conn, SIGMA_ERROR,
+			  "ErrorCode,Failed to set non-preferred channel list");
+		return 0;
+	}
+
+	return 1;
+}
+
+
 static int cmd_sta_preset_testparameters(struct sigma_dut *dut,
 					 struct sigma_conn *conn,
 					 struct sigma_cmd *cmd)
@@ -2910,6 +2998,11 @@
 		if (val &&
 		    mbo_set_cellular_data_capa(dut, conn, intf, atoi(val)) == 0)
 			return 0;
+
+		val = get_param(cmd, "Ch_Pref");
+		if (val && mbo_set_non_pref_ch_list(dut, conn, intf, cmd) == 0)
+			return 0;
+
 		return 1;
 	}
 
@@ -4552,6 +4645,11 @@
 		wpa_command(get_station_ifname(), "SET interworking 0");
 	}
 
+	if (dut->program == PROGRAM_MBO) {
+		free(dut->non_pref_ch_list);
+		dut->non_pref_ch_list = NULL;
+	}
+
 	if (dut->program != PROGRAM_VHT)
 		return cmd_sta_p2p_reset(dut, conn, cmd);
 	return 1;
@@ -7120,6 +7218,11 @@
 		if (val &&
 		    mbo_set_cellular_data_capa(dut, conn, intf, atoi(val)) == 0)
 			return 0;
+
+		val = get_param(cmd, "Ch_Pref");
+		if (val && mbo_set_non_pref_ch_list(dut, conn, intf, cmd) == 0)
+			return 0;
+
 		return 1;
 	}