/*
 * Copyright (C) 2014 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.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#include <gtest/gtest.h>
#include <linux/binder.h>
#include <binder/IBinder.h>
#include <sys/mman.h>
#include <poll.h>

#define BINDER_DEV_NAME "/dev/binder"

testing::Environment* binder_env;

class BinderDriverInterfaceTestEnv : public ::testing::Environment {
        virtual void SetUp() {
            int ret;
            uint32_t max_threads = 0;

            m_binderFd = open(BINDER_DEV_NAME, O_RDWR | O_NONBLOCK | O_CLOEXEC);
            ASSERT_GE(m_binderFd, 0);
            m_buffer = mmap(NULL, 64*1024, PROT_READ, MAP_SHARED, m_binderFd, 0);
            ASSERT_NE(m_buffer, (void *)NULL);
            ret = ioctl(m_binderFd, BINDER_SET_MAX_THREADS, &max_threads);
            EXPECT_EQ(0, ret);
            EnterLooper();
        }
        virtual void TearDown() {
            close(m_binderFd);
        }
    private:
        int m_binderFd;
        void *m_buffer;
    public:
        int getBinderFd(void) {
            return m_binderFd;
        }
        void EnterLooper(void) {
            int ret;
            const uint32_t bc[] = {
                BC_ENTER_LOOPER,
            };
            struct binder_write_read bwr = binder_write_read();
            bwr.write_buffer = (uintptr_t)bc;
            bwr.write_size = sizeof(bc);
            ret = ioctl(m_binderFd, BINDER_WRITE_READ, &bwr);
            EXPECT_EQ(0, ret);
            if (ret < 0) {
                    EXPECT_EQ(0, errno);
            }
            EXPECT_EQ(sizeof(bc), bwr.write_consumed);
        }
};

class BinderDriverInterfaceTest : public ::testing::Test {
    public:
        virtual void SetUp() {
            m_binderFd = static_cast<BinderDriverInterfaceTestEnv *>(binder_env)->getBinderFd();
        }
        virtual void TearDown() {
        }
    protected:
        void binderTestIoctlRetErr2(int cmd, void *arg, int expect_ret, int expect_errno, int accept_errno) {
            int ret;

            ret = ioctl(m_binderFd, cmd, arg);
            EXPECT_EQ(expect_ret, ret);
            if (ret < 0) {
                if (errno != accept_errno)
                    EXPECT_EQ(expect_errno, errno);
            }
        }
        void binderTestIoctlErr2(int cmd, void *arg, int expect_errno, int accept_errno) {
            binderTestIoctlRetErr2(cmd, arg, -1, expect_errno, accept_errno);
        }
        void binderTestIoctlErr1(int cmd, void *arg, int expect_errno) {
            binderTestIoctlErr2(cmd, arg, expect_errno, expect_errno);
        }
        void binderTestIoctl(int cmd, void *arg) {
            binderTestIoctlRetErr2(cmd, arg, 0, 0, 0);
        }
        void binderTestIoctlUnimplemented(int cmd, void *arg) {
            int ret;

            ret = ioctl(m_binderFd, cmd, arg);
            if (ret < 0) {
                /* Not currently implmented. Allow ret == -1, errno == EINVAL */
                EXPECT_EQ(-1, ret);
                EXPECT_EQ(EINVAL, errno);
            }
        }
        void binderTestReadEmpty(void) {
            size_t i;
            uint32_t br[32];
            struct binder_write_read bwr = binder_write_read();
            SCOPED_TRACE("TestReadEmpty");
            bwr.read_buffer = (uintptr_t)br;
            bwr.read_size = sizeof(br);
            binderTestIoctlErr1(BINDER_WRITE_READ, &bwr, EAGAIN);
            EXPECT_EQ(0u, bwr.read_consumed);
            for (i = 0; i * sizeof(uint32_t) < bwr.read_consumed; i++) {
                SCOPED_TRACE(testing::Message() << "i = " << i);
                EXPECT_EQ(BR_NOOP, br[i]);
            }
        }
        void binderWaitForReadData(int timeout_ms) {
            int ret;
            pollfd pfd = pollfd();

            pfd.fd = m_binderFd;
            pfd.events = POLLIN;
            ret = poll(&pfd, 1, timeout_ms);
            EXPECT_EQ(1, ret);
        }
    private:
        int m_binderFd;
};

