/*
 * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "utils/includes.h"
#include "utils/common.h"
#include "utils/eloop.h"
#include "common/defs.h"

#include "esp_dpp_i.h"
#include "esp_dpp.h"
#include "esp_wpa.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "common/ieee802_11_defs.h"
#include "common/ieee802_11_common.h"
#include "esp_wps_i.h"
#include "rsn_supp/wpa.h"
#include "rsn_supp/pmksa_cache.h"
#include <stdatomic.h>

#ifdef CONFIG_DPP

struct action_rx_param {
    u8 sa[ETH_ALEN];
    u32 channel;
    u32 frm_len;
    u32 vendor_data_len;
    struct ieee80211_action action_frm[];
};

static void *s_dpp_api_lock = NULL;
static void *s_dpp_event_group = NULL;

#define DPP_ROC_EVENT_HANDLED  BIT0

static atomic_bool roc_in_progress;
static struct esp_dpp_context_t s_dpp_ctx;
static int esp_supp_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel);
static wifi_action_rx_cb_t s_action_rx_cb = esp_supp_rx_action;
static uint8_t s_current_tx_op_id;
static void tx_status_handler(void *arg, esp_event_base_t event_base,
                              int32_t event_id, void *event_data);
static void roc_status_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data);
static void dpp_listen_next_channel(void *data, void *user_ctx);
static esp_err_t dpp_api_lock(void)
{
    if (!s_dpp_api_lock) {
        s_dpp_api_lock = os_recursive_mutex_create();
    }
    if (!s_dpp_api_lock) {
        wpa_printf(MSG_ERROR, "DPP: dpp_init: failed to create DPP API lock");
        return ESP_ERR_NO_MEM;
    }

    os_mutex_lock(s_dpp_api_lock);

    return ESP_OK;
}

static esp_err_t dpp_api_unlock(void)
{
    if (s_dpp_api_lock) {
        os_mutex_unlock(s_dpp_api_lock);
    }

    return ESP_OK;
}

static void dpp_event_handler(void *arg, esp_event_base_t event_base,
                              int32_t event_id, void *event_data)
{
    if (!s_dpp_ctx.dpp_event_cb) {
        return;
    }
    switch (event_id) {
    case WIFI_EVENT_DPP_URI_READY:
        wifi_event_dpp_uri_ready_t *event = (wifi_event_dpp_uri_ready_t *) event_data;
        s_dpp_ctx.dpp_event_cb(ESP_SUPP_DPP_URI_READY, (void *)(event->uri));
        break;
    case WIFI_EVENT_DPP_CFG_RECVD:
        s_dpp_ctx.dpp_event_cb(ESP_SUPP_DPP_CFG_RECVD, (wifi_config_t *)event_data);
        break;
    case WIFI_EVENT_DPP_FAILED:
        s_dpp_ctx.dpp_event_cb(ESP_SUPP_DPP_FAIL, (void *)event_data);
        break;
    default:
        break;
    }
    return;
}

static uint8_t dpp_deinit_auth(void)
{
    if (s_dpp_ctx.dpp_auth) {
        dpp_auth_deinit(s_dpp_ctx.dpp_auth);
        s_dpp_ctx.dpp_auth = NULL;
    }

    return ESP_OK;
}

static int listen_stop_handler(void *data, void *user_ctx)
{
    wifi_roc_req_t req = {0};

    wpa_printf(MSG_DEBUG, "DPP: Stoping ROC");
    req.ifx = WIFI_IF_STA;
    req.type = WIFI_ROC_CANCEL;
    eloop_cancel_timeout(dpp_listen_next_channel, NULL, NULL);
    s_dpp_ctx.dpp_listen_ongoing = false;

    esp_wifi_remain_on_channel(&req);

    return 0;
}

static void dpp_stop(void)
{
    if (s_dpp_ctx.dpp_auth) {
        dpp_deinit_auth();
        listen_stop_handler(NULL, NULL);
    }
}

static void dpp_abort_with_failure(uint32_t failure_reason)
{
    /* Stop DPP*/
    dpp_stop();

    /* Send event to APP */
    wifi_event_dpp_failed_t event = {0};
    event.failure_reason = failure_reason;
    esp_event_post(WIFI_EVENT, WIFI_EVENT_DPP_FAILED, &event, sizeof(event), OS_BLOCK);
}

static void esp_dpp_auth_conf_wait_timeout(void *eloop_ctx, void *timeout_ctx)
{
    if (!s_dpp_ctx.dpp_auth || !s_dpp_ctx.dpp_auth->waiting_auth_conf) {
        return;
    }

    wpa_printf(MSG_INFO,
               "DPP: Terminate authentication exchange due to Auth Confirm timeout");
    dpp_abort_with_failure(ESP_ERR_DPP_AUTH_TIMEOUT);
}

esp_err_t esp_dpp_send_action_frame(uint8_t *dest_mac, const uint8_t *buf, uint32_t len,
                                    uint8_t channel, uint32_t wait_time_ms)
{
    wifi_action_tx_req_t *req = os_zalloc(sizeof(*req) + len);
    if (!req) {
        return ESP_FAIL;
    }

    req->ifx = WIFI_IF_STA;
    os_memcpy(req->dest_mac, dest_mac, ETH_ALEN);
    req->no_ack = false;
    req->data_len = len;
    req->rx_cb = s_action_rx_cb;
    req->channel = channel;
    req->sec_channel = WIFI_SECOND_CHAN_NONE;
    req->wait_time_ms = wait_time_ms;
    req->type = WIFI_OFFCHAN_TX_REQ;
    os_memcpy(req->data, buf, req->data_len);

    wpa_printf(MSG_DEBUG, "DPP: Mgmt Tx - MAC:" MACSTR ", Channel-%d, WaitT-%d",
               MAC2STR(dest_mac), channel, wait_time_ms);

    if (ESP_OK != esp_wifi_action_tx_req(req)) {
        wpa_printf(MSG_ERROR, "DPP: Failed to perform offchannel operation");
        dpp_abort_with_failure(ESP_ERR_DPP_TX_FAILURE);
        os_free(req);
        return ESP_FAIL;
    }

    wpa_printf(MSG_DEBUG, "Sent DPP action frame %d", req->op_id);
    s_current_tx_op_id = req->op_id;
    os_free(req);
    return ESP_OK;
}

