/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of The Linux Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 * Changes from Qualcomm Innovation Center are provided under the following license:

 *  Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
 *  SPDX-License-Identifier: BSD-3-Clause-Clear
 */

#include "sync.h"

#include <utils/Log.h>

#include <hardware_legacy/wifi_hal.h>
#include "common.h"
#include "cpp_bindings.h"
#include "radio_mode.h"
#include "vendor_definitions.h"
#include <netlink/genl/genl.h>
#include <string.h>
#include <net/if.h>

/* Used to handle radio command events from driver/firmware. */
typedef struct radio_event_handler_s {
    RADIOModeCommand* mRADIOModeCommandInstance;
} radio_event_handlers;

wifi_error initializeRadioHandler(hal_info *info)
{
    info->radio_handlers = (radio_event_handlers *)malloc(
            sizeof(radio_event_handlers));
    if (info->radio_handlers) {
        memset(info->radio_handlers, 0, sizeof(radio_event_handlers));
    } else {
        ALOGE("%s: Allocation of radio event handlers failed",
                __FUNCTION__);
        return WIFI_ERROR_OUT_OF_MEMORY;
    }
    return WIFI_SUCCESS;
}

wifi_error cleanupRadioHandler(hal_info *info) {
    radio_event_handlers* event_handlers;
    if (info && info->radio_handlers) {
        event_handlers = (radio_event_handlers*) info->radio_handlers;
        if (event_handlers->mRADIOModeCommandInstance) {
            delete event_handlers->mRADIOModeCommandInstance;
        }
        memset(event_handlers, 0, sizeof(radio_event_handlers));
        free(info->radio_handlers);
        info->radio_handlers = NULL;
        return WIFI_SUCCESS;
    }
    ALOGE ("%s: info or info->radio_handlers NULL", __FUNCTION__);
    return WIFI_ERROR_UNKNOWN;
}

/* Used to handle radio mode command events from driver/firmware.*/
void RADIOModeCommand::setCallbackHandler(wifi_radio_mode_change_handler handler)
{
    mHandler = handler;
}

void RADIOModeCommand::setReqId(wifi_request_id id)
{
    mreqId =  id;
}

RADIOModeCommand::RADIOModeCommand(wifi_handle handle, int id,
                                   u32 vendor_id, u32 subcmd)
        : WifiVendorCommand(handle, id, vendor_id, subcmd)
{
    memset(&mHandler, 0, sizeof(mHandler));
    if (registerVendorHandler(vendor_id, subcmd)) {
        /* Error case should not happen print log */
        ALOGE("%s: Unable to register Vendor Handler Vendor Id=0x%x subcmd=%u",
              __FUNCTION__, vendor_id, subcmd);
    }
}

RADIOModeCommand::~RADIOModeCommand()
{
    unregisterVendorHandler(mVendor_id, mSubcmd);
}


RADIOModeCommand* RADIOModeCommand::instance(wifi_handle handle,
                                             wifi_request_id id)
{
    if (handle == NULL) {
        ALOGE("Interface Handle is invalid");
        return NULL;
    }
    hal_info *info = getHalInfo(handle);
    if (!info) {
        ALOGE("hal_info is invalid");
        return NULL;
    }
    RADIOModeCommand* instance = info->radio_handlers->mRADIOModeCommandInstance;
    if (instance) {
        if (handle != getWifiHandle(instance->mInfo)) {
            ALOGV("%s - Handle different, update the handle", __FUNCTION__);
            instance->mInfo = (hal_info *)handle;
        }
        instance->setReqId(id);
    } else {
        info->radio_handlers->mRADIOModeCommandInstance =
            new RADIOModeCommand(handle, id, OUI_QCA,
                QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO);
        instance = info->radio_handlers->mRADIOModeCommandInstance;
    }
    return instance;
}

/* This function will be the main handler for incoming event.
 * Call the appropriate callback handler after parsing the vendor data.
 */
