/*
 * Copyright (C) 2018-2022 Jolla Ltd.
 * Copyright (C) 2018-2022 Slava Monich <slava.monich@jolla.com>
 *
 * You may use this file under the terms of BSD license as follows:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. 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.
 *   3. Neither the names of the copyright holders 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 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 "test_binder.h"
#include "gbinder_local_object_p.h"
#include "gbinder_system.h"

#define GLOG_MODULE_NAME test_binder_log
#include <gutil_log.h>
GLOG_MODULE_DEFINE2("test_binder", gutil_log_default);

#include <glib-object.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <linux/ioctl.h>

#define gettid() ((int)syscall(SYS_gettid))

static GHashTable* test_fd_map = NULL;
static GHashTable* test_node_map = NULL;
static GPrivate test_looper = G_PRIVATE_INIT(NULL);
static GPrivate test_tx_state = G_PRIVATE_INIT(NULL);
static guint32 last_auto_handle = 0;

G_LOCK_DEFINE_STATIC(test_binder);
static GMainLoop* test_binder_exit_loop = NULL;

#define PUBLIC (0)
#define PRIVATE (1)
#define public_fd  node[PUBLIC].fd
#define private_fd node[PRIVATE].fd

#define BINDER_VERSION _IOWR('b', 9, gint32)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, guint32)
#define BINDER_BUFFER_FLAG_HAS_PARENT 0x01

#define B_TYPE_LARGE 0x85
#define BINDER_TYPE_BINDER  GBINDER_FOURCC('s', 'b', '*', B_TYPE_LARGE)
#define BINDER_TYPE_HANDLE  GBINDER_FOURCC('s', 'h', '*', B_TYPE_LARGE)
#define BINDER_TYPE_PTR     GBINDER_FOURCC('p', 't', '*', B_TYPE_LARGE)

#define TF_ONE_WAY     0x01
#define TF_ROOT_OBJECT 0x04
#define TF_STATUS_CODE 0x08
#define TF_ACCEPT_FDS  0x10

#define READ_FLAG_TX_COMPLETION (0x01)
#define READ_FLAG_TX_INCOMING (0x02)
#define READ_FLAG_TX_REPLY (0x04)
#define READ_FLAG_TX_ERROR (0x08)
#define READ_FLAG_TX_OTHER (0x10)
#define READ_FLAGS_ALL (\
  READ_FLAG_TX_COMPLETION | \
  READ_FLAG_TX_INCOMING | \
  READ_FLAG_TX_REPLY | \
  READ_FLAG_TX_ERROR | \
  READ_FLAG_TX_OTHER)

typedef struct test_binder_io TestBinderIo;

typedef
void
(*TestBinderPushDataFunc)(
    int fd,
    const void* data);

typedef struct test_binder_submit_thread {
    GThread* thread;
    GCond cond;
    GMutex mutex;
    gboolean run;
    GSList* queue;
    TestBinder* binder;
} TestBinderSubmitThread;

typedef enum test_tx_state {
    TEST_TX_STATE_NONE,
    TEST_TX_STATE_ONEWAY,
    TEST_TX_STATE_ACTIVE,
    TEST_TX_STATE_REPLY
} TEST_TX_STATE;

/* TestBinderTxState has to be stacked to support nested transactions */
typedef struct test_binder_tx_state {
    int tid;
    int depth;
    TEST_TX_STATE* stack;
} TestBinderTxState;

typedef struct test_binder_node TestBinderNode;
struct test_binder_node {
    int fd;
    char* path;
    const char* name;
    TestBinder* binder;
    TestBinderNode* other;
    TEST_LOOPER looper_enabled;
    TestBinderTxState* tx_state;
    gint looper_count;
    GMutex mutex; /* Protects reads and next_cmd */
    guint32* next_cmd;
};

typedef struct test_binder_fd {
    int fd;
    GHashTable* destroy_map;
    TestBinderNode* node;
} TestBinderFd;

typedef struct test_binder {
    gint refcount;
    const TestBinderIo* io;
    GHashTable* object_map; /* GBinderLocalObject* => handle */
    GHashTable* handle_map; /* handle => GBinderLocalObject* */
    TestBinderSubmitThread* submit_thread;
    GMutex mutex;
    gboolean passthrough;
    TestBinderNode node[2];
} TestBinder;

struct test_binder_io {
    int version;
    int write_read_request;
    int (*handle_write_read)(TestBinderFd* fd, void* data);
};

typedef struct binder_write_read_64 {
    guint64 write_size;
    guint64 write_consumed;
    guint64 write_buffer;
    guint64 read_size;
    guint64 read_consumed;
    guint64 read_buffer;
} BinderWriteRead64;

typedef struct binder_transaction_data_64 {
    guint64 handle;
    guint64 cookie;
    guint32 code;
    guint32 flags;
    gint32 sender_pid;
    gint32 sender_euid;
    guint64 data_size;
    guint64 offsets_size;
    guint64 data_buffer;
    guint64 data_offsets;
} BinderTransactionData64;

typedef struct binder_transaction_data_sg_64 {
    BinderTransactionData64 tx;
    guint64 buffers_size;
} BinderTransactionDataSg64;

typedef struct binder_pre_cookie_64 {
    guint64 ptr;
    guint64 cookie;
} BinderPtrCookie64;

typedef struct binder_handle_cookie_64 {
    guint32 handle;
    guint64 cookie;
} __attribute__((packed)) BinderHandleCookie64;

typedef struct binder_object_64 {
    guint32 type;  /* BINDER_TYPE_BINDER */
    guint32 flags;
    guint64 object;
    guint64 cookie;
} BinderObject64;

typedef  struct binder_buffer_64 {
    guint32 type;  /* BINDER_TYPE_PTR */
    guint32 flags;
    guint64 buffer;
    guint64 length;
    guint64 parent;
    guint64 parent_offset;
} BinderBuffer64;

#define BC_TRANSACTION_64       _IOW('c', 0, BinderTransactionData64)
#define BC_REPLY_64             _IOW('c', 1, BinderTransactionData64)
#define BC_FREE_BUFFER_64       _IOW('c', 3, guint64)
#define BC_INCREFS              _IOW('c', 4, guint32)
#define BC_ACQUIRE              _IOW('c', 5, guint32)
#define BC_RELEASE              _IOW('c', 6, guint32)
#define BC_DECREFS              _IOW('c', 7, guint32)
#define BC_ENTER_LOOPER          _IO('c', 12)
#define BC_EXIT_LOOPER           _IO('c', 13)
#define BC_REQUEST_DEATH_NOTIFICATION_64 _IOW('c', 14, BinderHandleCookie64)
#define BC_CLEAR_DEATH_NOTIFICATION_64   _IOW('c', 15, BinderHandleCookie64)
#define BC_DEAD_BINDER_DONE     _IOW('c', 16, guint64)
#define BC_TRANSACTION_SG_64    _IOW('c', 17, BinderTransactionDataSg64)
#define BC_REPLY_SG_64          _IOW('c', 18, BinderTransactionDataSg64)