static void esp_dpp_auth_resp_retry_timeout(void *eloop_ctx, void *timeout_ctx)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;

    if (!auth || !auth->resp_msg) {
        return;
    }

    wpa_printf(MSG_DEBUG,
               "DPP: Retry Authentication Response after timeout");
    wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
            " chan=%u type=%d",
            MAC2STR(auth->peer_mac_addr), auth->curr_chan,
            DPP_PA_AUTHENTICATION_RESP);

    esp_dpp_send_action_frame(s_dpp_ctx.dpp_auth->peer_mac_addr,
                              wpabuf_head(s_dpp_ctx.dpp_auth->resp_msg),
                              wpabuf_len(s_dpp_ctx.dpp_auth->resp_msg),
                              auth->curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME);
}

static esp_err_t esp_dpp_rx_auth_req(struct action_rx_param *rx_param, uint8_t *dpp_data)
{
    size_t len = rx_param->vendor_data_len - 2;
    const u8 *r_bootstrap, *i_bootstrap;
    u16 r_bootstrap_len, i_bootstrap_len;
    struct dpp_bootstrap_info *own_bi;
    int rc;

    wpa_printf(MSG_INFO, "DPP: Authentication Request from " MACSTR " chan=%d",
               MAC2STR(rx_param->sa), rx_param->channel);

    r_bootstrap = dpp_get_attr(dpp_data, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
                               &r_bootstrap_len);
    if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) {
        wpa_printf(MSG_INFO, "DPP: Missing or invalid Responder Bootstrapping Key Hash attribute");
        rc = ESP_ERR_DPP_INVALID_ATTR;
        return rc;
    }
    wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash", r_bootstrap, r_bootstrap_len);

    i_bootstrap = dpp_get_attr(dpp_data, len, DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
                               &i_bootstrap_len);
    if (!i_bootstrap || i_bootstrap_len != SHA256_MAC_LEN) {
        wpa_printf(MSG_INFO, "DPP: Missing or invalid Initiator Bootstrapping Key Hash attribute");
        rc = ESP_ERR_DPP_INVALID_ATTR;
        return rc;
    }
    wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Bootstrapping Key Hash", i_bootstrap, i_bootstrap_len);

    own_bi = dpp_bootstrap_get_id(s_dpp_ctx.dpp_global, s_dpp_ctx.id);
    /* Try to find own and peer bootstrapping key matches based on the
     * received hash values */
    if (os_memcmp(own_bi->pubkey_hash, r_bootstrap, SHA256_MAC_LEN)) {
        wpa_printf(MSG_INFO, "DPP: No matching own bootstrapping key found as responder - ignore message");
        rc = ESP_ERR_DPP_INVALID_ATTR;
        return ESP_ERR_DPP_INVALID_ATTR;
    }
    if (s_dpp_ctx.dpp_auth) {
        wpa_printf(MSG_DEBUG, "DPP: Already in DPP authentication exchange - ignore new one");
        return ESP_OK;
    }
    s_dpp_ctx.dpp_auth = dpp_auth_req_rx(NULL, DPP_CAPAB_ENROLLEE, 0, NULL,
                                         own_bi, rx_param->channel,
                                         (const u8 *)&rx_param->action_frm->u.public_action.v, dpp_data, len);
    os_memcpy(s_dpp_ctx.dpp_auth->peer_mac_addr, rx_param->sa, ETH_ALEN);

    wpa_printf(MSG_INFO, "DPP: Sending authentication response chan(%d)", rx_param->channel);
    eloop_cancel_timeout(esp_dpp_auth_resp_retry_timeout, NULL, NULL);
    eloop_register_timeout(0, 200000, esp_dpp_auth_resp_retry_timeout, NULL, NULL);
    esp_dpp_send_action_frame(rx_param->sa, wpabuf_head(s_dpp_ctx.dpp_auth->resp_msg),
                              wpabuf_len(s_dpp_ctx.dpp_auth->resp_msg),
                              rx_param->channel, 1000 + OFFCHAN_TX_WAIT_TIME);

    return ESP_OK;
}

static void gas_query_timeout(void *eloop_data, void *user_ctx)
{
    struct dpp_authentication *auth = user_ctx;

    if (!s_dpp_ctx.dpp_auth || !s_dpp_ctx.dpp_auth->auth_success || (s_dpp_ctx.dpp_auth != auth)) {
        wpa_printf(MSG_INFO, "DPP-GAS: Auth %p state not correct", auth);
        return;
    }

    wpa_printf(MSG_DEBUG, "GAS: No response received for GAS query");
    if (auth->conf_req) {
        wpabuf_free(auth->conf_req);
        auth->conf_req = NULL;
    }
    dpp_abort_with_failure(ESP_ERR_DPP_CONF_TIMEOUT);
}

