/*
 * Copyright (C) 2018-2024 Slava Monich <slava@monich.com>
 * Copyright (C) 2018-2022 Jolla Ltd.
 *
 * 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_buffer_p.h"
#include "gbinder_driver.h"
#include "gbinder_ipc.h"
#include "gbinder_local_object_p.h"
#include "gbinder_local_reply_p.h"
#include "gbinder_object_registry.h"
#include "gbinder_output_data.h"
#include "gbinder_reader_p.h"
#include "gbinder_remote_request_p.h"
#include "gbinder_rpc_protocol.h"

#include <gutil_intarray.h>
#include <gutil_strv.h>
#include <gutil_log.h>

#include <errno.h>

static TestOpt test_opt;
static const char TMP_DIR_TEMPLATE[] = "gbinder-test-local-object-XXXXXX";

/* android.hidl.base@1.0::IBase */
#define TEST_BASE_INTERFACE_BYTES \
    'a', 'n', 'd', 'r', 'o', 'i', 'd', '.', \
    'h', 'i', 'd', 'l', '.', 'b', 'a', 's', \
    'e', '@', '1', '.', '0', ':', ':', 'I', \
    'B', 'a', 's', 'e'
#define TEST_BASE_INTERFACE_HEADER_BYTES \
    TEST_BASE_INTERFACE_BYTES, 0x00, 0x00, 0x00, 0x00
static const char base_interface[] = { TEST_BASE_INTERFACE_BYTES, 0x00 };

static
void
test_reader_data_init_for_reply(
    GBinderReaderData* data,
    GBinderLocalObject* obj,
    GBinderLocalReply* reply)
{
    GBinderIpc* ipc = obj->ipc;
    GBinderOutputData* out = gbinder_local_reply_data(reply);
    GUtilIntArray* offsets = gbinder_output_data_offsets(out);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderBuffer* buf = gbinder_buffer_new(ipc->driver,
        g_memdup(out->bytes->data, out->bytes->len),
        out->bytes->len, NULL);

    memset(data, 0, sizeof(*data));
    data->buffer = buf;
    data->reg = gbinder_object_registry_ref(reg);
    g_assert(!gbinder_object_registry_get_local(reg, NULL));
    g_assert(gbinder_object_registry_get_local(reg, obj) == obj);
    gbinder_local_object_unref(obj); /* ref added by the above call */
    if (offsets && offsets->count > 0) {
        guint i;

        data->objects = g_new(void*, offsets->count + 1);
        for (i = 0; i < offsets->count; i++) {
            data->objects[i] = (guint8*)buf->data + offsets->data[i];
        }
        data->objects[i] = NULL;
    }
}

static
void
test_reader_data_cleanup(
    GBinderReaderData* data)
{
    gbinder_object_registry_unref(data->reg);
    gbinder_buffer_free(data->buffer);
    g_free(data->objects);
}

/*==========================================================================*
 * null
 *==========================================================================*/

static
void
test_null(
    void)
{
    int status = 0;

    g_assert(!gbinder_local_object_new(NULL, NULL, NULL, NULL));
    g_assert(!gbinder_local_object_ref(NULL));
    gbinder_local_object_unref(NULL);
    gbinder_local_object_drop(NULL);
    g_assert(!gbinder_local_object_new_reply(NULL));
    g_assert(!gbinder_local_object_add_weak_refs_changed_handler(NULL,
        NULL, NULL));
    g_assert(!gbinder_local_object_add_strong_refs_changed_handler(NULL,
        NULL, NULL));
    gbinder_local_object_remove_handler(NULL, 0);
    g_assert(gbinder_local_object_can_handle_transaction(NULL, NULL, 0) ==
        GBINDER_LOCAL_TRANSACTION_NOT_SUPPORTED);
    g_assert(!gbinder_local_object_handle_transaction(NULL, NULL, 0, 0, NULL));
    g_assert(!gbinder_local_object_handle_transaction(NULL, NULL, 0, 0,
        &status));
    g_assert(!gbinder_local_object_handle_looper_transaction(NULL, NULL, 0, 0,
        NULL));
    g_assert(!gbinder_local_object_handle_looper_transaction(NULL, NULL, 0, 0,
        &status));
    g_assert(status == (-EBADMSG));
    g_assert(!gbinder_ipc_transact_custom(NULL, NULL, NULL, NULL, NULL));
    gbinder_local_object_handle_increfs(NULL);
    gbinder_local_object_handle_decrefs(NULL);
    gbinder_local_object_handle_acquire(NULL, NULL);
    gbinder_local_object_handle_release(NULL);
    gbinder_local_object_handle_release(NULL);
    gbinder_local_object_set_stability(NULL, GBINDER_STABILITY_UNDECLARED);
}

