/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define TLOG_TAG "ipc_ql_tipc"

#include <lib/tidl/tidl.h>
#include <trusty/sysdeps.h>
#include <trusty/trusty_ipc.h>
#include <trusty/trusty_log.h>
#include <trusty/util.h>

#define TIDL_CHANNEL_MAX 8

static struct trusty_ipc_dev* tidl_dev = (struct trusty_ipc_dev*)NULL;
static struct trusty_ipc_chan tidl_channels[TIDL_CHANNEL_MAX];

extern "C" __attribute__((weak)) int __cxa_atexit(void (*destructor)(void*),
                                                  void* arg,
                                                  void* dso) {
    (void)arg;
    (void)dso;
    return 0;
}

__attribute__((weak)) void operator delete(void* ptr) noexcept {
    if (ptr)
        trusty_free(ptr);
}
namespace tidl {

int handle::reset() {
    if (mFd == INVALID_IPC_HANDLE) {
        return NO_ERROR;
    }
    int rc = tidl_chan_close(mFd);
    mFd = INVALID_IPC_HANDLE;
    return rc;
}

int handle::reset(Handle fd) {
    if (mFd == INVALID_IPC_HANDLE) {
        mFd = fd;
        return NO_ERROR;
    }
    int rc = tidl_chan_close(mFd);
    if (rc != NO_ERROR) {
        mFd = INVALID_IPC_HANDLE;
        return rc;
    }
    mFd = fd;
    return rc;
}
Handle handle::get() {
    return mFd;
}
namespace ipc {

static __inline__ struct trusty_ipc_chan* tidl_get_channel(handle_t fd) {
    TLOGD("looking for channel %u\n", fd);
    for (int i = 0; i < TIDL_CHANNEL_MAX; i++) {
        TLOGD("channel[%d]=%u\n", i, tidl_channels[i].handle);
        if (fd == tidl_channels[i].handle) {
            return &tidl_channels[i];
        }
    }
    if (fd == INVALID_IPC_HANDLE) {
        TLOGE("tidl_get_channel failed, no available channel\n");
    } else {
        TLOGE("tidl_get_channel failed, channel not found!\n");
    }
    return (struct trusty_ipc_chan*)NULL;
}

static __inline__ void binder_init_channels() {
    TLOGD("init channels\n");
    for (int i = 0; i < TIDL_CHANNEL_MAX; i++) {
        trusty_ipc_chan_init(&tidl_channels[i], tidl_dev);
    }
}

static __inline__ void binder_check_channels() {
    TLOGD("check that all channels have been closed\n");
    for (int i = 0; i < TIDL_CHANNEL_MAX; i++) {
        assert(tidl_channels[i].handle == INVALID_IPC_HANDLE);
    }
}

/* TODO: create a TIDL sysdeps mapping to allow non lk-based bootloaders
 * to map errors into their own system. For now tidl returns lk errors.
 */
static int to_tidl_err(int err) {
    switch (err) {
    case TRUSTY_ERR_NONE:
        return NO_ERROR;

    case TRUSTY_ERR_GENERIC:
        return ERR_GENERIC;

    case TRUSTY_ERR_NOT_SUPPORTED:
        return ERR_NOT_SUPPORTED;

    case TRUSTY_ERR_NO_MEMORY:
        return ERR_NO_MEMORY;

    case TRUSTY_ERR_INVALID_ARGS:
        return ERR_INVALID_ARGS;

    case TRUSTY_ERR_SECOS_ERR:
        return ERR_GENERIC;

    case TRUSTY_ERR_MSG_TOO_BIG:
        return ERR_BAD_LEN;

    case TRUSTY_ERR_NO_MSG:
        return ERR_GENERIC;

    case TRUSTY_ERR_CHANNEL_CLOSED:
        return ERR_CHANNEL_CLOSED;

    case TRUSTY_ERR_SEND_BLOCKED:
        return ERR_GENERIC;

    default:
        return err;
    }
}

extern "C" int tidl_init(struct trusty_ipc_dev* dev) {
    assert(dev);
    if (tidl_dev) {
        assert(tidl_dev == dev);
        return NO_ERROR;
    }
    tidl_dev = dev;
    binder_init_channels();
    return NO_ERROR;
}

/*
 * Shutdown binder clients
 *
 */
extern "C" void tidl_shutdown(void) {
    tidl_dev = (struct trusty_ipc_dev*)NULL;
    binder_check_channels();
}

extern "C" int tidl_chan_close(handle_t fd) {
    struct trusty_ipc_chan* chan = tidl_get_channel(fd);
    assert(chan);
    TLOGD("tidl_chan_close fd (%u)\n", fd);
    int rc = trusty_ipc_close(chan);
    if (rc < 0) {
        TLOGE("trusty_ipc_close error (%d)\n", rc);
    }
    return to_tidl_err(rc);
}

int connect(const char* path, uint32_t flags, tidl::handle& out_fd) {
    (void)flags;
    if (!tidl_dev) {
        TLOGE("trusty::aidl::ipc::connect ERROR: device not initialised.\n");
        return ERR_GENERIC;
    }
    struct trusty_ipc_chan* chan = tidl_get_channel(INVALID_IPC_HANDLE);
    if (!chan) {
        return ERR_GENERIC;
    }
    assert(chan->handle == INVALID_IPC_HANDLE);

    TLOGD("Connecting to %s\n", path);
    int rc = trusty_ipc_connect(chan, path, true);
    if (rc < 0) {
        TLOGE("failed (%d) to connect to '%s'\n", rc, path);
        return to_tidl_err(rc);
    }

    TLOGD("channel (%u)\n", chan->handle);
    out_fd.reset(chan->handle);
    return NO_ERROR;
}

static int send_iovs(handle_t fd,
                     const struct trusty_ipc_iovec* iovs,
                     size_t iovs_cnt,
                     handle_t* handles,
                     uint32_t num_handles) {
    assert(num_handles == 0);
    struct trusty_ipc_chan* chan = tidl_get_channel(fd);
    assert(chan);
    trusty_assert(chan->dev);
    trusty_assert(chan->handle);
    int rc = trusty_ipc_send(chan, iovs, iovs_cnt, true);
    return to_tidl_err(rc);
}

int send(handle_t fd,
         const void* hdr,
         size_t hdr_len,
         handle_t* handles,
         uint32_t num_handles) {
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = (void*)hdr,
                    .len = hdr_len,
            },
    };
    return send_iovs(fd, iovs, 1, handles, num_handles);
}

