/* Copyright (c) 2019 - 2020 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.
 *
 */
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>

#ifdef USE_QSOCKET
#include <sys/ioctl.h>
#include <poll.h>
#include <qsocket.h>
#include <qsocket_ipcr.h>
#else
#include <endian.h>
#include <linux/qrtr.h>
#endif

#include <log_util.h>
#include <LocIpc.h>

namespace loc_util {

#ifdef LOG_TAG
#undef LOG_TAG
#endif
#define LOG_TAG "LocSvc_Qrtr"

class ServiceInfo {
    const int32_t mServiceId;
    const int32_t mInstanceId;
    const string mName;
public:
    inline ServiceInfo(int32_t service, int32_t instance) :
            mServiceId(service), mInstanceId(instance),
            mName(to_string(service) + ":" + to_string(instance)) {}
    inline const char* getName() const { return mName.data(); }
    inline int32_t getServiceId() const { return mServiceId; }
    inline int32_t getInstanceId() const { return mInstanceId; }
};

#ifdef USE_QSOCKET

class LocIpcQsockSender : public LocIpcSender {
protected:
    shared_ptr<Sock> mSock;
    const ServiceInfo mServiceInfo;
    qsockaddr_ipcr mAddr;
    mutable bool mLookupPending;
    inline virtual bool isOperable() const override {
        return mSock != nullptr && mSock->mSid != -1;
    }
    inline void serviceLookup() const {
        if (mLookupPending && mSock->isValid()) {
            ipcr_name_t ipcrName = {
                .service = (uint32_t)mServiceInfo.getServiceId(),
                .instance = (uint32_t)mServiceInfo.getInstanceId()
            };
            uint32_t numEntries = 1;
            int rc = ipcr_find_name(mSock->mSid, &ipcrName,
                    (struct qsockaddr_ipcr*)&mAddr, NULL, &numEntries, 0);
            LOC_LOGd("serviceLookup, rc = %d, numEntries = %d\n", rc, numEntries);
            if (rc < 0 || 1 != numEntries) {
                mSock->close();
            }
            mLookupPending = false;
        }
    }
    inline virtual ssize_t send(const uint8_t data[], uint32_t length,
                                int32_t /* msgId */) const override {
        serviceLookup();
        return mSock->send(data, length, 0, (struct sockaddr*)&mAddr,
                           sizeof(mAddr));
    }
public:
    inline LocIpcQsockSender(const qsockaddr_ipcr& destAddr) :
            LocIpcSender(),
            mSock(make_shared<Sock>(::socket(AF_IPC_ROUTER, SOCK_DGRAM, 0))),
            mServiceInfo(0, 0), mAddr(destAddr), mLookupPending(false) {
    }
    inline LocIpcQsockSender(int service, int instance) :
            LocIpcSender(),
            mSock(make_shared<Sock>(::socket(AF_IPC_ROUTER, SOCK_DGRAM, 0))),
            mServiceInfo(service, instance), mAddr({}), mLookupPending(true) {
    }