/*==========================================================================*
 * basic
 *==========================================================================*/

static
void
test_basic(
    void)
{
    const char* const ifaces_foo[] = { "foo", NULL };
    const char* const ifaces_bar[] = { "bar", NULL };
    GBinderIpc* ipc = gbinder_ipc_new(GBINDER_DEFAULT_BINDER, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderLocalObject* foo;
    GBinderLocalObject* bar;

    /* ipc is not a local object */
    g_assert(!gbinder_object_registry_get_local(reg, ipc));

    /* Create a new local objects */
    foo = gbinder_local_object_new(ipc, ifaces_foo, NULL, NULL);
    bar = gbinder_local_object_new(ipc, ifaces_bar, NULL, NULL);

    /* But ipc is still not a local object! */
    g_assert(!gbinder_object_registry_get_local(reg, ipc));

    gbinder_ipc_unref(ipc);

    g_assert(foo);
    g_assert(!gbinder_local_object_add_weak_refs_changed_handler(foo,
        NULL, NULL));
    g_assert(!gbinder_local_object_add_strong_refs_changed_handler(foo,
        NULL, NULL));
    gbinder_local_object_remove_handler(foo, 0);
    g_assert(gbinder_local_object_can_handle_transaction(foo,
        base_interface, -1) == GBINDER_LOCAL_TRANSACTION_NOT_SUPPORTED);
    gbinder_local_object_handle_increfs(foo);
    gbinder_local_object_handle_decrefs(foo);
    gbinder_local_object_handle_acquire(foo, NULL);
    gbinder_local_object_handle_release(foo);
    gbinder_local_object_unref(foo);

    g_assert(bar);
    g_assert(gbinder_local_object_ref(bar) == bar);
    gbinder_local_object_drop(bar);
    gbinder_local_object_unref(bar);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * ping
 *==========================================================================*/

static
void
test_ping(
    void)
{
    int status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, NULL, NULL, NULL);
    GBinderLocalReply* reply;
    GBinderOutputData* out_data;
    static const guint8 result[] = { 0x00, 0x00, 0x00, 0x00 };

    g_assert(gbinder_local_object_can_handle_transaction(obj, NULL,
        GBINDER_PING_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);

    /* If can_handle_transaction() returns TRANSACTION_LOOPER then it must be
     * handled by handle_looper_transaction() */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        GBINDER_PING_TRANSACTION, 0, &status));
    g_assert(status == (-EBADMSG));
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        GBINDER_PING_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    out_data = gbinder_local_reply_data(reply);
    g_assert(out_data);
    g_assert(out_data->bytes);
    g_assert(out_data->bytes->len == sizeof(result));
    g_assert(!memcmp(out_data->bytes->data, result, sizeof(result)));

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * interface
 *==========================================================================*/

static
void
test_interface(
    void)
{
    int status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    const char* const ifaces[] = { "x", NULL };
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, ifaces, NULL, NULL);
    GBinderLocalReply* reply;
    GBinderOutputData* out_data;
    static const guint8 result[] = {
        TEST_INT32_BYTES(1),
        TEST_INT16_BYTES('x'), 0x00, 0x00
    };

    g_assert(gbinder_local_object_can_handle_transaction(obj, NULL,
        GBINDER_INTERFACE_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);

    /* If can_handle_transaction() returns TRANSACTION_LOOPER then it must be
     * handled by handle_looper_transaction() */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        GBINDER_INTERFACE_TRANSACTION, 0, &status));
    g_assert(status == (-EBADMSG));
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        GBINDER_INTERFACE_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    out_data = gbinder_local_reply_data(reply);
    g_assert(out_data);
    g_assert(out_data->bytes);
    g_assert(out_data->bytes->len == sizeof(result));
    g_assert(!memcmp(out_data->bytes->data, result, sizeof(result)));

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * hidl_ping
 *==========================================================================*/