TEST_F(BinderDriverInterfaceTest, Version) {
    struct binder_version version;
    binderTestIoctl(BINDER_VERSION, &version);
    ASSERT_EQ(BINDER_CURRENT_PROTOCOL_VERSION, version.protocol_version);
}

TEST_F(BinderDriverInterfaceTest, WriteReadNull) {
    binderTestIoctlErr1(BINDER_WRITE_READ, NULL, EFAULT);
}

TEST_F(BinderDriverInterfaceTest, SetIdleTimeoutNull) {
    binderTestIoctlErr2(BINDER_SET_IDLE_TIMEOUT, NULL, EFAULT, EINVAL);
}

TEST_F(BinderDriverInterfaceTest, SetMaxThreadsNull) {
    binderTestIoctlErr2(BINDER_SET_MAX_THREADS, NULL, EFAULT, EINVAL); /* TODO: don't accept EINVAL */
}

TEST_F(BinderDriverInterfaceTest, SetIdlePriorityNull) {
    binderTestIoctlErr2(BINDER_SET_IDLE_PRIORITY, NULL, EFAULT, EINVAL);
}

TEST_F(BinderDriverInterfaceTest, VersionNull) {
    binderTestIoctlErr2(BINDER_VERSION, NULL, EFAULT, EINVAL); /* TODO: don't accept EINVAL */
}

TEST_F(BinderDriverInterfaceTest, SetIdleTimeoutNoTest) {
    int64_t idle_timeout = 100000;
    binderTestIoctlUnimplemented(BINDER_SET_IDLE_TIMEOUT, &idle_timeout);
}

TEST_F(BinderDriverInterfaceTest, SetMaxThreads) {
    uint32_t max_threads = 0;
    binderTestIoctl(BINDER_SET_MAX_THREADS, &max_threads);
}

TEST_F(BinderDriverInterfaceTest, SetIdlePriorityNoTest) {
    int idle_priority = 0;
    binderTestIoctlUnimplemented(BINDER_SET_IDLE_PRIORITY, &idle_priority);
}

TEST_F(BinderDriverInterfaceTest, SetContextMgrBusy) {
    int32_t dummy = 0;
    binderTestIoctlErr1(BINDER_SET_CONTEXT_MGR, &dummy, EBUSY);
}

TEST_F(BinderDriverInterfaceTest, ThreadExit) {
    int32_t dummy = 0;
    binderTestIoctl(BINDER_THREAD_EXIT, &dummy);
    static_cast<BinderDriverInterfaceTestEnv *>(binder_env)->EnterLooper();
}

TEST_F(BinderDriverInterfaceTest, WriteReadEmpty) {
    struct binder_write_read bwr = binder_write_read();
    binderTestIoctl(BINDER_WRITE_READ, &bwr);
}

TEST_F(BinderDriverInterfaceTest, Read) {
    binderTestReadEmpty();
}

TEST_F(BinderDriverInterfaceTest, IncRefsAcquireReleaseDecRefs) {
    const uint32_t bc[] = {
        BC_INCREFS,
        0,
        BC_ACQUIRE,
        0,
        BC_RELEASE,
        0,
        BC_DECREFS,
        0,
    };
    struct binder_write_read bwr = binder_write_read();
    bwr.write_buffer = (uintptr_t)bc;
    bwr.write_size = sizeof(bc);
    binderTestIoctl(BINDER_WRITE_READ, &bwr);
    EXPECT_EQ(sizeof(bc), bwr.write_consumed);
    binderTestReadEmpty();
}

