/*
 * Copyright (c) 2017, 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.
 */
#ifndef DBG
    #define DBG true
#endif /* DBG */
#define LOG_TAG "IPAHALService"

/* HIDL Includes */
#include <hwbinder/IPCThreadState.h>
#include <hwbinder/ProcessState.h>

/* Kernel Includes */
#include <linux/netfilter/nfnetlink_compat.h>

/* External Includes */
#include <log/log.h>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <vector>

/* Internal Includes */
#include "HAL.h"
#include "LocalLogBuffer.h"
#include "PrefixParser.h"

/* Namespace pollution avoidance */
using ::android::hardware::Void;
using ::android::status_t;

using RET = ::IOffloadManager::RET;
using Prefix = ::IOffloadManager::Prefix;

using ::std::map;
using ::std::vector;


/* ------------------------------ PUBLIC ------------------------------------ */
HAL* HAL::makeIPAHAL(int version, IOffloadManager* mgr) {
    android::hardware::ProcessState::initWithMmapSize((size_t)(2 * KERNEL_PAGE));

    if (DBG)
        ALOGI("makeIPAHAL(%d, %s)", version,
                (mgr != nullptr) ? "provided" : "null");
    if (nullptr == mgr) return NULL;
    else if (version != 1) return NULL;
    HAL* ret = new HAL(mgr);
    if (nullptr == ret) return NULL;
    configureRpcThreadpool(1, false);
    ret->registerAsSystemService("ipacm");
    return ret;
} /* makeIPAHAL */


/* ------------------------------ PRIVATE ----------------------------------- */
HAL::HAL(IOffloadManager* mgr) : mLogs("HAL Function Calls", 50) {
    mIPA = mgr;
    mCb.clear();
    mCbIpa = nullptr;
    mCbCt = nullptr;
} /* HAL */

void HAL::registerAsSystemService(const char* name __unused) {
    status_t ret = 0;

    ret = IOffloadControl::registerAsService();
    if (ret != 0) ALOGE("Failed to register IOffloadControl (%d) name(%s)", ret, name);
    else if (DBG) {
        ALOGI("Successfully registered IOffloadControl");
    }

    ret = IOffloadConfig::registerAsService();
    if (ret != 0) ALOGE("Failed to register IOffloadConfig (%d)", ret);
    else if (DBG) {
        ALOGI("Successfully registered IOffloadConfig");
    }
} /* registerAsSystemService */

void HAL::doLogcatDump() {
    ALOGD("mHandles");
    ALOGD("========");
    /* @TODO This will segfault if they aren't initialized and I don't currently
     * care to check for initialization in a function that isn't used anyways
     * ALOGD("fd1->%d", mHandle1->data[0]);
     * ALOGD("fd2->%d", mHandle2->data[0]);
     */
    ALOGD("========");
} /* doLogcatDump */

HAL::BoolResult HAL::makeInputCheckFailure(string customErr) {
    BoolResult ret;
    ret.success = false;
    ret.errMsg = "Failed Input Checks: " + customErr;
    return ret;
} /* makeInputCheckFailure */

HAL::BoolResult HAL::ipaResultToBoolResult(RET in) {
    BoolResult ret;
    ret.success = (in >= RET::SUCCESS);
    switch (in) {
        case RET::FAIL_TOO_MANY_PREFIXES:
            ret.errMsg = "Too Many Prefixes Provided";
            break;
        case RET::FAIL_UNSUPPORTED:
            ret.errMsg = "Unsupported by Hardware";
            break;
        case RET::FAIL_INPUT_CHECK:
            ret.errMsg = "Failed Input Checks";
            break;
        case RET::FAIL_HARDWARE:
            ret.errMsg = "Hardware did not accept";
            break;
        case RET::FAIL_TRY_AGAIN:
            ret.errMsg = "Try Again";
            break;
        case RET::SUCCESS:
            ret.errMsg = "Successful";
            break;
        case RET::SUCCESS_DUPLICATE_CONFIG:
            ret.errMsg = "Successful: Was a duplicate configuration";
            break;
        case RET::SUCCESS_NO_OP:
            ret.errMsg = "Successful: No action needed";
            break;
        case RET::SUCCESS_OPTIMIZED:
            ret.errMsg = "Successful: Performed optimized version of action";
            break;
        default:
            ret.errMsg = "Unknown Error";
            break;
    }
    return ret;
} /* ipaResultToBoolResult */

/* This will likely always result in doubling the number of loops the execution
 * goes through.  Obviously that is suboptimal.  But if we first translate
 * away from all HIDL specific code, then we can avoid sprinkling HIDL
 * dependencies everywhere.
 */