static int gas_query_req_tx(struct dpp_authentication *auth)
{
    struct wpabuf *buf;
    int supp_op_classes[] = {81, 0};
    int ret;

    buf = dpp_build_conf_req_helper(auth, NULL, 0, NULL,
                                    supp_op_classes);
    if (!buf) {
        wpa_printf(MSG_ERROR, "DPP: No configuration request data available");
        dpp_abort_with_failure(ESP_ERR_DPP_FAILURE);
        return ESP_FAIL;
    }

    wpa_printf(MSG_INFO, "DPP: GAS request to " MACSTR " (chan %u)",
               MAC2STR(auth->peer_mac_addr), auth->curr_chan);

    ret = esp_dpp_send_action_frame(auth->peer_mac_addr, wpabuf_head(buf), wpabuf_len(buf),
                                    auth->curr_chan, 1000 + OFFCHAN_TX_WAIT_TIME);
    if (ret != ESP_OK) {
        wpabuf_free(buf);
        return ret;
    }

    auth->conf_req = buf;
    eloop_register_timeout(2, 0, gas_query_timeout, NULL, auth);

    return ret;
}

static int esp_dpp_handle_config_obj(struct dpp_authentication *auth,
                                     struct dpp_config_obj *conf)
{
    wifi_config_t *wifi_cfg = &s_dpp_ctx.wifi_cfg;
    os_memset(wifi_cfg, 0, sizeof(wifi_config_t));

    if (conf->ssid_len) {
        os_memcpy(wifi_cfg->sta.ssid, conf->ssid, conf->ssid_len);
    }

    if (dpp_akm_legacy(conf->akm)) {
        if (conf->passphrase[0])
            os_memcpy(wifi_cfg->sta.password, conf->passphrase,
                      sizeof(wifi_cfg->sta.password));
        if (conf->akm == DPP_AKM_PSK_SAE) {
            wifi_cfg->sta.pmf_cfg.required = true;
        }
    }

    if (conf->connector) {
        /* TODO: Save the Connector and consider using a command
         * to fetch the value instead of sending an event with
         * it. The Connector could end up being larger than what
         * most clients are ready to receive as an event
         * message. */
        wpa_printf(MSG_INFO, DPP_EVENT_CONNECTOR "%s",
                   conf->connector);
    }
    if (atomic_load(&roc_in_progress)) {
        listen_stop_handler(NULL, NULL);
    }
    /* deinit AUTH since authentication is done */
    dpp_deinit_auth();

    wifi_event_dpp_config_received_t event = {0};
    event.wifi_cfg = s_dpp_ctx.wifi_cfg;
    esp_event_post(WIFI_EVENT, WIFI_EVENT_DPP_CFG_RECVD, &event, sizeof(event), OS_BLOCK);

    return 0;
}

static int esp_dpp_rx_auth_conf(struct action_rx_param *rx_param, uint8_t *dpp_data)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;
    struct ieee80211_public_action *public_action =
            &rx_param->action_frm->u.public_action;
    size_t len = rx_param->vendor_data_len - 2;
    int rc;

    wpa_printf(MSG_INFO, "DPP: Authentication Confirmation from " MACSTR,
               MAC2STR(rx_param->sa));

    if (!auth) {
        wpa_printf(MSG_DEBUG, "DPP: No DPP Authentication in progress - drop");
        return ESP_ERR_DPP_FAILURE;
    }

    if (os_memcmp(rx_param->sa, auth->peer_mac_addr, ETH_ALEN) != 0) {
        wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
                   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
        return ESP_ERR_DPP_FAILURE;
    }

    eloop_cancel_timeout(esp_dpp_auth_conf_wait_timeout, NULL, NULL);

    if (dpp_auth_conf_rx(auth, (const u8 *)&public_action->v,
                         dpp_data, len) < 0) {
        wpa_printf(MSG_ERROR, "DPP: Authentication failed");
        return ESP_ERR_DPP_FAILURE;
    }

    /* Send GAS Query Req */
    rc = gas_query_req_tx(auth);
    if (rc != ESP_OK) {
        wpa_printf(MSG_ERROR, "DPP: GAS query Tx failed");
        return rc;
    }

    return rc;
}