static int recv_iovs(handle_t fd,
                     struct trusty_ipc_iovec* iovs,
                     size_t iovs_cnt,
                     handle_t* handles,
                     uint32_t num_handles) {
    assert(num_handles == 0);
    struct trusty_ipc_chan* chan = tidl_get_channel(fd);
    assert(chan);
    trusty_assert(chan->dev);
    trusty_assert(chan->handle);
    int rc = trusty_ipc_recv(chan, iovs, iovs_cnt, true);
    return to_tidl_err(rc);
}

int recv(handle_t fd,
         size_t min_sz,
         void* buf,
         size_t buf_sz,
         handle_t* handles,
         uint32_t num_handles) {
    (void)min_sz;
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = (void*)buf,
                    .len = buf_sz,
            },
    };
    return recv_iovs(fd, iovs, 1, handles, num_handles);
}

int send(handle_t fd,
         const void* hdr,
         size_t hdr_len,
         const void* payload1,
         size_t payload1_len,
         handle_t* handles,
         uint32_t num_handles) {
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = (void*)hdr,
                    .len = hdr_len,
            },
            {
                    .base = (void*)payload1,
                    .len = payload1_len,
            },
    };
    return send_iovs(fd, iovs, 2, handles, num_handles);
}

int recv(handle_t fd,
         size_t min_sz,
         void* buf1,
         size_t buf1_sz,
         void* buf2,
         size_t buf2_sz,
         handle_t* handles,
         uint32_t num_handles) {
    assert(num_handles == 0);
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = buf1,
                    .len = buf1_sz,
            },
            {
                    .base = buf2,
                    .len = buf2_sz,
            },
    };
    return recv_iovs(fd, iovs, 2, handles, num_handles);
}

int send(handle_t fd,
         const void* hdr,
         size_t hdr_len,
         const void* payload1,
         size_t payload1_len,
         const void* payload2,
         size_t payload2_len,
         handle_t* handles,
         uint32_t num_handles) {
    assert(num_handles == 0);
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = (void*)hdr,
                    .len = hdr_len,
            },
            {
                    .base = (void*)payload1,
                    .len = payload1_len,
            },
            {
                    .base = (void*)payload2,
                    .len = payload2_len,
            },
    };
    return send_iovs(fd, iovs, 3, handles, num_handles);
}

int recv(handle_t fd,
         size_t min_sz,
         void* buf1,
         size_t buf1_sz,
         void* buf2,
         size_t buf2_sz,
         void* buf3,
         size_t buf3_sz,
         handle_t* handles,
         uint32_t num_handles) {
    assert(num_handles == 0);
    struct trusty_ipc_iovec iovs[] = {
            {
                    .base = buf1,
                    .len = buf1_sz,
            },
            {
                    .base = buf2,
                    .len = buf2_sz,
            },
            {
                    .base = buf3,
                    .len = buf3_sz,
            },
    };
    return recv_iovs(fd, iovs, 3, handles, num_handles);
}

int wait_for_msg(handle_t chan) {
    (void)chan;
    return NO_ERROR;
}

}  // namespace ipc
}  // namespace tidl