vector<string> HAL::convertHidlStrToStdStr(hidl_vec<hidl_string> in) {
    vector<string> ret;
    for (size_t i = 0; i < in.size(); i++) {
        string add = in[i];
        ret.push_back(add);
    }
    return ret;
} /* convertHidlStrToStdStr */

void HAL::registerEventListeners() {
    registerIpaCb();
    registerCtCb();
} /* registerEventListeners */

void HAL::registerIpaCb() {
    if (isInitialized() && mCbIpa == nullptr) {
        LocalLogBuffer::FunctionLog fl("registerEventListener");
        mCbIpa = new IpaEventRelay(mCb);
        mIPA->registerEventListener(mCbIpa);
        mLogs.addLog(fl);
    } else {
        ALOGE("Failed to registerIpaCb (isInitialized()=%s, (mCbIpa == nullptr)=%s)",
                isInitialized() ? "true" : "false",
                (mCbIpa == nullptr) ? "true" : "false");
    }
} /* registerIpaCb */

void HAL::registerCtCb() {
    if (isInitialized() && mCbCt == nullptr) {
        LocalLogBuffer::FunctionLog fl("registerCtTimeoutUpdater");
        mCbCt = new CtUpdateAmbassador(mCb);
        mIPA->registerCtTimeoutUpdater(mCbCt);
        mLogs.addLog(fl);
    } else {
        ALOGE("Failed to registerCtCb (isInitialized()=%s, (mCbCt == nullptr)=%s)",
                isInitialized() ? "true" : "false",
                (mCbCt == nullptr) ? "true" : "false");
    }
} /* registerCtCb */

void HAL::unregisterEventListeners() {
    unregisterIpaCb();
    unregisterCtCb();
} /* unregisterEventListeners */

void HAL::unregisterIpaCb() {
    if (mCbIpa != nullptr) {
        LocalLogBuffer::FunctionLog fl("unregisterEventListener");
        mIPA->unregisterEventListener(mCbIpa);
        mCbIpa = nullptr;
        mLogs.addLog(fl);
    } else {
        ALOGE("Failed to unregisterIpaCb");
    }
} /* unregisterIpaCb */

void HAL::unregisterCtCb() {
    if (mCbCt != nullptr) {
        LocalLogBuffer::FunctionLog fl("unregisterCtTimeoutUpdater");
        mIPA->unregisterCtTimeoutUpdater(mCbCt);
        mCbCt = nullptr;
        mLogs.addLog(fl);
    } else {
        ALOGE("Failed to unregisterCtCb");
    }
} /* unregisterCtCb */

void HAL::clearHandles() {
    ALOGI("clearHandles()");
    /* @TODO handle this more gracefully... also remove the log
     *
     * Things that would be nice, but I can't do:
     * [1] Destroy the object (it's on the stack)
     * [2] Call freeHandle (it's private)
     *
     * Things I can do but are hacks:
     * [1] Look at code and notice that setTo immediately calls freeHandle
     */
    mHandle1.setTo(nullptr, true);
    mHandle2.setTo(nullptr, true);
} /* clearHandles */

bool HAL::isInitialized() {
    return mCb.get() != nullptr;
} /* isInitialized */