static esp_err_t esp_dpp_rx_peer_disc_resp(struct action_rx_param *rx_param)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;
    uint8_t *buf;
    unsigned int seconds;
    struct os_reltime rnow;
    const uint8_t *connector, *trans_id, *status = NULL;
    uint16_t connector_len, trans_id_len, status_len;
    enum dpp_status_error res = 0;
    struct dpp_introduction intro;
    os_time_t expiry;
    struct os_time now;
    struct wpa_sm *sm = get_wpa_sm();
    struct rsn_pmksa_cache_entry *entry = NULL;
    int i = 0;

    if (!rx_param) {
        return ESP_ERR_INVALID_ARG;
    }

    if (rx_param->vendor_data_len < 2) {
        wpa_printf(MSG_INFO, "DPP: Too short vendor specific data");
        return ESP_FAIL;
    }
    size_t len = rx_param->vendor_data_len - 2;

    buf = rx_param->action_frm->u.public_action.v.pa_vendor_spec.vendor_data;

    if (os_memcmp(auth->peer_mac_addr, rx_param->sa, ETH_ALEN) != 0) {
        wpa_printf(MSG_DEBUG, "DPP: Not expecting Peer Discovery response from " MACSTR, MAC2STR(rx_param->sa));
        return ESP_OK;
    }

    wpa_printf(MSG_DEBUG, "DPP: Peer Discovery from " MACSTR, MAC2STR(rx_param->sa));

    for (i = 0; i < auth->num_conf_obj; i++) {

        if (!auth->conf_obj[i].connector
                || !auth->net_access_key
                || !auth->conf_obj[i].c_sign_key
                || dpp_akm_legacy(auth->conf_obj[i].akm)) {
            wpa_printf(MSG_DEBUG, "DPP: Profile not found for network introduction or akm mismatch");
            continue;
        }

        trans_id = dpp_get_attr(&buf[2], len, DPP_ATTR_TRANSACTION_ID, &trans_id_len);
        if (!trans_id || trans_id_len != 1) {
            wpa_printf(MSG_ERROR, "DPP: Peer did not include Transaction ID");
            return ESP_FAIL;
        }
        if (trans_id[0] != TRANSACTION_ID) {
            wpa_printf(MSG_ERROR, "DPP: Ignore frame with unexpected Transaction ID %u", trans_id[0]);
            return ESP_FAIL;
        }

        status = dpp_get_attr(&buf[2], len, DPP_ATTR_STATUS, &status_len);
        if (!status || status_len != 1) {
            wpa_printf(MSG_ERROR, "DPP: Peer did not include Status");
            return ESP_FAIL;
        }
        if (status[0] != DPP_STATUS_OK) {
            wpa_printf(MSG_ERROR, "DPP: Peer rejected network introduction: Status %u", status[0]);
            return ESP_FAIL;
        }

        connector = dpp_get_attr(&buf[2], len, DPP_ATTR_CONNECTOR, &connector_len);
        if (!connector) {
            wpa_printf(MSG_ERROR, "DPP: Peer did not include its Connector");
            return ESP_FAIL;
        }

        res = dpp_peer_intro(&intro, auth->conf_obj[i].connector,
                             wpabuf_head(auth->net_access_key),
                             wpabuf_len(auth->net_access_key),
                             wpabuf_head(auth->conf_obj[i].c_sign_key),
                             wpabuf_len(auth->conf_obj[i].c_sign_key),
                             connector, connector_len, &expiry);

        if (res == DPP_STATUS_OK) {
            entry = os_zalloc(sizeof(*entry));
            if (!entry) {
                goto fail;
            }
            os_memcpy(entry->aa, rx_param->sa, ETH_ALEN);
            os_memcpy(entry->pmkid, intro.pmkid, PMKID_LEN);
            os_memcpy(entry->pmk, intro.pmk, intro.pmk_len);
            entry->pmk_len = intro.pmk_len;
            entry->akmp = WPA_KEY_MGMT_DPP;

            if (expiry) {
                os_get_time(&now);
                seconds = expiry - now.sec;
            } else {
                seconds = ESP_DPP_PMK_CACHE_DEFAULT_TIMEOUT;
            }
            os_get_reltime(&rnow);
            entry->expiration = rnow.sec + seconds;
            entry->reauth_time = rnow.sec + seconds;
            entry->network_ctx = auth;

            pmksa_cache_add_entry(sm->pmksa, entry);

            wpa_printf(MSG_INFO, "peer=" MACSTR " status=%u", MAC2STR(rx_param->sa), status[0]);
            break;
        }
    }

    if (res != DPP_STATUS_OK) {
        wpa_printf(MSG_ERROR, "DPP: Network Introduction protocol resulted in failure");
        goto fail;
    }

    wpa_printf(MSG_DEBUG,
               "DPP: Try connection after successful network introduction");
    dpp_connect(rx_param->sa, true);
    return ESP_OK;
fail:
    os_memset(&intro, 0, sizeof(intro));
    if (entry != NULL) {
        os_free(entry);
    }
    return ESP_FAIL;
}

static esp_err_t esp_dpp_rx_frm(struct action_rx_param *rx_param)
{
    uint8_t crypto_suit, type;
    uint8_t *tmp;
    int ret = ESP_OK;

    tmp = rx_param->action_frm->u.public_action.v.pa_vendor_spec.vendor_data;
    crypto_suit = tmp[0];
    type = tmp[1];

    if (crypto_suit != 1) {
        wpa_printf(MSG_ERROR, "DPP: Unsupported crypto suit");
        return ESP_FAIL;
    }

    switch (type) {
    case DPP_PA_AUTHENTICATION_REQ:
        ret = esp_dpp_rx_auth_req(rx_param, &tmp[2]);
        break;
    case DPP_PA_AUTHENTICATION_CONF:
        ret = esp_dpp_rx_auth_conf(rx_param, &tmp[2]);
        break;
    case DPP_PA_PEER_DISCOVERY_RESP:
        ret = esp_dpp_rx_peer_disc_resp(rx_param);
        break;
    }

    return ret;
}

static esp_err_t gas_query_resp_rx(struct action_rx_param *rx_param)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;
    uint8_t *pos = rx_param->action_frm->u.public_action.v.pa_gas_resp.data;
    uint8_t *resp = &pos[10];  /* first byte of DPP attributes */
    size_t vendor_len = rx_param->vendor_data_len;
    int i, res;

    /* Basic structural checks on the Advertisement Protocol payload */
    if (!(pos[1] == WLAN_EID_VENDOR_SPECIFIC && pos[2] == 5 &&
            WPA_GET_BE24(&pos[3]) == OUI_WFA && pos[6] == 0x1a && pos[7] == 1 && auth)) {
        wpa_hexdump(MSG_INFO, "DPP: Failed, Configuration Response adv_proto", pos, 8);
        return ESP_OK;
    }

    /* DPP attribute length = vendor_data_len - 2, caller validated vendor_data_len
     * (we skip the 2-byte length field and pass only the attributes). */
    size_t dpp_data_len = vendor_len - 2;

    eloop_cancel_timeout(gas_query_timeout, NULL, auth);

    if (dpp_conf_resp_rx(auth, resp, dpp_data_len) < 0) {
        wpa_printf(MSG_INFO, "DPP: Configuration attempt failed");
        goto fail;
    }

    for (i = 0; i < auth->num_conf_obj; i++) {
        res = esp_dpp_handle_config_obj(auth, &auth->conf_obj[i]);
        if (res < 0) {
            wpa_printf(MSG_INFO, "DPP: Configuration parsing failed");
            goto fail;
        }
    }

    return ESP_OK;

fail:
    return ESP_FAIL;
}