int RADIOModeCommand::handleEvent(WifiEvent &event)
{
    wifi_error ret = WIFI_ERROR_UNKNOWN;
    int num_of_mac = 0;
    wifi_mac_info mode_info;
    memset(&mode_info, 0, sizeof(mode_info));

    WifiVendorCommand::handleEvent(event);

    /* Parse the vendordata and get the attribute */
    switch(mSubcmd)
    {
        case QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO:
        {
            struct nlattr *mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_MAX + 1];
            struct nlattr *modeInfo;
            int rem;

            nla_parse(mtb_vendor, QCA_WLAN_VENDOR_ATTR_MAC_MAX,
                      (struct nlattr *)mVendorData,
                      mDataLen, NULL);

            if (mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO])
            {
                for (modeInfo = (struct nlattr *) nla_data(mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO]),
                     rem = nla_len(mtb_vendor[QCA_WLAN_VENDOR_ATTR_MAC_INFO]);
                     nla_ok(modeInfo, rem);modeInfo = nla_next(modeInfo, &(rem))) {

                     struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAX+ 1];
                     nla_parse(tb2, QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAX,
                                (struct nlattr *) nla_data(modeInfo), nla_len(modeInfo), NULL);
                     if (!tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID])
                     {
                        ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID"
                               " not found", __FUNCTION__);
                        ret = WIFI_ERROR_INVALID_ARGS;
                        goto cleanup;
                     }
                     mode_info.wlan_mac_id = nla_get_u32(tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_MAC_ID]);
                     ALOGV("mac_id[%d]: %d ", num_of_mac, mode_info.wlan_mac_id);

                     if (!tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND])
                     {
                         ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND"
                               " NOT FOUND", __FUNCTION__);
                         ret = WIFI_ERROR_INVALID_ARGS;
                         goto cleanup;
                     }
                     mode_info.mac_band = (wlan_mac_band) nla_get_u32(tb2[QCA_WLAN_VENDOR_ATTR_MAC_INFO_BAND]);
                     ALOGV("mac_band[%d]: %d ", num_of_mac, mode_info.mac_band);

                     if (tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO])
                     {
                       int num_of_iface = 0;
                       struct nlattr *tb_iface;
                       int rem_info;

                       for (tb_iface = (struct nlattr *) nla_data(tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO]),
                            rem_info = nla_len(tb2[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO]);
                            nla_ok(tb_iface, rem_info);tb_iface = nla_next(tb_iface, &(rem_info))) {

                            struct nlattr *tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_MAX+ 1];
                            wifi_iface_info miface_info;

                            nla_parse(tb3, QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_MAX,
                                      (struct nlattr *) nla_data(tb_iface), nla_len(tb_iface), NULL);

                            if (!tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX])
                            {
                                ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX"
                                      " NOT FOUND", __FUNCTION__);
                                ret = WIFI_ERROR_INVALID_ARGS;
                                goto cleanup;
                            }
                            if (if_indextoname(nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX]),
                                               miface_info.iface_name) == NULL)
                            {
                                ALOGE("%s: Failed to convert %d IFINDEX to IFNAME", __FUNCTION__,
                                      nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_IFINDEX]));
                            }
                            ALOGV("ifname[%d]: %s ", num_of_iface, miface_info.iface_name);

                            if (!tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ])
                            {
                                ALOGE("%s: QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ"
                                      " NOT FOUND", __FUNCTION__);
                                ret = WIFI_ERROR_INVALID_ARGS;
                                goto cleanup;
                            }
                            miface_info.channel = nla_get_u32(tb3[QCA_WLAN_VENDOR_ATTR_MAC_IFACE_INFO_FREQ]);
                            ALOGV("channel[%d]: %d ", num_of_iface, miface_info.channel);

                            if (!num_of_iface)
                               mode_info.iface_info = (wifi_iface_info *)
                                         malloc(sizeof(wifi_iface_info));
                            else
                               mode_info.iface_info = (wifi_iface_info *)
                                         realloc(mode_info.iface_info, (num_of_iface + 1) * sizeof(wifi_iface_info));

                            if (mode_info.iface_info != NULL) {
                                memcpy(&mode_info.iface_info[num_of_iface], &miface_info, sizeof(wifi_iface_info));
                                num_of_iface++;
                                mode_info.num_iface = num_of_iface;
                            }
                       }
                    }
                    if (!num_of_mac)
                       mwifi_iface_mac_info = (wifi_mac_info *)
                          malloc(sizeof(wifi_mac_info));
                    else
                       mwifi_iface_mac_info = (wifi_mac_info *)
                          realloc(mwifi_iface_mac_info, (num_of_mac + 1) * (sizeof(wifi_mac_info)));

                    if (mwifi_iface_mac_info != NULL) {
                        memcpy(&mwifi_iface_mac_info[num_of_mac], &mode_info, sizeof(wifi_mac_info));
                        num_of_mac++;
                    }
                }
            }

            if (mHandler.on_radio_mode_change && num_of_mac) {
                (*mHandler.on_radio_mode_change)(mreqId, num_of_mac, mwifi_iface_mac_info);
            }
            else {
                  ALOGE("No Callback registered: on radio mode change");
                  ret = WIFI_ERROR_UNKNOWN;
                  goto cleanup;
            }
            ret = WIFI_SUCCESS;
        }
        break;

        default:
            /* Error case should not happen print log */
            ALOGE("%s: Wrong subcmd received %d", __FUNCTION__, mSubcmd);
    }

cleanup:
    if (mode_info.iface_info != NULL) {
       free(mode_info.iface_info);
       mode_info.iface_info = NULL;
    }
    if (mwifi_iface_mac_info != NULL) {
       free(mwifi_iface_mac_info);
       mwifi_iface_mac_info = NULL;
    }

    return ret;
}

wifi_error wifi_set_radio_mode_change_handler(wifi_request_id id,
                                      wifi_interface_handle iface,
                                      wifi_radio_mode_change_handler eh)
{
    wifi_error ret;
    WifiVendorCommand *vCommand = NULL;
    wifi_handle wifiHandle = getWifiHandle(iface);
    RADIOModeCommand *radiomodeCommand;

    ret = initialize_vendor_cmd(iface, id,
                                QCA_NL80211_VENDOR_SUBCMD_WLAN_MAC_INFO,
                                &vCommand);
    if (ret != WIFI_SUCCESS) {
        ALOGE("%s: Initialization failed", __FUNCTION__);
        return ret;
    }

    radiomodeCommand = RADIOModeCommand::instance(wifiHandle, id);
    if (radiomodeCommand == NULL) {
        ALOGE("%s: Error RadioModeCommand NULL", __FUNCTION__);
        ret = WIFI_ERROR_OUT_OF_MEMORY;
        goto cleanup;
    }
    radiomodeCommand->setCallbackHandler(eh);
    radiomodeCommand->setReqId(id);

cleanup:
    delete vCommand;
    return mapKernelErrortoWifiHalError(ret);
}