#define BR_TRANSACTION_64       _IOR('r', 2, BinderTransactionData64)
#define BR_REPLY_64             _IOR('r', 3, BinderTransactionData64)
#define BR_DEAD_REPLY            _IO('r', 5)
#define BR_TRANSACTION_COMPLETE  _IO('r', 6)
#define BR_INCREFS_64           _IOR('r', 7, BinderPtrCookie64)
#define BR_ACQUIRE_64           _IOR('r', 8, BinderPtrCookie64)
#define BR_RELEASE_64           _IOR('r', 9, BinderPtrCookie64)
#define BR_DECREFS_64           _IOR('r', 10, BinderPtrCookie64)
#define BR_NOOP                  _IO('r', 12)
#define BR_DEAD_BINDER_64       _IOR('r', 15, guint64)
#define BR_CLEAR_DEATH_NOTIFICATION_DONE_64 _IOR('r', 16, guint64)
#define BR_FAILED_REPLY          _IO('r', 17)

static
gpointer
test_binder_submit_thread_proc(
    gpointer data)
{
    TestBinderSubmitThread* submit = data;
    TestBinder* binder = submit->binder;
    GMutex* mutex = &submit->mutex;
    GCond* cond = &submit->cond;

    GDEBUG("Submit thread started");
    g_mutex_lock(mutex);
    while (submit->run) {
        GBytes* next = NULL;

        while (submit->run && !next) {
            if (submit->queue) {
                next = submit->queue->data;
                submit->queue = g_slist_remove(submit->queue, next);
                break;
            } else {
                g_cond_wait(cond, mutex);
            }
        }

        if (next) {
            int bytes_available = 0;
            int err = ioctl(binder->public_fd, FIONREAD, &bytes_available);

            /* Wait until the queue is empty */
            g_assert(err >= 0);
            while (bytes_available > 0 && submit->run) {
                /* Wait a bit between polls */
                g_cond_wait_until(cond, mutex, g_get_monotonic_time () +
                    100 * G_TIME_SPAN_MILLISECOND);
                err = ioctl(binder->public_fd, FIONREAD, &bytes_available);
                g_assert(err >= 0);
            }

            if (submit->run) {
                gsize len;
                gconstpointer data = g_bytes_get_data(next, &len);

                GDEBUG("Submitting command 0x%08x", *(guint32*)data);
                g_assert(write(binder->private_fd, data, len) == len);
            }
            g_bytes_unref(next);
        }
    }
    g_mutex_unlock(mutex);
    GDEBUG("Submit thread exiting");
    return NULL;
}

static
TestBinderSubmitThread*
test_binder_submit_thread_new(
    TestBinder* binder)
{
    TestBinderSubmitThread* submit = g_new0(TestBinderSubmitThread, 1);

    submit->run = TRUE;
    submit->binder = binder;
    g_cond_init(&submit->cond);
    g_mutex_init(&submit->mutex);
    submit->thread = g_thread_new(binder->node->path,
        test_binder_submit_thread_proc, submit);
    return submit;
}

static
void
test_binder_submit_thread_free(
    TestBinderSubmitThread* submit)
{
    if (submit) {
        g_mutex_lock(&submit->mutex);
        submit->run = FALSE;
        g_cond_signal(&submit->cond);
        g_mutex_unlock(&submit->mutex);
        g_thread_join(submit->thread);

        g_slist_free_full(submit->queue, (GDestroyNotify) g_bytes_unref);
        g_cond_clear(&submit->cond);
        g_mutex_clear(&submit->mutex);
        g_free(submit);
    }
}

static
void
test_binder_submit_later(
    TestBinderSubmitThread* submit,
    const void* data)
{
    const guint32* cmd = data;

    g_mutex_lock(&submit->mutex);
    submit->queue = g_slist_append(submit->queue,
        g_bytes_new(cmd, sizeof(*cmd) + _IOC_SIZE(*cmd)));
    g_cond_signal(&submit->cond);
    g_mutex_unlock(&submit->mutex);
}

static
void
test_io_free_buffer(
    TestBinderFd* fd,
    void* ptr)
{
    if (ptr) {
        GDestroyNotify destroy;

        G_LOCK(test_binder);
        destroy = g_hash_table_lookup(fd->destroy_map, ptr);
        if (destroy) {
            g_hash_table_remove(fd->destroy_map, ptr);
            destroy(ptr);
        } else {
            g_free(ptr);
        }
        G_UNLOCK(test_binder);
    }
}

void
test_binder_exit_wait(
    const TestOpt* opt,
    GMainLoop* loop)
{
    G_LOCK(test_binder);
    if (test_node_map) {
        g_assert(!test_binder_exit_loop);
        if (loop) {
            g_main_loop_ref(loop);
        } else {
            loop = g_main_loop_new(NULL, FALSE);
        }
        test_binder_exit_loop = loop;
        GDEBUG("Waiting for loopers to exit...");
        G_UNLOCK(test_binder);

        test_run(opt, loop);

        G_LOCK(test_binder);
        test_binder_exit_loop = NULL;
        g_main_loop_unref(loop);
    }
    G_UNLOCK(test_binder);
}

static
void
test_binder_object_dead_locked(
    TestBinder* binder,
    guint64 handle)
{
    /* Caller has to remove the object from handle_map and object_map */
    guint32 cmd[3];

    /* Send DEAD_BINDER to both ends of the socket */
    cmd[0] = BR_DEAD_BINDER_64;
    *(guint64*)(cmd + 1) = GPOINTER_TO_SIZE(handle);
    write(binder->node[0].fd, cmd, sizeof(cmd));
    write(binder->node[1].fd, cmd, sizeof(cmd));
}

static
void
test_binder_local_object_gone(
    gpointer data,
    GObject* obj)
{
    TestBinder* binder = data;

    G_LOCK(test_binder);
    GDEBUG("Object %p is gone", obj);
    if (g_hash_table_contains(binder->object_map, obj)) {
        gpointer handle = g_hash_table_lookup(binder->object_map, obj);

        test_binder_object_dead_locked(binder, GPOINTER_TO_SIZE(handle));
        g_hash_table_remove(binder->handle_map, handle);
        g_hash_table_remove(binder->object_map, obj);
    }
    G_UNLOCK(test_binder);
}

