/*
 * hidl interface for wpa_supplicant daemon
 * Copyright (c) 2004-2016, Jouni Malinen <j@w1.fi>
 * Copyright (c) 2004-2016, Roshan Pius <rpius@google.com>
 *
 * This software may be distributed under the terms of the BSD license.
 * See README for more details.
 */

#include "hidl_manager.h"
#include "hidl_return_util.h"
#include "p2p_network.h"

extern "C" {
#include "config_ssid.h"
}

namespace android {
namespace hardware {
namespace wifi {
namespace supplicant {
namespace V1_0 {
namespace implementation {
using hidl_return_util::validateAndCall;

P2pNetwork::P2pNetwork(
    struct wpa_global *wpa_global, const char ifname[], int network_id)
    : wpa_global_(wpa_global),
      ifname_(ifname),
      network_id_(network_id),
      is_valid_(true)
{
}

void P2pNetwork::invalidate() { is_valid_ = false; }
bool P2pNetwork::isValid()
{
	return (is_valid_ && (retrieveNetworkPtr() != nullptr));
}

Return<void> P2pNetwork::getId(getId_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getIdInternal, _hidl_cb);
}

Return<void> P2pNetwork::getInterfaceName(getInterfaceName_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getInterfaceNameInternal, _hidl_cb);
}

Return<void> P2pNetwork::getType(getType_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getTypeInternal, _hidl_cb);
}

Return<void> P2pNetwork::registerCallback(
    const sp<ISupplicantP2pNetworkCallback> &callback,
    registerCallback_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::registerCallbackInternal, _hidl_cb, callback);
}

Return<void> P2pNetwork::getSsid(getSsid_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getSsidInternal, _hidl_cb);
}

Return<void> P2pNetwork::getBssid(getBssid_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getBssidInternal, _hidl_cb);
}

Return<void> P2pNetwork::isCurrent(isCurrent_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::isCurrentInternal, _hidl_cb);
}

Return<void> P2pNetwork::isPersistent(isPersistent_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::isPersistentInternal, _hidl_cb);
}

Return<void> P2pNetwork::isGo(isGo_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::isGoInternal, _hidl_cb);
}

Return<void> P2pNetwork::setClientList(
    const hidl_vec<hidl_array<uint8_t, 6>> &clients, setClientList_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::setClientListInternal, _hidl_cb, clients);
}

Return<void> P2pNetwork::getClientList(getClientList_cb _hidl_cb)
{
	return validateAndCall(
	    this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
	    &P2pNetwork::getClientListInternal, _hidl_cb);
}

std::pair<SupplicantStatus, uint32_t> P2pNetwork::getIdInternal()
{
	return {{SupplicantStatusCode::SUCCESS, ""}, network_id_};
}

std::pair<SupplicantStatus, std::string> P2pNetwork::getInterfaceNameInternal()
{
	return {{SupplicantStatusCode::SUCCESS, ""}, ifname_};
}

std::pair<SupplicantStatus, IfaceType> P2pNetwork::getTypeInternal()
{
	return {{SupplicantStatusCode::SUCCESS, ""}, IfaceType::P2P};
}

SupplicantStatus P2pNetwork::registerCallbackInternal(
    const sp<ISupplicantP2pNetworkCallback> &callback)
{
	HidlManager *hidl_manager = HidlManager::getInstance();
	if (!hidl_manager ||
	    hidl_manager->addP2pNetworkCallbackHidlObject(
		ifname_, network_id_, callback)) {
		return {SupplicantStatusCode::FAILURE_UNKNOWN, ""};
	}
	return {SupplicantStatusCode::SUCCESS, ""};
}

std::pair<SupplicantStatus, std::vector<uint8_t>> P2pNetwork::getSsidInternal()
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	return {{SupplicantStatusCode::SUCCESS, ""},
		{wpa_ssid->ssid, wpa_ssid->ssid + wpa_ssid->ssid_len}};
}