/* -------------------------- IOffloadConfig -------------------------------- */
Return<void> HAL::setHandles(
    const hidl_handle &fd1,
    const hidl_handle &fd2,
    setHandles_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);

    if (fd1->numFds != 1) {
        BoolResult res = makeInputCheckFailure("Must provide exactly one FD per handle (fd1)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);

        mLogs.addLog(fl);
        return Void();
    }

    if (fd2->numFds != 1) {
        BoolResult res = makeInputCheckFailure("Must provide exactly one FD per handle (fd2)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);

        mLogs.addLog(fl);
        return Void();
    }

    /* The = operator calls freeHandle internally.  Therefore, if we were using
     * these handles previously, they're now gone... forever.  But hopefully the
     * new ones kick in very quickly.
     *
     * After freeing anything previously held, it will dup the FD so we have our
     * own copy.
     */
    mHandle1 = fd1;
    mHandle2 = fd2;

    /* Log the DUPed FD instead of the actual input FD so that we can lookup
     * this value in ls -l /proc/<pid>/<fd>
     */
    fl.addArg("fd1", mHandle1->data[0]);
    fl.addArg("fd2", mHandle2->data[0]);

    /* Try to provide each handle to IPACM.  Destroy our DUPed hidl_handles if
     * IPACM does not like either input.  This keeps us from leaking FDs or
     * providing half solutions.
     *
     * @TODO unfortunately, this does not cover duplicate configs where IPACM
     * thinks it is still holding on to a handle that we would have freed above.
     * It also probably means that IPACM would not know about the first FD being
     * freed if it rejects the second FD.
     */
    RET ipaReturn = mIPA->provideFd(mHandle1->data[0], UDP_SUBSCRIPTIONS);
    if (ipaReturn == RET::SUCCESS) {
        ipaReturn = mIPA->provideFd(mHandle2->data[0], TCP_SUBSCRIPTIONS);
    }

    if (ipaReturn != RET::SUCCESS) {
        ALOGE("IPACM failed to accept the FDs (%d %d)", mHandle1->data[0],
                mHandle2->data[0]);
        clearHandles();
    } else {
        /* @TODO remove logs after stabilization */
        ALOGI("IPACM was provided two FDs (%d, %d)", mHandle1->data[0],
                mHandle2->data[0]);
    }

    BoolResult res = ipaResultToBoolResult(ipaReturn);
    hidl_cb(res.success, res.errMsg);

    fl.setResult(res.success, res.errMsg);
    mLogs.addLog(fl);
    return Void();
} /* setHandles */


/* -------------------------- IOffloadControl ------------------------------- */
Return<void> HAL::initOffload
(
    const ::android::sp<ITetheringOffloadCallback>& cb,
    initOffload_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);

    if (isInitialized()) {
        BoolResult res = makeInputCheckFailure("Already initialized");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
        mLogs.addLog(fl);
    } else {
        /* Should storing the CB be a function? */
        mCb = cb;
        registerEventListeners();
        BoolResult res = ipaResultToBoolResult(RET::SUCCESS);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
        mLogs.addLog(fl);
    }

    return Void();
} /* initOffload */

Return<void> HAL::stopOffload
(
    stopOffload_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);

    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Was never initialized");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
        mLogs.addLog(fl);
    } else {
        /* Should removing the CB be a function? */
        mCb.clear();
        unregisterEventListeners();

        RET ipaReturn = mIPA->stopAllOffload();
        if (ipaReturn != RET::SUCCESS) {
            /* Ignore IPAs return value here and provide why stopAllOffload
             * failed.  However, if IPA failed to clearAllFds, then we can't
             * clear our map because they may still be in use.
             */
            RET ret = mIPA->clearAllFds();
            if (ret == RET::SUCCESS) {
                clearHandles();
            }
        } else {
            ipaReturn = mIPA->clearAllFds();
            /* If IPA fails, they may still be using these for some reason. */
            if (ipaReturn == RET::SUCCESS) {
                clearHandles();
            } else {
                ALOGE("IPACM failed to return success for clearAllFds so they will not be released...");
            }
        }

        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);

        fl.setResult(res.success, res.errMsg);
        mLogs.addLog(fl);
    }

    return Void();
} /* stopOffload */

Return<void> HAL::setLocalPrefixes
(
    const hidl_vec<hidl_string>& prefixes,
    setLocalPrefixes_cb hidl_cb
) {
    BoolResult res;
    PrefixParser parser;
    vector<string> prefixesStr = convertHidlStrToStdStr(prefixes);

    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("prefixes", prefixesStr);

    memset(&res,0,sizeof(BoolResult));

    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Not initialized");
    } else if(prefixesStr.size() < 1) {
        res = ipaResultToBoolResult(RET::FAIL_INPUT_CHECK);
    } else if (!parser.add(prefixesStr)) {
        res = makeInputCheckFailure(parser.getLastErrAsStr());
    } else {
        res = ipaResultToBoolResult(RET::SUCCESS);
    }

    hidl_cb(res.success, res.errMsg);
    fl.setResult(res.success, res.errMsg);
    mLogs.addLog(fl);
    return Void();
} /* setLocalPrefixes */

Return<void> HAL::getForwardedStats
(
    const hidl_string& upstream,
    getForwardedStats_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("upstream", upstream);

    OffloadStatistics ret;
    RET ipaReturn = mIPA->getStats(upstream.c_str(), true, ret);
    if (ipaReturn == RET::SUCCESS) {
        hidl_cb(ret.getTotalRxBytes(), ret.getTotalTxBytes());
        fl.setResult(ret.getTotalRxBytes(), ret.getTotalTxBytes());
    } else {
        /* @TODO Ensure the output is zeroed, but this is probably not enough to
         * tell Framework that an error has occurred.  If, for example, they had
         * not yet polled for statistics previously, they may incorrectly assume
         * that simply no statistics have transpired on hardware path.
         *
         * Maybe ITetheringOffloadCallback:onEvent(OFFLOAD_STOPPED_ERROR) is
         * enough to handle this case, time will tell.
         */
        hidl_cb(0, 0);
        fl.setResult(0, 0);
    }

    mLogs.addLog(fl);
    return Void();
} /* getForwardedStats */