static
void
test_hidl_ping(
    void)
{
    static const guint8 req_data [] = { TEST_BASE_INTERFACE_HEADER_BYTES };
    int status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, NULL, NULL, NULL);
    GBinderLocalReply* reply;
    GBinderOutputData* out_data;
    static const guint8 result[] = { 0x00, 0x00, 0x00, 0x00 };

    gbinder_remote_request_set_data(req, HIDL_PING_TRANSACTION,
        gbinder_buffer_new(ipc->driver, g_memdup(req_data, sizeof(req_data)),
        sizeof(req_data), NULL));
    g_assert(!g_strcmp0(gbinder_remote_request_interface(req), base_interface));
    g_assert(gbinder_local_object_can_handle_transaction(obj, base_interface,
        HIDL_PING_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);

    /* If can_handle_transaction() returns TRANSACTION_LOOPER then it must be
     * handled by handle_looper_transaction() */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        HIDL_PING_TRANSACTION, 0, &status));
    g_assert(status == (-EBADMSG));
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        HIDL_PING_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    out_data = gbinder_local_reply_data(reply);
    g_assert(out_data);
    g_assert(out_data->bytes);
    g_assert(out_data->bytes->len == sizeof(result));
    g_assert(!memcmp(out_data->bytes->data, result, sizeof(result)));

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * get_descriptor
 *==========================================================================*/

static
void
test_get_descriptor(
    void)
{
    static const guint8 req_data [] = { TEST_BASE_INTERFACE_HEADER_BYTES };
    int status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, NULL, NULL, NULL);
    GBinderLocalReply* reply;

    gbinder_remote_request_set_data(req, HIDL_PING_TRANSACTION,
        gbinder_buffer_new(ipc->driver, g_memdup(req_data, sizeof(req_data)),
        sizeof(req_data), NULL));
    g_assert(!g_strcmp0(gbinder_remote_request_interface(req), base_interface));
    g_assert(gbinder_local_object_can_handle_transaction(obj, base_interface,
        HIDL_GET_DESCRIPTOR_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);

    /* If can_handle_transaction() returns TRANSACTION_LOOPER then it must be
     * handled by handle_looper_transaction() */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        HIDL_GET_DESCRIPTOR_TRANSACTION, 0, &status));
    g_assert(status == (-EBADMSG));
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        HIDL_GET_DESCRIPTOR_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    /* Unsupported transaction */
    g_assert(!gbinder_local_object_handle_looper_transaction
        (obj, req, -1, 0, NULL));
    g_assert(!gbinder_local_object_handle_looper_transaction
        (obj, req, -1, 0, &status));
    g_assert(status == (-EBADMSG));
    g_assert(!gbinder_local_object_handle_transaction(obj, req, -1, 0, NULL));
    g_assert(!gbinder_local_object_handle_transaction(obj, req, -1, 0,
        &status));
    g_assert(status == (-EBADMSG));

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * descriptor_chain
 *==========================================================================*/

static
void
test_descriptor_chain(
    void)
{
    static const guint8 req_data [] = {
        TEST_BASE_INTERFACE_HEADER_BYTES
    };
    int status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const char* const ifaces[] = { "android.hidl.base@1.0::IBase", NULL };
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, ifaces, NULL, NULL);
    GBinderLocalReply* reply;
    GBinderOutputData* reply_data;

    gbinder_remote_request_set_data(req, HIDL_DESCRIPTOR_CHAIN_TRANSACTION,
        gbinder_buffer_new(ipc->driver, g_memdup(req_data, sizeof(req_data)),
        sizeof(req_data), NULL));
    g_assert(!g_strcmp0(gbinder_remote_request_interface(req), base_interface));
    g_assert(gbinder_local_object_can_handle_transaction(obj, base_interface,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);

    /* If can_handle_transaction() returns TRANSACTION_LOOPER then it must be
     * handled by handle_looper_transaction() */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION, 0, &status));
    g_assert(status == (-EBADMSG));
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    /* Should get 3 buffers - vector, string and its contents */
    reply_data = gbinder_local_reply_data(reply);
    g_assert(gbinder_output_data_offsets(reply_data)->count == 3);
    g_assert(gbinder_output_data_buffers_size(reply_data) == 64);

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * custom_call
 *==========================================================================*/

#define CUSTOM_TRANSACTION (GBINDER_FIRST_CALL_TRANSACTION + 1)
#define CUSTOM_INTERFACE_BYTES 'f', 'o', 'o'
#define CUSTOM_INTERFACE_HEADER_BYTES  CUSTOM_INTERFACE_BYTES, 0x00
static const char custom_iface[] = { CUSTOM_INTERFACE_BYTES, 0x00 };