static
guint
test_binder_register_object_locked(
    TestBinder* binder,
    GBinderLocalObject* obj,
    guint h)
{
    g_assert(G_TYPE_CHECK_INSTANCE_TYPE(obj, GBINDER_TYPE_LOCAL_OBJECT));
    g_assert(!g_hash_table_contains(binder->object_map, obj));
    g_assert(!g_hash_table_contains(binder->handle_map, GINT_TO_POINTER(h)));
    if (h == AUTO_HANDLE) {
        h = ++last_auto_handle;
        while (g_hash_table_contains(binder->handle_map, GINT_TO_POINTER(h)) ||
            g_hash_table_contains(binder->object_map, GINT_TO_POINTER(h))) {
            h = ++last_auto_handle;
        }
    } else if (last_auto_handle < h) {
        /* Avoid re-using handles, to make debugging easier */
        last_auto_handle = h;
    }
    GDEBUG("Object %p <=> handle %u %s", obj, h, binder->node[0].name);
    g_hash_table_insert(binder->handle_map, GINT_TO_POINTER(h), obj);
    g_hash_table_insert(binder->object_map, obj, GINT_TO_POINTER(h));
    g_object_weak_ref(G_OBJECT(obj), test_binder_local_object_gone, binder);
    return h;
}

static
guint64
test_io_passthough_handle_to_object(
    TestBinder* binder,
    guint64 handle)
{
    gpointer key = GSIZE_TO_POINTER(handle);

    /* Invoked under lock */
    if (g_hash_table_contains(binder->handle_map, key)) {
        gpointer obj = g_hash_table_lookup(binder->handle_map, key);

        GDEBUG("Handle %u => object %p %s", (guint) handle, obj,
            binder->node[0].name);
        return GPOINTER_TO_SIZE(obj);
    }
    GDEBUG("Unexpected handle %u %s", (guint) handle, binder->node[0].name);
    return 0;
}

static
guint64
test_io_passthough_object_to_handle(
    TestBinder* binder,
    guint64 object)
{
    gpointer key = GSIZE_TO_POINTER(object);

    /* Invoked under lock */
    if (g_hash_table_contains(binder->object_map, key)) {
        gpointer value = g_hash_table_lookup(binder->object_map, key);
        guint64 handle = GPOINTER_TO_SIZE(value);

        GDEBUG("Object %p => handle %u %s", key, (guint) handle,
            binder->node[0].name);
        return handle;
    } else if (key) {
        GDEBUG("Auto-registering object %p %s", key, binder->node[0].name);
        return test_binder_register_object_locked(binder, key, AUTO_HANDLE);
    } else {
        GDEBUG("Unexpected object %p %s", key, binder->node[0].name);
        return 0;
    }
}

static
int
test_binder_bytes_available(
    int fd)
{
    int bytes_available = 0;
    int err = ioctl(fd, FIONREAD, &bytes_available);

    return (err >= 0) ? bytes_available : err;
}

static
int
test_binder_node_read_all(
    int fd,
    void* buf,
    int nbytes)
{
    int out = 0;
    guint8* ptr = buf;

    while (nbytes > 0) {
        out = read(fd, ptr, nbytes);
        if (out < 0) {
            break;
        } else {
            g_assert_cmpint(out, <= ,nbytes);
            ptr += out;
            nbytes -= out;
        }
    }
    return out;
}

static
int
test_binder_cmd_read_flags(
    guint32 cmd)
{
    switch (cmd) {
    case BR_TRANSACTION_COMPLETE:
        return READ_FLAG_TX_COMPLETION;
    case BR_TRANSACTION_64:
        return READ_FLAG_TX_INCOMING;
    case BR_REPLY_64:
        return READ_FLAG_TX_REPLY;
    case BR_FAILED_REPLY:
    case BR_DEAD_REPLY:
        return READ_FLAG_TX_ERROR;
    default:
        return READ_FLAG_TX_OTHER;
    }
}

static
guint32*
test_binder_node_read(
    TestBinderNode* node,
    gsize max_bytes,
    int* bytes_read,
    int flags)
{
    guint32* out = NULL;

    /* Ensures that we never read partial commands */
    g_mutex_lock(&node->mutex);
    if (node->next_cmd) {
        const guint32 cmd = node->next_cmd[0];
        const guint total = 4 + _IOC_SIZE(cmd);

        /* Already have one ready */
        if (!(test_binder_cmd_read_flags(cmd) & flags)) {
            GDEBUG("Cmd 0x%08x not for %d", cmd, gettid());
            *bytes_read = 0;
        } else if (max_bytes < total) {
            GDEBUG("Buffer full (%u > %d)", total, (int)max_bytes);
            *bytes_read = 0;
        } else {
            /* Yey! */
            out = node->next_cmd;
            node->next_cmd = NULL;
            *bytes_read = total;
        }
    } else {
        /* Read the next one from the socket */
        int available = test_binder_bytes_available(node->fd);

        if (available >= 4) {
            guint32 cmd;
            int err = test_binder_node_read_all(node->fd, &cmd, sizeof(cmd));
            static const guint32 noop = BR_NOOP;

            if (err < 0) {
                GDEBUG("Read error %d", err);
                *bytes_read = err;
            } else {
                guint datasize = _IOC_SIZE(cmd);
                guint total = 4 + datasize;
                guint32* buf = g_malloc(total);

                err = test_binder_node_read_all(node->fd, buf + 1, datasize);
                buf[0] = cmd;
                if (err < 0) {
                    GDEBUG("Read error %d", err);
                    *bytes_read = err;
                    g_free(buf);
                } else if (!(test_binder_cmd_read_flags(cmd) & flags)) {
                    GDEBUG("Stashed cmd 0x%08x not for %d", cmd, gettid());
                    node->next_cmd = buf;
                    *bytes_read = 0;

                    /*
                     * Make sure looper comes back for it and doesn't
                     * get stuck in poll() forever.
                     */
                    g_assert_cmpint(write(node->other->fd, &noop,
                        sizeof(noop)), == ,sizeof(noop));
                } else if (max_bytes < total) {
                    GDEBUG("Stashed cmd 0x%08x because %u > %d", cmd,
                        total, (int)max_bytes);
                    node->next_cmd = buf;
                    *bytes_read = 0;

                    /*
                     * Make sure looper comes back for it and doesn't
                     * get stuck in poll() forever.
                     */
                    g_assert_cmpint(write(node->other->fd, &noop,
                        sizeof(noop)), == ,sizeof(noop));
                } else {
                    /* Yey! */
                    out = buf;
                    *bytes_read = total;
                }
            }
        } else {
            /* Not enough data to read */
            *bytes_read = 0;
        }
    }
    g_mutex_unlock(&node->mutex);
    return out;
}

static
void
test_io_short_wait()
{
    usleep(100000); /* 100 ms */
}