Return<void> HAL::setDataLimit
(
    const hidl_string& upstream,
    uint64_t limit,
    setDataLimit_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("upstream", upstream);
    fl.addArg("limit", limit);

    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Not initialized (setDataLimit)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else {
        RET ipaReturn = mIPA->setQuota(upstream.c_str(), limit);
        if(ipaReturn == RET::FAIL_TRY_AGAIN) {
            ipaReturn = RET::SUCCESS;
        }
        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }

    mLogs.addLog(fl);
    return Void();
} /* setDataLimit */

Return<void> HAL::setUpstreamParameters
(
    const hidl_string& iface,
    const hidl_string& v4Addr,
    const hidl_string& v4Gw,
    const hidl_vec<hidl_string>& v6Gws,
    setUpstreamParameters_cb hidl_cb
) {
    vector<string> v6GwStrs = convertHidlStrToStdStr(v6Gws);

    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("iface", iface);
    fl.addArg("v4Addr", v4Addr);
    fl.addArg("v4Gw", v4Gw);
    fl.addArg("v6Gws", v6GwStrs);

    PrefixParser v4AddrParser;
    PrefixParser v4GwParser;
    PrefixParser v6GwParser;

    /* @TODO maybe we should enforce that these addresses and gateways are fully
     * qualified here.  But then, how do we allow them to be empty/null as well
     * while still preserving a sane API on PrefixParser?
     */
    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Not initialized (setUpstreamParameters)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else if (!v4AddrParser.addV4(v4Addr) && !v4Addr.empty()) {
        BoolResult res = makeInputCheckFailure(v4AddrParser.getLastErrAsStr());
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else if (!v4GwParser.addV4(v4Gw) && !v4Gw.empty()) {
        BoolResult res = makeInputCheckFailure(v4GwParser.getLastErrAsStr());
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else if (v6GwStrs.size() >= 1 && !v6GwParser.addV6(v6GwStrs)) {
        BoolResult res = makeInputCheckFailure(v6GwParser.getLastErrAsStr());
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else if (iface.size()>= 1) {
        RET ipaReturn = mIPA->setUpstream(
                iface.c_str(),
                v4GwParser.getFirstPrefix(),
                v6GwParser.getFirstPrefix());
        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else {
	/* send NULL iface string when upstream down */
        RET ipaReturn = mIPA->setUpstream(
                NULL,
                v4GwParser.getFirstPrefix(IP_FAM::V4),
                v6GwParser.getFirstPrefix(IP_FAM::V6));
        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }

    mLogs.addLog(fl);
    return Void();
} /* setUpstreamParameters */

Return<void> HAL::addDownstream
(
    const hidl_string& iface,
    const hidl_string& prefix,
    addDownstream_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("iface", iface);
    fl.addArg("prefix", prefix);

    PrefixParser prefixParser;

    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Not initialized (setUpstreamParameters)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }
    else if (!prefixParser.add(prefix)) {
        BoolResult res = makeInputCheckFailure(prefixParser.getLastErrAsStr());
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else {
        RET ipaReturn = mIPA->addDownstream(
                iface.c_str(),
                prefixParser.getFirstPrefix());
        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }

    mLogs.addLog(fl);
    return Void();
} /* addDownstream */

Return<void> HAL::removeDownstream
(
    const hidl_string& iface,
    const hidl_string& prefix,
    removeDownstream_cb hidl_cb
) {
    LocalLogBuffer::FunctionLog fl(__func__);
    fl.addArg("iface", iface);
    fl.addArg("prefix", prefix);

    PrefixParser prefixParser;

    if (!isInitialized()) {
        BoolResult res = makeInputCheckFailure("Not initialized (setUpstreamParameters)");
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }
    else if (!prefixParser.add(prefix)) {
        BoolResult res = makeInputCheckFailure(prefixParser.getLastErrAsStr());
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    } else {
        RET ipaReturn = mIPA->removeDownstream(
                iface.c_str(),
                prefixParser.getFirstPrefix());
        BoolResult res = ipaResultToBoolResult(ipaReturn);
        hidl_cb(res.success, res.errMsg);
        fl.setResult(res.success, res.errMsg);
    }

    mLogs.addLog(fl);
    return Void();
} /* removeDownstream */