static
GBinderLocalReply*
test_custom_iface_handler(
    GBinderLocalObject* obj,
    GBinderRemoteRequest* req,
    guint code,
    guint flags,
    int* status,
    void* user_data)
{
    int* count = user_data;

    g_assert(!flags);
    g_assert(!g_strcmp0(gbinder_remote_request_interface(req), custom_iface));
    g_assert(code == CUSTOM_TRANSACTION);
    *status = GBINDER_STATUS_OK;
    (*count)++;
    return gbinder_local_object_new_reply(obj);
}

static
void
test_custom_iface(
    void)
{
    static const guint8 req_data [] = { CUSTOM_INTERFACE_HEADER_BYTES };
    const char* const ifaces[] = { custom_iface, NULL };
    int count = 0, status = INT_MAX;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, ifaces,
        test_custom_iface_handler, &count);
    GBinderLocalReply* reply;
    GBinderReaderData reader_data;
    GBinderReader reader;
    char** strv;
    char* str;

    gbinder_remote_request_set_data(req, HIDL_PING_TRANSACTION,
        gbinder_buffer_new(ipc->driver, g_memdup(req_data, sizeof(req_data)),
        sizeof(req_data), NULL));
    g_assert(gbinder_local_object_can_handle_transaction(obj, base_interface,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_LOOPER);
    g_assert(gbinder_local_object_can_handle_transaction(obj, custom_iface,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION) ==
        GBINDER_LOCAL_TRANSACTION_SUPPORTED);
    g_assert(gbinder_local_object_can_handle_transaction(obj, custom_iface,
        CUSTOM_TRANSACTION) == GBINDER_LOCAL_TRANSACTION_SUPPORTED);

    /* This returns the custom interface */
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        HIDL_GET_DESCRIPTOR_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    /* Parse the reply and check the interface */
    test_reader_data_init_for_reply(&reader_data, obj, reply);
    gbinder_reader_init(&reader, &reader_data, 0, reader_data.buffer->size);
    g_assert(gbinder_reader_read_int32(&reader, &status));
    g_assert(status == GBINDER_STATUS_OK);
    str = gbinder_reader_read_hidl_string(&reader);
    g_assert(!g_strcmp0(str, custom_iface));
    g_free(str);
    test_reader_data_cleanup(&reader_data);
    gbinder_local_reply_unref(reply);

    /* And this returns two interfaces */
    reply = gbinder_local_object_handle_looper_transaction(obj, req,
        HIDL_DESCRIPTOR_CHAIN_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);

    /* Parse the reply and check the interface */
    test_reader_data_init_for_reply(&reader_data, obj, reply);
    gbinder_reader_init(&reader, &reader_data, 0, reader_data.buffer->size);
    g_assert(gbinder_reader_read_int32(&reader, &status));
    g_assert(status == GBINDER_STATUS_OK);
    strv = gbinder_reader_read_hidl_string_vec(&reader);
    g_assert(gutil_strv_length(strv) == 2);
    g_assert(!g_strcmp0(strv[0], custom_iface));
    g_assert(!g_strcmp0(strv[1], base_interface));
    g_strfreev(strv);
    test_reader_data_cleanup(&reader_data);
    gbinder_local_reply_unref(reply);

    /* Execute the custom transaction */
    reply = gbinder_local_object_handle_transaction(obj, req,
        CUSTOM_TRANSACTION, 0, &status);
    g_assert(reply);
    g_assert(status == GBINDER_STATUS_OK);
    g_assert(count == 1);

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_local_reply_unref(reply);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * reply_status
 *==========================================================================*/

#define EXPECTED_STATUS (424242)

static
GBinderLocalReply*
test_reply_status_handler(
    GBinderLocalObject* obj,
    GBinderRemoteRequest* req,
    guint code,
    guint flags,
    int* status,
    void* user_data)
{
    int* count = user_data;

    g_assert(!flags);
    g_assert(!g_strcmp0(gbinder_remote_request_interface(req), custom_iface));
    g_assert(code == CUSTOM_TRANSACTION);
    *status = EXPECTED_STATUS;
    (*count)++;
    return NULL;
}

static
void
test_reply_status(
    void)
{
    static const guint8 req_data [] = { CUSTOM_INTERFACE_HEADER_BYTES };
    const char* const ifaces[] = { custom_iface, NULL };
    int count = 0, status = 0;
    const char* dev = GBINDER_DEFAULT_HWBINDER;
    const GBinderRpcProtocol* prot = gbinder_rpc_protocol_for_device(dev);
    GBinderIpc* ipc = gbinder_ipc_new(dev, NULL);
    GBinderObjectRegistry* reg = gbinder_ipc_object_registry(ipc);
    GBinderRemoteRequest* req = gbinder_remote_request_new(reg, prot, 0, 0);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, ifaces,
        test_reply_status_handler, &count);

    gbinder_remote_request_set_data(req, HIDL_PING_TRANSACTION,
        gbinder_buffer_new(ipc->driver, g_memdup(req_data, sizeof(req_data)),
        sizeof(req_data), NULL));

    /* Execute the custom transaction */
    g_assert(!gbinder_local_object_handle_transaction(obj, req,
        CUSTOM_TRANSACTION, 0, &status));
    g_assert(status == EXPECTED_STATUS);
    g_assert(count == 1);

    gbinder_ipc_unref(ipc);
    gbinder_local_object_unref(obj);
    gbinder_remote_request_unref(req);
    test_binder_exit_wait(&test_opt, NULL);
}