TEST_F(BinderDriverInterfaceTest, Transaction) {
    binder_uintptr_t cookie = 1234;
    struct {
        uint32_t cmd1;
        struct binder_transaction_data arg1;
    } __attribute__((packed)) bc1 = {
        .cmd1 = BC_TRANSACTION,
        .arg1 = {
            .target = { 0 },
            .cookie = 0,
            .code = android::IBinder::PING_TRANSACTION,
            .flags = 0,
            .sender_pid = 0,
            .sender_euid = 0,
            .data_size = 0,
            .offsets_size = 0,
            .data = {0, 0},
        },
    };
    struct {
        uint32_t cmd0;
        uint32_t cmd1;
        uint32_t cmd2;
        binder_transaction_data arg2;
        uint32_t pad[16];
    } __attribute__((packed)) br;
    struct binder_write_read bwr = binder_write_read();

    bwr.write_buffer = (uintptr_t)&bc1;
    bwr.write_size = sizeof(bc1);
    bwr.read_buffer = (uintptr_t)&br;
    bwr.read_size = sizeof(br);

    {
        SCOPED_TRACE("1st WriteRead");
        binderTestIoctl(BINDER_WRITE_READ, &bwr);
    }
    EXPECT_EQ(sizeof(bc1), bwr.write_consumed);
    if (bwr.read_consumed < offsetof(typeof(br), pad)) {
        SCOPED_TRACE("2nd WriteRead");
        binderWaitForReadData(10000);
        binderTestIoctl(BINDER_WRITE_READ, &bwr);
    }
    EXPECT_EQ(offsetof(typeof(br), pad), bwr.read_consumed);
    if (bwr.read_consumed > offsetof(typeof(br), cmd0))
        EXPECT_EQ(BR_NOOP, br.cmd0);
    if (bwr.read_consumed > offsetof(typeof(br), cmd1))
        EXPECT_EQ(BR_TRANSACTION_COMPLETE, br.cmd1);
    if (bwr.read_consumed > offsetof(typeof(br), cmd2))
        EXPECT_EQ(BR_REPLY, br.cmd2);
    if (bwr.read_consumed >= offsetof(typeof(br), pad)) {
        EXPECT_EQ(0u, br.arg2.target.ptr);
        EXPECT_EQ(0u, br.arg2.cookie);
        EXPECT_EQ(0u, br.arg2.code);
        EXPECT_EQ(0u, br.arg2.flags);
        EXPECT_EQ(0u, br.arg2.data_size);
        EXPECT_EQ(0u, br.arg2.offsets_size);

        SCOPED_TRACE("3rd WriteRead");

        binderTestReadEmpty();

        struct {
            uint32_t cmd1;
            binder_uintptr_t arg1;
        } __attribute__((packed)) bc2 = {
            .cmd1 = BC_FREE_BUFFER,
            .arg1 = br.arg2.data.ptr.buffer,
        };

        bwr.write_buffer = (uintptr_t)&bc2;
        bwr.write_size = sizeof(bc2);
        bwr.write_consumed = 0;
        bwr.read_size = 0;

        binderTestIoctl(BINDER_WRITE_READ, &bwr);
        EXPECT_EQ(sizeof(bc2), bwr.write_consumed);
    }
    binderTestReadEmpty();
}

TEST_F(BinderDriverInterfaceTest, RequestDeathNotification) {
    binder_uintptr_t cookie = 1234;
    struct {
        uint32_t cmd0;
        uint32_t arg0;
        uint32_t cmd1;
        struct binder_handle_cookie arg1;
        uint32_t cmd2;
        struct binder_handle_cookie arg2;
        uint32_t cmd3;
        uint32_t arg3;
    } __attribute__((packed)) bc = {
        .cmd0 = BC_INCREFS,
        .arg0 = 0,
        .cmd1 = BC_REQUEST_DEATH_NOTIFICATION,
        .arg1 = {
            .handle = 0,
            .cookie = cookie,
        },
        .cmd2 = BC_CLEAR_DEATH_NOTIFICATION,
        .arg2 = {
            .handle = 0,
            .cookie = cookie,
        },
        .cmd3 = BC_DECREFS,
        .arg3 = 0,
    };
    struct {
        uint32_t cmd0;
        uint32_t cmd1;
        binder_uintptr_t arg1;
        uint32_t pad[16];
    } __attribute__((packed)) br;
    struct binder_write_read bwr = binder_write_read();

    bwr.write_buffer = (uintptr_t)&bc;
    bwr.write_size = sizeof(bc);
    bwr.read_buffer = (uintptr_t)&br;
    bwr.read_size = sizeof(br);

    binderTestIoctl(BINDER_WRITE_READ, &bwr);
    EXPECT_EQ(sizeof(bc), bwr.write_consumed);
    EXPECT_EQ(sizeof(br) - sizeof(br.pad), bwr.read_consumed);
    EXPECT_EQ(BR_NOOP, br.cmd0);
    EXPECT_EQ(BR_CLEAR_DEATH_NOTIFICATION_DONE, br.cmd1);
    EXPECT_EQ(cookie, br.arg1);
    binderTestReadEmpty();
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);

    binder_env = AddGlobalTestEnvironment(new BinderDriverInterfaceTestEnv());

    return RUN_ALL_TESTS();
}