static void esp_dpp_rx_action(void *data, void *user_ctx)
{
    struct action_rx_param *rx_param = data;
    struct ieee80211_public_action *public_action;
    int ret = ESP_OK;

    if (!rx_param) {
        return;
    }

    /* we don't cater other action frames except public here */
    if (rx_param->action_frm->category != WLAN_ACTION_PUBLIC) {
        return;
    }

    public_action = &rx_param->action_frm->u.public_action;

    wpa_printf(MSG_DEBUG, "DPP: Rx Public Action frame: action - %d",
               public_action->action);

    if (public_action->action == WLAN_PA_VENDOR_SPECIFIC &&
            WPA_GET_BE24(public_action->v.pa_vendor_spec.oui) == OUI_WFA &&
            public_action->v.pa_vendor_spec.wfa_stype == DPP_OUI_TYPE) {

        rx_param->vendor_data_len = rx_param->frm_len -
                                    (size_t)(public_action->v.pa_vendor_spec.vendor_data -
                                             (u8 *)rx_param->action_frm);

        if (atomic_load(&roc_in_progress)) {
            listen_stop_handler(NULL, NULL);
        }

        ret = esp_dpp_rx_frm(rx_param);
    } else if (public_action->action == WLAN_PA_GAS_INITIAL_RESP &&
               public_action->v.pa_gas_resp.type == WLAN_EID_ADV_PROTO &&
               public_action->v.pa_gas_resp.length == 8 &&
               public_action->v.pa_gas_resp.status_code == 0) {

        rx_param->vendor_data_len = rx_param->frm_len -
                                    (size_t)(public_action->v.pa_gas_resp.data +
                                             public_action->v.pa_gas_resp.length -
                                             (u8 *)rx_param->action_frm);

        wpa_printf(MSG_DEBUG, "DPP: Gas response received");
        ret = gas_query_resp_rx(rx_param);
    }

    os_free(rx_param);

    if (ret != ESP_OK) {
        dpp_abort_with_failure(ESP_ERR_DPP_FAILURE);
    }
}

static void dpp_listen_next_channel(void *data, void *user_ctx)
{
    struct dpp_bootstrap_params_t *p = &s_dpp_ctx.bootstrap_params;
    static int counter;
    int channel;
    esp_err_t ret = 0;
    wifi_roc_req_t req = {0};

    if (!s_dpp_ctx.dpp_listen_ongoing) {
        return;
    }
    if (p->num_chan <= 0) {
        wpa_printf(MSG_ERROR, "Listen channel not set");
        return;
    }

    channel = p->chan_list[counter++ % p->num_chan];

    wpa_printf(MSG_DEBUG, "DPP: Starting ROC on channel %d", channel);

    req.ifx = WIFI_IF_STA;
    req.type = WIFI_ROC_REQ;
    req.channel = channel;
    req.wait_time_ms = BOOTSTRAP_ROC_WAIT_TIME;
    req.rx_cb = s_action_rx_cb;
    ret = esp_wifi_remain_on_channel(&req);
    if (ret != ESP_OK) {
        wpa_printf(MSG_ERROR, "Failed ROC. error : 0x%x", ret);
        return;
    }
    atomic_store(&roc_in_progress, true);
    os_event_group_clear_bits(s_dpp_event_group, DPP_ROC_EVENT_HANDLED);
}

static void esp_dpp_bootstrap_gen(void *data, void *user_ctx)
{
    char *command = data;
    const char *uri;
    uint32_t len;

    s_dpp_ctx.id = dpp_bootstrap_gen(s_dpp_ctx.dpp_global, command);

    if (s_dpp_ctx.id < 0) {
        struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params;
        if (params->info) {
            os_free(params->info);
            params->info = NULL;
        }
    }
    uri = dpp_bootstrap_get_uri(s_dpp_ctx.dpp_global, s_dpp_ctx.id);

    wifi_event_dpp_uri_ready_t *event;
    len = sizeof(*event) + os_strlen(uri) + 1;
    event = os_malloc(len);
    if (!event) {
        return;
    }
    event->uri_data_len = os_strlen(uri);
    os_memcpy(event->uri, uri, event->uri_data_len);
    event->uri[event->uri_data_len++] = '\0';
    esp_event_post(WIFI_EVENT, WIFI_EVENT_DPP_URI_READY, event, len, OS_BLOCK);
    os_free(event);
    os_free(command);
    dpp_api_lock();
    s_dpp_ctx.bootstrap_done = true;
    dpp_api_unlock();
}

static int esp_dpp_deinit(void *data, void *user_ctx)
{
    struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params;

    wpa_printf(MSG_DEBUG, "DPP: Deinitializing DPP");
    /* Cancel all registered timeouts */
    eloop_cancel_timeout(esp_dpp_auth_conf_wait_timeout, NULL, NULL);

    esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_ACTION_TX_STATUS,
                                 &tx_status_handler);
    esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_ROC_DONE,
                                 &roc_status_handler);

    if (s_dpp_ctx.dpp_event_cb) {
        esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_DPP_URI_READY,
                                     &dpp_event_handler);
        esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_DPP_CFG_RECVD,
                                     &dpp_event_handler);
        esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_DPP_FAILED,
                                     &dpp_event_handler);
    }
    if (params->info) {
        os_free(params->info);
        params->info = NULL;
    }

    if (s_dpp_ctx.dpp_global) {
        dpp_global_deinit(s_dpp_ctx.dpp_global);
        s_dpp_ctx.dpp_global = NULL;
    }
    if (s_dpp_ctx.dpp_auth) {
        dpp_auth_deinit(s_dpp_ctx.dpp_auth);
        s_dpp_ctx.dpp_auth = NULL;
    }
    s_dpp_ctx.dpp_init_done = false;
    s_dpp_ctx.bootstrap_done = false;
    s_dpp_ctx.dpp_event_cb = NULL;
    if (s_dpp_event_group) {
        os_event_group_delete(s_dpp_event_group);
        s_dpp_event_group = NULL;
    }

    return 0;
}