    unique_ptr<LocIpcRecver> getRecver(const shared_ptr<ILocIpcListener>& listener) override {
        return make_unique<SockRecver>(listener, *this, mSock);
    }
};

class LocIpcQsockRecver : public LocIpcQsockSender, public LocIpcRecver {
protected:
    inline virtual ssize_t recv() const override {
        socklen_t size = sizeof(mAddr);
        return mSock->recv(*this, mDataCb, 0, (struct sockaddr*)&mAddr, &size);
    }
public:
    inline LocIpcQsockRecver(const shared_ptr<ILocIpcListener>& listener,
                             int service, int instance) :
            LocIpcQsockSender(service, instance),
            LocIpcRecver(listener, *this) {
        qsockaddr_ipcr addr = {
            AF_IPC_ROUTER,
            {
                IPCR_ADDR_NAME,
                {
                    (uint32_t)service,
                    (uint32_t)instance
                }
            },
            0
        };
        if (mSock->isValid() &&
            ::bind(mSock->mSid, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            LOC_LOGe("bind socket error. sock fd: %d,reason: %s",
                     mSock->mSid, strerror(errno));
            mSock->close();
        }
    }
    inline virtual unique_ptr<LocIpcSender> getLastSender() const override {
        return make_unique<LocIpcQsockSender>(mAddr);
    }
    inline virtual const char* getName() const override {
        return mServiceInfo.getName();
    }
    inline virtual void abort() const override {
        if (isSendable()) {
            serviceLookup();
            mSock->sendAbort(0, (struct sockaddr*)&mAddr, sizeof(mAddr));
        }
    }
};

shared_ptr<LocIpcSender> createLocIpcQrtrSender(int service, int instance) {
    return make_shared<LocIpcQsockSender>(service, instance);
}
unique_ptr<LocIpcRecver> createLocIpcQrtrRecver(const shared_ptr<ILocIpcListener>& listener,
                                                int service, int instance,
                                                const shared_ptr<LocIpcQrtrWatcher>& qrtrWatcher) {
    return make_unique<LocIpcQsockRecver>(listener, service, instance);
}

#else

#define SOCKET_TIMEOUT_SEC 2

static inline __le32 cpu_to_le32(uint32_t x) { return htole32(x); }
static inline uint32_t le32_to_cpu(__le32 x) { return le32toh(x); }

class LocIpcQrtrSender : public LocIpcSender {
protected:
    const ServiceInfo mServiceInfo;
    shared_ptr<Sock> mSock;
    mutable sockaddr_qrtr mAddr;
    mutable struct qrtr_ctrl_pkt mCtrlPkt;
    mutable bool mLookupPending;
    bool ctrlCmdAndResponse(enum qrtr_pkt_type cmd) const {
        if (mSock->isValid()) {
            int rc = 0;
            sockaddr_qrtr addr = mAddr;
            addr.sq_port = QRTR_PORT_CTRL;
            mCtrlPkt.cmd = cpu_to_le32(cmd);
            if ((rc = ::sendto(mSock->mSid, &mCtrlPkt, sizeof(mCtrlPkt), 0,
                               (const struct sockaddr *)&addr, sizeof(addr))) < 0) {
                LOC_LOGe("failed: sendto rc=%d reason=(%s)", rc, strerror(errno));
            } else if (QRTR_TYPE_NEW_LOOKUP == cmd) {
                int len;
                struct qrtr_ctrl_pkt pkt;
                while ((len = ::recv(mSock->mSid, &pkt, sizeof(pkt), 0)) > 0) {
                    if (len >= (decltype(len))sizeof(pkt) && 0 != pkt.server.service &&
                            cpu_to_le32(QRTR_TYPE_NEW_SERVER) == pkt.cmd) {
                        mAddr.sq_node = le32_to_cpu(pkt.server.node);
                        mAddr.sq_port = le32_to_cpu(pkt.server.port);
                        LOC_LOGv("server found pkt.cmd %d, service %d, node, %d, port %d",
                                 le32_to_cpu(pkt.cmd), le32_to_cpu(pkt.server.service),
                                 mAddr.sq_node, mAddr.sq_port);
                        break;
                    }
                }
            }
        }
        return mSock->isValid();
    }

    inline virtual bool isOperable() const override {
        return mSock != nullptr && mSock->isValid() &&
            (mAddr.sq_node != 0 || mAddr.sq_port != 0);
    }

