diff --git a/NOTICE b/NOTICE index 3102b689b..eb2486afe 100644 --- a/NOTICE +++ b/NOTICE @@ -53,3 +53,6 @@ Licensed under the BSD-3 License. The Data_Elements_JSON_Schema_v3.0.json file is: Copyright (C) 2021 Wi-Fi Alliance. All Rights Reserved. The file is licensed according to the conditions in the header, and your use of the file must be in accordance with the permissions given in that header. + +Copyright 2026 MaxLinear, Inc +Licensed under the Apache License, Version 2.0 diff --git a/build/linux/bpi/makefile b/build/linux/bpi/makefile index fd31016d6..fbbe08bb8 100644 --- a/build/linux/bpi/makefile +++ b/build/linux/bpi/makefile @@ -429,6 +429,7 @@ WEBCONFIG_SOURCES = $(ONE_WIFI_HOME)/source/webconfig/wifi_decoder.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_assocdevice_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_memwraptool.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_ignite.c \ + $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_nasta.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radio_temperature.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radiodiag_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_multivap.c \ diff --git a/build/openwrt/makefile b/build/openwrt/makefile index 7f0377efe..f612f3154 100644 --- a/build/openwrt/makefile +++ b/build/openwrt/makefile @@ -428,6 +428,7 @@ WEBCONFIG_SOURCES = $(ONE_WIFI_HOME)/source/webconfig/wifi_decoder.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radiodiag_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_multivap.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_ignite.c \ + $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_nasta.c \ $(ONE_WIFI_HOME)/source/utils/wifi_util.c \ diff --git a/include/wifi_base.h b/include/wifi_base.h index 7c4945bca..4b3e5e954 100644 --- a/include/wifi_base.h +++ b/include/wifi_base.h @@ -87,6 +87,8 @@ extern "C" { #define WIFI_CSA_BEACON_FRAME_RECEIVED "Device.WiFi.CSABeaconFrameRecieved" #define HOTSPOT_CLIENT_DHCP_FAILURE_DISCONNECTED "Device.X_COMCAST-COM_GRE.Hotspot.RejectAssociatedClient" #define WIFI_STUCK_DETECT_FILE_NAME "/nvram/wifi_stuck_detect" +#define WIFI_ACCESSPOINT_GET_NASTA "Device.WiFi.AccessPoint.{i}.X_RDKCENTRAL-COM_GetNaSta" +#define WIFI_NASTA_RESPONSE_EVENT "Device.WiFi.EM.NaStaResponse" #define WIFI_QUALITY_LINKREPORT "Device.WiFi.LinkReport" #define WIFI_LINK_QUALITY_DATA "Device.WiFi.LinkQualityData" #define WIFI_LINK_QUALITY_FLAGS "Device.WiFi.LinkQualityFlags" @@ -535,6 +537,35 @@ typedef struct { link_report_t *links; } report_batch_t; +/* Unassociated STA (NaSta) query/response structures */ +#define MAX_NASTA_OPCLASS_ENTRIES 8 +#define MAX_NASTA_CHANNELS 8 +#define MAX_NASTA_STA_PER_CHANNEL 8 +#define MAX_NASTA_RESPONSE_STAS (MAX_NASTA_OPCLASS_ENTRIES * MAX_NASTA_CHANNELS * MAX_NASTA_STA_PER_CHANNEL) + +typedef struct { + unsigned int num_sta; + wifi_na_sta_info sta_list[MAX_NASTA_RESPONSE_STAS]; +} nasta_response_t; + +typedef struct { + mac_address_t sta_macs[MAX_NASTA_STA_PER_CHANNEL]; + unsigned int sta_list_length; + unsigned int channel; +} nasta_channel_entry_t; + +typedef struct { + unsigned int opclass; + unsigned int channels_length; + nasta_channel_entry_t channels[MAX_NASTA_CHANNELS]; +} nasta_opclass_entry_t; + +typedef struct { + unsigned int vap_index; + unsigned int num_opclass; + nasta_opclass_entry_t opclass_list[MAX_NASTA_OPCLASS_ENTRIES]; +} nasta_query_t; + typedef struct { unsigned int rss_check_interval; //minutes unsigned int rss_threshold; //kbytes diff --git a/include/wifi_events.h b/include/wifi_events.h index fd4f38976..61c0c70ea 100644 --- a/include/wifi_events.h +++ b/include/wifi_events.h @@ -85,6 +85,7 @@ typedef enum { wifi_event_webconfig_em_config, wifi_event_webconfig_br_report, wifi_event_webconfig_set_ignite_data, + wifi_event_webconfig_set_data_nasta, wifi_event_webconfig_max, // HAL events diff --git a/include/wifi_webconfig.h b/include/wifi_webconfig.h index 0b8a35271..489f43f71 100644 --- a/include/wifi_webconfig.h +++ b/include/wifi_webconfig.h @@ -146,6 +146,7 @@ typedef enum { webconfig_subdoc_type_memwraptool, webconfig_subdoc_type_link_report, webconfig_subdoc_type_ignite, + webconfig_subdoc_type_nasta_query, webconfig_subdoc_type_max } webconfig_subdoc_type_t; @@ -174,6 +175,7 @@ typedef enum { webconfig_subdoc_object_type_em_sta_link_metrics, webconfig_subdoc_object_type_em_ap_metrics_report, webconfig_subdoc_object_type_link_report, + webconfig_subdoc_object_type_nasta_query, webconfig_subdoc_object_max } webconfig_subdoc_object_type_t; @@ -233,6 +235,8 @@ typedef struct { em_ap_metrics_report_t em_ap_metrics_report; #endif report_batch_t *qmgr_report; + nasta_query_t nasta_query; + nasta_response_t *nasta_response; } webconfig_subdoc_decoded_data_t; typedef char * webconfig_subdoc_encoded_raw_t; @@ -602,6 +606,14 @@ webconfig_error_t encode_cac_config_subdoc(webconfig_t *config, webconfig_ webconfig_error_t translate_to_cac_config_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); webconfig_error_t translate_from_cac_config_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); +// nasta query +webconfig_error_t init_nasta_query_subdoc(webconfig_subdoc_t *doc); +webconfig_error_t access_check_nasta_query_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); +webconfig_error_t decode_nasta_query_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); +webconfig_error_t encode_nasta_query_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); +webconfig_error_t translate_to_nasta_query_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); +webconfig_error_t translate_from_nasta_query_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); + // radio channel stats webconfig_error_t init_radio_channel_stats_subdoc(webconfig_subdoc_t *doc); webconfig_error_t access_check_radio_channel_stats_subdoc(webconfig_t *config, webconfig_subdoc_data_t *data); diff --git a/source/core/wifi_ctrl_queue_handlers.c b/source/core/wifi_ctrl_queue_handlers.c index 1a8d06743..3cf86b044 100644 --- a/source/core/wifi_ctrl_queue_handlers.c +++ b/source/core/wifi_ctrl_queue_handlers.c @@ -4712,6 +4712,20 @@ void handle_webconfig_event(wifi_ctrl_t *ctrl, const char *raw, unsigned int len webconfig_data_free(data); break; + case wifi_event_webconfig_set_data_nasta: + memcpy((unsigned char *)&data->u.decoded.hal_cap, (unsigned char *)&mgr->hal_cap, + sizeof(wifi_hal_capability_t)); + if (raw == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Empty raw data for NaSta\n", + __func__, __LINE__); + free(data); + data = NULL; + return; + } + webconfig_decode(config, data, raw); + webconfig_data_free(data); + break; + case wifi_event_webconfig_set_data_tunnel: memcpy((unsigned char *)&data->u.decoded.hal_cap, (unsigned char *)&mgr->hal_cap, sizeof(wifi_hal_capability_t)); diff --git a/source/core/wifi_ctrl_rbus_handlers.c b/source/core/wifi_ctrl_rbus_handlers.c index 52b15e76f..c3e14d773 100644 --- a/source/core/wifi_ctrl_rbus_handlers.c +++ b/source/core/wifi_ctrl_rbus_handlers.c @@ -4266,6 +4266,71 @@ void register_endpoint_components(wifi_ctrl_t *ctrl) return; } +bus_error_t get_NaSta(char const* methodName, bus_data_prop_t *inParams, + bus_data_prop_t *outParams, void *asyncHandle) +{ + unsigned vap_idx; + char *json_str = NULL; + char *enriched_str = NULL; + cJSON *json = NULL; + int ret; + + if (methodName == NULL || inParams == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Invalid input parameters\r\n", __func__, __LINE__); + return bus_error_invalid_input; + } + + if (inParams->value.data_type != bus_data_type_string || inParams->value.raw_data.bytes == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Invalid input data type:0x%x\r\n", + __func__, __LINE__, inParams->value.data_type); + return bus_error_invalid_input; + } + + ret = sscanf(methodName, "Device.WiFi.AccessPoint.%u.X_RDKCENTRAL-COM_GetNaSta", &vap_idx); + if (ret != 1 || vap_idx < 1 || vap_idx > MAX_VAP) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Invalid vap index %u\r\n", __func__, __LINE__, vap_idx); + return bus_error_destination_not_found; + } + + json_str = (char *)inParams->value.raw_data.bytes; + + /* Parse the incoming JSON and inject vap_index (0-based) */ + json = cJSON_Parse(json_str); + if (json == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Failed to parse JSON input\r\n", __func__, __LINE__); + return bus_error_invalid_input; + } + cJSON_AddNumberToObject(json, "VapIndex", vap_idx - 1); + enriched_str = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + + if (enriched_str == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d Failed to serialize JSON\r\n", __func__, __LINE__); + return bus_error_out_of_resources; + } + + wifi_util_info_print(WIFI_CTRL, "%s:%d NaSta query for vap %u, pushing to ctrl queue\r\n", + __func__, __LINE__, vap_idx); + + push_event_to_ctrl_queue(enriched_str, (strlen(enriched_str) + 1), + wifi_event_type_webconfig, wifi_event_webconfig_set_data_nasta, NULL); + + cJSON_free(enriched_str); + + /* Return an ack so the synchronous RBUS invoke succeeds; + the actual NaSta response is published asynchronously via + Device.WiFi.EM.NaStaResponse event. */ + if (outParams != NULL) { + char *ack = strdup("{\"Status\":\"Accepted\"}"); + if (ack != NULL) { + outParams->value.data_type = bus_data_type_string; + outParams->value.raw_data.bytes = ack; + outParams->value.raw_data_len = strlen(ack); + } + } + + return bus_error_success; +} void bus_register_handlers(wifi_ctrl_t *ctrl) { @@ -4440,6 +4505,12 @@ void bus_register_handlers(wifi_ctrl_t *ctrl) { WIFI_IGNITE_STATUS, bus_element_type_event, { NULL, NULL, NULL, NULL, NULL, NULL }, slow_speed, ZERO_TABLE, { bus_data_type_string, false, 0, 0, 0, NULL } }, + { WIFI_ACCESSPOINT_GET_NASTA, bus_element_type_method, + { NULL, NULL, NULL, NULL, NULL, get_NaSta }, slow_speed, ZERO_TABLE, + { bus_data_type_string, true, 0, 0, 0, NULL } }, + { WIFI_NASTA_RESPONSE_EVENT, bus_element_type_event, + { NULL, NULL, NULL, NULL, eventSubHandler, NULL }, slow_speed, ZERO_TABLE, + { bus_data_type_string, false, 0, 0, 0, NULL } }, }; rc = get_bus_descriptor()->bus_open_fn(&ctrl->handle, component_name); diff --git a/source/core/wifi_ctrl_webconfig.c b/source/core/wifi_ctrl_webconfig.c index d0b26feeb..e7d6bb202 100644 --- a/source/core/wifi_ctrl_webconfig.c +++ b/source/core/wifi_ctrl_webconfig.c @@ -1764,6 +1764,113 @@ int webconfig_global_config_apply(wifi_ctrl_t *ctrl, webconfig_subdoc_decoded_da return RETURN_OK; } +int webconfig_nasta_apply(wifi_ctrl_t *ctrl, webconfig_subdoc_decoded_data_t *data) +{ + nasta_query_t *query; + nasta_response_t response; + unsigned int oc_idx, ch_idx, sta_idx; + int rc; + raw_data_t rdata; + webconfig_subdoc_data_t resp_data; + webconfig_t *config; + + if (ctrl == NULL || data == NULL) { + wifi_util_error_print(WIFI_CTRL, "%s:%d: NULL pointer\n", __func__, __LINE__); + return RETURN_ERR; + } + + query = &data->nasta_query; + config = &ctrl->webconfig; + memset(&response, 0, sizeof(nasta_response_t)); + + wifi_util_info_print(WIFI_CTRL, "%s:%d: Processing NaSta query: vap=%u, %u opclass entries\n", + __func__, __LINE__, query->vap_index, query->num_opclass); + + for (oc_idx = 0; oc_idx < query->num_opclass && oc_idx < MAX_NASTA_OPCLASS_ENTRIES; oc_idx++) { + nasta_opclass_entry_t *oc = &query->opclass_list[oc_idx]; + + for (ch_idx = 0; ch_idx < oc->channels_length && ch_idx < MAX_NASTA_CHANNELS; ch_idx++) { + nasta_channel_entry_t *ch = &oc->channels[ch_idx]; + + for (sta_idx = 0; sta_idx < ch->sta_list_length && sta_idx < MAX_NASTA_STA_PER_CHANNEL; sta_idx++) { + wifi_na_sta_req_params req_params; + wifi_na_sta_info sta_info; + + if (response.num_sta >= MAX_NASTA_RESPONSE_STAS) { + wifi_util_error_print(WIFI_CTRL, + "%s:%d: Response STA list full (%d)\n", + __func__, __LINE__, MAX_NASTA_RESPONSE_STAS); + goto publish_response; + } + + memset(&req_params, 0, sizeof(req_params)); + memset(&sta_info, 0, sizeof(sta_info)); + + memcpy(req_params.sta_addr, ch->sta_macs[sta_idx], sizeof(mac_address_t)); + req_params.op_class = oc->opclass; + req_params.channel = ch->channel; + + rc = wifi_getNASta(query->vap_index, &req_params, &sta_info); + if (rc != WIFI_HAL_SUCCESS) { + wifi_util_error_print(WIFI_CTRL, + "%s:%d: wifi_getNASta failed for STA " + "%02X:%02X:%02X:%02X:%02X:%02X on ch %u (rc=%d)\n", + __func__, __LINE__, + ch->sta_macs[sta_idx][0], ch->sta_macs[sta_idx][1], + ch->sta_macs[sta_idx][2], ch->sta_macs[sta_idx][3], + ch->sta_macs[sta_idx][4], ch->sta_macs[sta_idx][5], + ch->channel, rc); + continue; + } + + memcpy(&response.sta_list[response.num_sta], &sta_info, sizeof(wifi_na_sta_info)); + response.num_sta++; + } + } + } + +publish_response: + wifi_util_info_print(WIFI_CTRL, "%s:%d: NaSta query complete, %u STAs in response\n", + __func__, __LINE__, response.num_sta); + + /* Encode the response as JSON and publish via RBUS event */ + memset(&resp_data, 0, sizeof(webconfig_subdoc_data_t)); + resp_data.type = webconfig_subdoc_type_nasta_query; + resp_data.descriptor = webconfig_data_descriptor_decoded; + resp_data.u.decoded.nasta_response = &response; + memcpy(&resp_data.u.decoded.hal_cap, &data->hal_cap, sizeof(wifi_hal_capability_t)); + + webconfig_subdoc_t *nasta_doc = &config->subdocs[webconfig_subdoc_type_nasta_query]; + if (nasta_doc->encode_subdoc(config, &resp_data) != webconfig_error_none) { + wifi_util_error_print(WIFI_CTRL, "%s:%d: Failed to encode NaSta response\n", + __func__, __LINE__); + return RETURN_ERR; + } + + if (resp_data.u.encoded.raw != NULL) { + memset(&rdata, 0, sizeof(raw_data_t)); + rdata.data_type = bus_data_type_string; + rdata.raw_data.bytes = (void *)resp_data.u.encoded.raw; + rdata.raw_data_len = (strlen(resp_data.u.encoded.raw) + 1); + + rc = get_bus_descriptor()->bus_event_publish_fn(&ctrl->handle, + WIFI_NASTA_RESPONSE_EVENT, &rdata); + if (rc != bus_error_success) { + wifi_util_error_print(WIFI_CTRL, + "%s:%d: Failed to publish NaSta response event (rc=%d)\n", + __func__, __LINE__, rc); + } else { + wifi_util_info_print(WIFI_CTRL, + "%s:%d: Published NaSta response: %u STAs\n", + __func__, __LINE__, response.num_sta); + } + + free(resp_data.u.encoded.raw); + } + + return RETURN_OK; +} + int webconfig_cac_apply(wifi_ctrl_t *ctrl, webconfig_subdoc_decoded_data_t *data) { wifi_util_dbg_print(WIFI_CTRL,"Inside webconfig_cac_apply\n"); @@ -3169,6 +3276,12 @@ webconfig_error_t webconfig_ctrl_apply(webconfig_subdoc_t *doc, webconfig_subdoc } break; + case webconfig_subdoc_type_nasta_query: + if (!(data->descriptor & webconfig_data_descriptor_encoded)) { + ret = webconfig_nasta_apply(ctrl, &data->u.decoded); + } + break; + default: break; } diff --git a/source/webconfig/Makefile.am b/source/webconfig/Makefile.am index 465cce09e..0b23c4e84 100644 --- a/source/webconfig/Makefile.am +++ b/source/webconfig/Makefile.am @@ -72,7 +72,7 @@ libwifi_webconfig_la_CPPFLAGS += -I$(top_srcdir)/source/dml/wifi_ssp -I$(top_src libwifi_webconfig_la_CFLAGS = $(SYSTEMD_CFLAGS) -libwifi_webconfig_la_SOURCES = wifi_webconfig.c wifi_encoder.c wifi_decoder.c wifi_ovsdb_translator.c wifi_webconfig_home.c wifi_webconfig_mesh.c wifi_webconfig_private.c wifi_webconfig_radio.c wifi_webconfig_xfinity.c wifi_webconfig_associated_client.c wifi_webconfig_wifiapi_radio.c wifi_webconfig_wifiapi_vap.c wifi_webconfig_macfilter.c wifi_webconfig_blaster.c wifi_webconfig_harvester.c wifi_webconfig_wifi_config.c wifi_webconfig_csi.c wifi_webconfig_mesh_sta.c wifi_webconfig_mesh_backhaul.c wifi_webconfig_null.c wifi_webconfig_stats_config.c wifi_webconfig_steering_config.c wifi_webconfig_steering_clients.c wifi_webconfig_lnf.c wifi_webconfig_vif_neighbors.c wifi_webconfig_mesh_backhaul_sta.c wifi_webconfig_dml.c wifi_webconfig_levl.c wifi_webconfig_cac.c wifi_webconfig_radio_stats.c wifi_webconfig_neighbor_stats.c wifi_webconfig_assocdevice_stats.c wifi_webconfig_radiodiag_stats.c wifi_webconfig_radio_temperature.c wifi_webconfig_multivap.c wifi_webconfig_easymesh_config.c wifi_webconfig_beacon_report.c wifi_webconfig_memwraptool.c wifi_webconfig_link_report.c wifi_webconfig_ignite.c +libwifi_webconfig_la_SOURCES = wifi_webconfig.c wifi_encoder.c wifi_decoder.c wifi_ovsdb_translator.c wifi_webconfig_home.c wifi_webconfig_mesh.c wifi_webconfig_private.c wifi_webconfig_radio.c wifi_webconfig_xfinity.c wifi_webconfig_associated_client.c wifi_webconfig_wifiapi_radio.c wifi_webconfig_wifiapi_vap.c wifi_webconfig_macfilter.c wifi_webconfig_blaster.c wifi_webconfig_harvester.c wifi_webconfig_wifi_config.c wifi_webconfig_csi.c wifi_webconfig_mesh_sta.c wifi_webconfig_mesh_backhaul.c wifi_webconfig_null.c wifi_webconfig_stats_config.c wifi_webconfig_steering_config.c wifi_webconfig_steering_clients.c wifi_webconfig_lnf.c wifi_webconfig_vif_neighbors.c wifi_webconfig_mesh_backhaul_sta.c wifi_webconfig_dml.c wifi_webconfig_levl.c wifi_webconfig_cac.c wifi_webconfig_radio_stats.c wifi_webconfig_neighbor_stats.c wifi_webconfig_assocdevice_stats.c wifi_webconfig_radiodiag_stats.c wifi_webconfig_radio_temperature.c wifi_webconfig_multivap.c wifi_webconfig_easymesh_config.c wifi_webconfig_beacon_report.c wifi_webconfig_memwraptool.c wifi_webconfig_link_report.c wifi_webconfig_ignite.c wifi_webconfig_nasta.c if EM_APP_SUPPORT libwifi_webconfig_la_SOURCES += wifi_webconfig_em_channel_stats.c wifi_webconfig_em_sta_link_metrics.c wifi_webconfig_em_ap_metrics_report.c libwifi_webconfig_la_CFLAGS += $(EM_APP_FLAG) diff --git a/source/webconfig/wifi_webconfig.c b/source/webconfig/wifi_webconfig.c index 71289dfd6..f9004e56a 100644 --- a/source/webconfig/wifi_webconfig.c +++ b/source/webconfig/wifi_webconfig.c @@ -464,6 +464,19 @@ webconfig_error_t webconfig_init(webconfig_t *config) config->subdocs[webconfig_subdoc_type_ignite].translate_to_subdoc = translate_to_ignite_subdoc; config->subdocs[webconfig_subdoc_type_ignite].translate_from_subdoc = translate_from_ignite_subdoc; + //webconfig_subdoc_type_nasta_query + config->subdocs[webconfig_subdoc_type_nasta_query].type = webconfig_subdoc_type_nasta_query; + strcpy(config->subdocs[webconfig_subdoc_type_nasta_query].name, "UnassocStaQuery"); + config->subdocs[webconfig_subdoc_type_nasta_query].major = 1; + config->subdocs[webconfig_subdoc_type_nasta_query].minor = 0; + config->subdocs[webconfig_subdoc_type_nasta_query].init_subdoc = init_nasta_query_subdoc; + config->subdocs[webconfig_subdoc_type_nasta_query].init_subdoc(&config->subdocs[webconfig_subdoc_type_nasta_query]); + config->subdocs[webconfig_subdoc_type_nasta_query].access_check_subdoc = access_check_nasta_query_subdoc; + config->subdocs[webconfig_subdoc_type_nasta_query].encode_subdoc = encode_nasta_query_subdoc; + config->subdocs[webconfig_subdoc_type_nasta_query].decode_subdoc = decode_nasta_query_subdoc; + config->subdocs[webconfig_subdoc_type_nasta_query].translate_to_subdoc = translate_to_nasta_query_subdoc; + config->subdocs[webconfig_subdoc_type_nasta_query].translate_from_subdoc = translate_from_nasta_query_subdoc; + #ifdef ONEWIFI_HARVESTER_APP_SUPPORT config->subdocs[webconfig_subdoc_type_harvester].type = webconfig_subdoc_type_harvester; strcpy(config->subdocs[webconfig_subdoc_type_harvester].name, "instant measurement config"); diff --git a/source/webconfig/wifi_webconfig_nasta.c b/source/webconfig/wifi_webconfig_nasta.c new file mode 100644 index 000000000..4788c8ca0 --- /dev/null +++ b/source/webconfig/wifi_webconfig_nasta.c @@ -0,0 +1,352 @@ +/************************************************************************************ + If not stated otherwise in this file or this component's LICENSE file the + following copyright and licenses apply: + + Copyright 2026 RDK Management + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +**************************************************************************/ +#include +#include +#include +#include +#include +#include +#include "collection.h" +#include "wifi_webconfig.h" +#include "wifi_monitor.h" +#include "wifi_util.h" +#include "wifi_ctrl.h" + +/* + * UnassocStaQuery subdoc — request decoder and response encoder. + * + * Request JSON (decoded by decode_nasta_query_subdoc): + * { + * "Version": "1.0", + * "SubDocName": "UnassocStaQuery", + * "UnassocStaQueryList": [ + * { + * "opclass": 115, + * "channels_length": 2, + * "channels": [ + * { "channel": 36, "sta_list_length": 2, + * "sta_macs": ["AA:BB:CC:DD:EE:01", "AA:BB:CC:DD:EE:02"] }, + * { "channel": 40, "sta_list_length": 1, + * "sta_macs": ["AA:BB:CC:DD:EE:03"] } + * ] + * } + * ] + * } + * + * Response JSON (produced by encode_nasta_query_subdoc): + * { + * "Version": "1.0", + * "SubDocName": "UnassocStaQuery", + * "UnassociatedSTALinkMetricsResponse": { + * "num_sta": 2, + * "sta_list": [ + * { "sta_mac": "AA:BB:CC:DD:EE:FF", "channel": 6, "op_class": 81, "rcpi": 120 }, + * ... + * ] + * } + * } + */ + +webconfig_subdoc_object_t nasta_query_objects[3] = { + { webconfig_subdoc_object_type_version, "Version" }, + { webconfig_subdoc_object_type_subdoc, "SubDocName" }, + { webconfig_subdoc_object_type_nasta_query, "UnassocStaQueryList" }, +}; + +webconfig_error_t init_nasta_query_subdoc(webconfig_subdoc_t *doc) +{ + doc->num_objects = sizeof(nasta_query_objects) / sizeof(webconfig_subdoc_object_t); + memcpy((unsigned char *)doc->objects, (unsigned char *)&nasta_query_objects, + sizeof(nasta_query_objects)); + return webconfig_error_none; +} + +webconfig_error_t access_check_nasta_query_subdoc(webconfig_t *config, + webconfig_subdoc_data_t *data) +{ + return webconfig_error_none; +} + +webconfig_error_t translate_from_nasta_query_subdoc(webconfig_t *config, + webconfig_subdoc_data_t *data) +{ + return webconfig_error_none; +} + +webconfig_error_t translate_to_nasta_query_subdoc(webconfig_t *config, + webconfig_subdoc_data_t *data) +{ + return webconfig_error_none; +} + +webconfig_error_t encode_nasta_query_subdoc(webconfig_t *config, + webconfig_subdoc_data_t *data) +{ + cJSON *json; + cJSON *resp_obj, *sta_array, *sta_obj; + webconfig_subdoc_decoded_data_t *params; + nasta_response_t *resp; + char mac_str[18]; + char *str; + unsigned int i; + + params = &data->u.decoded; + resp = params->nasta_response; + + if (resp == NULL) { + wifi_util_error_print(WIFI_WEBCONFIG, "%s:%d: nasta_response is NULL\n", + __func__, __LINE__); + return webconfig_error_encode; + } + + json = cJSON_CreateObject(); + if (json == NULL) { + wifi_util_error_print(WIFI_WEBCONFIG, "%s:%d: Failed to create JSON object\n", + __func__, __LINE__); + return webconfig_error_encode; + } + data->u.encoded.json = json; + + cJSON_AddStringToObject(json, "Version", "1.0"); + cJSON_AddStringToObject(json, "SubDocName", "UnassocStaQuery"); + + resp_obj = cJSON_CreateObject(); + if (resp_obj == NULL) { + cJSON_Delete(json); + return webconfig_error_encode; + } + cJSON_AddItemToObject(json, "UnassociatedSTALinkMetricsResponse", resp_obj); + + cJSON_AddNumberToObject(resp_obj, "num_sta", resp->num_sta); + + sta_array = cJSON_CreateArray(); + if (sta_array == NULL) { + cJSON_Delete(json); + return webconfig_error_encode; + } + cJSON_AddItemToObject(resp_obj, "sta_list", sta_array); + + for (i = 0; i < resp->num_sta && i < MAX_NASTA_RESPONSE_STAS; i++) { + sta_obj = cJSON_CreateObject(); + if (sta_obj == NULL) { + cJSON_Delete(json); + return webconfig_error_encode; + } + cJSON_AddItemToArray(sta_array, sta_obj); + + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + resp->sta_list[i].sta_mac[0], resp->sta_list[i].sta_mac[1], + resp->sta_list[i].sta_mac[2], resp->sta_list[i].sta_mac[3], + resp->sta_list[i].sta_mac[4], resp->sta_list[i].sta_mac[5]); + + cJSON_AddStringToObject(sta_obj, "sta_mac", mac_str); + cJSON_AddNumberToObject(sta_obj, "channel", resp->sta_list[i].channel); + cJSON_AddNumberToObject(sta_obj, "op_class", resp->sta_list[i].op_class); + cJSON_AddNumberToObject(sta_obj, "rcpi", resp->sta_list[i].rcpi); + } + + str = cJSON_Print(json); + if (str == NULL) { + cJSON_Delete(json); + return webconfig_error_encode; + } + + data->u.encoded.raw = (webconfig_subdoc_encoded_raw_t)calloc(strlen(str) + 1, sizeof(char)); + if (data->u.encoded.raw == NULL) { + wifi_util_error_print(WIFI_WEBCONFIG, "%s:%d: Failed to allocate memory\n", + __func__, __LINE__); + cJSON_free(str); + cJSON_Delete(json); + return webconfig_error_encode; + } + + memcpy(data->u.encoded.raw, str, strlen(str)); + wifi_util_dbg_print(WIFI_WEBCONFIG, "%s:%d: NaSta Encoded JSON:\n%s\n", + __func__, __LINE__, str); + cJSON_free(str); + cJSON_Delete(json); + + return webconfig_error_none; +} + +webconfig_error_t decode_nasta_query_subdoc(webconfig_t *config, + webconfig_subdoc_data_t *data) +{ + webconfig_subdoc_t *doc; + cJSON *json = data->u.encoded.json; + webconfig_subdoc_decoded_data_t *params; + nasta_query_t *query; + unsigned int i, oc_size, ch_size, sta_size; + cJSON *opclass_arr, *opclass_obj, *chan_arr, *chan_obj, *sta_arr, *sta_item; + + params = &data->u.decoded; + if (params == NULL || json == NULL) { + wifi_util_error_print(WIFI_WEBCONFIG, "%s:%d: NULL pointer\n", __func__, __LINE__); + return webconfig_error_decode; + } + + doc = &config->subdocs[data->type]; + + /* Validate all expected top-level objects are present */ + for (i = 0; i < doc->num_objects; i++) { + if (cJSON_GetObjectItem(json, doc->objects[i].name) == NULL) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: object:%s not present, validation failed\n", + __func__, __LINE__, doc->objects[i].name); + cJSON_Delete(json); + return webconfig_error_invalid_subdoc; + } + } + + query = ¶ms->nasta_query; + memset(query, 0, sizeof(nasta_query_t)); + + /* Extract VapIndex (injected by RBUS handler) */ + cJSON *vap_idx_obj = cJSON_GetObjectItem(json, "VapIndex"); + if (vap_idx_obj && cJSON_IsNumber(vap_idx_obj)) { + query->vap_index = (unsigned int)cJSON_GetNumberValue(vap_idx_obj); + } + + opclass_arr = cJSON_GetObjectItem(json, "UnassocStaQueryList"); + if (!cJSON_IsArray(opclass_arr)) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: UnassocStaQueryList is not an array\n", __func__, __LINE__); + cJSON_Delete(json); + return webconfig_error_invalid_subdoc; + } + + oc_size = cJSON_GetArraySize(opclass_arr); + if (oc_size > MAX_NASTA_OPCLASS_ENTRIES) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Too many opclass entries: %u (max %d)\n", + __func__, __LINE__, oc_size, MAX_NASTA_OPCLASS_ENTRIES); + cJSON_Delete(json); + return webconfig_error_invalid_subdoc; + } + query->num_opclass = oc_size; + + for (i = 0; i < oc_size; i++) { + nasta_opclass_entry_t *oc = &query->opclass_list[i]; + unsigned int j; + + opclass_obj = cJSON_GetArrayItem(opclass_arr, i); + if (!cJSON_IsObject(opclass_obj)) { + cJSON_Delete(json); + return webconfig_error_decode; + } + + cJSON *oc_val = cJSON_GetObjectItem(opclass_obj, "opclass"); + if (!oc_val || !cJSON_IsNumber(oc_val)) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Missing or invalid opclass field\n", __func__, __LINE__); + cJSON_Delete(json); + return webconfig_error_decode; + } + oc->opclass = (unsigned int)cJSON_GetNumberValue(oc_val); + + chan_arr = cJSON_GetObjectItem(opclass_obj, "channels"); + if (!cJSON_IsArray(chan_arr)) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: channels is not an array\n", __func__, __LINE__); + cJSON_Delete(json); + return webconfig_error_decode; + } + + ch_size = cJSON_GetArraySize(chan_arr); + if (ch_size > MAX_NASTA_CHANNELS) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Too many channels: %u (max %d)\n", + __func__, __LINE__, ch_size, MAX_NASTA_CHANNELS); + cJSON_Delete(json); + return webconfig_error_decode; + } + oc->channels_length = ch_size; + + for (j = 0; j < ch_size; j++) { + nasta_channel_entry_t *ch = &oc->channels[j]; + unsigned int k; + + chan_obj = cJSON_GetArrayItem(chan_arr, j); + if (!cJSON_IsObject(chan_obj)) { + cJSON_Delete(json); + return webconfig_error_decode; + } + + cJSON *ch_val = cJSON_GetObjectItem(chan_obj, "channel"); + if (!ch_val || !cJSON_IsNumber(ch_val)) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Missing or invalid channel field\n", __func__, __LINE__); + cJSON_Delete(json); + return webconfig_error_decode; + } + ch->channel = (unsigned int)cJSON_GetNumberValue(ch_val); + + sta_arr = cJSON_GetObjectItem(chan_obj, "sta_macs"); + if (sta_arr == NULL || !cJSON_IsArray(sta_arr)) { + /* Zero STAs per channel is valid per spec */ + ch->sta_list_length = 0; + continue; + } + + sta_size = cJSON_GetArraySize(sta_arr); + if (sta_size > MAX_NASTA_STA_PER_CHANNEL) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Too many STAs per channel: %u (max %d)\n", + __func__, __LINE__, sta_size, MAX_NASTA_STA_PER_CHANNEL); + cJSON_Delete(json); + return webconfig_error_decode; + } + ch->sta_list_length = sta_size; + + for (k = 0; k < sta_size; k++) { + sta_item = cJSON_GetArrayItem(sta_arr, k); + if (!cJSON_IsString(sta_item) || !sta_item->valuestring) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Invalid STA MAC entry\n", __func__, __LINE__); + cJSON_Delete(json); + return webconfig_error_decode; + } + + unsigned int m[6]; + if (sscanf(sta_item->valuestring, + "%02x:%02x:%02x:%02x:%02x:%02x", + &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) != 6) { + wifi_util_error_print(WIFI_WEBCONFIG, + "%s:%d: Invalid MAC format: %s\n", + __func__, __LINE__, sta_item->valuestring); + cJSON_Delete(json); + return webconfig_error_decode; + } + ch->sta_macs[k][0] = (unsigned char)m[0]; + ch->sta_macs[k][1] = (unsigned char)m[1]; + ch->sta_macs[k][2] = (unsigned char)m[2]; + ch->sta_macs[k][3] = (unsigned char)m[3]; + ch->sta_macs[k][4] = (unsigned char)m[4]; + ch->sta_macs[k][5] = (unsigned char)m[5]; + } + } + } + + wifi_util_info_print(WIFI_WEBCONFIG, + "%s:%d: NaSta query decoded: %u opclass entries, vap_index=%u\n", + __func__, __LINE__, query->num_opclass, query->vap_index); + + cJSON_Delete(json); + return webconfig_error_none; +}