static
TestBinderTxState*
test_tx_state_acquire(
    TestBinderNode* node,
    TEST_TX_STATE state)
{
    TestBinderTxState* my_tx_state = g_private_get(&test_tx_state);

    if (my_tx_state) {
        g_assert_cmpint(my_tx_state->tid, == ,gettid());
    } else {
        my_tx_state = g_new0(TestBinderTxState, 1);
        my_tx_state->tid = gettid(); /* For debugging */
        g_private_set(&test_tx_state, my_tx_state);
    }
    my_tx_state->stack = g_renew(TEST_TX_STATE, my_tx_state->stack,
        my_tx_state->depth + 1);
    my_tx_state->stack[my_tx_state->depth++] = state;
    while (g_atomic_pointer_get(&node->tx_state) != my_tx_state &&
        !g_atomic_pointer_compare_and_exchange(&node->tx_state, NULL,
         my_tx_state)) {
        GDEBUG("Thread %d is waiting to become a transaction thread",
            my_tx_state->tid);
        test_io_short_wait();
    }
    return my_tx_state;
}

static
void
test_tx_state_release(
    TestBinderNode* node)
{
    TestBinderTxState* my_tx_state = g_private_get(&test_tx_state);

    g_assert(my_tx_state);
    g_assert(my_tx_state->depth > 0);
    g_assert_cmpint(my_tx_state->tid, == ,gettid());

    if (my_tx_state->depth == 1) {
        GDEBUG("Thread %d done with the transaction", my_tx_state->tid);
        g_assert(g_atomic_pointer_compare_and_exchange(&node->tx_state,
            my_tx_state, NULL));
        g_private_set(&test_tx_state, NULL);
        g_free(my_tx_state->stack);
        g_free(my_tx_state);
    } else {
        my_tx_state->depth--;
        my_tx_state->stack = g_renew(TEST_TX_STATE, my_tx_state->stack,
            my_tx_state->depth);
        GDEBUG("Thread %d is still a transaction thread", my_tx_state->tid);
    }
}

static
TEST_TX_STATE
test_tx_state_get(
    TestBinderTxState* tx_state)
{
    if (tx_state) {
        g_assert_cmpint(tx_state->depth, > ,0);
        return tx_state->stack[tx_state->depth - 1];
    } else {
        return TEST_TX_STATE_NONE;
    }
}

static
void
test_tx_state_set(
    TestBinderTxState* tx_state,
    TEST_TX_STATE state)
{
    g_assert(tx_state);
    g_assert_cmpint(tx_state->depth, > ,0);
    tx_state->stack[tx_state->depth - 1] = state;
}

static
gssize
test_io_passthough_write_64(
    TestBinderFd* fd,
    const void* bytes,
    gsize bytes_to_write)
{
    const guint32 code = *(guint32*)bytes;
    TestBinderNode* node = fd->node;
    TestBinderNode* other = node->other;
    TestBinder* binder = node->binder;
    BinderTransactionData64* tx = NULL;
    TestBinderTxState* my_tx_state = g_private_get(&test_tx_state);
    const BinderHandleCookie64* hc;
    gssize bytes_written;
    guint32* buf;
    guint32* cmd;
    guint64* cookie;
    guint32 buflen;
    void* data;

    /* Just ignore some codes for now */
    switch (code) {
    case BC_ACQUIRE:
    case BC_RELEASE:
    case BC_REQUEST_DEATH_NOTIFICATION_64:
    case BC_DEAD_BINDER_DONE:
        return bytes_to_write;
    case BC_CLEAR_DEATH_NOTIFICATION_64:
        hc = (const BinderHandleCookie64*)((guint32*)bytes + 1);
        g_assert(bytes_to_write == (sizeof(guint32) + sizeof(*hc)));
        buflen = sizeof(guint32) + sizeof(*cookie);
        cmd = g_memdup(bytes, buflen);
        *cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE_64;
        *(guint64*)(cmd + 1) = hc->cookie;
        g_assert(write(other->fd, cmd, buflen) == buflen);
        g_free(cmd);
        return bytes_to_write;
    default:
        break;
    }

    buf = cmd = g_memdup(bytes, bytes_to_write);
    data = cmd + 1;
    switch (*cmd) {
    case BC_TRANSACTION_64:
    case BC_TRANSACTION_SG_64:
        *cmd = BR_TRANSACTION_64;
        tx = data;
        tx->sender_pid = getpid();
        tx->sender_euid = geteuid();
        my_tx_state = test_tx_state_acquire(node, (tx->flags & TF_ONE_WAY) ?
            TEST_TX_STATE_ONEWAY : TEST_TX_STATE_ACTIVE);
        GDEBUG("Transaction thread %d", my_tx_state->tid);
        break;
    case BC_REPLY_64:
    case BC_REPLY_SG_64:
        /* Prepend BR_TRANSACTION_COMPLETE */
        GDEBUG("Thread %d inserting BR_TRANSACTION_COMPLETE before reply => "
            "fd %d", gettid(), fd->fd);
        buf = g_realloc(buf, bytes_to_write + 4);
        cmd = buf + 1;
        data = cmd + 1;
        memmove(cmd, buf, bytes_to_write);
        *buf = BR_TRANSACTION_COMPLETE;
        *cmd = BR_REPLY_64;
        tx = data;
        my_tx_state = test_tx_state_acquire(node, TEST_TX_STATE_ONEWAY);
        GDEBUG("Reply thread %d", my_tx_state->tid);
        break;
    }

    if (tx) {
        const guint32 handle = tx->handle;
        const gboolean is_reply = (*cmd == BR_REPLY_64);

        G_LOCK(test_binder);
        if (!is_reply) {
            tx->handle = test_io_passthough_handle_to_object(binder, handle);
        }
        if (is_reply || tx->handle) {
            guint i;
            guint8* data_buffer = g_memdup
                (GSIZE_TO_POINTER(tx->data_buffer), tx->data_size);
            guint64* data_offsets = g_memdup
                (GSIZE_TO_POINTER(tx->data_offsets), tx->offsets_size);

            for (i = 0; i < tx->offsets_size/sizeof(guint64); i++) {
                guint32* obj_ptr = (guint32*)(data_buffer + data_offsets[i]);

                if (*obj_ptr == BINDER_TYPE_BINDER) {
                    BinderObject64* object = (BinderObject64*) obj_ptr;

                    if (object->object) {
                        object->type = BINDER_TYPE_HANDLE;
                        object->object = test_io_passthough_object_to_handle
                            (binder, object->object);
                    }
                } else if (*obj_ptr == BINDER_TYPE_PTR) {
                    BinderBuffer64* buffer = (BinderBuffer64*) obj_ptr;

                    if (buffer->buffer) {
                        void* copy = g_memdup
                            (GSIZE_TO_POINTER(buffer->buffer), buffer->length);

                        if (buffer->flags & BINDER_BUFFER_FLAG_HAS_PARENT) {
                            /* Fix pointer from the parent buffer */
                            BinderBuffer64* parent_buffer = (void*)
                                (data_buffer + data_offsets[buffer->parent]);
                            guint64* parent_ptr = GSIZE_TO_POINTER
                                (parent_buffer->buffer + buffer->parent_offset);

                            g_assert(parent_buffer->type == BINDER_TYPE_PTR);
                            g_assert(*parent_ptr == buffer->buffer);
                            *parent_ptr = GPOINTER_TO_SIZE(copy);
                        }
                        buffer->buffer = GPOINTER_TO_SIZE(copy);
                        g_hash_table_replace(fd->destroy_map, copy, NULL);
                    }
                } else {
                    GDEBUG("Unexpected object type 0x%08x", *obj_ptr);
                }
            }
            g_hash_table_replace(fd->destroy_map, data_offsets, NULL);
            tx->data_buffer = GPOINTER_TO_SIZE(data_buffer);
            tx->data_offsets = GPOINTER_TO_SIZE(data_offsets);
            if ((tx->flags & TF_ONE_WAY) || is_reply) {
                const guint32 c = BR_TRANSACTION_COMPLETE;

                GDEBUG("Thread %d inserting BR_TRANSACTION_COMPLETE for %s "
                    "=> fd %d", gettid(), is_reply ? "reply" :
                    "one-way transaction", other->fd);
                g_assert(write(other->fd, &c, sizeof(c)) == sizeof(c));
            }
        } else {
            const guint32 c = BR_DEAD_REPLY;

            /* Fail the transaction */
            GDEBUG("Thread %d inserting BR_DEAD_REPLY => fd %d", gettid(),
                other->fd);
            g_assert(write(other->fd, &c, sizeof(c)) == sizeof(c));
            data = buf;
            *cmd = 0;
        }
        G_UNLOCK(test_binder);
    }

    /* Real number of bytes to write may have changed */
    buflen = ((guint8*)data - (guint8*)buf) + _IOC_SIZE(*cmd);
    bytes_written = buflen ? write(fd->fd, buf, buflen) : 0;
    g_free(buf);
    g_assert(bytes_written == buflen || bytes_written <= 0);
    return (bytes_written >= 0) ? bytes_to_write : bytes_written;
}