static int esp_supp_rx_action(uint8_t *hdr, uint8_t *payload, size_t len, uint8_t channel)
{
    struct ieee80211_hdr *rx_hdr = (struct ieee80211_hdr *)hdr;
    struct action_rx_param *rx_param;
    int ret = ESP_ERR_NOT_SUPPORTED;
    struct ieee80211_action *frm = (struct ieee80211_action *)payload;

    if (WLAN_FC_GET_STYPE(rx_hdr->frame_control) == WLAN_FC_STYPE_ACTION) {
        if (frm->category != WLAN_ACTION_PUBLIC) {
            wpa_printf(MSG_DEBUG, "Not public action frame");
            return -1;
        }

        rx_param = os_zalloc(sizeof(struct action_rx_param) + len);
        if (!rx_param) {
            wpa_printf(MSG_ERROR, "Failed to allocate memory for Rx Action");
            return ESP_ERR_NO_MEM;
        }
        os_memcpy(rx_param->sa, rx_hdr->addr2, ETH_ALEN);
        rx_param->channel = channel;
        rx_param->frm_len = len;
        os_memcpy(rx_param->action_frm, payload, len);

        if (eloop_register_timeout(0, 0, esp_dpp_rx_action, rx_param, NULL) != 0) {
            os_free(rx_param);
            return ESP_ERR_NO_MEM;
        }
    }

    return ret;
}

static void esp_dpp_auth_resp_retry(void *eloop_ctx, void *timeout_ctx)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;
    unsigned int max_tries = 5;

    if (!auth || !auth->resp_msg) {
        return;
    }

    auth->auth_resp_tries++;
    if (auth->auth_resp_tries >= max_tries) {
        wpa_printf(MSG_INFO, "DPP: No confirm received from initiator - stopping exchange");
        dpp_abort_with_failure(ESP_ERR_DPP_TX_FAILURE);
        return;
    }

    wpa_printf(MSG_INFO,
               "DPP: Retransmiting Authentication Response frame");
    eloop_cancel_timeout(esp_dpp_auth_resp_retry_timeout, NULL, NULL);
    esp_dpp_auth_resp_retry_timeout(NULL, NULL);
}

static void tx_status_handler(void *arg, esp_event_base_t event_base,
                              int32_t event_id, void *event_data)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;

    wifi_event_action_tx_status_t *evt = event_data;

    wpa_printf(MSG_DEBUG, "Mgmt Tx Status - %d, Cookie - 0x%x",
               evt->status, (uint32_t)evt->context);

    if (evt->op_id != s_current_tx_op_id) {
        wpa_printf(MSG_DEBUG, "DPP: status not for recent frame op_id=%u, s_current_tx_op_id=%u",
                   evt->op_id, s_current_tx_op_id);
        return;
    }
    if (!auth) {
        wpa_printf(MSG_DEBUG, "Auth already deinitialized, return");
        return;
    }
    if (auth->waiting_auth_conf) {
        eloop_cancel_timeout(esp_dpp_auth_resp_retry_timeout, NULL, NULL);
        if (evt->status == WIFI_ACTION_TX_FAILED) {
            /* failed to send auth response frame */
            eloop_cancel_timeout(esp_dpp_auth_conf_wait_timeout, NULL, NULL);
            eloop_register_timeout(1, 0, esp_dpp_auth_resp_retry, NULL, NULL);
        } else if (evt->status == WIFI_ACTION_TX_DONE) {
            eloop_cancel_timeout(esp_dpp_auth_conf_wait_timeout, NULL, NULL);
            eloop_register_timeout(ESP_DPP_AUTH_TIMEOUT_SECS, 0, esp_dpp_auth_conf_wait_timeout, NULL, NULL);
        }
    } else if (auth->auth_success) {
        if (evt->status == WIFI_ACTION_TX_FAILED) {
            /* failed to send gas query frame, retry logic needed? */
            wpa_printf(MSG_WARNING, "DPP: failed to send GAS query frame");
            dpp_abort_with_failure(ESP_ERR_DPP_TX_FAILURE);
        } else if (evt->status == WIFI_ACTION_TX_DONE) {
            eloop_cancel_timeout(gas_query_timeout, NULL, auth);
            eloop_register_timeout(ESP_GAS_TIMEOUT_SECS, 0, gas_query_timeout, NULL, auth);
        }
    }
}

static void roc_status_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    wifi_event_roc_done_t *evt = (wifi_event_roc_done_t *)event_data;

    if (evt->context == (uint32_t)s_action_rx_cb) {
        eloop_cancel_timeout(dpp_listen_next_channel, NULL, NULL);
        eloop_register_timeout(0, 0, dpp_listen_next_channel, NULL, NULL);
    }

    atomic_store(&roc_in_progress, false);
    os_event_group_set_bits(s_dpp_event_group, DPP_ROC_EVENT_HANDLED);
}