    inline virtual ssize_t send(const uint8_t data[], uint32_t length,
                                int32_t /* msgId */) const override {
        if (mLookupPending) {
            mLookupPending = false;
            ctrlCmdAndResponse(QRTR_TYPE_NEW_LOOKUP);
        }
        return mSock->send(data, length, 0, (struct sockaddr*)&mAddr, sizeof(mAddr));
    }
public:
    inline LocIpcQrtrSender(const sockaddr_qrtr& destAddr) :
            LocIpcSender(), mServiceInfo(0, 0),
            mSock(make_shared<Sock>(::socket(AF_QIPCRTR, SOCK_DGRAM, 0))),
            mAddr(destAddr), mCtrlPkt({}), mLookupPending(false) {
    }
    inline LocIpcQrtrSender(int service, int instance) : LocIpcSender(),
            mServiceInfo(service, instance),
            mSock(make_shared<Sock>(::socket(AF_QIPCRTR, SOCK_DGRAM, 0))),
            mAddr({AF_QIPCRTR, 0, 0}),
            mCtrlPkt({}),
            mLookupPending(true) {
        // set timeout so if failed to send, call will return after 2 seconds timeout
        // otherwise, call may never return
        timeval timeout;
        timeout.tv_sec = SOCKET_TIMEOUT_SEC;
        timeout.tv_usec = 0;
        setsockopt(mSock->mSid, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

        socklen_t sl = sizeof(mAddr);
        int rc = 0;
        if ((rc = getsockname(mSock->mSid, (struct sockaddr*)&mAddr, &sl)) ||
            mAddr.sq_family != AF_QIPCRTR || sl != sizeof(mAddr)) {
            LOC_LOGe("failed: getsockname rc=%d reason=(%s), mAddr.sq_family=%d",
                     rc, strerror(errno), mAddr.sq_family);
            mSock->close();
        } else {
            mCtrlPkt.server.service = cpu_to_le32(service);
            mCtrlPkt.server.instance = cpu_to_le32(instance);
            mCtrlPkt.server.node = cpu_to_le32(mAddr.sq_node);
            mCtrlPkt.server.port = cpu_to_le32(mAddr.sq_port);
        }
    }

    unique_ptr<LocIpcRecver> getRecver(const shared_ptr<ILocIpcListener>& listener) override {
        return make_unique<SockRecver>(listener, *this, mSock);
    }

    inline virtual void copyDestAddrFrom(const LocIpcSender& otherSender) override {
        mLookupPending = false;
        mAddr = (reinterpret_cast<const LocIpcQrtrSender&>(otherSender)).mAddr;
    }
};

class LocIpcQrtrListener : public ILocIpcListener {
    const shared_ptr<ILocIpcListener> mRealListener;
    const shared_ptr<LocIpcQrtrWatcher> mQrtrWatcher;
    inline bool handleQrtrCtrlMsg(const char* data, uint32_t len) {
        const struct qrtr_ctrl_pkt* pkt = reinterpret_cast<const struct qrtr_ctrl_pkt*>(data);
        bool handledAsQrtrCtrlMsg = false;

        if (sizeof(*pkt) == len) {
            const uint32_t cmd = le32_to_cpu(pkt->cmd);
            if (cmd >= QRTR_TYPE_DATA && cmd <= QRTR_TYPE_DEL_LOOKUP) {
                handledAsQrtrCtrlMsg = true;
                int serviceId = le32_to_cpu(pkt->server.service);
                int instanceId = le32_to_cpu(pkt->server.instance);
                uint32_t serverNodeId = le32_to_cpu(pkt->server.node);
                uint32_t serverPort = le32_to_cpu(pkt->server.port);
                int clientNodeId = le32_to_cpu(pkt->client.node);
                int clientPort = le32_to_cpu(pkt->client.port);

                LOC_LOGv("qrtr control msg: cmd %d, server.service:instance-node:port %d:%d-%d:%d,"
                         " client node:port %d:%d", cmd, serviceId, instanceId, serverNodeId,
                         serverPort, clientNodeId, clientPort);

                if (nullptr != mQrtrWatcher) {
                    if ((QRTR_TYPE_NEW_SERVER == cmd || QRTR_TYPE_DEL_SERVER == cmd) &&
                        mQrtrWatcher->isServiceInWatch(serviceId)) {
                        LocIpcQrtrWatcher::ServiceStatus status = (QRTR_TYPE_NEW_SERVER == cmd) ?
                                LocIpcQrtrWatcher::ServiceStatus::UP :
                                LocIpcQrtrWatcher::ServiceStatus::DOWN;
                        sockaddr_qrtr addr = {AF_QIPCRTR, serverNodeId, serverPort};
                        const LocIpcQrtrSender sender(addr);
                        mQrtrWatcher->onServiceStatusChange(serviceId, instanceId, status, sender);
                    } else if ((QRTR_TYPE_DEL_CLIENT == cmd || QRTR_TYPE_BYE == cmd) &&
                               mQrtrWatcher->isClientInWatch(clientNodeId)) {
                        mQrtrWatcher->onClientGone(clientNodeId, clientPort);
                    }
                }
            }
        }
        return handledAsQrtrCtrlMsg;
    }
public:
    inline LocIpcQrtrListener(const shared_ptr<ILocIpcListener>& listener,
                              const shared_ptr<LocIpcQrtrWatcher>& qrtrWatcher) :
            mRealListener(listener), mQrtrWatcher(qrtrWatcher) {}
    inline virtual void onListenerReady() override {
        if (nullptr != mRealListener) mRealListener->onListenerReady();
    }
    inline virtual void onReceive(const char* data, uint32_t len,
                                  const LocIpcRecver* recver) override {
        if ((!handleQrtrCtrlMsg(data, len)) && (nullptr != mRealListener)) {
            mRealListener->onReceive(data, len, recver);
        }
    }
};

class LocIpcQrtrRecver : public LocIpcQrtrSender, public LocIpcRecver {
protected:
    inline virtual ssize_t recv() const override {
        socklen_t size = sizeof(mAddr);
        return mSock->recv(*this, mDataCb, 0, (struct sockaddr*)&mAddr, &size);
    }
public:
    inline LocIpcQrtrRecver(const shared_ptr<ILocIpcListener>& listener,
                            int service, int instance,
                            const shared_ptr<LocIpcQrtrWatcher>& qrtrWatcher) :
            LocIpcQrtrSender(service, instance),
            LocIpcRecver(make_shared<LocIpcQrtrListener>(listener, qrtrWatcher), *this) {
        ctrlCmdAndResponse(QRTR_TYPE_NEW_SERVER);
        if (nullptr != qrtrWatcher) {
            sockaddr_qrtr addr = mAddr;
            addr.sq_port = QRTR_PORT_CTRL;
            struct qrtr_ctrl_pkt pkt = {};
            pkt.cmd = cpu_to_le32(QRTR_TYPE_NEW_LOOKUP);
            int rc = 0;
            for (int serviceId : qrtrWatcher->getServicesToWatch()) {
                pkt.server.service = cpu_to_le32(serviceId);
                if ((rc = ::sendto(mSock->mSid, &pkt, sizeof(pkt), 0,
                                   (const struct sockaddr *)&addr, sizeof(addr))) < 0) {
                    LOC_LOGe("failed: sendto rc=%d reason=(%s)\n", rc, strerror(errno));
                }
            }
        }
    }
    inline ~LocIpcQrtrRecver() { ctrlCmdAndResponse(QRTR_TYPE_DEL_SERVER); }
    inline virtual unique_ptr<LocIpcSender> getLastSender() const override {
        return make_unique<LocIpcQrtrSender>(mAddr);
    }
    inline virtual const char* getName() const override {
        return mServiceInfo.getName();
    }
    inline virtual void abort() const override {
        if (isSendable()) {
            mSock->sendAbort(0, (struct sockaddr*)&mAddr, sizeof(mAddr));
        }
    }
};

shared_ptr<LocIpcSender> createLocIpcQrtrSender(int service, int instance) {
    return make_shared<LocIpcQrtrSender>(service, instance);
}
unique_ptr<LocIpcRecver> createLocIpcQrtrRecver(const shared_ptr<ILocIpcListener>& listener,
                                                int service, int instance,
                                                const shared_ptr<LocIpcQrtrWatcher>& qrtrWatcher) {
    return make_unique<LocIpcQrtrRecver>(listener, service, instance, qrtrWatcher);
}

#endif

}