/*==========================================================================*
 * increfs
 *==========================================================================*/

static
void
test_increfs_cb(
    GBinderLocalObject* obj,
    void* user_data)
{
    GVERBOSE_("%d", obj->weak_refs);
    g_assert(obj->weak_refs == 1);
    test_quit_later((GMainLoop*)user_data);
}

static
void
test_increfs_run(
    void)
{
    GBinderIpc* ipc = gbinder_ipc_new(GBINDER_DEFAULT_BINDER, NULL);
    GBinderLocalObject* obj = gbinder_local_object_new
        (ipc, NULL, NULL, NULL);
    GMainLoop* loop = g_main_loop_new(NULL, FALSE);
    int fd = gbinder_driver_fd(ipc->driver);
    gulong id = gbinder_local_object_add_weak_refs_changed_handler(obj,
        test_increfs_cb, loop);

    /* ipc is not an object, will be ignored */
    test_binder_br_increfs(fd, ANY_THREAD, ipc);
    test_binder_br_increfs(fd, ANY_THREAD, obj);
    test_run(&test_opt, loop);

    g_assert(obj->weak_refs == 1);
    gbinder_local_object_remove_handler(obj, id);
    gbinder_local_object_unref(obj);
    gbinder_ipc_unref(ipc);
    test_binder_exit_wait(&test_opt, loop);
    g_main_loop_unref(loop);
}

static
void
test_increfs(
    void)
{
    test_run_in_context(&test_opt, test_increfs_run);
}

/*==========================================================================*
 * decrefs
 *==========================================================================*/

static
void
test_decrefs_cb(
    GBinderLocalObject* obj,
    void* user_data)
{
    GVERBOSE_("%d", obj->weak_refs);
    if (!obj->weak_refs) {
        test_quit_later((GMainLoop*)user_data);
    }
}

static
void
test_decrefs_run(
    void)
{
    GBinderIpc* ipc = gbinder_ipc_new(GBINDER_DEFAULT_BINDER, NULL);
    GBinderLocalObject* obj = gbinder_local_object_new
        (ipc, NULL, NULL, NULL);
    GMainLoop* loop = g_main_loop_new(NULL, FALSE);
    int fd = gbinder_driver_fd(ipc->driver);
    gulong id = gbinder_local_object_add_weak_refs_changed_handler(obj,
        test_decrefs_cb, loop);

    /* ipc is not an object, will be ignored */
    test_binder_br_decrefs(fd, ANY_THREAD, ipc);
    test_binder_br_increfs(fd, ANY_THREAD, obj);
    test_binder_br_decrefs(fd, ANY_THREAD, obj);
    test_run(&test_opt, loop);

    g_assert(obj->weak_refs == 0);
    gbinder_local_object_remove_handler(obj, id);
    gbinder_local_object_unref(obj);
    gbinder_ipc_unref(ipc);
    test_binder_exit_wait(&test_opt, loop);
    g_main_loop_unref(loop);
}

static
void
test_decrefs(
    void)
{
    test_run_in_context(&test_opt, test_decrefs_run);
}

/*==========================================================================*
 * acquire
 *==========================================================================*/