static
int
test_io_handle_write_read_64(
    TestBinderFd* fd,
    void* data)
{
    TestBinderNode* node = fd->node;
    TestBinder* binder = node->binder;
    BinderWriteRead64* wr = data;
    gssize bytes_left = wr->write_size - wr->write_consumed;
    const guint8* write_ptr = (void*)(gsize)(wr->write_buffer +
        wr->write_consumed);
    TestBinderTxState* my_tx_state = g_private_get(&test_tx_state);
    TestBinderTxState* node_tx_state = NULL;
    int can_read = 0;

    while (bytes_left >= sizeof(guint32)) {
        const guint cmd = *(guint32*)write_ptr;
        const guint cmdsize = _IOC_SIZE(cmd);
        const void* cmddata = write_ptr + sizeof(guint32);
        const gsize bytes_to_write = sizeof(guint32) + cmdsize;
        int looper;

        GASSERT(bytes_left >= bytes_to_write);
        if (bytes_left >= bytes_to_write) {
            gssize bytes_written = bytes_to_write;

            switch (cmd) {
            case BC_FREE_BUFFER_64:
                test_io_free_buffer(fd, GSIZE_TO_POINTER(*(guint64*)cmddata));
                break;
            case BC_ENTER_LOOPER:
                g_assert(g_private_get(&test_looper) >= 0);
                looper = g_atomic_int_add(&node->looper_count, 1) + 1;
                g_private_set(&test_looper, GINT_TO_POINTER(looper));
                GDEBUG("Thread %d is looper #%d", gettid(), looper);
                break;
            case BC_EXIT_LOOPER:
                GDEBUG("Thread %d is no longer a looper", gettid());
                g_atomic_int_add(&node->looper_count, -1);
                g_private_set(&test_looper, NULL);
                break;
            default:
                if (binder->passthrough) {
                    bytes_written = test_io_passthough_write_64(fd,
                        write_ptr, bytes_to_write);
                }
                break;
            }
            if (bytes_written >= 0) {
                wr->write_consumed += bytes_written;
                write_ptr += bytes_written;
                bytes_left -= bytes_written;
            } else {
                GDEBUG("Write failed, %s", strerror(errno));
                return -1;
            }
        } else {
            /* Partial command in the buffer */
            errno = EINVAL;
            return -1;
        }
    }

    node_tx_state = g_atomic_pointer_get(&node->tx_state);
    if (node_tx_state && node_tx_state != my_tx_state) {
        can_read |= READ_FLAG_TX_OTHER;
    } else {
        /*
         * If this thread is not performing a transaction (and passthrough
         * mode is enabled), don't steal completion commands from other
         * threads (but allow incoming transactions).
         */
        const gint max_read_flags = (!binder->passthrough ||
            (node_tx_state && node_tx_state == my_tx_state)) ?
            READ_FLAGS_ALL : (READ_FLAG_TX_OTHER | READ_FLAG_TX_INCOMING);
        const gint looper = GPOINTER_TO_INT(g_private_get(&test_looper));

        if (looper <= 0) {
            /* Main or pooled client thread */
            can_read |= max_read_flags;
        } else {
            switch (node->looper_enabled) {
            case TEST_LOOPER_DISABLE:
                break;
            case TEST_LOOPER_ENABLE:
                if (looper > 0) {
                    can_read |= max_read_flags;
                }
                break;
            case TEST_LOOPER_ENABLE_ONE:
                if (looper == 1) {
                    can_read |= max_read_flags;
                }
                break;
            }
        }
    }

    if (can_read && (wr->read_size > wr->read_consumed)) {
        int nbytes = 0;
        int avail = wr->read_size - wr->read_consumed;
        int total = 0;
        guint8* buf = GSIZE_TO_POINTER(wr->read_buffer + wr->read_consumed);
        guint32* cmd;

        while ((cmd = test_binder_node_read(node, avail, &nbytes, can_read))) {
            g_assert_cmpint(nbytes, <= ,avail);
            switch (cmd[0]) {
            case BR_TRANSACTION_COMPLETE:
                if (node_tx_state) {
                    g_assert(node_tx_state == my_tx_state);
                    switch (test_tx_state_get(my_tx_state)) {
                    case TEST_TX_STATE_REPLY:
                    case TEST_TX_STATE_ONEWAY:
                        /* Done with the transaction */
                        can_read &= ~(READ_FLAG_TX_COMPLETION |
                            READ_FLAG_TX_INCOMING | READ_FLAG_TX_REPLY |
                            READ_FLAG_TX_ERROR);
                        test_tx_state_set(my_tx_state, TEST_TX_STATE_NONE);
                        break;
                    case TEST_TX_STATE_ACTIVE:
                        can_read &= ~(READ_FLAG_TX_COMPLETION |
                            READ_FLAG_TX_INCOMING);
                        test_tx_state_set(my_tx_state, TEST_TX_STATE_REPLY);
                        break;
                    case TEST_TX_STATE_NONE:
                        g_assert_not_reached();
                        break;
                    }
                } else {
                    can_read &= ~(READ_FLAG_TX_COMPLETION |
                        READ_FLAG_TX_INCOMING | READ_FLAG_TX_REPLY |
                        READ_FLAG_TX_ERROR);
                }
                break;
            case BR_REPLY_64:
                if (node_tx_state) {
                    g_assert(node_tx_state == my_tx_state);
                    test_tx_state_set(my_tx_state, TEST_TX_STATE_NONE);
                }
                can_read &= ~(READ_FLAG_TX_COMPLETION |
                    READ_FLAG_TX_INCOMING | READ_FLAG_TX_REPLY |
                    READ_FLAG_TX_ERROR);
                break;
            case BR_FAILED_REPLY:
            case BR_DEAD_REPLY:
                if (node_tx_state) {
                    g_assert(node_tx_state == my_tx_state);
                    test_tx_state_set(my_tx_state, TEST_TX_STATE_NONE);
                }
                can_read &= ~(READ_FLAG_TX_COMPLETION |
                    READ_FLAG_TX_INCOMING | READ_FLAG_TX_REPLY |
                    READ_FLAG_TX_ERROR);
                break;
            case BR_TRANSACTION_64:
                /* Don't swallow transaction related commands too early */
                can_read &= ~(READ_FLAG_TX_COMPLETION |
                    READ_FLAG_TX_INCOMING | READ_FLAG_TX_REPLY |
                    READ_FLAG_TX_ERROR);
                break;
            }
            memcpy(buf, cmd, nbytes);
            wr->read_consumed += nbytes;
            total += nbytes;
            avail -= nbytes;
            buf += nbytes;
            g_free(cmd);
        }

        if (my_tx_state && my_tx_state == node_tx_state &&
            test_tx_state_get(my_tx_state) == TEST_TX_STATE_NONE) {
            test_tx_state_release(node);
        }

        if (nbytes < 0) {
            /* Error */
            return nbytes;
        } else if (!total) {
            /* Nothing was read */
            test_io_short_wait();
        }
    } else if (wr->read_size > 0) {
        test_io_short_wait();
    }
    return 0;
}