static char *esp_dpp_parse_chan_list(const char *chan_list)
{
    struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params;
    size_t max_uri_len = ESP_DPP_MAX_CHAN_COUNT * 8 + strlen(" chan=") + 1;

    char *uri_channels = os_zalloc(max_uri_len);
    if (!uri_channels) {
        wpa_printf(MSG_WARNING, "DPP: URI allocation failed");
        return NULL;
    }

    char *uri_ptr = uri_channels;
    size_t current_offset = 0; // Use an offset to track current position
    params->num_chan = 0;

    /* Append " chan=" at the beginning of the URI */
    int written = os_snprintf(uri_ptr + current_offset, max_uri_len - current_offset, " chan=");
    if (written < 0 || written >= max_uri_len - current_offset) { // Check for error or truncation
        wpa_printf(MSG_ERROR, "DPP: URI buffer too small for initial string");
        os_free(uri_channels);
        return NULL;
    }
    current_offset += written;

    while (*chan_list && params->num_chan < ESP_DPP_MAX_CHAN_COUNT) {
        int channel = 0;

        /* Parse the channel number */
        while (*chan_list >= '0' && *chan_list <= '9') {
            channel = channel * 10 + (*chan_list - '0');
            chan_list++;
        }

        /* Validate the channel number */
        if (CHANNEL_TO_BIT_NUMBER(channel) == 0) {
            wpa_printf(MSG_WARNING, "DPP: Skipping invalid channel %d", channel);
            /* Skip to the next valid entry */
            while (*chan_list == ',' || *chan_list == ' ') {
                chan_list++;
            }
            continue;  // Skip the bad channel and move to the next one
        }

        /* Get the operating class for the channel */
        u8 oper_class = get_operating_class(channel, 0);
        if (oper_class == 0) {
            wpa_printf(MSG_WARNING, "DPP: Skipping channel %d due to missing operating class", channel);
            /* Skip to the next valid entry */
            while (*chan_list == ',' || *chan_list == ' ') {
                chan_list++;
            }
            continue;  /* Skip to the next channel if no operating class found */
        }

        /* Add the valid channel to the list */
        params->chan_list[params->num_chan++] = channel;

        // Calculate space needed for current channel string (e.g., "81/1,")
        int needed_for_channel = os_snprintf(NULL, 0, "%d/%d,", oper_class, channel);

        if (current_offset + needed_for_channel + 1 > max_uri_len) { // +1 for null terminator
            wpa_printf(MSG_ERROR, "DPP: Not enough space in URI buffer for channel %d", channel);
            os_free(uri_channels);
            return NULL;
        }

        /* Append the operating class and channel to the URI */
        written = os_snprintf(uri_ptr + current_offset, max_uri_len - current_offset, "%d/%d,", oper_class, channel);
        if (written < 0 || written >= max_uri_len - current_offset) { // Check for error or truncation
            wpa_printf(MSG_ERROR, "DPP: Error writing channel %d to URI buffer", channel);
            os_free(uri_channels);
            return NULL;
        }
        current_offset += written;

        /* Skip any delimiters (comma or space) */
        while (*chan_list == ',' || *chan_list == ' ') {
            chan_list++;
        }
    }

    if (!params->num_chan) {
        wpa_printf(MSG_ERROR, "DPP: No valid channel in the list");
        os_free(uri_channels);
        return NULL;
    }

    /* Replace the last comma with a space if there was content added */
    if (current_offset > strlen(" chan=") && uri_ptr[current_offset - 1] == ',') {
        uri_ptr[current_offset - 1] = ' ';
    }

    return uri_channels;
}

esp_err_t
esp_supp_dpp_bootstrap_gen(const char *chan_list, enum dpp_bootstrap_type type,
                           const char *key, const char *uri_info)
{
    int ret = dpp_api_lock();
    if (ret != ESP_OK) {
        return ret;
    }
    if (!s_dpp_ctx.dpp_init_done) {
        dpp_api_unlock();
        wpa_printf(MSG_ERROR, "DPP: failed to bootstrap as dpp not initialized.");
        return ESP_FAIL;
    }
    dpp_api_unlock();

    struct dpp_bootstrap_params_t *params = &s_dpp_ctx.bootstrap_params;
    char *uri_chan_list = esp_dpp_parse_chan_list(chan_list);

    wpa_printf(MSG_DEBUG, "DPP: Starting bootstrap genration");
    if (params->num_chan > ESP_DPP_MAX_CHAN_COUNT) {
        os_free(uri_chan_list);
        wpa_printf(MSG_ERROR, "DPP: Invalid channel list");
        return ESP_ERR_DPP_INVALID_LIST;
    }

    char *command = os_zalloc(1200);

    if (!uri_chan_list || !command || params->num_chan > ESP_DPP_MAX_CHAN_COUNT || params->num_chan == 0) {
        wpa_printf(MSG_ERROR, "Invalid Channel list - %s", chan_list);
        if (command) {
            os_free(command);
        }
        ret = ESP_ERR_DPP_FAILURE;
        goto fail;
    }

    if (type != DPP_BOOTSTRAP_QR_CODE) {
        wpa_printf(MSG_INFO, "Bootstrap type %d not supported", type);
        os_free(command);
        ret = ESP_ERR_NOT_SUPPORTED;
        goto fail;
    }
    params->type = type;
    esp_wifi_get_mac(WIFI_IF_STA, params->mac);

    if (uri_info) {
        params->info_len = strlen(uri_info);
        if (params->info_len) {
            params->info = os_zalloc(params->info_len + 1);
            if (!params->info) {
                os_free(command);
                ret = ESP_ERR_NO_MEM;
                goto fail;
            }
            os_memcpy(params->info, uri_info, params->info_len);
        }
    }

    os_snprintf(command, 1200, "type=qrcode mac=" MACSTR "%s%s%s%s%s",
                MAC2STR(params->mac), uri_chan_list,
                key ? "key=" : "", key ? key : "",
                params->info_len ? " info=" : "",
                params->info_len ? params->info : "");

    eloop_register_timeout(0, 0, esp_dpp_bootstrap_gen, command, NULL);

    ret = ESP_OK;
fail:
    if (uri_chan_list) {
        os_free(uri_chan_list);
    }

    return ret;
}

static void dpp_listen_start(void *ctx, void *data)
{
    s_dpp_ctx.dpp_listen_ongoing = true;
    dpp_listen_next_channel(NULL, NULL);
}