std::pair<SupplicantStatus, std::array<uint8_t, 6>>
P2pNetwork::getBssidInternal()
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	std::array<uint8_t, 6> bssid{};
	if (wpa_ssid->bssid_set) {
		os_memcpy(bssid.data(), wpa_ssid->bssid, ETH_ALEN);
	}
	return {{SupplicantStatusCode::SUCCESS, ""}, bssid};
}

std::pair<SupplicantStatus, bool> P2pNetwork::isCurrentInternal()
{
	struct wpa_supplicant *wpa_s = retrieveIfacePtr();
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	return {{SupplicantStatusCode::SUCCESS, ""},
		(wpa_s->current_ssid == wpa_ssid)};
}

std::pair<SupplicantStatus, bool> P2pNetwork::isPersistentInternal()
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	return {{SupplicantStatusCode::SUCCESS, ""}, (wpa_ssid->disabled == 2)};
}

std::pair<SupplicantStatus, bool> P2pNetwork::isGoInternal()
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	return {{SupplicantStatusCode::SUCCESS, ""},
		(wpa_ssid->mode == wpa_ssid::wpas_mode::WPAS_MODE_P2P_GO)};
}

SupplicantStatus P2pNetwork::setClientListInternal(
    const std::vector<hidl_array<uint8_t, 6>> &clients)
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	os_free(wpa_ssid->p2p_client_list);
	// Internal representation uses a generic MAC addr/mask storage format
	// (even though the mask is always 0xFF'ed for p2p_client_list). So, the
	// first 6 bytes holds the client MAC address and the next 6 bytes are
	// OxFF'ed.
	wpa_ssid->p2p_client_list =
	    (u8 *)os_malloc(ETH_ALEN * 2 * clients.size());
	if (!wpa_ssid->p2p_client_list) {
		return {SupplicantStatusCode::FAILURE_UNKNOWN, ""};
	}
	u8 *list = wpa_ssid->p2p_client_list;
	for (const auto &client : clients) {
		os_memcpy(list, client.data(), ETH_ALEN);
		list += ETH_ALEN;
		os_memset(list, 0xFF, ETH_ALEN);
		list += ETH_ALEN;
	}
	wpa_ssid->num_p2p_clients = clients.size();
	return {SupplicantStatusCode::SUCCESS, ""};
}

std::pair<SupplicantStatus, std::vector<hidl_array<uint8_t, 6>>>
P2pNetwork::getClientListInternal()
{
	struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
	if (!wpa_ssid->p2p_client_list) {
		return {{SupplicantStatusCode::FAILURE_UNKNOWN, ""}, {}};
	}
	std::vector<hidl_array<uint8_t, 6>> clients;
	u8 *list = wpa_ssid->p2p_client_list;
	for (size_t i = 0; i < wpa_ssid->num_p2p_clients; i++) {
		clients.emplace_back(list);
		list += 2 * ETH_ALEN;
	}
	return {{SupplicantStatusCode::SUCCESS, ""}, clients};
}

/**
 * Retrieve the underlying |wpa_ssid| struct pointer for
 * this network.
 * If the underlying network is removed or the interface
 * this network belong to is removed, all RPC method calls
 * on this object will return failure.
 */
struct wpa_ssid *P2pNetwork::retrieveNetworkPtr()
{
	wpa_supplicant *wpa_s = retrieveIfacePtr();
	if (!wpa_s)
		return nullptr;
	return wpa_config_get_network(wpa_s->conf, network_id_);
}

/**
 * Retrieve the underlying |wpa_supplicant| struct
 * pointer for this network.
 */
struct wpa_supplicant *P2pNetwork::retrieveIfacePtr()
{
	return wpa_supplicant_get_iface(
	    (struct wpa_global *)wpa_global_, ifname_.c_str());
}
}  // namespace implementation
}  // namespace V1_0
}  // namespace wifi
}  // namespace supplicant
}  // namespace hardware
}  // namespace android