static const TestBinderIo test_io_64 = {
    .version = 8,
    .write_read_request = _IOWR('b', 1, BinderWriteRead64),
    .handle_write_read = test_io_handle_write_read_64
};

static
int
test_binder_ioctl_version(
    TestBinder* binder,
    int* version)
{
    *version = binder->io->version;
    return 0;
}

static
TestBinderFd*
test_binder_fd_from_fd_locked(
    int fd)
{
    g_assert(test_fd_map);
    return g_hash_table_lookup(test_fd_map, GINT_TO_POINTER(fd));
}

static
TestBinderFd*
test_binder_fd_from_fd(
    int fd)
{
    TestBinderFd* binder_fd;

    G_LOCK(test_binder);
    binder_fd = test_binder_fd_from_fd_locked(fd);
    g_assert(binder_fd);
    G_UNLOCK(test_binder);

    return binder_fd;
}

static
TestBinder*
test_binder_from_fd_locked(
    int fd)
{
    TestBinderFd* binder_fd = test_binder_fd_from_fd_locked(fd);

    g_assert(binder_fd);
    return binder_fd->node->binder;
}

static
TestBinder*
test_binder_from_fd(
    int fd)
{
    TestBinder* binder;

    G_LOCK(test_binder);
    binder = test_binder_from_fd_locked(fd);
    G_UNLOCK(test_binder);

    return binder;
}

static
void
test_io_destroy_none(
    gpointer data)
{
    GDEBUG("Not freeing %p", data);
}

void
test_binder_set_looper_enabled(
    int fd,
    TEST_LOOPER value)
{
    TestBinderFd* binder_fd = test_binder_fd_from_fd(fd);

    g_assert(binder_fd);
    binder_fd->node->looper_enabled = value;
}

void
test_binder_set_passthrough(
    int fd,
    gboolean passthrough)
{
    TestBinder* binder = test_binder_from_fd(fd);

    g_assert(binder);
    binder->passthrough = passthrough;
}

void
test_binder_set_destroy(
    int fd,
    gpointer ptr,
    GDestroyNotify destroy)
{
    TestBinderFd* binder_fd = test_binder_fd_from_fd(fd);

    if (binder_fd) {
        G_LOCK(test_binder);
        g_hash_table_replace(binder_fd->destroy_map, ptr,
            destroy ? destroy : test_io_destroy_none);
        G_UNLOCK(test_binder);
    }
}

static
void
test_binder_push_data(
    int fd,
    const void* data)
{
    const guint32* cmd = data;
    const int len = sizeof(*cmd) + _IOC_SIZE(*cmd);
    TestBinderFd* binder_fd = test_binder_fd_from_fd(fd);
    TestBinderNode* node = binder_fd->node;

    g_assert(write(node->other->fd, data, len) == len);
}

static
void
test_binder_push_data_later(
    int fd,
    const void* data)
{
    TestBinder* binder = test_binder_from_fd(fd);

    g_assert(binder);
    if (!binder->submit_thread) {
        binder->submit_thread = test_binder_submit_thread_new(binder);
    }
    test_binder_submit_later(binder->submit_thread, data);
}

void
test_binder_push_ptr_cookie(
    int fd,
    guint32 cmd,
    void* ptr)
{
    guint8 buf[sizeof(guint32) + sizeof(BinderPtrCookie64)];
    BinderPtrCookie64* data = (void*)(buf + sizeof(cmd));

    memcpy(buf, &cmd, sizeof(cmd));
    memset(data, 0, sizeof(*data));
    data->ptr = (gsize)ptr;
    test_binder_push_data(fd, buf);
}

void
test_binder_br_noop(
    int fd)
{
    guint32 cmd = BR_NOOP;

    test_binder_push_data(fd, &cmd);
}

void
test_binder_br_increfs(
    int fd,
    void* ptr)
{
    test_binder_push_ptr_cookie(fd, BR_INCREFS_64, ptr);
}

void
test_binder_br_acquire(
    int fd,
    void* ptr)
{
    test_binder_push_ptr_cookie(fd, BR_ACQUIRE_64, ptr);
}