static
void
test_acquire_cb(
    GBinderLocalObject* obj,
    void* user_data)
{
    GVERBOSE_("%d", obj->strong_refs);
    g_assert(obj->strong_refs == 1);
    test_quit_later((GMainLoop*)user_data);
}

static
void
test_acquire_run(
    void)
{
    GBinderIpc* ipc = gbinder_ipc_new(GBINDER_DEFAULT_BINDER, NULL);
    GBinderLocalObject* obj = gbinder_local_object_new
        (ipc, NULL, NULL, NULL);
    GMainLoop* loop = g_main_loop_new(NULL, FALSE);
    int fd = gbinder_driver_fd(ipc->driver);
    gulong id = gbinder_local_object_add_strong_refs_changed_handler(obj,
        test_acquire_cb, loop);

    /* ipc is not an object, will be ignored */
    test_binder_br_acquire(fd, ANY_THREAD, ipc);
    test_binder_br_acquire(fd, ANY_THREAD, obj);
    test_run(&test_opt, loop);

    g_assert(obj->strong_refs == 1);
    gbinder_local_object_remove_handler(obj, id);
    gbinder_local_object_unref(obj);
    gbinder_ipc_unref(ipc);
    test_binder_exit_wait(&test_opt, loop);
    g_main_loop_unref(loop);
}

static
void
test_acquire(
    void)
{
    test_run_in_context(&test_opt, test_acquire_run);
}

/*==========================================================================*
 * release
 *==========================================================================*/

static
void
test_release_cb(
    GBinderLocalObject* obj,
    void* user_data)
{
    GVERBOSE_("%d", obj->strong_refs);
    if (!obj->strong_refs) {
        test_quit_later((GMainLoop*)user_data);
    }
}

static
void
test_release_run(
    void)
{
    GBinderIpc* ipc = gbinder_ipc_new(GBINDER_DEFAULT_BINDER, NULL);
    GBinderLocalObject* obj = gbinder_local_object_new(ipc, NULL, NULL, NULL);
    GMainLoop* loop = g_main_loop_new(NULL, FALSE);
    int fd = gbinder_driver_fd(ipc->driver);
    gulong id = gbinder_local_object_add_strong_refs_changed_handler(obj,
        test_release_cb, loop);

    /* ipc is not an object, will be ignored */
    test_binder_br_release(fd, ANY_THREAD, ipc);
    test_binder_br_acquire(fd, ANY_THREAD, obj);
    test_binder_br_release(fd, ANY_THREAD, obj);
    test_run(&test_opt, loop);

    g_assert(obj->strong_refs == 0);
    gbinder_local_object_remove_handler(obj, id);
    gbinder_local_object_unref(obj);
    gbinder_ipc_unref(ipc);
    test_binder_exit_wait(&test_opt, loop);
    g_main_loop_unref(loop);
}

static
void
test_release(
    void)
{
    test_run_in_context(&test_opt, test_release_run);
}

/*==========================================================================*
 * Common
 *==========================================================================*/

#define TEST_PREFIX "/local_object/"

int main(int argc, char* argv[])
{
    TestConfig test_config;
    int result;

    G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
    g_type_init();
    G_GNUC_END_IGNORE_DEPRECATIONS;
    g_test_init(&argc, &argv, NULL);
    g_test_add_func(TEST_PREFIX "null", test_null);
    g_test_add_func(TEST_PREFIX "basic", test_basic);
    g_test_add_func(TEST_PREFIX "ping", test_ping);
    g_test_add_func(TEST_PREFIX "interface", test_interface);
    g_test_add_func(TEST_PREFIX "hidl_ping", test_hidl_ping);
    g_test_add_func(TEST_PREFIX "get_descriptor", test_get_descriptor);
    g_test_add_func(TEST_PREFIX "descriptor_chain", test_descriptor_chain);
    g_test_add_func(TEST_PREFIX "custom_iface", test_custom_iface);
    g_test_add_func(TEST_PREFIX "reply_status", test_reply_status);
    g_test_add_func(TEST_PREFIX "increfs", test_increfs);
    g_test_add_func(TEST_PREFIX "decrefs", test_decrefs);
    g_test_add_func(TEST_PREFIX "acquire", test_acquire);
    g_test_add_func(TEST_PREFIX "release", test_release);
    test_init(&test_opt, argc, argv);
    test_config_init(&test_config, TMP_DIR_TEMPLATE);
    result = g_test_run();
    test_config_cleanup(&test_config);
    return result;
}

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