esp_err_t esp_supp_dpp_start_listen(void)
{
    int ret = dpp_api_lock();
    if (ret != ESP_OK) {
        return ret;
    }
    if (!s_dpp_ctx.dpp_init_done || !s_dpp_ctx.bootstrap_done) {
        wpa_printf(MSG_ERROR, "DPP: failed to start listen as dpp not initialized or bootstrapped.");
        dpp_api_unlock();
        return ESP_FAIL;
    }
    dpp_api_unlock();

    if (esp_wifi_get_user_init_flag_internal() == 0) {
        wpa_printf(MSG_ERROR, "DPP: ROC not possible before wifi is started");
        return ESP_ERR_INVALID_STATE;
    }

    /* cancel previous ROC if ongoing */
    esp_supp_dpp_stop_listen();

    /* Give ample time to set the bit, timeout is necessary when ROC is not running previously */
    os_event_group_wait_bits(s_dpp_event_group, DPP_ROC_EVENT_HANDLED, 0, 0, os_task_ms_to_tick(100));
    wpa_printf(MSG_DEBUG, "DPP: Starting ROC");
    eloop_register_timeout(0, 0, dpp_listen_start, NULL, NULL);
    return 0;
}

esp_err_t esp_supp_dpp_stop_listen(void)
{
    int ret = eloop_register_timeout_blocking(listen_stop_handler, NULL, NULL);

    if (ret) {
        return ESP_FAIL;
    }
    return ESP_OK;
}

/* this is called from public API, need protection */
bool is_dpp_enabled(void)
{
    int ret;
    ret = dpp_api_lock();
    if (ret != ESP_OK) {
        return false;
    }
    ret = s_dpp_ctx.dpp_init_done;
    dpp_api_unlock();

    return ret;
}

/* Caller supposed to have dpp_api_lock() before calling this in blocking context */
static int esp_dpp_init(void *eloop_data, void *user_ctx)
{
    struct dpp_global_config cfg = {0};
    int ret;
    esp_supp_dpp_event_cb_t cb = user_ctx;

    cfg.cb_ctx = &s_dpp_ctx;
    cfg.msg_ctx = &s_dpp_ctx;
    os_bzero(&s_dpp_ctx, sizeof(s_dpp_ctx));

    s_dpp_ctx.dpp_global = dpp_global_init(&cfg);
    if (!s_dpp_ctx.dpp_global) {
        wpa_printf(MSG_ERROR, "DPP: failed to allocate memory for dpp_global");
        ret = ESP_ERR_NO_MEM;
        goto init_fail;
    }

    s_dpp_ctx.dpp_event_cb = cb;

    if (cb) {
        esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_DPP_URI_READY,
                                   &dpp_event_handler, NULL);
        esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_DPP_CFG_RECVD,
                                   &dpp_event_handler, NULL);
        esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_DPP_FAILED,
                                   &dpp_event_handler, NULL);
    }
    esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_ACTION_TX_STATUS,
                               &tx_status_handler, NULL);
    esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_ROC_DONE,
                               &roc_status_handler, NULL);

    s_dpp_event_group = os_event_group_create();
    wpa_printf(MSG_INFO, "DPP: dpp init done");
    s_dpp_ctx.dpp_init_done = true;

    return ESP_OK;
init_fail:
    if (s_dpp_ctx.dpp_global) {
        dpp_global_deinit(s_dpp_ctx.dpp_global);
        s_dpp_ctx.dpp_global = NULL;
    }
    return ret;

}

esp_err_t esp_supp_dpp_init(esp_supp_dpp_event_cb_t cb)
{
    esp_err_t ret = ESP_OK;

    wifi_mode_t mode = 0;
    if (esp_wifi_get_mode(&mode) || ((mode != WIFI_MODE_STA) && (mode != WIFI_MODE_APSTA))) {
        wpa_printf(MSG_ERROR, "DPP: failed to init as not in station mode.");
        return ESP_FAIL;
    }

    if (is_wps_enabled()) {
        wpa_printf(MSG_ERROR, "DPP: failed to init since WPS is enabled");
        return ESP_FAIL;
    }

    ret = dpp_api_lock();
    if (ret != ESP_OK) {
        return ret;
    }
    if (s_dpp_ctx.dpp_init_done) {
        wpa_printf(MSG_ERROR, "DPP: failed to init as init already done. Please deinit first and retry.");
        dpp_api_unlock();
        return ESP_FAIL;
    }
    ret = eloop_register_timeout_blocking(esp_dpp_init, NULL, cb);
    dpp_api_unlock();
    return ret;
}

esp_err_t esp_dpp_start_net_intro_protocol(uint8_t *bssid)
{
    struct dpp_authentication *auth = s_dpp_ctx.dpp_auth;
    struct wpabuf *buf = NULL;
    int ret = ESP_OK;

    for (int i = 0; i < auth->num_conf_obj; i++) {
        os_memcpy(auth->peer_mac_addr, bssid, ETH_ALEN);
        buf = dpp_build_peer_disc_req(auth, &auth->conf_obj[i]);

        if (!buf) {
            return ESP_ERR_NO_MEM;
        }

        if (esp_dpp_send_action_frame(bssid, wpabuf_head(buf), wpabuf_len(buf),
                                      auth->curr_chan, OFFCHAN_TX_WAIT_TIME) != ESP_OK) {
            ret = ESP_FAIL;
        }

        wpabuf_free(buf);
    }

    return ret;
}

esp_err_t esp_supp_dpp_deinit(void)
{
    int ret = dpp_api_lock();
    if (ret != ESP_OK) {
        return ret;
    }
    if (!s_dpp_ctx.dpp_init_done) {
        wpa_printf(MSG_DEBUG, "DPP: Already disabled");
        dpp_api_unlock();
        return ESP_OK;
    }
    dpp_api_unlock();
    eloop_register_timeout_blocking(esp_dpp_deinit, NULL, NULL);
    return ESP_OK;
}
#endif