void
test_binder_br_release(
    int fd,
    void* ptr)
{
    test_binder_push_ptr_cookie(fd, BR_RELEASE_64, ptr);
}

void
test_binder_br_decrefs(
    int fd,
    void* ptr)
{
    test_binder_push_ptr_cookie(fd, BR_DECREFS_64, ptr);
}

void
test_binder_br_transaction_complete(
    int fd)
{
    guint32 cmd = BR_TRANSACTION_COMPLETE;

    test_binder_push_data(fd, &cmd);
}

void
test_binder_br_transaction_complete_later(
    int fd)
{
    guint32 cmd = BR_TRANSACTION_COMPLETE;

    test_binder_push_data_later(fd, &cmd);
}

void
test_binder_br_dead_binder(
    int fd,
    guint handle)
{
    const guint64 handle64 = handle;
    guint32 buf[3];

    buf[0] = BR_DEAD_BINDER_64;
    memcpy(buf + 1, &handle64, sizeof(handle64));

    test_binder_push_data(fd, buf);
}

void
test_binder_br_dead_reply(
    int fd)
{
    guint32 cmd = BR_DEAD_REPLY;

    test_binder_push_data(fd, &cmd);
}

void
test_binder_br_failed_reply(
    int fd)
{
    guint32 cmd = BR_FAILED_REPLY;

    test_binder_push_data(fd, &cmd);
}

static
void
test_binder_fill_transaction_data(
    BinderTransactionData64* tr,
    guint64 handle,
    guint32 code,
    const GByteArray* bytes)
{
    memset(tr, 0, sizeof(*tr));
    tr->handle = handle;
    tr->code = code;
    tr->data_size = bytes ? bytes->len : 0;
    tr->sender_pid = getpid();
    tr->sender_euid = geteuid();
    /* This memory should eventually get deallocated with BC_FREE_BUFFER_64 */
    tr->data_buffer = (gsize)g_memdup(bytes ? (void*)bytes->data : (void*)"",
        tr->data_size);
}

void
test_binder_br_transaction(
    int fd,
    void* target,
    guint32 code,
    const GByteArray* bytes)
{
    guint32 cmd = BR_TRANSACTION_64;
    guint8 buf[sizeof(guint32) + sizeof(BinderTransactionData64)];

    memcpy(buf, &cmd, sizeof(cmd));
    test_binder_fill_transaction_data((void*)(buf + sizeof(cmd)),
        (gsize)target, code, bytes);

    test_binder_push_data(fd, buf);
}

static
void
test_binder_br_reply1(
    int fd,
    guint32 handle,
    guint32 code,
    const GByteArray* bytes,
    TestBinderPushDataFunc push)
{
    guint32 cmd = BR_REPLY_64;
    guint8 buf[sizeof(guint32) + sizeof(BinderTransactionData64)];

    memcpy(buf, &cmd, sizeof(cmd));
    test_binder_fill_transaction_data((void*)(buf + sizeof(cmd)),
        handle, code, bytes);

    push(fd, buf);
}

void
test_binder_br_reply(
    int fd,
    guint32 handle,
    guint32 code,
    const GByteArray* bytes)
{
    test_binder_br_reply1(fd, handle, code, bytes, test_binder_push_data);
}

void
test_binder_br_reply_later(
    int fd,
    guint32 handle,
    guint32 code,
    const GByteArray* bytes)
{
    test_binder_br_reply1(fd, handle, code, bytes, test_binder_push_data_later);
}

void
test_binder_br_reply_status1(
    int fd,
    gint32 status,
    TestBinderPushDataFunc push)
{
    guint8 buf[sizeof(guint32) + sizeof(BinderTransactionData64)];
    guint32* cmd = (void*)buf;
    BinderTransactionData64* tr = (void*)(buf + sizeof(*cmd));

    memset(buf, 0, sizeof(buf));
    *cmd = BR_REPLY_64;
    tr->flags = TF_STATUS_CODE;
    tr->data_size = sizeof(status);
    /* This memory should eventually get deallocated with BC_FREE_BUFFER_64 */
    tr->data_buffer = (gsize)g_memdup(&status, sizeof(status));

    push(fd, buf);
}

void
test_binder_br_reply_status(
    int fd,
    gint32 status)
{
    test_binder_br_reply_status1(fd, status, test_binder_push_data);
}

void
test_binder_br_reply_status_later(
    int fd,
    gint32 status)
{
    test_binder_br_reply_status1(fd, status, test_binder_push_data_later);
}

static
void
test_binder_node_clear(
    TestBinderNode* node)
{
    GDEBUG("Done with %s", node->path);
    g_hash_table_remove(test_node_map, node->path);
    if (!g_hash_table_size(test_node_map)) {
        g_hash_table_unref(test_node_map);
        test_node_map = NULL;
        if (test_binder_exit_loop) {
            GVERBOSE_("All nodes are gone");
            test_quit_later(test_binder_exit_loop);
        }
    }
    close(node->fd);
    g_mutex_clear(&node->mutex);
    g_free(node->next_cmd);
    g_free(node->path);
}

static
TestBinder*
test_binder_ref(
    TestBinder* binder)
{
    if (binder) {
        g_atomic_int_inc(&binder->refcount);
    }
    return binder;
}

static
void
test_binder_unref_internal(
    TestBinder* binder,
    gboolean need_lock)
{
    if (binder && g_atomic_int_dec_and_test(&binder->refcount)) {
        if (need_lock) {
            G_LOCK(test_binder);
        }
        test_binder_node_clear(binder->node + 0);
        test_binder_node_clear(binder->node + 1);
        g_assert_cmpuint(g_hash_table_size(binder->object_map), == ,0);
        g_assert_cmpuint(g_hash_table_size(binder->handle_map), == ,0);
        if (need_lock) {
            G_UNLOCK(test_binder);
        }
        test_binder_submit_thread_free(binder->submit_thread);
        g_hash_table_destroy(binder->object_map);
        g_hash_table_destroy(binder->handle_map);
        g_mutex_clear(&binder->mutex);
        g_free(binder);
    }
}

int
test_binder_handle(
    int fd,
    GBinderLocalObject* obj)
{
    TestBinder* binder = test_binder_from_fd(fd);
    int h = -1;

    g_assert(binder);
    g_assert(obj);

    G_LOCK(test_binder);
    if (g_hash_table_contains(binder->object_map, obj)) {
        h = GPOINTER_TO_INT(g_hash_table_lookup(binder->object_map, obj));
    }
    G_UNLOCK(test_binder);

    return h;
}

GBinderLocalObject*
test_binder_object(
    int fd,
    guint handle)
{
    TestBinder* binder = test_binder_from_fd(fd);
    gpointer key = GSIZE_TO_POINTER(handle);
    GBinderLocalObject* obj = NULL;

    g_assert(binder);

    G_LOCK(test_binder);
    if (g_hash_table_contains(binder->handle_map, key)) {
        obj = gbinder_local_object_ref
            (g_hash_table_lookup(binder->handle_map, key));
    }
    G_UNLOCK(test_binder);

    return obj;
}

guint
test_binder_register_object(
    int fd,
    GBinderLocalObject* obj,
    guint h)
{
    TestBinder* binder = test_binder_from_fd(fd);

    g_assert(binder);
    g_assert(obj);

    G_LOCK(test_binder);
    h = test_binder_register_object_locked(binder, obj, h);
    G_UNLOCK(test_binder);

    return h;
}

void
test_binder_unregister_objects(
    int fd)
{
    TestBinder* binder;
    GHashTableIter it;
    gpointer handle, obj;

    G_LOCK(test_binder);
    binder = test_binder_from_fd_locked(fd);
    g_hash_table_iter_init(&it, binder->handle_map);
    while (g_hash_table_iter_next(&it, &handle, &obj)) {
        test_binder_object_dead_locked(binder, GPOINTER_TO_SIZE(handle));
        g_hash_table_remove(binder->object_map, obj);
        g_hash_table_iter_remove(&it);
    }
    G_UNLOCK(test_binder);
}

static
void
test_fd_map_free(
    gpointer entry)
{
    TestBinderFd* binder_fd = entry;
    GHashTableIter it;
    gpointer key, value;

    g_hash_table_iter_init(&it, binder_fd->destroy_map);
    while (g_hash_table_iter_next(&it, &key, &value)) {
        if (value) {
            ((GDestroyNotify)value)(key);
        } else {
            g_free(key);
        }
    }
    g_hash_table_destroy(binder_fd->destroy_map);
    test_binder_unref_internal(binder_fd->node->binder, FALSE);
    close(binder_fd->fd);
    g_free(binder_fd);
}

static
TestBinderFd*
test_binder_fd_new(
    TestBinderNode* node)
{
    TestBinderFd* binder_fd = g_new0(TestBinderFd, 1);

    /* Assume it's being created by the main thread */
    g_assert(GPOINTER_TO_INT(g_private_get(&test_looper)) <= 0);
    g_private_set(&test_looper, GINT_TO_POINTER(-1));
    test_binder_ref(node->binder);
    binder_fd->node = node;
    binder_fd->fd = dup(node->fd);
    binder_fd->destroy_map = g_hash_table_new(g_direct_hash, g_direct_equal);
    GDEBUG("fd %d => %d", binder_fd->fd, node->fd);
    return binder_fd;
}

int
gbinder_system_open(
    const char* path,
    int flags)
{
    static const char path_prefix[] = "/dev/";
    static const char binder_suffix[] = "binder";
    static const char binder_private_suffix[] = "binder-private";
    static const char private_suffix[] = "-private";

    if (path && g_str_has_prefix(path, path_prefix) &&
        (g_str_has_suffix(path, binder_suffix) ||
         g_str_has_suffix(path, binder_private_suffix))) {
        TestBinderFd* fd;
        TestBinderNode* node;

        G_LOCK(test_binder);
        node = test_node_map ? g_hash_table_lookup(test_node_map, path) : NULL;
        if (!node) {
            int i, fds[2];
            TestBinder* binder = g_new0(TestBinder, 1);

            binder->io = &test_io_64;
            g_mutex_init(&binder->mutex);
            g_assert(!socketpair(AF_UNIX, SOCK_STREAM, 0, fds));

            if (g_str_has_suffix(path, binder_suffix)) {
                node = binder->node + PUBLIC;
                node->path = g_strdup(path);
                binder->node[PRIVATE].path = g_strconcat(path,
                    private_suffix, NULL);
            } else {
                node = binder->node + PRIVATE;
                node->path = g_strdup(path);
                binder->node[PUBLIC].path = g_strndup(path,
                    strlen(path) - strlen(private_suffix));
            }

            if (!test_node_map) {
                test_node_map = g_hash_table_new(g_str_hash, g_str_equal);
            }
            for (i = 0; i < 2; i++) {
                TestBinderNode* this_node = binder->node + i;

                g_mutex_init(&this_node->mutex);
                this_node->binder = binder;
                this_node->fd = fds[i];
                this_node->other = binder->node + ((i + 1) % 2);
                this_node->name = this_node->path + strlen(path_prefix);
                g_hash_table_replace(test_node_map, this_node->path, this_node);
            }
            binder->object_map = g_hash_table_new
                (g_direct_hash, g_direct_equal);
            binder->handle_map = g_hash_table_new
                (g_direct_hash, g_direct_equal);
            GDEBUG("Created %s (%d) <=> %s (%d) binder",
                binder->node[0].name, binder->node[0].fd,
                binder->node[1].name, binder->node[1].fd);
        }
        fd = test_binder_fd_new(node);
        if (!test_fd_map) {
            test_fd_map = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                NULL, test_fd_map_free);
        }
        g_hash_table_replace(test_fd_map, GINT_TO_POINTER(fd->fd), fd);
        G_UNLOCK(test_binder);

        return fd->fd;
    } else {
        errno = ENOENT;
        return -1;
    }
}

int
gbinder_system_close(
    int fd)
{
    int ret;

    G_LOCK(test_binder);
    if (g_hash_table_remove(test_fd_map, GINT_TO_POINTER(fd))) {
        if (!g_hash_table_size(test_fd_map)) {
            g_hash_table_unref(test_fd_map);
            test_fd_map = NULL;
        }
        ret = 0;
    } else {
        errno = EBADF;
        ret = -1;
    }
    G_UNLOCK(test_binder);

    return ret;
}

int
gbinder_system_ioctl(
    int fd,
    int request,
    void* data)
{
    TestBinderFd* binder_fd = test_binder_fd_from_fd(fd);

    if (binder_fd) {
        TestBinder* binder = binder_fd->node->binder;
        const TestBinderIo* io = binder->io;

        switch (request) {
        case BINDER_VERSION:
            return test_binder_ioctl_version(binder, data);
        case BINDER_SET_MAX_THREADS:
            return 0;
        default:
            if (request == io->write_read_request) {
                return io->handle_write_read(binder_fd, data);
            } else {
                errno = EINVAL;
                return -1;
            }
        }
    }
    errno = EBADF;
    return -1;
}

void*
gbinder_system_mmap(
   size_t length,
   int prot,
   int flags,
   int fd)
{
    return g_malloc(length);
}

int
gbinder_system_munmap(
    void* addr,
    size_t length)
{
    g_free(addr);
    return 0;
}

/*
 * Local Variables:
 * mode: C
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
